Merge branch 'main' into daily-pnl-history
This commit is contained in:
commit
f0db2495fe
|
@ -118,13 +118,23 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
|
||||
const handleDeposit = useCallback(
|
||||
async (amount: string) => {
|
||||
//to not leave some dust on account we round amount by this number
|
||||
//with reduce only set to true we take only what is needed to be
|
||||
//deposited in need to repay borrow
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
||||
if (!mangoAccount || !group || !bank || !publicKey) return
|
||||
console.log('inputAmount: ', amount)
|
||||
|
||||
//we don't want to left negative dust in account if someone wants to repay full amount
|
||||
const actualAmount =
|
||||
sizePercentage === '100'
|
||||
? mangoAccount.getTokenBorrowsUi(bank) < parseFloat(amount)
|
||||
? parseFloat(amount)
|
||||
: mangoAccount.getTokenBorrowsUi(bank)
|
||||
: parseFloat(amount)
|
||||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
|
@ -132,7 +142,8 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
group,
|
||||
mangoAccount,
|
||||
bank.mint,
|
||||
parseFloat(amount)
|
||||
actualAmount,
|
||||
true
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
|
@ -155,7 +166,7 @@ function RepayForm({ onSuccess, token }: RepayFormProps) {
|
|||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[bank, publicKey]
|
||||
[bank, publicKey?.toBase58(), sizePercentage]
|
||||
)
|
||||
|
||||
const banks = useMemo(() => {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useUnsettledSpotBalances } from 'hooks/useUnsettledSpotBalances'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
import TradeHistory from '@components/trade/TradeHistory'
|
||||
|
||||
const AccountTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState('balances')
|
||||
|
@ -23,15 +24,16 @@ const AccountTabs = () => {
|
|||
|
||||
return [
|
||||
['balances', 0],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['activity:activity', 0],
|
||||
['swap:swap-history', 0],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['trade-history', 0],
|
||||
]
|
||||
}, [unsettledPerpPositions, unsettledSpotBalances])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="border-b border-th-bkg-3">
|
||||
<div className="hide-scroll overflow-x-auto border-b border-th-bkg-3">
|
||||
<TabButtons
|
||||
activeValue={activeTab}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
|
@ -51,10 +53,6 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
switch (activeTab) {
|
||||
case 'balances':
|
||||
return <TokenList />
|
||||
case 'activity:activity':
|
||||
return <ActivityFeed />
|
||||
case 'swap:swap-history':
|
||||
return <SwapHistoryTable />
|
||||
case 'trade:unsettled':
|
||||
return (
|
||||
<UnsettledTrades
|
||||
|
@ -62,6 +60,12 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
unsettledPerpPositions={unsettledPerpPositions}
|
||||
/>
|
||||
)
|
||||
case 'activity:activity':
|
||||
return <ActivityFeed />
|
||||
case 'swap:swap-history':
|
||||
return <SwapHistoryTable />
|
||||
case 'trade-history':
|
||||
return <TradeHistory />
|
||||
default:
|
||||
return <TokenList />
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import { useViewport } from 'hooks/useViewport'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { Fragment, useCallback, useState } from 'react'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { PAGINATION_PAGE_LENGTH, PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
|
||||
|
@ -55,8 +55,12 @@ const ActivityFeedTable = ({
|
|||
s.activityFeed.loading = true
|
||||
})
|
||||
if (!mangoAccountAddress) return
|
||||
setOffset(offset + 25)
|
||||
actions.fetchActivityFeed(mangoAccountAddress, offset + 25, params)
|
||||
setOffset(offset + PAGINATION_PAGE_LENGTH)
|
||||
actions.fetchActivityFeed(
|
||||
mangoAccountAddress,
|
||||
offset + PAGINATION_PAGE_LENGTH,
|
||||
params
|
||||
)
|
||||
}, [actions, offset, params, mangoAccountAddress])
|
||||
|
||||
const getCreditAndDebit = (activity: any) => {
|
||||
|
@ -316,7 +320,8 @@ const ActivityFeedTable = ({
|
|||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{activityFeed.length && activityFeed.length % 25 === 0 ? (
|
||||
{activityFeed.length &&
|
||||
activityFeed.length % PAGINATION_PAGE_LENGTH === 0 ? (
|
||||
<div className="flex justify-center py-6">
|
||||
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
|
||||
</div>
|
||||
|
|
|
@ -4,25 +4,53 @@ import mangoStore from '@store/mangoStore'
|
|||
import { notify } from '../../utils/notifications'
|
||||
import Button from '../shared/Button'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import BounceLoader from '../shared/BounceLoader'
|
||||
import { MangoAccount } from '@blockworks-foundation/mango-v4'
|
||||
import { TrashIcon } from '@heroicons/react/20/solid'
|
||||
import {
|
||||
MangoAccount,
|
||||
TokenPosition,
|
||||
toUiDecimalsForQuote,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ExclamationCircleIcon,
|
||||
TrashIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||
import { getMultipleAccounts } from '@project-serum/anchor/dist/cjs/utils/rpc'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
|
||||
const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['close-account'])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const set = mangoStore((s) => s.set)
|
||||
const openOrders = Object.values(mangoStore((s) => s.mangoAccount.openOrders))
|
||||
const connection = mangoStore.getState().connection
|
||||
const hasOpenOrders =
|
||||
openOrders.length && openOrders.filter((x) => x.length).length > 0
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount)
|
||||
const perpPositions = mangoStore((s) => s.mangoAccount.perpPositions)
|
||||
const openPerpPositions = Object.values(perpPositions).filter((p) =>
|
||||
p.basePositionLots.toNumber()
|
||||
)
|
||||
const group = mangoStore.getState().group
|
||||
const unsettledBalances = Object.values(mangoAccount.spotBalances).filter(
|
||||
(x) => x.unsettled && x.unsettled > 0
|
||||
)
|
||||
const unsettledPerpPositions = useUnsettledPerpPositions()
|
||||
const [hasBorrows, setHasBorrows] = useState(false)
|
||||
const [hasOpenPositions, setHasOpenPositions] = useState(false)
|
||||
const [totalAccountSOL, setTotalAccountSOL] = useState(0)
|
||||
|
||||
const handleCloseMangoAccount = async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const mangoAccounts = mangoStore.getState().mangoAccounts
|
||||
const group = mangoStore.getState().group
|
||||
|
||||
if (!mangoAccount || !group) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const tx = await client.closeMangoAccount(group, mangoAccount)
|
||||
const tx = await client.emptyAndCloseMangoAccount(group, mangoAccount)
|
||||
if (tx) {
|
||||
const newMangoAccounts = mangoAccounts.filter(
|
||||
(ma) => !ma.publicKey.equals(mangoAccount.publicKey)
|
||||
|
@ -50,19 +78,126 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
const fetchTotalAccountSOL = useCallback(async () => {
|
||||
if (!mangoAccount) {
|
||||
return
|
||||
}
|
||||
const accountKeys = [
|
||||
mangoAccount.current!.publicKey,
|
||||
...mangoAccount.openOrderAccounts.map((x) => x.address),
|
||||
]
|
||||
const accounts = await getMultipleAccounts(connection, accountKeys)
|
||||
const lamports =
|
||||
accounts.reduce((total, account) => {
|
||||
return total + (account?.account.lamports || 0)
|
||||
}, 0) * 0.000000001
|
||||
|
||||
setTotalAccountSOL(lamports)
|
||||
}, [mangoAccount])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount && group) {
|
||||
if (
|
||||
mangoAccount.current
|
||||
?.tokensActive()
|
||||
.filter((token: TokenPosition) =>
|
||||
token
|
||||
.balance(group.getFirstBankByTokenIndex(token.tokenIndex))
|
||||
.isNeg()
|
||||
).length
|
||||
) {
|
||||
setHasBorrows(true)
|
||||
}
|
||||
if (openPerpPositions.length || unsettledPerpPositions.length) {
|
||||
setHasOpenPositions(true)
|
||||
}
|
||||
fetchTotalAccountSOL()
|
||||
}
|
||||
}, [mangoAccount, group])
|
||||
|
||||
const isDisabled =
|
||||
hasOpenOrders ||
|
||||
hasBorrows ||
|
||||
hasOpenPositions ||
|
||||
!!unsettledBalances.length
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<div className="h-56">
|
||||
<div className="h-[550px]">
|
||||
{loading ? (
|
||||
<BounceLoader loadingMessage={t('closing-account')} />
|
||||
) : (
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div className="pb-6">
|
||||
<div className="space-y-4">
|
||||
<h2 className="mb-1">{t('close-account')}</h2>
|
||||
<p>{t('close-account-desc')}</p>
|
||||
<p>{t('description')}</p>
|
||||
<p>{t('you-must')}:</p>
|
||||
<div className="overflow-none space-y-2 rounded-md bg-th-bkg-4 p-2 sm:p-4">
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
{hasBorrows ? (
|
||||
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
|
||||
) : (
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
)}
|
||||
{t('close-all-borrows')}
|
||||
</div>
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
{hasOpenPositions ? (
|
||||
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
|
||||
) : (
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
)}
|
||||
{t('close-perp-positions')}
|
||||
</div>
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
{hasOpenOrders ? (
|
||||
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
|
||||
) : (
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
)}
|
||||
{t('close-open-orders')}
|
||||
</div>
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
{unsettledBalances.length ? (
|
||||
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-down" />
|
||||
) : (
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
)}
|
||||
{t('settle-balances')}
|
||||
</div>
|
||||
</div>
|
||||
<p>By closing your account you will:</p>
|
||||
<div className="overflow-none space-y-2 rounded-md bg-th-bkg-4 p-2 sm:p-4">
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
{t('delete-your-account')}
|
||||
</div>
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
{t('withdraw-assets-worth', {
|
||||
value:
|
||||
mangoAccount && group
|
||||
? formatFixedDecimals(
|
||||
toUiDecimalsForQuote(
|
||||
mangoAccount!.current!.getEquity(group).toNumber()
|
||||
),
|
||||
false,
|
||||
true
|
||||
)
|
||||
: 0,
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
<CheckCircleIcon className="mr-1.5 h-4 w-4 text-th-success"></CheckCircleIcon>
|
||||
{t('recover-x-sol', {
|
||||
amount: totalAccountSOL.toFixed(3),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="w-full"
|
||||
disabled={isDisabled}
|
||||
onClick={handleCloseMangoAccount}
|
||||
size="large"
|
||||
>
|
||||
|
|
|
@ -29,8 +29,14 @@ const SearchPage = () => {
|
|||
const [searchResults, setSearchResults] = useState([])
|
||||
const [searchType, setSearchType] = useState('mango-account')
|
||||
const [showNoResults, setShowNoResults] = useState(false)
|
||||
const [isAccountSearch, setIsAccountSearch] = useState(true)
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (searchType === 'mango-account' || searchType === 'mango-account-name') {
|
||||
setIsAccountSearch(true)
|
||||
} else {
|
||||
setIsAccountSearch(false)
|
||||
}
|
||||
try {
|
||||
setLoading(true)
|
||||
const response = await fetch(
|
||||
|
@ -51,9 +57,6 @@ const SearchPage = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const isAccountSearch =
|
||||
searchType === 'mango-account' || searchType === 'mango-account-name'
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 p-8 pb-20 md:pb-16 lg:p-10">
|
||||
<div className="col-span-12 lg:col-span-8 lg:col-start-3">
|
||||
|
@ -140,7 +143,7 @@ const MangoAccountItem = ({
|
|||
type,
|
||||
}: {
|
||||
item: MangoAccountItem
|
||||
type: 'mango-account' | 'mango-account-name'
|
||||
type: string
|
||||
}) => {
|
||||
const { mango_account_name, mango_account_pk, profile_name } = item
|
||||
return (
|
||||
|
|
|
@ -9,16 +9,19 @@ type SideBadgeProps = {
|
|||
const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const isBid =
|
||||
typeof side === 'string' ? ['buy', 'long'].includes(side) : 'bid' in side
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`inline-block rounded uppercase ${
|
||||
side === 'buy' || side === 'long' || side === 0
|
||||
isBid
|
||||
? 'text-th-up md:border md:border-th-up'
|
||||
: 'text-th-down md:border md:border-th-down'
|
||||
}
|
||||
uppercase md:-my-0.5 md:px-1.5 md:py-0.5 md:text-xs`}
|
||||
>
|
||||
{typeof side === 'string' ? t(side) : side === 0 ? 'Buy' : 'Sell'}
|
||||
{typeof side === 'string' ? t(side) : 'bid' in side ? 'Long' : 'Short'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,11 @@ export const Table = ({
|
|||
}: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}) => <table className={`m-0 min-w-full p-0 ${className}`}>{children}</table>
|
||||
}) => (
|
||||
<div className="thin-scroll overflow-x-auto">
|
||||
<table className={`m-0 min-w-full p-0 ${className}`}>{children}</table>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const TrHead = ({
|
||||
children,
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import mangoStore, { TokenStatsItem } from '@store/mangoStore'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useMemo } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { numberCompacter } from 'utils/numbers'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import TotalDepositBorrowCharts from './TotalDepositBorrowCharts'
|
||||
// import { useTranslation } from 'next-i18next'
|
||||
// import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
const DetailedAreaChart = dynamic(
|
||||
() => import('@components/shared/DetailedAreaChart'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
interface TotalValueItem {
|
||||
date: string
|
||||
borrowValue: number
|
||||
depositValue: number
|
||||
}
|
||||
|
||||
const MangoStats = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
// const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
const tokenStats = mangoStore((s) => s.tokenStats.data)
|
||||
const loadingStats = mangoStore((s) => s.tokenStats.loading)
|
||||
// const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
// const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
// const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const { group } = useMangoGroup()
|
||||
|
||||
// const totalFeeValues = useMemo(() => {
|
||||
// if (!perpStats.length) return []
|
||||
|
@ -72,111 +56,12 @@ const MangoStats = () => {
|
|||
// }, 0)
|
||||
// }, [perpMarkets])
|
||||
|
||||
const totalDepositBorrowValues = useMemo(() => {
|
||||
if (!tokenStats) return []
|
||||
const values: TotalValueItem[] = tokenStats.reduce(
|
||||
(a: TotalValueItem[], c: TokenStatsItem) => {
|
||||
const hasDate = a.find((d: TotalValueItem) => d.date === c.date_hour)
|
||||
if (!hasDate) {
|
||||
a.push({
|
||||
date: c.date_hour,
|
||||
depositValue: Math.floor(c.total_deposits * c.price),
|
||||
borrowValue: Math.floor(c.total_borrows * c.price),
|
||||
})
|
||||
} else {
|
||||
hasDate.depositValue =
|
||||
hasDate.depositValue + Math.floor(c.total_deposits * c.price)
|
||||
hasDate.borrowValue =
|
||||
hasDate.borrowValue + Math.floor(c.total_borrows * c.price)
|
||||
}
|
||||
return a
|
||||
},
|
||||
[]
|
||||
)
|
||||
return values.reverse()
|
||||
}, [tokenStats])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
return rawBanks
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
||||
const [currentTotalDepositValue, currentTotalBorrowValue] = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return [
|
||||
banks.reduce(
|
||||
(a, c) => a + c.value[0].uiPrice * c.value[0].uiDeposits(),
|
||||
0
|
||||
),
|
||||
banks.reduce(
|
||||
(a, c) => a + c.value[0].uiPrice * c.value[0].uiBorrows(),
|
||||
0
|
||||
),
|
||||
]
|
||||
}
|
||||
return [0, 0]
|
||||
}, [banks])
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2">
|
||||
{loadingStats ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : totalDepositBorrowValues.length ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={totalDepositBorrowValues.concat([
|
||||
{
|
||||
date: dayjs().toISOString(),
|
||||
depositValue: Math.floor(currentTotalDepositValue),
|
||||
borrowValue: Math.floor(currentTotalBorrowValue),
|
||||
},
|
||||
])}
|
||||
daysToShow={'999'}
|
||||
heightClass="h-64"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${numberCompacter.format(x)}`}
|
||||
title={t('total-deposit-value')}
|
||||
xKey="date"
|
||||
yKey={'depositValue'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{loadingStats ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : totalDepositBorrowValues.length ? (
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={totalDepositBorrowValues.concat([
|
||||
{
|
||||
date: dayjs().toISOString(),
|
||||
borrowValue: Math.floor(currentTotalBorrowValue),
|
||||
depositValue: Math.floor(currentTotalDepositValue),
|
||||
},
|
||||
])}
|
||||
daysToShow={'999'}
|
||||
heightClass="h-64"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${numberCompacter.format(x)}`}
|
||||
title={t('total-borrow-value')}
|
||||
xKey="date"
|
||||
yKey={'borrowValue'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<TotalDepositBorrowCharts
|
||||
tokenStats={tokenStats}
|
||||
loadingStats={loadingStats}
|
||||
/>
|
||||
{/* uncomment below when perps launch */}
|
||||
|
||||
{/* {loadingPerpStats ? (
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { TokenStatsItem } from '@store/mangoStore'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useMemo, useState } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { numberCompacter } from 'utils/numbers'
|
||||
const DetailedAreaChart = dynamic(
|
||||
() => import('@components/shared/DetailedAreaChart'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
interface TotalValueItem {
|
||||
date: string
|
||||
borrowValue: number
|
||||
depositValue: number
|
||||
}
|
||||
|
||||
const TotalDepositBorrowCharts = ({
|
||||
tokenStats,
|
||||
loadingStats,
|
||||
}: {
|
||||
tokenStats: TokenStatsItem[] | null
|
||||
loadingStats: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
|
||||
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
|
||||
const { group } = useMangoGroup()
|
||||
|
||||
const totalDepositBorrowValues = useMemo(() => {
|
||||
if (!tokenStats) return []
|
||||
const values: TotalValueItem[] = tokenStats.reduce(
|
||||
(a: TotalValueItem[], c: TokenStatsItem) => {
|
||||
const hasDate = a.find((d: TotalValueItem) => d.date === c.date_hour)
|
||||
if (!hasDate) {
|
||||
a.push({
|
||||
date: c.date_hour,
|
||||
depositValue: Math.floor(c.total_deposits * c.price),
|
||||
borrowValue: Math.floor(c.total_borrows * c.price),
|
||||
})
|
||||
} else {
|
||||
hasDate.depositValue =
|
||||
hasDate.depositValue + Math.floor(c.total_deposits * c.price)
|
||||
hasDate.borrowValue =
|
||||
hasDate.borrowValue + Math.floor(c.total_borrows * c.price)
|
||||
}
|
||||
return a
|
||||
},
|
||||
[]
|
||||
)
|
||||
return values.reverse()
|
||||
}, [tokenStats])
|
||||
|
||||
const filteredBorrowValues = useMemo(() => {
|
||||
if (!totalDepositBorrowValues) return []
|
||||
if (borrowDaysToShow !== '30') {
|
||||
const seconds = Number(borrowDaysToShow) * 86400
|
||||
const data = totalDepositBorrowValues.filter((d) => {
|
||||
const dataTime = new Date(d.date).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
return dataTime >= limit
|
||||
})
|
||||
return data
|
||||
}
|
||||
return totalDepositBorrowValues
|
||||
}, [totalDepositBorrowValues, borrowDaysToShow])
|
||||
|
||||
const filteredDepositValues = useMemo(() => {
|
||||
if (!totalDepositBorrowValues) return []
|
||||
if (depositDaysToShow !== '30') {
|
||||
const seconds = Number(depositDaysToShow) * 86400
|
||||
const data = totalDepositBorrowValues.filter((d) => {
|
||||
const dataTime = new Date(d.date).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
return dataTime >= limit
|
||||
})
|
||||
return data
|
||||
}
|
||||
return totalDepositBorrowValues
|
||||
}, [totalDepositBorrowValues, depositDaysToShow])
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
return rawBanks
|
||||
}
|
||||
return []
|
||||
}, [group])
|
||||
|
||||
const [currentTotalDepositValue, currentTotalBorrowValue] = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return [
|
||||
banks.reduce(
|
||||
(a, c) => a + c.value[0].uiPrice * c.value[0].uiDeposits(),
|
||||
0
|
||||
),
|
||||
banks.reduce(
|
||||
(a, c) => a + c.value[0].uiPrice * c.value[0].uiBorrows(),
|
||||
0
|
||||
),
|
||||
]
|
||||
}
|
||||
return [0, 0]
|
||||
}, [banks])
|
||||
|
||||
return loadingStats ? (
|
||||
<>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-96 w-full rounded-lg bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
</>
|
||||
) : totalDepositBorrowValues.length ? (
|
||||
<>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1">
|
||||
<DetailedAreaChart
|
||||
data={filteredDepositValues.concat([
|
||||
{
|
||||
date: dayjs().toISOString(),
|
||||
depositValue: Math.floor(currentTotalDepositValue),
|
||||
borrowValue: Math.floor(currentTotalBorrowValue),
|
||||
},
|
||||
])}
|
||||
daysToShow={depositDaysToShow}
|
||||
setDaysToShow={setDepositDaysToShow}
|
||||
heightClass="h-64"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${numberCompacter.format(x)}`}
|
||||
title={t('total-deposit-value')}
|
||||
xKey="date"
|
||||
yKey={'depositValue'}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-l md:pl-6">
|
||||
<DetailedAreaChart
|
||||
data={filteredBorrowValues.concat([
|
||||
{
|
||||
date: dayjs().toISOString(),
|
||||
borrowValue: Math.floor(currentTotalBorrowValue),
|
||||
depositValue: Math.floor(currentTotalDepositValue),
|
||||
},
|
||||
])}
|
||||
daysToShow={borrowDaysToShow}
|
||||
setDaysToShow={setBorrowDaysToShow}
|
||||
heightClass="h-64"
|
||||
prefix="$"
|
||||
tickFormat={(x) => `$${numberCompacter.format(x)}`}
|
||||
title={t('total-borrow-value')}
|
||||
xKey="date"
|
||||
yKey={'borrowValue'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default TotalDepositBorrowCharts
|
|
@ -23,7 +23,7 @@ import { Transition } from '@headlessui/react'
|
|||
import Button, { IconButton } from '../shared/Button'
|
||||
import Loading from '../shared/Loading'
|
||||
import { EnterBottomExitBottom } from '../shared/Transitions'
|
||||
import useJupiterRoutes from './useJupiterRoutes'
|
||||
import useQuoteRoutes from './useQuoteRoutes'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
|
@ -80,7 +80,7 @@ const SwapForm = () => {
|
|||
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
|
||||
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
const { connected, publicKey } = useWallet()
|
||||
|
||||
const amountInAsDecimal: Decimal | null = useMemo(() => {
|
||||
return Number(debouncedAmountIn)
|
||||
|
@ -94,12 +94,13 @@ const SwapForm = () => {
|
|||
: new Decimal(0)
|
||||
}, [debouncedAmountOut])
|
||||
|
||||
const { bestRoute, routes } = useJupiterRoutes({
|
||||
const { bestRoute, routes } = useQuoteRoutes({
|
||||
inputMint: inputBank?.mint.toString() || USDC_MINT,
|
||||
outputMint: outputBank?.mint.toString() || MANGO_MINT,
|
||||
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
|
||||
slippage,
|
||||
swapMode,
|
||||
wallet: publicKey?.toBase58(),
|
||||
})
|
||||
|
||||
const setAmountInFormValue = useCallback(
|
||||
|
|
|
@ -61,7 +61,8 @@ const SwapHistoryTable = () => {
|
|||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('date')}</Th>
|
||||
<Th className="w-1/3 text-left">{t('swap')}</Th>
|
||||
<Th className="text-left">{t('swap:paid')}</Th>
|
||||
<Th className="text-left">{t('swap:received')}</Th>
|
||||
<Th className="text-right">{t('value')}</Th>
|
||||
<Th className="text-right">{t('borrow')}</Th>
|
||||
<Th className="text-right">{t('borrow-fee')}</Th>
|
||||
|
@ -103,14 +104,14 @@ const SwapHistoryTable = () => {
|
|||
|
||||
if (mangoTokens.length) {
|
||||
baseLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === inSymbol
|
||||
(t) => t.symbol.toUpperCase() === inSymbol.toUpperCase()
|
||||
)?.logoURI
|
||||
quoteLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === outSymbol
|
||||
(t) => t.symbol.toUpperCase() === outSymbol.toUpperCase()
|
||||
)?.logoURI
|
||||
}
|
||||
|
||||
const inDecimals = countLeadingZeros(swap_in_amount)
|
||||
const inDecimals = countLeadingZeros(swap_in_amount) + 2
|
||||
const outDecimals = countLeadingZeros(swap_out_amount) + 2
|
||||
return (
|
||||
<TrBody key={signature}>
|
||||
|
@ -122,54 +123,53 @@ const SwapHistoryTable = () => {
|
|||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="w-1/3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex w-1/2 items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={baseLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${formatDecimal(swap_in_amount, inDecimals)}`}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
{formatFixedDecimals(swap_in_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={baseLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<ArrowRightIcon className="mx-4 h-4 w-4 flex-shrink-0 text-th-fgd-4" />
|
||||
<div className="flex w-1/2 items-center">
|
||||
<div className="mr-2 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={quoteLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${formatDecimal(swap_out_amount, outDecimals)}`}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{outSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
<span className="font-body">{t('price')}:</span>{' '}
|
||||
{formatFixedDecimals(swap_out_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${formatDecimal(swap_in_amount, inDecimals)}`}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{t('price')}:
|
||||
</span>{' '}
|
||||
{formatFixedDecimals(swap_in_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={quoteLogoURI || ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap">
|
||||
{`${formatDecimal(swap_out_amount, outDecimals)}`}
|
||||
<span className="ml-1 font-body text-th-fgd-3">
|
||||
{outSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
<span className="font-body">{t('price')}:</span>{' '}
|
||||
{formatFixedDecimals(swap_out_price_usd, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
|
@ -266,13 +266,16 @@ const SwapHistoryTable = () => {
|
|||
|
||||
if (mangoTokens.length) {
|
||||
baseLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === inSymbol
|
||||
(t) => t.symbol.toUpperCase() === inSymbol.toUpperCase()
|
||||
)?.logoURI
|
||||
quoteLogoURI = mangoTokens.find(
|
||||
(t) => t.symbol === outSymbol
|
||||
(t) => t.symbol.toUpperCase() === outSymbol.toUpperCase()
|
||||
)?.logoURI
|
||||
}
|
||||
|
||||
const inDecimals = countLeadingZeros(swap_in_amount) + 2
|
||||
const outDecimals = countLeadingZeros(swap_out_amount) + 2
|
||||
|
||||
return (
|
||||
<div key={signature} className="border-b border-th-bkg-3 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
@ -288,7 +291,7 @@ const SwapHistoryTable = () => {
|
|||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap font-mono text-th-fgd-1">
|
||||
{swap_in_amount.toFixed(2)}{' '}
|
||||
{formatDecimal(swap_in_amount, inDecimals)}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{inSymbol}
|
||||
</span>
|
||||
|
@ -313,7 +316,7 @@ const SwapHistoryTable = () => {
|
|||
</div>
|
||||
<div>
|
||||
<p className="whitespace-nowrap font-mono text-th-fgd-1">
|
||||
{swap_out_amount.toFixed(2)}{' '}
|
||||
{formatDecimal(swap_out_amount, outDecimals)}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{outSymbol}
|
||||
</span>
|
||||
|
@ -327,7 +330,10 @@ const SwapHistoryTable = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<IconButton onClick={() => handleShowSwapDetails(signature)}>
|
||||
<IconButton
|
||||
onClick={() => handleShowSwapDetails(signature)}
|
||||
size="medium"
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showSwapDetails === signature
|
||||
|
|
|
@ -42,6 +42,7 @@ import Tooltip from '@components/shared/Tooltip'
|
|||
import { Disclosure } from '@headlessui/react'
|
||||
import RoutesModal from './RoutesModal'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { createAssociatedTokenAccountIdempotentInstruction } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
type JupiterRouteInfoProps = {
|
||||
amountIn: Decimal
|
||||
|
@ -78,6 +79,40 @@ const deserializeJupiterIxAndAlt = async (
|
|||
return [decompiledMessage.instructions, addressLookupTables]
|
||||
}
|
||||
|
||||
const prepareMangoRouterInstructions = async (
|
||||
selectedRoute: RouteInfo,
|
||||
inputMint: PublicKey,
|
||||
outputMint: PublicKey,
|
||||
userPublicKey: PublicKey
|
||||
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
|
||||
if (!selectedRoute || !selectedRoute.mints || !selectedRoute.instructions) {
|
||||
return [[], []]
|
||||
}
|
||||
const mintsToFilterOut = [inputMint, outputMint]
|
||||
const filteredOutMints = [
|
||||
...selectedRoute.mints.filter(
|
||||
(routeMint) =>
|
||||
!mintsToFilterOut.find((filterOutMint) =>
|
||||
filterOutMint.equals(routeMint)
|
||||
)
|
||||
),
|
||||
]
|
||||
const additionalInstructions = []
|
||||
for (const mint of filteredOutMints) {
|
||||
const ix = await createAssociatedTokenAccountIdempotentInstruction(
|
||||
userPublicKey,
|
||||
userPublicKey,
|
||||
mint
|
||||
)
|
||||
additionalInstructions.push(ix)
|
||||
}
|
||||
const instructions = [
|
||||
...additionalInstructions,
|
||||
...selectedRoute.instructions,
|
||||
]
|
||||
return [instructions, []]
|
||||
}
|
||||
|
||||
const fetchJupiterTransaction = async (
|
||||
connection: Connection,
|
||||
selectedRoute: RouteInfo,
|
||||
|
@ -217,14 +252,22 @@ const SwapReviewRouteInfo = ({
|
|||
|
||||
if (!mangoAccount || !group || !inputBank || !outputBank) return
|
||||
setSubmitting(true)
|
||||
const [ixs, alts] = await fetchJupiterTransaction(
|
||||
connection,
|
||||
selectedRoute,
|
||||
mangoAccount.owner,
|
||||
slippage,
|
||||
inputBank.mint,
|
||||
outputBank.mint
|
||||
)
|
||||
const [ixs, alts] =
|
||||
selectedRoute.routerName === 'Mango'
|
||||
? await prepareMangoRouterInstructions(
|
||||
selectedRoute,
|
||||
inputBank.mint,
|
||||
outputBank.mint,
|
||||
mangoAccount.owner
|
||||
)
|
||||
: await fetchJupiterTransaction(
|
||||
connection,
|
||||
selectedRoute,
|
||||
mangoAccount.owner,
|
||||
slippage,
|
||||
inputBank.mint,
|
||||
outputBank.mint
|
||||
)
|
||||
|
||||
try {
|
||||
const tx = await client.marginTrade({
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
import Decimal from 'decimal.js'
|
||||
import { RouteInfo } from 'types/jupiter'
|
||||
import useJupiterSwapData from './useJupiterSwapData'
|
||||
|
||||
type useJupiterPropTypes = {
|
||||
inputMint: string
|
||||
outputMint: string
|
||||
amount: string
|
||||
slippage: number
|
||||
swapMode: string
|
||||
}
|
||||
|
||||
const fetchJupiterRoutes = async (
|
||||
inputMint = 'So11111111111111111111111111111111111111112',
|
||||
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
amount = 0,
|
||||
slippage = 50,
|
||||
swapMode = 'ExactIn',
|
||||
feeBps = 0
|
||||
) => {
|
||||
{
|
||||
const paramsString = new URLSearchParams({
|
||||
inputMint: inputMint.toString(),
|
||||
outputMint: outputMint.toString(),
|
||||
amount: amount.toString(),
|
||||
slippageBps: Math.ceil(slippage * 100).toString(),
|
||||
feeBps: feeBps.toString(),
|
||||
swapMode,
|
||||
}).toString()
|
||||
|
||||
const response = await fetch(
|
||||
`https://quote-api.jup.ag/v4/quote?${paramsString}`
|
||||
)
|
||||
|
||||
const res = await response.json()
|
||||
const data = res.data
|
||||
|
||||
return {
|
||||
routes: res.data,
|
||||
bestRoute: data[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const useJupiterRoutes = ({
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
}: useJupiterPropTypes) => {
|
||||
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
|
||||
|
||||
const decimals =
|
||||
swapMode === 'ExactIn'
|
||||
? inputTokenInfo?.decimals || 6
|
||||
: outputTokenInfo?.decimals || 6
|
||||
|
||||
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],
|
||||
async () =>
|
||||
fetchJupiterRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
nativeAmount.toNumber(),
|
||||
slippage,
|
||||
swapMode
|
||||
),
|
||||
{
|
||||
enabled: amount ? true : false,
|
||||
}
|
||||
)
|
||||
|
||||
return amount
|
||||
? {
|
||||
...(res.data ?? {
|
||||
routes: [],
|
||||
bestRoute: undefined,
|
||||
}),
|
||||
isLoading: res.isLoading,
|
||||
}
|
||||
: {
|
||||
routes: [],
|
||||
bestRoute: undefined,
|
||||
isLoading: false,
|
||||
}
|
||||
}
|
||||
|
||||
export default useJupiterRoutes
|
|
@ -0,0 +1,207 @@
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import Decimal from 'decimal.js'
|
||||
import { RouteInfo } from 'types/jupiter'
|
||||
import { MANGO_ROUTER_API_URL } from 'utils/constants'
|
||||
import useJupiterSwapData from './useJupiterSwapData'
|
||||
|
||||
type useQuoteRoutesPropTypes = {
|
||||
inputMint: string
|
||||
outputMint: string
|
||||
amount: string
|
||||
slippage: number
|
||||
swapMode: string
|
||||
wallet: string | undefined | null
|
||||
}
|
||||
|
||||
const fetchJupiterRoutes = async (
|
||||
inputMint = 'So11111111111111111111111111111111111111112',
|
||||
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
amount = 0,
|
||||
slippage = 50,
|
||||
swapMode = 'ExactIn',
|
||||
feeBps = 0
|
||||
) => {
|
||||
{
|
||||
const paramsString = new URLSearchParams({
|
||||
inputMint: inputMint.toString(),
|
||||
outputMint: outputMint.toString(),
|
||||
amount: amount.toString(),
|
||||
slippageBps: Math.ceil(slippage * 100).toString(),
|
||||
feeBps: feeBps.toString(),
|
||||
swapMode,
|
||||
}).toString()
|
||||
|
||||
const response = await fetch(
|
||||
`https://quote-api.jup.ag/v4/quote?${paramsString}`
|
||||
)
|
||||
|
||||
const res = await response.json()
|
||||
const data = res.data
|
||||
|
||||
return {
|
||||
routes: res.data as RouteInfo[],
|
||||
bestRoute: (data.length ? data[0] : null) as RouteInfo | null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fetchMangoRoutes = async (
|
||||
inputMint = 'So11111111111111111111111111111111111111112',
|
||||
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
amount = 0,
|
||||
slippage = 50,
|
||||
swapMode = 'ExactIn',
|
||||
feeBps = 0,
|
||||
wallet = PublicKey.default.toBase58()
|
||||
) => {
|
||||
{
|
||||
const defaultOtherAmount =
|
||||
swapMode === 'ExactIn' ? 0 : Number.MAX_SAFE_INTEGER
|
||||
|
||||
const paramsString = new URLSearchParams({
|
||||
inputMint: inputMint.toString(),
|
||||
outputMint: outputMint.toString(),
|
||||
amount: amount.toString(),
|
||||
slippage: ((slippage * 1) / 100).toString(),
|
||||
feeBps: feeBps.toString(),
|
||||
mode: swapMode,
|
||||
wallet: wallet,
|
||||
otherAmountThreshold: defaultOtherAmount.toString(),
|
||||
}).toString()
|
||||
|
||||
const response = await fetch(`${MANGO_ROUTER_API_URL}/swap?${paramsString}`)
|
||||
|
||||
const res = await response.json()
|
||||
const data: RouteInfo[] = res.map((route: any) => ({
|
||||
...route,
|
||||
priceImpactPct: route.priceImpact,
|
||||
slippageBps: slippage,
|
||||
marketInfos: route.marketInfos.map((mInfo: any) => ({
|
||||
...mInfo,
|
||||
lpFee: {
|
||||
...mInfo.fee,
|
||||
pct: mInfo.fee.rate,
|
||||
},
|
||||
})),
|
||||
mints: route.mints.map((x: string) => new PublicKey(x)),
|
||||
instructions: route.instructions.map((ix: any) => ({
|
||||
...ix,
|
||||
programId: new PublicKey(ix.programId),
|
||||
data: Buffer.from(ix.data, 'base64'),
|
||||
keys: ix.keys.map((key: any) => ({
|
||||
...key,
|
||||
pubkey: new PublicKey(key.pubkey),
|
||||
})),
|
||||
})),
|
||||
routerName: 'Mango',
|
||||
}))
|
||||
return {
|
||||
routes: data,
|
||||
bestRoute: (data.length ? data[0] : null) as RouteInfo | null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleGetRoutes = async (
|
||||
inputMint = 'So11111111111111111111111111111111111111112',
|
||||
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
amount = 0,
|
||||
slippage = 50,
|
||||
swapMode = 'ExactIn',
|
||||
feeBps = 0,
|
||||
wallet: string | undefined | null
|
||||
) => {
|
||||
wallet ||= PublicKey.default.toBase58()
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
fetchMangoRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps,
|
||||
wallet
|
||||
),
|
||||
fetchJupiterRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
feeBps
|
||||
),
|
||||
])
|
||||
const responses = results
|
||||
.filter((x) => x.status === 'fulfilled' && x.value.bestRoute !== null)
|
||||
.map((x) => (x as any).value)
|
||||
|
||||
const sortedByBiggestOutAmount = (
|
||||
responses as {
|
||||
routes: RouteInfo[]
|
||||
bestRoute: RouteInfo
|
||||
}[]
|
||||
).sort(
|
||||
(a, b) => Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount)
|
||||
)
|
||||
|
||||
return {
|
||||
routes: sortedByBiggestOutAmount[0].routes,
|
||||
bestRoute: sortedByBiggestOutAmount[0].bestRoute,
|
||||
}
|
||||
}
|
||||
|
||||
const useQuoteRoutes = ({
|
||||
inputMint,
|
||||
outputMint,
|
||||
amount,
|
||||
slippage,
|
||||
swapMode,
|
||||
wallet,
|
||||
}: useQuoteRoutesPropTypes) => {
|
||||
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
|
||||
|
||||
const decimals =
|
||||
swapMode === 'ExactIn'
|
||||
? inputTokenInfo?.decimals || 6
|
||||
: outputTokenInfo?.decimals || 6
|
||||
|
||||
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, wallet],
|
||||
async () =>
|
||||
handleGetRoutes(
|
||||
inputMint,
|
||||
outputMint,
|
||||
nativeAmount.toNumber(),
|
||||
slippage,
|
||||
swapMode,
|
||||
0,
|
||||
wallet
|
||||
),
|
||||
{
|
||||
enabled: amount ? true : false,
|
||||
}
|
||||
)
|
||||
|
||||
return amount
|
||||
? {
|
||||
...(res.data ?? {
|
||||
routes: [],
|
||||
bestRoute: undefined,
|
||||
}),
|
||||
isLoading: res.isLoading,
|
||||
}
|
||||
: {
|
||||
routes: [],
|
||||
bestRoute: undefined,
|
||||
isLoading: false,
|
||||
}
|
||||
}
|
||||
|
||||
export default useQuoteRoutes
|
|
@ -153,6 +153,12 @@ const AdvancedTradeForm = () => {
|
|||
})
|
||||
}, [])
|
||||
|
||||
const handleReduceOnlyChange = useCallback((reduceOnly: boolean) => {
|
||||
set((s) => {
|
||||
s.tradeForm.reduceOnly = reduceOnly
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleSetSide = useCallback((side: 'buy' | 'sell') => {
|
||||
set((s) => {
|
||||
s.tradeForm.side = side
|
||||
|
@ -296,6 +302,7 @@ const AdvancedTradeForm = () => {
|
|||
undefined, // maxQuoteQuantity
|
||||
Date.now(),
|
||||
perpOrderType,
|
||||
selectedMarket.reduceOnly || tradeForm.reduceOnly,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
@ -533,7 +540,27 @@ const AdvancedTradeForm = () => {
|
|||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="mr-3 mt-4">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={250}
|
||||
placement="left"
|
||||
content={
|
||||
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
|
||||
}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={tradeForm.reduceOnly}
|
||||
onChange={(e) => handleReduceOnlyChange(e.target.checked)}
|
||||
>
|
||||
Reduce Only
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 mb-4 flex px-3 md:px-4">
|
||||
{ipAllowed ? (
|
||||
|
|
|
@ -33,8 +33,12 @@ const MarketLogos = ({
|
|||
)
|
||||
} else {
|
||||
jupiterBaseToken =
|
||||
mangoTokens.find((t) => t.symbol === market.name.split('-')[0]) ||
|
||||
mangoTokens.find((t) => t.symbol.includes(market.name.split('-')[0]))
|
||||
mangoTokens.find(
|
||||
(t) => t.symbol.toUpperCase() === market.name.split('-')[0]
|
||||
) ||
|
||||
mangoTokens.find((t) =>
|
||||
t.symbol.toUpperCase()?.includes(market.name.split('-')[0])
|
||||
)
|
||||
}
|
||||
const baseLogoURI = jupiterBaseToken ? jupiterBaseToken.logoURI : ''
|
||||
const quoteLogoURI = jupiterQuoteToken ? jupiterQuoteToken.logoURI : ''
|
||||
|
|
|
@ -10,8 +10,8 @@ import { useMemo, useState } from 'react'
|
|||
import { DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||
import MarketLogos from './MarketLogos'
|
||||
|
||||
const TAB_VALUES =
|
||||
process.env.NEXT_PUBLIC_SHOW_PERPS === 'true' ? ['spot', 'perp'] : ['spot']
|
||||
const isTesting = process.env.NEXT_PUBLIC_SHOW_PERPS === 'true'
|
||||
const TAB_VALUES = isTesting ? ['spot', 'perp'] : ['spot']
|
||||
|
||||
const MarketSelectDropdown = () => {
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
|
@ -114,36 +114,38 @@ const MarketSelectDropdown = () => {
|
|||
) : null}
|
||||
{activeTab === 'perp'
|
||||
? perpMarkets?.length
|
||||
? perpMarkets.map((m) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between py-2 px-4"
|
||||
key={m.publicKey.toString()}
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
shallow={true}
|
||||
? perpMarkets
|
||||
.filter((m) => m.name !== 'MNGO-PERP' || isTesting)
|
||||
.map((m) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between py-2 px-4"
|
||||
key={m.publicKey.toString()}
|
||||
>
|
||||
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-active'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-active'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
: null
|
||||
: null}
|
||||
</Popover.Panel>
|
||||
|
|
|
@ -204,7 +204,7 @@ const Orderbook = () => {
|
|||
const isMobile = width ? width < breakpoints.md : false
|
||||
|
||||
const depthArray = useMemo(() => {
|
||||
const bookDepth = !isMobile ? depth : 7
|
||||
const bookDepth = !isMobile ? depth : 9
|
||||
return Array(bookDepth).fill(0)
|
||||
}, [isMobile])
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import useMangoGroup from 'hooks/useMangoGroup'
|
|||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import {
|
||||
formatDecimal,
|
||||
formatFixedDecimals,
|
||||
getDecimalCount,
|
||||
numberFormat,
|
||||
|
@ -66,8 +67,8 @@ const PerpPositions = () => {
|
|||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('trade:notional')}</Th>
|
||||
<Th className="text-right">{t('trade:entry-price')}</Th>
|
||||
<Th className="text-right">Redeemable PnL</Th>
|
||||
<Th className="text-right">Realized PnL</Th>
|
||||
<Th className="text-right">Unsettled PnL</Th>
|
||||
<Th className="text-right">PnL</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -86,7 +87,11 @@ const PerpPositions = () => {
|
|||
|
||||
if (!basePosition) return null
|
||||
|
||||
const unsettledPnl = position.getEquityUi(group, market)
|
||||
const unsettledPnl = position.getUnsettledPnlUi(group, market)
|
||||
const cummulativePnl = position.cumulativePnlOverPositionLifetimeUi(
|
||||
group,
|
||||
market
|
||||
)
|
||||
|
||||
return (
|
||||
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
|
||||
|
@ -96,7 +101,7 @@ const PerpPositions = () => {
|
|||
<Td className="text-right">
|
||||
<PerpSideBadge basePosition={basePosition} />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<Td className="text-right font-mono">
|
||||
<p className="flex justify-end">
|
||||
{isSelectedMarket ? (
|
||||
<LinkButton
|
||||
|
@ -123,20 +128,15 @@ const PerpPositions = () => {
|
|||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td className={`text-right font-mono`}>
|
||||
<div>{formatDecimal(unsettledPnl, market.baseDecimals)}</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
unsettledPnl > 0 ? 'text-th-up' : 'text-th-down'
|
||||
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<div>{formatFixedDecimals(unsettledPnl, true)}</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<div>
|
||||
$
|
||||
{/* {numberFormat.format(
|
||||
position.perpSpotTransfers.toNumber()
|
||||
)} */}
|
||||
</div>
|
||||
<div>{formatFixedDecimals(cummulativePnl, true)}</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import SideBadge from '@components/shared/SideBadge'
|
||||
|
||||
const PerpSideBadge = ({ basePosition }: { basePosition: number }) => (
|
||||
<>
|
||||
{basePosition !== 0 ? (
|
||||
<SideBadge side={basePosition > 0 ? 'long' : 'short'} />
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</>
|
||||
)
|
||||
const PerpSideBadge = ({ basePosition }: { basePosition: number }) => {
|
||||
return (
|
||||
<>
|
||||
{basePosition !== 0 ? (
|
||||
<SideBadge side={basePosition > 0 ? 'long' : 'short'} />
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PerpSideBadge
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { I80F48, PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import {
|
||||
Table,
|
||||
|
@ -14,7 +16,9 @@ import mangoStore from '@store/mangoStore'
|
|||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { PAGINATION_PAGE_LENGTH } from 'utils/constants'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import TableMarketName from './TableMarketName'
|
||||
|
@ -86,11 +90,17 @@ const formatTradeHistory = (
|
|||
}
|
||||
|
||||
const TradeHistory = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const group = mangoStore.getState().group
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const fills = mangoStore((s) => s.selectedMarket.fills)
|
||||
const tradeHistory = mangoStore((s) => s.mangoAccount.tradeHistory)
|
||||
const tradeHistory = mangoStore((s) => s.mangoAccount.tradeHistory.data)
|
||||
const loadingTradeHistory = mangoStore(
|
||||
(s) => s.mangoAccount.tradeHistory.loading
|
||||
)
|
||||
const [offset, setOffset] = useState(0)
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
|
@ -151,27 +161,35 @@ const TradeHistory = () => {
|
|||
return [...newFills, ...tradeHistory]
|
||||
}, [eventQueueFillsForAccount, tradeHistory])
|
||||
|
||||
console.log('trade history', tradeHistory)
|
||||
const handleShowMore = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
set((s) => {
|
||||
s.mangoAccount.tradeHistory.loading = true
|
||||
})
|
||||
setOffset(offset + PAGINATION_PAGE_LENGTH)
|
||||
actions.fetchTradeHistory(offset + PAGINATION_PAGE_LENGTH)
|
||||
}, [actions, offset])
|
||||
|
||||
if (!selectedMarket || !group) return null
|
||||
|
||||
return mangoAccount && combinedTradeHistory.length ? (
|
||||
showTableView ? (
|
||||
<div>
|
||||
return mangoAccount &&
|
||||
(combinedTradeHistory.length || loadingTradeHistory) ? (
|
||||
<>
|
||||
{showTableView ? (
|
||||
<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>
|
||||
<Th className="text-right">Time</Th>
|
||||
<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('price')}</Th>
|
||||
<Th className="text-right">{t('value')}</Th>
|
||||
<Th className="text-right">{t('fee')}</Th>
|
||||
<Th className="text-right">{t('date')}</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{combinedTradeHistory.map((trade: any) => {
|
||||
{combinedTradeHistory.map((trade: any, index: number) => {
|
||||
let market
|
||||
if ('market' in trade) {
|
||||
market = group.getSerum3MarketByExternalMarket(
|
||||
|
@ -202,7 +220,7 @@ const TradeHistory = () => {
|
|||
|
||||
return (
|
||||
<TrBody
|
||||
key={`${trade.signature || trade.marketIndex}${size}`}
|
||||
key={`${trade.signature || trade.marketIndex}${index}`}
|
||||
className="my-1 p-2"
|
||||
>
|
||||
<Td className="">
|
||||
|
@ -216,7 +234,7 @@ const TradeHistory = () => {
|
|||
{formatDecimal(trade.price)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatFixedDecimals(trade.price * size)}
|
||||
{formatFixedDecimals(trade.price * size, true, true)}
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">{formatDecimal(fee)}</span>
|
||||
|
@ -240,36 +258,64 @@ const TradeHistory = () => {
|
|||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{eventQueueFillsForAccount.map((trade: any) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${trade.marketIndex}`}
|
||||
>
|
||||
<div>
|
||||
<TableMarketName market={selectedMarket} />
|
||||
<div className="mt-1 flex items-center space-x-1">
|
||||
<SideBadge side={trade.side} />
|
||||
<p className="text-th-fgd-4">
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{trade.size}
|
||||
</span>
|
||||
{' for '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{formatDecimal(trade.price)}
|
||||
</span>
|
||||
) : (
|
||||
<div>
|
||||
{combinedTradeHistory.map((trade: any, index: number) => {
|
||||
const size = trade.size || trade.quantity
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${trade.marketIndex}${index}`}
|
||||
>
|
||||
<div>
|
||||
<TableMarketName market={selectedMarket} />
|
||||
<div className="mt-1 flex items-center space-x-1">
|
||||
<SideBadge side={trade.side} />
|
||||
<p className="text-th-fgd-4">
|
||||
<span className="font-mono text-th-fgd-2">{size}</span>
|
||||
{' for '}
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
{formatDecimal(trade.price)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<span className="mb-0.5 flex items-center space-x-1.5">
|
||||
{trade.block_datetime ? (
|
||||
<TableDateDisplay
|
||||
date={trade.block_datetime}
|
||||
showSeconds
|
||||
/>
|
||||
) : (
|
||||
'Recent'
|
||||
)}
|
||||
</span>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
{formatFixedDecimals(trade.price * size, true, true)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-mono">${trade.value.toFixed(2)}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{loadingTradeHistory ? (
|
||||
<div className="mt-4 space-y-1.5">
|
||||
{[...Array(4)].map((x, i) => (
|
||||
<SheenLoader className="mx-4 flex flex-1 md:mx-6" key={i}>
|
||||
<div className="h-16 w-full bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{combinedTradeHistory.length &&
|
||||
combinedTradeHistory.length % PAGINATION_PAGE_LENGTH === 0 ? (
|
||||
<div className="flex justify-center py-6">
|
||||
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
|
|
|
@ -204,7 +204,7 @@ const UnsettledTrades = ({
|
|||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{formatDecimal(
|
||||
position.getEquityUi(group, market),
|
||||
position.getUnsettledPnlUi(group, market),
|
||||
market.baseDecimals
|
||||
)}
|
||||
</Td>
|
||||
|
|
|
@ -29,24 +29,28 @@ export const ConnectWalletButton: React.FC = () => {
|
|||
>
|
||||
<div className="relative flex h-16 w-44 bg-th-bkg-2 py-2 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:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100">
|
||||
<div className="default-transition relative z-10 flex h-full items-center justify-center space-x-3 px-4">
|
||||
<div
|
||||
className={`flex h-[28px] w-[28px] items-center justify-center rounded-full ${
|
||||
wallet?.adapter.name === 'Solflare' ? 'bg-black' : ''
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={wallet?.adapter.icon || selectedWallet?.adapter.icon}
|
||||
className={
|
||||
wallet?.adapter.name === 'Solflare'
|
||||
? 'h-auto w-[20px]'
|
||||
: 'h-auto w-[28px]'
|
||||
}
|
||||
alt={`${wallet?.adapter.name} icon`}
|
||||
/>
|
||||
</div>
|
||||
{connecting ? (
|
||||
<Loading className="h-[28px] w-[28px]" />
|
||||
) : (
|
||||
<div
|
||||
className={`flex h-[28px] w-[28px] items-center justify-center rounded-full ${
|
||||
wallet?.adapter.name === 'Solflare' ? 'bg-black' : ''
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={wallet?.adapter.icon || selectedWallet?.adapter.icon}
|
||||
className={
|
||||
wallet?.adapter.name === 'Solflare'
|
||||
? 'h-auto w-[20px]'
|
||||
: 'h-auto w-[28px]'
|
||||
}
|
||||
alt={`${wallet?.adapter.name} icon`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-left">
|
||||
<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 className="mb-1.5 flex font-display text-base leading-none text-th-fgd-1">
|
||||
{t('connect')}
|
||||
</div>
|
||||
|
||||
<div className="text-xxs font-normal leading-3 text-th-fgd-3">
|
||||
|
|
|
@ -40,8 +40,8 @@ export default function useSelectedMarket() {
|
|||
const baseLogoURI = useMemo(() => {
|
||||
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.toUpperCase() === baseSymbol) ||
|
||||
mangoTokens.find((t) => t.symbol.toUpperCase()?.includes(baseSymbol))
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
|
@ -64,7 +64,9 @@ export default function useSelectedMarket() {
|
|||
|
||||
const quoteLogoURI = useMemo(() => {
|
||||
if (!quoteSymbol || !mangoTokens.length) return ''
|
||||
const token = mangoTokens.find((t) => t.symbol === quoteSymbol)
|
||||
const token = mangoTokens.find(
|
||||
(t) => t.symbol.toUpperCase() === quoteSymbol
|
||||
)
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ const useUnsettledPerpPositions = () => {
|
|||
|
||||
return perpPositions.filter((p) => {
|
||||
const market = group?.getPerpMarketByMarketIndex(p.marketIndex)
|
||||
if (!market) return false
|
||||
return p.getPnl(market).toNumber() > 0
|
||||
if (!market || !group) return false
|
||||
return p.getUnsettledPnlUi(group, market) !== 0
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '@heroicons/react/20/solid'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import MarketLogos from '@components/trade/MarketLogos'
|
||||
import Button from '@components/shared/Button'
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
|
@ -55,248 +56,284 @@ const Dashboard: NextPage = () => {
|
|||
address={group?.publicKey.toString()}
|
||||
anchorData
|
||||
></ExplorerLink>
|
||||
<div className="mt-4 flex space-x-4">
|
||||
<Button
|
||||
secondary
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const panels = [
|
||||
...document.querySelectorAll(
|
||||
'[aria-expanded=false][aria-label=panel]'
|
||||
),
|
||||
]
|
||||
panels.map((panel) => (panel as HTMLElement).click())
|
||||
}}
|
||||
>
|
||||
Expand All
|
||||
</Button>
|
||||
<Button
|
||||
secondary
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const panels = [
|
||||
...document.querySelectorAll(
|
||||
'[aria-expanded=true][aria-label=panel]'
|
||||
),
|
||||
]
|
||||
panels.map((panel) => (panel as HTMLElement).click())
|
||||
}}
|
||||
>
|
||||
Collpase All
|
||||
</Button>
|
||||
</div>
|
||||
<h3 className="mt-6 mb-3 text-base text-th-fgd-3">Banks</h3>
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{Array.from(group.banksMapByMint).map(([mintAddress, banks]) =>
|
||||
banks.map((bank) => {
|
||||
const logoUri = mangoTokens.length
|
||||
? mangoTokens.find((t) => t.address === mintAddress)
|
||||
?.logoURI
|
||||
: ''
|
||||
return (
|
||||
<Disclosure key={bank.publicKey.toString()}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={`default-transition flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-2 ${
|
||||
open ? 'bg-th-bkg-2' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{logoUri ? (
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={logoUri}
|
||||
/>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)}
|
||||
<p className="ml-2 text-th-fgd-2">
|
||||
{bank.name} Bank
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} h-5 w-5 text-th-fgd-3`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<KeyValuePair
|
||||
label="Mint"
|
||||
value={<ExplorerLink address={mintAddress} />}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Bank"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.publicKey.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Vault"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.vault.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.oracle.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Token Index"
|
||||
value={bank.tokenIndex}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle Price"
|
||||
value={`$${bank.uiPrice}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price"
|
||||
value={`$${group.toUiPrice(
|
||||
I80F48.fromNumber(
|
||||
bank.stablePriceModel.stablePrice
|
||||
),
|
||||
bank.mintDecimals
|
||||
)}`}
|
||||
/>
|
||||
<VaultData bank={bank} />
|
||||
<KeyValuePair
|
||||
label="Loan Fee Rate"
|
||||
value={`${(
|
||||
10000 * bank.loanFeeRate.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Loan origination fee rate"
|
||||
value={`${(
|
||||
10000 * bank.loanOriginationFeeRate.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Collected fees native"
|
||||
value={bank.collectedFeesNative.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${(
|
||||
10000 * bank.liquidationFee.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Dust"
|
||||
value={bank.dust.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposits"
|
||||
value={toUiDecimals(
|
||||
bank.indexedDeposits
|
||||
.mul(bank.depositIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrows"
|
||||
value={toUiDecimals(
|
||||
bank.indexedBorrows
|
||||
.mul(bank.borrowIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Utilization"
|
||||
value={`${
|
||||
bank.avgUtilization.toNumber() * 100
|
||||
}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint Asset/Liab Weight"
|
||||
value={`${bank.maintAssetWeight.toFixed(2)}/
|
||||
{Array.from(group.banksMapByMint)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.map(([mintAddress, banks]) =>
|
||||
banks.map((bank) => {
|
||||
const logoUri = mangoTokens.length
|
||||
? mangoTokens.find((t) => t.address === mintAddress)
|
||||
?.logoURI
|
||||
: ''
|
||||
return (
|
||||
<Disclosure key={bank.publicKey.toString()}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
aria-label="panel"
|
||||
className={`default-transition flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-2 ${
|
||||
open ? 'bg-th-bkg-2' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{logoUri ? (
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={logoUri}
|
||||
/>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)}
|
||||
<p className="ml-2 text-th-fgd-2">
|
||||
{bank.name} Bank
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} h-5 w-5 text-th-fgd-3`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<KeyValuePair
|
||||
label="Mint"
|
||||
value={<ExplorerLink address={mintAddress} />}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Bank"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.publicKey.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Vault"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.vault.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.oracle.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Token Index"
|
||||
value={bank.tokenIndex}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle Price"
|
||||
value={`$${bank.uiPrice}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price"
|
||||
value={`$${group.toUiPrice(
|
||||
I80F48.fromNumber(
|
||||
bank.stablePriceModel.stablePrice
|
||||
),
|
||||
bank.mintDecimals
|
||||
)}`}
|
||||
/>
|
||||
<VaultData bank={bank} />
|
||||
<KeyValuePair
|
||||
label="Loan Fee Rate"
|
||||
value={`${(
|
||||
10000 * bank.loanFeeRate.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Loan origination fee rate"
|
||||
value={`${(
|
||||
10000 *
|
||||
bank.loanOriginationFeeRate.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Collected fees native"
|
||||
value={bank.collectedFeesNative.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${(
|
||||
10000 * bank.liquidationFee.toNumber()
|
||||
).toFixed(2)} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Dust"
|
||||
value={bank.dust.toNumber()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposits"
|
||||
value={toUiDecimals(
|
||||
bank.indexedDeposits
|
||||
.mul(bank.depositIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrows"
|
||||
value={toUiDecimals(
|
||||
bank.indexedBorrows
|
||||
.mul(bank.borrowIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Utilization"
|
||||
value={`${
|
||||
bank.avgUtilization.toNumber() * 100
|
||||
}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint Asset/Liab Weight"
|
||||
value={`${bank.maintAssetWeight.toFixed(2)}/
|
||||
${bank.maintLiabWeight.toFixed(2)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${bank.initAssetWeight.toFixed(2)}/
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${bank.initAssetWeight.toFixed(2)}/
|
||||
${bank.initLiabWeight.toFixed(2)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Scaled Init Asset/Liab Weight"
|
||||
value={`${bank
|
||||
.scaledInitAssetWeight()
|
||||
.toFixed(4)}/
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Scaled Init Asset/Liab Weight"
|
||||
value={`${bank
|
||||
.scaledInitAssetWeight()
|
||||
.toFixed(4)}/
|
||||
${bank.scaledInitLiabWeight().toFixed(4)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit weight scale start quote"
|
||||
value={bank.depositWeightScaleStartQuote}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow weight scale start quote"
|
||||
value={bank.borrowWeightScaleStartQuote}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Rate params"
|
||||
value={
|
||||
<span className="text-right">
|
||||
{`${(100 * bank.rate0.toNumber()).toFixed(
|
||||
2
|
||||
)}% @ ${(
|
||||
100 * bank.util0.toNumber()
|
||||
).toFixed()}% util, `}
|
||||
{`${(100 * bank.rate1.toNumber()).toFixed(
|
||||
2
|
||||
)}% @ ${(
|
||||
100 * bank.util1.toNumber()
|
||||
).toFixed()}% util, `}
|
||||
{`${(100 * bank.maxRate.toNumber()).toFixed(
|
||||
2
|
||||
)}% @ 100% util`}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit rate"
|
||||
value={`${bank.getDepositRateUi()}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow rate"
|
||||
value={`${bank.getBorrowRateUi()}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last index update"
|
||||
value={new Date(
|
||||
1000 * bank.indexLastUpdated.toNumber()
|
||||
).toUTCString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last rates updated"
|
||||
value={new Date(
|
||||
1000 * bank.bankRateLastUpdated.toNumber()
|
||||
).toUTCString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last stable price updated"
|
||||
value={new Date(
|
||||
1000 *
|
||||
bank.stablePriceModel.lastUpdateTimestamp.toNumber()
|
||||
).toUTCString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: delay interval"
|
||||
value={`${bank.stablePriceModel.delayIntervalSeconds}s`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: growth limits"
|
||||
value={`${(
|
||||
100 * bank.stablePriceModel.delayGrowthLimit
|
||||
).toFixed(2)}% delay / ${(
|
||||
100 * bank.stablePriceModel.stableGrowthLimit
|
||||
).toFixed(2)}% stable`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Conf Filter"
|
||||
value={`${(
|
||||
100 * bank.oracleConfig.confFilter.toNumber()
|
||||
).toFixed(2)}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Max Staleness"
|
||||
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="netBorrowsInWindow / netBorrowLimitPerWindowQuote"
|
||||
value={`${bank.netBorrowsInWindow.toNumber()} / ${bank.netBorrowLimitPerWindowQuote.toNumber()}`}
|
||||
/>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
})
|
||||
)}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit weight scale start quote"
|
||||
value={bank.depositWeightScaleStartQuote}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow weight scale start quote"
|
||||
value={bank.borrowWeightScaleStartQuote}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Rate params"
|
||||
value={
|
||||
<span className="text-right">
|
||||
{`${(100 * bank.rate0.toNumber()).toFixed(
|
||||
2
|
||||
)}% @ ${(
|
||||
100 * bank.util0.toNumber()
|
||||
).toFixed()}% util, `}
|
||||
{`${(100 * bank.rate1.toNumber()).toFixed(
|
||||
2
|
||||
)}% @ ${(
|
||||
100 * bank.util1.toNumber()
|
||||
).toFixed()}% util, `}
|
||||
{`${(
|
||||
100 * bank.maxRate.toNumber()
|
||||
).toFixed(2)}% @ 100% util`}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit rate"
|
||||
value={`${bank.getDepositRateUi()}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow rate"
|
||||
value={`${bank.getBorrowRateUi()}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last index update"
|
||||
value={new Date(
|
||||
1000 * bank.indexLastUpdated.toNumber()
|
||||
).toUTCString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last rates updated"
|
||||
value={new Date(
|
||||
1000 * bank.bankRateLastUpdated.toNumber()
|
||||
).toUTCString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last stable price updated"
|
||||
value={new Date(
|
||||
1000 *
|
||||
bank.stablePriceModel.lastUpdateTimestamp.toNumber()
|
||||
).toUTCString()}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: delay interval"
|
||||
value={`${bank.stablePriceModel.delayIntervalSeconds}s`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: growth limits"
|
||||
value={`${(
|
||||
100 * bank.stablePriceModel.delayGrowthLimit
|
||||
).toFixed(2)}% delay / ${(
|
||||
100 *
|
||||
bank.stablePriceModel.stableGrowthLimit
|
||||
).toFixed(2)}% stable`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Conf Filter"
|
||||
value={`${(
|
||||
100 *
|
||||
bank.oracleConfig.confFilter.toNumber()
|
||||
).toFixed(2)}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Max Staleness"
|
||||
value={`${bank.oracleConfig.maxStalenessSlots} slots`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="netBorrowsInWindow / netBorrowLimitPerWindowQuote"
|
||||
value={`${bank.netBorrowsInWindow.toNumber()} / ${bank.netBorrowLimitPerWindowQuote.toNumber()}`}
|
||||
/>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="mt-6 mb-3 text-base text-th-fgd-3">
|
||||
|
@ -310,6 +347,7 @@ const Dashboard: NextPage = () => {
|
|||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
aria-label="panel"
|
||||
className={`default-transition flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-2 ${
|
||||
open ? 'bg-th-bkg-2' : ''
|
||||
}`}
|
||||
|
@ -375,6 +413,10 @@ const Dashboard: NextPage = () => {
|
|||
label="Perp Market Index"
|
||||
value={perpMarket.perpMarketIndex}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Reduce Only"
|
||||
value={perpMarket.reduceOnly ? 'True' : 'False'}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle Price"
|
||||
value={`$${perpMarket.uiPrice}`}
|
||||
|
@ -399,17 +441,29 @@ const Dashboard: NextPage = () => {
|
|||
/>
|
||||
<KeyValuePair
|
||||
label="Maint Asset/Liab Weight"
|
||||
value={`${perpMarket.maintAssetWeight.toFixed(
|
||||
value={`${perpMarket.maintBaseAssetWeight.toFixed(
|
||||
4
|
||||
)}/
|
||||
${perpMarket.maintLiabWeight.toFixed(4)}`}
|
||||
${perpMarket.maintBaseLiabWeight.toFixed(4)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${perpMarket.initAssetWeight.toFixed(
|
||||
value={`${perpMarket.initBaseAssetWeight.toFixed(
|
||||
4
|
||||
)}/
|
||||
${perpMarket.initLiabWeight.toFixed(4)}`}
|
||||
${perpMarket.initBaseLiabWeight.toFixed(4)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint PNL Asset weight"
|
||||
value={`${perpMarket.maintPnlAssetWeight.toFixed(
|
||||
4
|
||||
)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init PNL Asset weight"
|
||||
value={`${perpMarket.initPnlAssetWeight.toFixed(
|
||||
4
|
||||
)}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation Fee"
|
||||
|
@ -458,10 +512,6 @@ const Dashboard: NextPage = () => {
|
|||
label="Oracle: Max Staleness"
|
||||
value={`${perpMarket.oracleConfig.maxStalenessSlots} slots`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Trusted Market"
|
||||
value={`${perpMarket.trustedMarket}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Group Insurance Fund"
|
||||
value={`${perpMarket.groupInsuranceFund}`}
|
||||
|
@ -492,7 +542,7 @@ const KeyValuePair = ({
|
|||
value: number | ReactNode | string
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex justify-between border-t border-th-bkg-3 p-4 xl:py-1.5">
|
||||
<div className="flex justify-between border-t border-th-bkg-3 p-4 xl:py-3">
|
||||
<span className="mr-4 whitespace-nowrap text-th-fgd-3">{label}</span>
|
||||
{value}
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'swap',
|
||||
'token',
|
||||
'trade',
|
||||
'close-account'
|
||||
])),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'swap',
|
||||
'settings',
|
||||
'trade',
|
||||
'close-account'
|
||||
])),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'search',
|
||||
'settings',
|
||||
'trade',
|
||||
'close-account',
|
||||
])),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
|
||||
"you-must": "To close account you must",
|
||||
"are-you-sure": "Are you sure?",
|
||||
"before-you-continue": "Before you can continue",
|
||||
"close-all-borrows": "Close all borrows",
|
||||
"close-open-orders": "Close all open orders",
|
||||
"close-perp-positions": "Close and settle all futures positons",
|
||||
"closing-account-will": "Closing your Mango Account will:",
|
||||
"delete-your-account": "Delete your Mango Account",
|
||||
"error-deleting-account": "Error deleting your Mango Account",
|
||||
"goodbye": "Until next time 👋",
|
||||
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "Transaction Confirmed",
|
||||
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
|
||||
"close-account": "Close Account",
|
||||
"closing-account": "Closing your account..."
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"mango-account": "Account Address",
|
||||
"mango-account-name": "Account Name",
|
||||
"no-results": "No Results",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive",
|
||||
"profile-name": "Profile Name",
|
||||
"results": "Results",
|
||||
"search": "Search",
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"received": "Received",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
|
||||
"you-must": "To close account you must",
|
||||
"are-you-sure": "Are you sure?",
|
||||
"before-you-continue": "Before you can continue",
|
||||
"close-all-borrows": "Close all borrows",
|
||||
"close-open-orders": "Close all open orders",
|
||||
"close-perp-positions": "Close and settle all futures positons",
|
||||
"closing-account-will": "Closing your Mango Account will:",
|
||||
"delete-your-account": "Delete your Mango Account",
|
||||
"error-deleting-account": "Error deleting your Mango Account",
|
||||
"goodbye": "Until next time 👋",
|
||||
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "Transaction Confirmed",
|
||||
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
|
||||
"close-account": "Close Account",
|
||||
"closing-account": "Closing your account..."
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"mango-account": "Account Address",
|
||||
"mango-account-name": "Account Name",
|
||||
"no-results": "No Results",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive",
|
||||
"profile-name": "Profile Name",
|
||||
"results": "Results",
|
||||
"search": "Search",
|
||||
|
|
|
@ -11,18 +11,20 @@
|
|||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"received": "Received",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
|
||||
"you-must": "To close account you must",
|
||||
"are-you-sure": "Are you sure?",
|
||||
"before-you-continue": "Before you can continue",
|
||||
"close-all-borrows": "Close all borrows",
|
||||
"close-open-orders": "Close all open orders",
|
||||
"close-perp-positions": "Close and settle all futures positons",
|
||||
"closing-account-will": "Closing your Mango Account will:",
|
||||
"delete-your-account": "Delete your Mango Account",
|
||||
"error-deleting-account": "Error deleting your Mango Account",
|
||||
"goodbye": "Until next time 👋",
|
||||
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "Transaction Confirmed",
|
||||
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
|
||||
"close-account": "Close Account",
|
||||
"closing-account": "Closing your account..."
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"mango-account": "Account Address",
|
||||
"mango-account-name": "Account Name",
|
||||
"no-results": "No Results",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive",
|
||||
"profile-name": "Profile Name",
|
||||
"results": "Results",
|
||||
"search": "Search",
|
||||
|
|
|
@ -11,18 +11,20 @@
|
|||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"received": "Received",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
|
||||
"you-must": "To close account you must",
|
||||
"are-you-sure": "Are you sure?",
|
||||
"before-you-continue": "Before you can continue",
|
||||
"close-all-borrows": "Close all borrows",
|
||||
"close-open-orders": "Close all open orders",
|
||||
"close-perp-positions": "Close and settle all futures positons",
|
||||
"closing-account-will": "Closing your Mango Account will:",
|
||||
"delete-your-account": "Delete your Mango Account",
|
||||
"error-deleting-account": "Error deleting your Mango Account",
|
||||
"goodbye": "Until next time 👋",
|
||||
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "Transaction Confirmed",
|
||||
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
|
||||
"close-account": "Close Account",
|
||||
"closing-account": "Closing your account..."
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"mango-account": "Account Address",
|
||||
"mango-account-name": "Account Name",
|
||||
"no-results": "No Results",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive",
|
||||
"profile-name": "Profile Name",
|
||||
"results": "Results",
|
||||
"search": "Search",
|
||||
|
|
|
@ -11,18 +11,20 @@
|
|||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"received": "Received",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"description": "You can close your Mango account and recover the small amount amount of SOL used to cover rent exemption.",
|
||||
"you-must": "To close account you must",
|
||||
"are-you-sure": "Are you sure?",
|
||||
"before-you-continue": "Before you can continue",
|
||||
"close-all-borrows": "Close all borrows",
|
||||
"close-open-orders": "Close all open orders",
|
||||
"close-perp-positions": "Close and settle all futures positons",
|
||||
"closing-account-will": "Closing your Mango Account will:",
|
||||
"delete-your-account": "Delete your Mango Account",
|
||||
"error-deleting-account": "Error deleting your Mango Account",
|
||||
"goodbye": "Until next time 👋",
|
||||
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "Transaction Confirmed",
|
||||
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
|
||||
"close-account": "Close Account",
|
||||
"closing-account": "Closing your account..."
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"mango-account": "Account Address",
|
||||
"mango-account-name": "Account Name",
|
||||
"no-results": "No Results",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive execpt for profile names which are lowercase",
|
||||
"no-results-desc": "Try another search. Searches are case-sensitive",
|
||||
"profile-name": "Profile Name",
|
||||
"results": "Results",
|
||||
"search": "Search",
|
||||
|
|
|
@ -11,18 +11,20 @@
|
|||
"maximum-cost": "Maximum Cost",
|
||||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"paid": "Paid",
|
||||
"pay": "You Pay",
|
||||
"preset": "Preset",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"received": "Received",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"use-margin": "Allow Margin"
|
||||
}
|
|
@ -34,6 +34,7 @@ import {
|
|||
INPUT_TOKEN_DEFAULT,
|
||||
LAST_ACCOUNT_KEY,
|
||||
OUTPUT_TOKEN_DEFAULT,
|
||||
PAGINATION_PAGE_LENGTH,
|
||||
RPC_PROVIDER_KEY,
|
||||
} from '../utils/constants'
|
||||
import { OrderbookL2, SpotBalances, SpotTradeHistory } from 'types'
|
||||
|
@ -207,6 +208,7 @@ interface TradeForm {
|
|||
tradeType: 'Market' | 'Limit'
|
||||
postOnly: boolean
|
||||
ioc: boolean
|
||||
reduceOnly: boolean
|
||||
}
|
||||
|
||||
export const DEFAULT_TRADE_FORM: TradeForm = {
|
||||
|
@ -217,6 +219,7 @@ export const DEFAULT_TRADE_FORM: TradeForm = {
|
|||
tradeType: 'Limit',
|
||||
postOnly: false,
|
||||
ioc: false,
|
||||
reduceOnly: false,
|
||||
}
|
||||
|
||||
export type MangoStore = {
|
||||
|
@ -248,7 +251,7 @@ export type MangoStore = {
|
|||
data: SwapHistoryItem[]
|
||||
initialLoad: boolean
|
||||
}
|
||||
tradeHistory: SpotTradeHistory[]
|
||||
tradeHistory: { data: SpotTradeHistory[]; loading: boolean }
|
||||
}
|
||||
mangoAccounts: MangoAccount[]
|
||||
markets: Serum3Market[] | undefined
|
||||
|
@ -332,7 +335,7 @@ export type MangoStore = {
|
|||
) => Promise<void>
|
||||
fetchTokenStats: () => void
|
||||
fetchTourSettings: (walletPk: string) => void
|
||||
fetchTradeHistory: () => Promise<void>
|
||||
fetchTradeHistory: (offset?: number) => Promise<void>
|
||||
fetchWalletTokens: (walletPk: PublicKey) => Promise<void>
|
||||
connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise<void>
|
||||
loadMarketFills: () => Promise<void>
|
||||
|
@ -380,10 +383,12 @@ const mangoStore = create<MangoStore>()(
|
|||
openOrders: {},
|
||||
perpPositions: [],
|
||||
spotBalances: {},
|
||||
interestTotals: { data: [], loading: false },
|
||||
performance: { data: [], loading: false, initialLoad: false },
|
||||
swapHistory: { data: [], initialLoad: false },
|
||||
tradeHistory: [],
|
||||
stats: {
|
||||
interestTotals: { data: [], loading: false },
|
||||
performance: { data: [], loading: false },
|
||||
swapHistory: { data: [], initialLoad: false },
|
||||
},
|
||||
tradeHistory: { data: [], loading: true },
|
||||
},
|
||||
mangoAccounts: [],
|
||||
markets: undefined,
|
||||
|
@ -540,7 +545,7 @@ const mangoStore = create<MangoStore>()(
|
|||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/activity-feed?mango-account=${mangoAccountPk}&offset=${offset}&limit=25${
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/activity-feed?mango-account=${mangoAccountPk}&offset=${offset}&limit=${PAGINATION_PAGE_LENGTH}${
|
||||
params ? params : ''
|
||||
}`
|
||||
)
|
||||
|
@ -723,6 +728,10 @@ const mangoStore = create<MangoStore>()(
|
|||
})
|
||||
} catch (e) {
|
||||
console.error('Error fetching mango accts', e)
|
||||
} finally {
|
||||
set((state) => {
|
||||
state.mangoAccount.initialLoad = false
|
||||
})
|
||||
}
|
||||
},
|
||||
fetchNfts: async (connection: Connection, ownerPk: PublicKey) => {
|
||||
|
@ -1010,36 +1019,33 @@ const mangoStore = create<MangoStore>()(
|
|||
console.log('Error fetching fills:', err)
|
||||
}
|
||||
},
|
||||
async fetchTradeHistory() {
|
||||
async fetchTradeHistory(offset = 0) {
|
||||
const set = get().set
|
||||
const mangoAccount = get().mangoAccount.current
|
||||
const mangoAccountPk =
|
||||
get().mangoAccount?.current?.publicKey.toString()
|
||||
const loadedHistory =
|
||||
mangoStore.getState().mangoAccount.tradeHistory.data
|
||||
try {
|
||||
const [spotRes, perpRes] = await Promise.all([
|
||||
fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/openbook-trades?address=${mangoAccount?.publicKey.toString()}&address-type=mango-account`
|
||||
),
|
||||
fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/perp-trade-history?mango-account=${mangoAccount?.publicKey.toString()}&limit=1000`
|
||||
),
|
||||
])
|
||||
const spotHistory = await spotRes.json()
|
||||
const perpHistory = await perpRes.json()
|
||||
console.log('th', spotHistory, perpHistory)
|
||||
let tradeHistory: any[] = []
|
||||
if (spotHistory?.length) {
|
||||
tradeHistory = tradeHistory.concat(spotHistory)
|
||||
}
|
||||
if (perpHistory?.length) {
|
||||
tradeHistory = tradeHistory.concat(perpHistory)
|
||||
}
|
||||
const response = await fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/stats/trade-history?mango-account=${mangoAccountPk}&limit=${PAGINATION_PAGE_LENGTH}&offset=${offset}`
|
||||
)
|
||||
const parsedHistory = await response.json()
|
||||
const newHistory = parsedHistory.map((h: any) => h.activity_details)
|
||||
|
||||
const history =
|
||||
offset !== 0 ? loadedHistory.concat(newHistory) : newHistory
|
||||
|
||||
set((s) => {
|
||||
s.mangoAccount.tradeHistory = tradeHistory.sort(
|
||||
s.mangoAccount.tradeHistory.data = history?.sort(
|
||||
(x: any) => x.block_datetime
|
||||
)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Unable to fetch trade history', e)
|
||||
} finally {
|
||||
set((s) => {
|
||||
s.mangoAccount.tradeHistory.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
updateConnection(endpointUrl) {
|
||||
|
|
|
@ -60,7 +60,7 @@ export const COLORS: any = {
|
|||
Olive: '#acaa8b',
|
||||
},
|
||||
UP: {
|
||||
'Mango Classic': '#A6CD03',
|
||||
'Mango Classic': '#89B92A',
|
||||
Dark: '#4aa13a',
|
||||
Light: '#60bf4f',
|
||||
'High Contrast': '#50f434',
|
||||
|
|
|
@ -35,17 +35,17 @@ module.exports = {
|
|||
},
|
||||
link: { DEFAULT: 'hsl(33, 100%, 57%)', hover: 'hsl(33, 100%, 52%)' },
|
||||
down: {
|
||||
DEFAULT: 'hsl(4, 93%, 60%)',
|
||||
DEFAULT: 'hsl(4, 63%, 55%)',
|
||||
dark: 'hsl(4, 93%, 55%)',
|
||||
muted: 'hsl(4, 53%, 55%)',
|
||||
muted: 'hsl(4, 43%, 38%)',
|
||||
},
|
||||
up: {
|
||||
DEFAULT: 'hsl(72, 97%, 41%)',
|
||||
dark: 'hsl(72, 97%, 36%)',
|
||||
muted: 'hsl(72, 57%, 36%)',
|
||||
DEFAULT: 'hsl(77, 63%, 40%)',
|
||||
dark: 'hsl(85, 50%, 36%)',
|
||||
muted: 'hsl(84, 40%, 32%)',
|
||||
},
|
||||
error: 'hsl(4, 93%, 60%)',
|
||||
success: 'hsl(72, 97%, 41%)',
|
||||
success: 'hsl(82, 97%, 41%)',
|
||||
warning: 'hsl(33, 100%, 57%)',
|
||||
'bkg-1': 'hsl(256, 18%, 12%)',
|
||||
'bkg-2': 'hsl(256, 18%, 17%)',
|
||||
|
@ -109,14 +109,14 @@ module.exports = {
|
|||
},
|
||||
link: { DEFAULT: 'hsl(45, 86%, 62%)', hover: 'hsl(45, 86%, 57%)' },
|
||||
down: {
|
||||
DEFAULT: 'hsl(0, 59%, 58%)',
|
||||
dark: 'hsl(0, 59%, 53%)',
|
||||
muted: 'hsl(0, 19%, 53%)',
|
||||
DEFAULT: 'hsl(358, 55%, 50%)',
|
||||
dark: 'hsl(0, 45%, 26%)',
|
||||
muted: 'hsl(0, 45%, 30%)',
|
||||
},
|
||||
up: {
|
||||
DEFAULT: 'hsl(111, 47%, 43%)',
|
||||
dark: 'hsl(111, 47%, 38%)',
|
||||
muted: 'hsl(111, 7%, 38%)',
|
||||
muted: 'hsl(130, 34%, 26%)',
|
||||
},
|
||||
error: 'hsl(0, 59%, 58%)',
|
||||
success: 'hsl(111, 47%, 43%)',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { AccountMeta } from '@solana/web3.js'
|
||||
import { AccountInfo } from '@solana/web3.js'
|
||||
import { AccountInfo, PublicKey, TransactionInstruction } from '@solana/web3.js'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
export declare type SideType = typeof Side.Ask | typeof Side.Bid
|
||||
export declare const Side: {
|
||||
Bid: {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
bid: {}
|
||||
}
|
||||
Ask: {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
ask: {}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ export interface ExactOutSwapParams extends SwapParams {
|
|||
}
|
||||
export declare type AccountInfoMap = Map<string, AccountInfo<Buffer> | null>
|
||||
|
||||
declare type AmmLabel =
|
||||
export declare type AmmLabel =
|
||||
| 'Aldrin'
|
||||
| 'Crema'
|
||||
| 'Cropper'
|
||||
|
@ -127,6 +128,9 @@ export interface RouteInfo {
|
|||
priceImpactPct: number
|
||||
slippageBps: number
|
||||
swapMode: SwapMode
|
||||
instructions?: TransactionInstruction[]
|
||||
mints?: PublicKey[]
|
||||
routerName?: 'Mango'
|
||||
}
|
||||
|
||||
export type Routes = {
|
||||
|
|
|
@ -58,6 +58,8 @@ export const PROFILE_CATEGORIES = [
|
|||
|
||||
export const CHART_DATA_FEED = `https://dry-ravine-67635.herokuapp.com/tv`
|
||||
|
||||
export const MANGO_ROUTER_API_URL = 'https://api.mngo.cloud/router/v1'
|
||||
|
||||
export const DEFAULT_MARKET_NAME = 'SOL/USDC'
|
||||
|
||||
export const MIN_SOL_BALANCE = 0.001
|
||||
|
@ -65,3 +67,5 @@ export const MIN_SOL_BALANCE = 0.001
|
|||
export const ACCOUNT_ACTION_MODAL_HEIGHT = '506px'
|
||||
|
||||
export const ACCOUNT_ACTION_MODAL_INNER_HEIGHT = '444px'
|
||||
|
||||
export const PAGINATION_PAGE_LENGTH = 25
|
||||
|
|
11
yarn.lock
11
yarn.lock
|
@ -52,7 +52,7 @@
|
|||
|
||||
"@blockworks-foundation/mango-v4@https://github.com/blockworks-foundation/mango-v4.git#ts-client":
|
||||
version "0.0.1-beta.6"
|
||||
resolved "https://github.com/blockworks-foundation/mango-v4.git#59ef05ebe95ed5ca947ab5cfecf97c4c9fc99740"
|
||||
resolved "https://github.com/blockworks-foundation/mango-v4.git#1ca560c007081127ac31486a96a3729da22f99fb"
|
||||
dependencies:
|
||||
"@project-serum/anchor" "^0.25.0"
|
||||
"@project-serum/serum" "^0.13.65"
|
||||
|
@ -4844,7 +4844,14 @@ node-addon-api@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
|
||||
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
|
||||
|
||||
node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1:
|
||||
node-fetch@2, node-fetch@^2.6.1:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e"
|
||||
integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
|
|
Loading…
Reference in New Issue