merge main
This commit is contained in:
commit
840991961f
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowPathIcon,
|
||||
ExclamationCircleIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
|
@ -17,7 +18,7 @@ import { TokenAccount } from './../utils/tokens'
|
|||
import ActionTokenList from './account/ActionTokenList'
|
||||
import ButtonGroup from './forms/ButtonGroup'
|
||||
import Label from './forms/Label'
|
||||
import Button from './shared/Button'
|
||||
import Button, { IconButton } from './shared/Button'
|
||||
import Loading from './shared/Loading'
|
||||
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
|
||||
import { withValueLimit } from './swap/MarketSwapForm'
|
||||
|
@ -63,7 +64,7 @@ export const walletBalanceForToken = (
|
|||
}
|
||||
|
||||
function DepositForm({ onSuccess, token }: DepositFormProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
const [inputAmount, setInputAmount] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [selectedToken, setSelectedToken] = useState(
|
||||
|
@ -71,6 +72,7 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
)
|
||||
const [showTokenList, setShowTokenList] = useState(false)
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const [refreshingWalletTokens, setRefreshingWalletTokens] = useState(false)
|
||||
const { connect } = useWallet()
|
||||
const { maxSolDeposit } = useSolBalance()
|
||||
const banks = useBanksWithBalances('walletBalance')
|
||||
|
@ -110,6 +112,14 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
setShowTokenList(false)
|
||||
}
|
||||
|
||||
const handleRefreshWalletBalances = useCallback(async () => {
|
||||
if (!publicKey) return
|
||||
const actions = mangoStore.getState().actions
|
||||
setRefreshingWalletTokens(true)
|
||||
await actions.fetchWalletTokens(publicKey)
|
||||
setRefreshingWalletTokens(false)
|
||||
}, [publicKey])
|
||||
|
||||
const handleDeposit = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -196,13 +206,23 @@ function DepositForm({ onSuccess, token }: DepositFormProps) {
|
|||
<div className="grid grid-cols-2">
|
||||
<div className="col-span-2 flex justify-between">
|
||||
<Label text={`${t('deposit')} ${t('token')}`} />
|
||||
<MaxAmountButton
|
||||
className="mb-2"
|
||||
decimals={tokenMax.maxDecimals}
|
||||
label={t('wallet-balance')}
|
||||
onClick={setMax}
|
||||
value={tokenMax.maxAmount}
|
||||
/>
|
||||
<div className="mb-2 flex items-center space-x-2">
|
||||
<MaxAmountButton
|
||||
decimals={tokenMax.maxDecimals}
|
||||
label={t('wallet-balance')}
|
||||
onClick={setMax}
|
||||
value={tokenMax.maxAmount}
|
||||
/>
|
||||
<Tooltip content={t('account:refresh-balance')}>
|
||||
<IconButton
|
||||
className={refreshingWalletTokens ? 'animate-spin' : ''}
|
||||
onClick={handleRefreshWalletBalances}
|
||||
hideBg
|
||||
>
|
||||
<ArrowPathIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<TokenListButton
|
||||
|
|
|
@ -2,7 +2,6 @@ import { useEffect } from 'react'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { Keypair, PublicKey } from '@solana/web3.js'
|
||||
import { useRouter } from 'next/router'
|
||||
import { MangoAccount } from '@blockworks-foundation/mango-v4'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useInterval from './shared/useInterval'
|
||||
import { SECONDS } from 'utils/constants'
|
||||
|
@ -99,13 +98,9 @@ const HydrateStore = () => {
|
|||
if (!mangoAccount) return
|
||||
|
||||
if (context.slot > lastSeenSlot) {
|
||||
const decodedMangoAccount = client.program.coder.accounts.decode(
|
||||
'mangoAccount',
|
||||
info?.data,
|
||||
)
|
||||
const newMangoAccount = MangoAccount.from(
|
||||
const newMangoAccount = await client.getMangoAccountFromAi(
|
||||
mangoAccount.publicKey,
|
||||
decodedMangoAccount,
|
||||
info,
|
||||
)
|
||||
if (newMangoAccount.serum3Active().length > 0) {
|
||||
await newMangoAccount.reloadSerum3OpenOrders(client)
|
||||
|
|
|
@ -17,7 +17,14 @@ import Tooltip from './shared/Tooltip'
|
|||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useJupiterMints from '../hooks/useJupiterMints'
|
||||
import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from './shared/TableElements'
|
||||
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||
import BorrowRepayModal from './modals/BorrowRepayModal'
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||
|
@ -33,6 +40,22 @@ import useUnownedAccount from 'hooks/useUnownedAccount'
|
|||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import TokenLogo from './shared/TokenLogo'
|
||||
import useHealthContributions from 'hooks/useHealthContributions'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
|
||||
type TableData = {
|
||||
bank: Bank
|
||||
balance: number
|
||||
symbol: string
|
||||
interestAmount: number
|
||||
interestValue: number
|
||||
inOrders: number
|
||||
unsettled: number
|
||||
collateralValue: number
|
||||
assetWeight: string
|
||||
liabWeight: string
|
||||
depositRate: number
|
||||
borrowRate: number
|
||||
}
|
||||
|
||||
const TokenList = () => {
|
||||
const { t } = useTranslation(['common', 'token', 'trade'])
|
||||
|
@ -50,26 +73,80 @@ const TokenList = () => {
|
|||
const showTableView = width ? width > breakpoints.md : false
|
||||
const banks = useBanksWithBalances('balance')
|
||||
|
||||
const filteredBanks = useMemo(() => {
|
||||
if (banks.length) {
|
||||
return showZeroBalances || !mangoAccountAddress
|
||||
? banks
|
||||
: banks.filter((b) => Math.abs(b.balance) > 0)
|
||||
}
|
||||
return []
|
||||
}, [banks, mangoAccountAddress, showZeroBalances])
|
||||
const formattedTableData = useCallback(
|
||||
(banks: BankWithBalance[]) => {
|
||||
const formatted = []
|
||||
for (const b of banks) {
|
||||
const bank = b.bank
|
||||
const balance = b.balance
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
// const filteredBanks = useMemo(() => {
|
||||
// if (!banks.length) return []
|
||||
// if (showZeroBalances || !mangoAccountAddress) return banks
|
||||
// const filtered = banks.filter((b) => {
|
||||
// const contribution =
|
||||
// initContributions.find((cont) => cont.asset === b.bank.name)
|
||||
// ?.contribution || 0
|
||||
// return Math.abs(contribution) > 0
|
||||
// })
|
||||
// return filtered
|
||||
// }, [banks, mangoAccountAddress, showZeroBalances, initContributions])
|
||||
const hasInterestEarned = totalInterestData.find(
|
||||
(d) =>
|
||||
d.symbol.toLowerCase() === symbol.toLowerCase() ||
|
||||
(symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
|
||||
)
|
||||
|
||||
const interestAmount = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest * -1 +
|
||||
hasInterestEarned.deposit_interest
|
||||
: 0
|
||||
|
||||
const interestValue = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest_usd * -1 +
|
||||
hasInterestEarned.deposit_interest_usd
|
||||
: 0.0
|
||||
|
||||
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
const collateralValue =
|
||||
initContributions.find((val) => val.asset === bank.name)
|
||||
?.contribution || 0
|
||||
|
||||
const assetWeight = bank.scaledInitAssetWeight(bank.price).toFixed(2)
|
||||
const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2)
|
||||
|
||||
const depositRate = bank.getDepositRateUi()
|
||||
const borrowRate = bank.getBorrowRateUi()
|
||||
|
||||
const data = {
|
||||
balance,
|
||||
bank,
|
||||
symbol,
|
||||
interestAmount,
|
||||
interestValue,
|
||||
inOrders,
|
||||
unsettled,
|
||||
collateralValue,
|
||||
assetWeight,
|
||||
liabWeight,
|
||||
depositRate,
|
||||
borrowRate,
|
||||
}
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted
|
||||
},
|
||||
[initContributions, spotBalances, totalInterestData],
|
||||
)
|
||||
|
||||
const unsortedTableData = useMemo(() => {
|
||||
if (!banks.length) return []
|
||||
if (showZeroBalances || !mangoAccountAddress) {
|
||||
return formattedTableData(banks)
|
||||
} else {
|
||||
const filtered = banks.filter((b) => Math.abs(b.balance) > 0)
|
||||
return formattedTableData(filtered)
|
||||
}
|
||||
}, [banks, mangoAccountAddress, showZeroBalances, totalInterestData])
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(unsortedTableData)
|
||||
|
||||
return (
|
||||
<ContentBox hideBorder hidePadding>
|
||||
|
@ -89,36 +166,84 @@ const TokenList = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('token')}</Th>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="symbol"
|
||||
sort={() => requestSort('symbol')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('token')}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="A negative balance represents a borrow">
|
||||
<span className="tooltip-underline">{t('balance')}</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="balance"
|
||||
sort={() => requestSort('balance')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('balance')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('tooltip-collateral-value')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('collateral-value')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="collateralValue"
|
||||
sort={() => requestSort('collateralValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('collateral-value')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="text-right">{t('trade:in-orders')}</Th>
|
||||
<Th className="text-right">{t('trade:unsettled')}</Th>
|
||||
<Th className="flex justify-end" id="account-step-nine">
|
||||
<Tooltip content="The sum of interest earned and interest paid for each token">
|
||||
<span className="tooltip-underline">
|
||||
{t('interest-earned-paid')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="inOrders"
|
||||
sort={() => requestSort('inOrders')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:in-orders')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th id="account-step-ten">
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="unsettled"
|
||||
sort={() => requestSort('unsettled')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:unsettled')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The sum of interest earned and interest paid for each token">
|
||||
<SortableColumnHeader
|
||||
sortKey="interestValue"
|
||||
sort={() => requestSort('interestValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('interest-earned-paid')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The interest rates for depositing (green/left) and borrowing (red/right)">
|
||||
<span className="tooltip-underline">{t('rates')}</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="depositRate"
|
||||
sort={() => requestSort('depositRate')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('rates')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
{/* <span className="tooltip-underline">{t('rates')}</span> */}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
|
@ -128,47 +253,24 @@ const TokenList = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredBanks.map((b) => {
|
||||
const bank = b.bank
|
||||
|
||||
const tokenBalance = b.balance
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
const hasInterestEarned = totalInterestData.find(
|
||||
(d) =>
|
||||
d.symbol.toLowerCase() === symbol.toLowerCase() ||
|
||||
(symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
|
||||
)
|
||||
|
||||
const interestAmount = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest * -1 +
|
||||
hasInterestEarned.deposit_interest
|
||||
: 0
|
||||
|
||||
const interestValue = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest_usd * -1 +
|
||||
hasInterestEarned.deposit_interest_usd
|
||||
: 0.0
|
||||
|
||||
const inOrders =
|
||||
spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
|
||||
const unsettled =
|
||||
spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
const collateralValue =
|
||||
initContributions.find((val) => val.asset === bank.name)
|
||||
?.contribution || 0
|
||||
|
||||
const assetWeight = bank
|
||||
.scaledInitAssetWeight(bank.price)
|
||||
.toFixed(2)
|
||||
const liabWeight = bank
|
||||
.scaledInitLiabWeight(bank.price)
|
||||
.toFixed(2)
|
||||
{tableData.map((data) => {
|
||||
const {
|
||||
balance,
|
||||
bank,
|
||||
symbol,
|
||||
interestAmount,
|
||||
interestValue,
|
||||
inOrders,
|
||||
unsettled,
|
||||
collateralValue,
|
||||
assetWeight,
|
||||
liabWeight,
|
||||
depositRate,
|
||||
borrowRate,
|
||||
} = data
|
||||
|
||||
return (
|
||||
<TrBody key={bank.name}>
|
||||
<TrBody key={symbol}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
|
@ -179,7 +281,7 @@ const TokenList = () => {
|
|||
</Td>
|
||||
<Td className="text-right">
|
||||
<BankAmountWithValue
|
||||
amount={tokenBalance}
|
||||
amount={balance}
|
||||
bank={bank}
|
||||
stacked
|
||||
/>
|
||||
|
@ -229,17 +331,14 @@ const TokenList = () => {
|
|||
<div className="flex justify-end space-x-1.5">
|
||||
<p className="text-th-up">
|
||||
<FormatNumericValue
|
||||
value={bank.getDepositRateUi()}
|
||||
value={depositRate}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
</p>
|
||||
<span className="text-th-fgd-4">|</span>
|
||||
<p className="text-th-down">
|
||||
<FormatNumericValue
|
||||
value={bank.getBorrowRateUi()}
|
||||
decimals={2}
|
||||
/>
|
||||
<FormatNumericValue value={borrowRate} decimals={2} />
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
|
@ -257,8 +356,8 @@ const TokenList = () => {
|
|||
</>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{filteredBanks.map((b) => {
|
||||
return <MobileTokenListItem key={b.bank.name} bank={b} />
|
||||
{tableData.map((data) => {
|
||||
return <MobileTokenListItem key={data.bank.name} data={data} />
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
@ -268,46 +367,23 @@ const TokenList = () => {
|
|||
|
||||
export default TokenList
|
||||
|
||||
const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
||||
const MobileTokenListItem = ({ data }: { data: TableData }) => {
|
||||
const { t } = useTranslation(['common', 'token'])
|
||||
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { initContributions } = useHealthContributions()
|
||||
const totalInterestData = mangoStore(
|
||||
(s) => s.mangoAccount.interestTotals.data,
|
||||
)
|
||||
const tokenBank = bank.bank
|
||||
const mint = tokenBank.mint
|
||||
const symbol = tokenBank.name === 'MSOL' ? 'mSOL' : tokenBank.name
|
||||
|
||||
const hasInterestEarned = totalInterestData.find(
|
||||
(d) =>
|
||||
d.symbol.toLowerCase() === symbol.toLowerCase() ||
|
||||
(symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
|
||||
)
|
||||
|
||||
const interestAmount = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest * -1 +
|
||||
hasInterestEarned.deposit_interest
|
||||
: 0
|
||||
|
||||
const interestValue = hasInterestEarned
|
||||
? hasInterestEarned.borrow_interest_usd * -1 +
|
||||
hasInterestEarned.deposit_interest_usd
|
||||
: 0
|
||||
|
||||
const tokenBalance = bank.balance
|
||||
const unsettled = spotBalances[mint.toString()]?.unsettled || 0
|
||||
const inOrders = spotBalances[mint.toString()]?.inOrders || 0
|
||||
|
||||
const collateralValue =
|
||||
initContributions.find((val) => val.asset === tokenBank.name)
|
||||
?.contribution || 0
|
||||
|
||||
const assetWeight = tokenBank
|
||||
.scaledInitAssetWeight(tokenBank.price)
|
||||
.toFixed(2)
|
||||
const liabWeight = tokenBank.scaledInitLiabWeight(tokenBank.price).toFixed(2)
|
||||
const {
|
||||
bank,
|
||||
balance,
|
||||
symbol,
|
||||
interestAmount,
|
||||
interestValue,
|
||||
inOrders,
|
||||
unsettled,
|
||||
collateralValue,
|
||||
assetWeight,
|
||||
liabWeight,
|
||||
depositRate,
|
||||
borrowRate,
|
||||
} = data
|
||||
|
||||
return (
|
||||
<Disclosure>
|
||||
|
@ -319,7 +395,7 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
|||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5">
|
||||
<TokenLogo bank={tokenBank} />
|
||||
<TokenLogo bank={bank} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-0.5 leading-none text-th-fgd-1">{symbol}</p>
|
||||
|
@ -329,15 +405,13 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
|||
<div className="text-right">
|
||||
<p className="font-mono text-sm text-th-fgd-2">
|
||||
<FormatNumericValue
|
||||
value={tokenBalance}
|
||||
decimals={tokenBank.mintDecimals}
|
||||
value={balance}
|
||||
decimals={bank.mintDecimals}
|
||||
/>
|
||||
</p>
|
||||
<span className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
mangoAccount ? tokenBalance * tokenBank.uiPrice : 0
|
||||
}
|
||||
value={mangoAccount ? balance * bank.uiPrice : 0}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
|
@ -385,13 +459,13 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
|||
<p className="text-xs text-th-fgd-3">
|
||||
{t('trade:in-orders')}
|
||||
</p>
|
||||
<BankAmountWithValue amount={inOrders} bank={tokenBank} />
|
||||
<BankAmountWithValue amount={inOrders} bank={bank} />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('trade:unsettled')}
|
||||
</p>
|
||||
<BankAmountWithValue amount={unsettled} bank={tokenBank} />
|
||||
<BankAmountWithValue amount={unsettled} bank={bank} />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
|
@ -399,7 +473,7 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
|||
</p>
|
||||
<BankAmountWithValue
|
||||
amount={interestAmount}
|
||||
bank={tokenBank}
|
||||
bank={bank}
|
||||
value={interestValue}
|
||||
/>
|
||||
</div>
|
||||
|
@ -407,24 +481,16 @@ const MobileTokenListItem = ({ bank }: { bank: BankWithBalance }) => {
|
|||
<p className="text-xs text-th-fgd-3">{t('rates')}</p>
|
||||
<p className="space-x-2 font-mono">
|
||||
<span className="text-th-up">
|
||||
<FormatNumericValue
|
||||
value={tokenBank.getDepositRateUi()}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
<FormatNumericValue value={depositRate} decimals={2} />%
|
||||
</span>
|
||||
<span className="font-normal text-th-fgd-4">|</span>
|
||||
<span className="text-th-down">
|
||||
<FormatNumericValue
|
||||
value={tokenBank.getBorrowRateUi()}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
<FormatNumericValue value={borrowRate} decimals={2} />%
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<ActionsMenu bank={tokenBank} mangoAccount={mangoAccount} />
|
||||
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
|
|
|
@ -959,6 +959,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
|
|||
type="error"
|
||||
/>
|
||||
<CreateSwitchboardOracleModal
|
||||
tier={coinTier}
|
||||
orcaPoolAddress={orcaPoolAddress}
|
||||
raydiumPoolAddress={raydiumPoolAddress}
|
||||
baseTokenName={currentTokenInfo.symbol}
|
||||
|
|
|
@ -30,6 +30,7 @@ type BaseProps = ModalProps & {
|
|||
openbookMarketPk: string
|
||||
baseTokenPk: string
|
||||
baseTokenName: string
|
||||
tier: string
|
||||
}
|
||||
|
||||
type RaydiumProps = BaseProps & {
|
||||
|
@ -45,21 +46,30 @@ type OrcaProps = BaseProps & {
|
|||
const CreateSwitchboardOracleModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
openbookMarketPk,
|
||||
baseTokenPk,
|
||||
baseTokenName,
|
||||
raydiumPoolAddress,
|
||||
orcaPoolAddress,
|
||||
tier,
|
||||
}: RaydiumProps | OrcaProps) => {
|
||||
const { t } = useTranslation(['governance'])
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const wallet = useWallet()
|
||||
const quoteTokenName = 'USDC'
|
||||
const pythUsdOracle = 'Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD'
|
||||
const tierToSwapValue: { [key: string]: string } = {
|
||||
PREMIUM: '10000',
|
||||
MID: '2000',
|
||||
MEME: '500',
|
||||
SHIT: '100',
|
||||
UNTRUSTED: '100',
|
||||
}
|
||||
|
||||
const [creatingOracle, setCreatingOracle] = useState(false)
|
||||
|
||||
const create = useCallback(async () => {
|
||||
try {
|
||||
const swapValue = tierToSwapValue[tier]
|
||||
setCreatingOracle(true)
|
||||
const payer = wallet!.publicKey!
|
||||
if (!orcaPoolAddress && !raydiumPoolAddress) {
|
||||
|
@ -106,7 +116,7 @@ const CreateSwitchboardOracleModal = ({
|
|||
attempt: [
|
||||
{
|
||||
valueTask: {
|
||||
big: '100',
|
||||
big: swapValue,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -117,7 +127,7 @@ const CreateSwitchboardOracleModal = ({
|
|||
jupiterSwapTask: {
|
||||
inTokenAddress: USDC_MINT,
|
||||
outTokenAddress: baseTokenPk,
|
||||
baseAmountString: '100',
|
||||
baseAmountString: swapValue,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -134,6 +144,20 @@ const CreateSwitchboardOracleModal = ({
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
multiplyTask: {
|
||||
job: {
|
||||
tasks: [
|
||||
{
|
||||
oracleTask: {
|
||||
pythAddress: pythUsdOracle,
|
||||
pythAllowedConfidenceInterval: 0.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).finish(),
|
||||
|
@ -150,26 +174,14 @@ const CreateSwitchboardOracleModal = ({
|
|||
cacheTask: {
|
||||
cacheItems: [
|
||||
{
|
||||
variableName: 'IN_TOKEN_QTY',
|
||||
variableName: 'QTY',
|
||||
job: {
|
||||
tasks: [
|
||||
{
|
||||
valueTask: {
|
||||
big: '100',
|
||||
},
|
||||
},
|
||||
{
|
||||
divideTask: {
|
||||
job: {
|
||||
tasks: [
|
||||
{
|
||||
serumSwapTask: {
|
||||
serumPoolAddress:
|
||||
openbookMarketPk,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
jupiterSwapTask: {
|
||||
inTokenAddress: USDC_MINT,
|
||||
outTokenAddress: baseTokenPk,
|
||||
baseAmountString: swapValue,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -182,12 +194,12 @@ const CreateSwitchboardOracleModal = ({
|
|||
jupiterSwapTask: {
|
||||
inTokenAddress: baseTokenPk,
|
||||
outTokenAddress: USDC_MINT,
|
||||
baseAmountString: '${IN_TOKEN_QTY}',
|
||||
baseAmountString: '${QTY}',
|
||||
},
|
||||
},
|
||||
{
|
||||
divideTask: {
|
||||
big: '${IN_TOKEN_QTY}',
|
||||
big: '${QTY}',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -200,6 +212,20 @@ const CreateSwitchboardOracleModal = ({
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
multiplyTask: {
|
||||
job: {
|
||||
tasks: [
|
||||
{
|
||||
oracleTask: {
|
||||
pythAddress: pythUsdOracle,
|
||||
pythAllowedConfidenceInterval: 0.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).finish(),
|
||||
|
@ -264,7 +290,6 @@ const CreateSwitchboardOracleModal = ({
|
|||
baseTokenPk,
|
||||
connection,
|
||||
onClose,
|
||||
openbookMarketPk,
|
||||
orcaPoolAddress,
|
||||
raydiumPoolAddress,
|
||||
wallet,
|
||||
|
|
|
@ -10,7 +10,14 @@ import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
|||
import { breakpoints } from 'utils/theme'
|
||||
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
||||
import { LinkButton } from './Button'
|
||||
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from './TableElements'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import ConnectEmptyState from './ConnectEmptyState'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
@ -27,6 +34,7 @@ import Tooltip from './Tooltip'
|
|||
import { PublicKey } from '@solana/web3.js'
|
||||
import { USDC_MINT } from 'utils/constants'
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
|
||||
const BalancesTable = () => {
|
||||
const { t } = useTranslation(['common', 'account', 'trade'])
|
||||
|
@ -52,44 +60,115 @@ const BalancesTable = () => {
|
|||
return []
|
||||
}, [banks])
|
||||
|
||||
const formattedTableData = useCallback(() => {
|
||||
const formatted = []
|
||||
for (const b of filteredBanks) {
|
||||
const bank = b.bank
|
||||
const balance = b.balance
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
const collateralValue =
|
||||
initContributions.find((val) => val.asset === bank.name)
|
||||
?.contribution || 0
|
||||
|
||||
const assetWeight = bank.scaledInitAssetWeight(bank.price)
|
||||
const liabWeight = bank.scaledInitLiabWeight(bank.price)
|
||||
|
||||
const data = {
|
||||
assetWeight,
|
||||
balance,
|
||||
bankWithBalance: b,
|
||||
collateralValue,
|
||||
inOrders,
|
||||
liabWeight,
|
||||
symbol,
|
||||
unsettled,
|
||||
}
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted
|
||||
}, [filteredBanks])
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(formattedTableData())
|
||||
|
||||
return filteredBanks.length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="bg-th-bkg-1 text-left">{t('token')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('balance')}</Th>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="symbol"
|
||||
sort={() => requestSort('symbol')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('token')}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="balance"
|
||||
sort={() => requestSort('balance')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('balance')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('account:tooltip-collateral-value')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('collateral-value')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="collateralValue"
|
||||
sort={() => requestSort('collateralValue')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('collateral-value')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="bg-th-bkg-1 text-right">{t('trade:in-orders')}</Th>
|
||||
<Th className="bg-th-bkg-1 text-right" id="trade-step-ten">
|
||||
{t('trade:unsettled')}
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="inOrders"
|
||||
sort={() => requestSort('inOrders')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:in-orders')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="unsettled"
|
||||
sort={() => requestSort('unsettled')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:unsettled')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredBanks.map((b) => {
|
||||
const bank = b.bank
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
const collateralValue =
|
||||
initContributions.find((val) => val.asset === bank.name)
|
||||
?.contribution || 0
|
||||
|
||||
const assetWeight = bank
|
||||
.scaledInitAssetWeight(bank.price)
|
||||
.toFixed(2)
|
||||
const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2)
|
||||
{tableData.map((data) => {
|
||||
const {
|
||||
assetWeight,
|
||||
balance,
|
||||
bankWithBalance,
|
||||
collateralValue,
|
||||
inOrders,
|
||||
liabWeight,
|
||||
symbol,
|
||||
unsettled,
|
||||
} = data
|
||||
const bank = bankWithBalance.bank
|
||||
|
||||
return (
|
||||
<TrBody key={bank.name} className="text-sm">
|
||||
|
@ -102,10 +181,10 @@ const BalancesTable = () => {
|
|||
</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<Balance bank={b} />
|
||||
<Balance bank={bankWithBalance} />
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
<FormatNumericValue
|
||||
value={mangoAccount ? b.balance * bank.uiPrice : 0}
|
||||
value={mangoAccount ? balance * bank.uiPrice : 0}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
|
@ -121,7 +200,9 @@ const BalancesTable = () => {
|
|||
<p className="text-sm text-th-fgd-4">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
collateralValue <= -0.01 ? liabWeight : assetWeight
|
||||
collateralValue <= -0.01
|
||||
? liabWeight.toFixed(2)
|
||||
: assetWeight.toFixed(2)
|
||||
}
|
||||
/>
|
||||
x
|
||||
|
@ -140,19 +221,18 @@ const BalancesTable = () => {
|
|||
</Table>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{filteredBanks.map((b, i) => {
|
||||
const bank = b.bank
|
||||
const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
|
||||
|
||||
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
|
||||
const collateralValue =
|
||||
initContributions.find((val) => val.asset === bank.name)
|
||||
?.contribution || 0
|
||||
|
||||
const assetWeight = bank.scaledInitAssetWeight(bank.price).toFixed(2)
|
||||
const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2)
|
||||
{tableData.map((data, i) => {
|
||||
const {
|
||||
assetWeight,
|
||||
balance,
|
||||
bankWithBalance,
|
||||
collateralValue,
|
||||
inOrders,
|
||||
liabWeight,
|
||||
symbol,
|
||||
unsettled,
|
||||
} = data
|
||||
const bank = bankWithBalance.bank
|
||||
|
||||
return (
|
||||
<Disclosure key={bank.name}>
|
||||
|
@ -172,12 +252,10 @@ const BalancesTable = () => {
|
|||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="text-right">
|
||||
<Balance bank={b} />
|
||||
<Balance bank={bankWithBalance} />
|
||||
<span className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
mangoAccount ? b.balance * bank.uiPrice : 0
|
||||
}
|
||||
value={mangoAccount ? balance * bank.uiPrice : 0}
|
||||
isUsd
|
||||
/>
|
||||
</span>
|
||||
|
@ -216,8 +294,8 @@ const BalancesTable = () => {
|
|||
<FormatNumericValue
|
||||
value={
|
||||
collateralValue <= -0.01
|
||||
? liabWeight
|
||||
: assetWeight
|
||||
? liabWeight.toFixed(2)
|
||||
: assetWeight.toFixed(2)
|
||||
}
|
||||
/>
|
||||
x
|
||||
|
|
|
@ -40,7 +40,7 @@ const Change = ({
|
|||
? 'text-th-up'
|
||||
: change < 0
|
||||
? 'text-th-down'
|
||||
: 'text-th-fgd-4'
|
||||
: 'text-th-fgd-2'
|
||||
}`}
|
||||
>
|
||||
{prefix ? prefix : ''}
|
||||
|
|
|
@ -163,7 +163,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
|||
) : filteredData.length ? (
|
||||
<div className="relative">
|
||||
{setDaysToShow ? (
|
||||
<div className="mb-4 sm:absolute sm:-top-1 sm:right-0 sm:mb-0 sm:-mb-2 sm:flex sm:justify-end">
|
||||
<div className="mb-4 sm:absolute sm:-top-1 sm:right-0 sm:mb-0 sm:flex sm:justify-end">
|
||||
<ChartRangeButtons
|
||||
activeValue={daysToShow}
|
||||
names={['24H', '7D', '30D']}
|
||||
|
|
|
@ -28,23 +28,29 @@ const MarketChange = ({
|
|||
|
||||
const change = useMemo(() => {
|
||||
if (!market || !marketsData) return
|
||||
const isPerp = market instanceof PerpMarket
|
||||
let pastPrice = 0
|
||||
if (market instanceof PerpMarket) {
|
||||
let dailyVolume = 0
|
||||
if (isPerp) {
|
||||
const perpData: MarketData = marketsData?.perpData
|
||||
const perpEntries = Object.entries(perpData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
pastPrice = perpEntries ? perpEntries[1][0]?.price_24h : 0
|
||||
dailyVolume = perpEntries ? perpEntries[1][0]?.quote_volume_24h : 0
|
||||
} else {
|
||||
const spotData: MarketData = marketsData?.spotData
|
||||
const spotEntries = Object.entries(spotData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
pastPrice = spotEntries ? spotEntries[1][0]?.price_24h : 0
|
||||
dailyVolume = spotEntries ? spotEntries[1][0]?.quote_volume_24h : 0
|
||||
}
|
||||
const currentPrice =
|
||||
market instanceof PerpMarket ? market.uiPrice : currentSpotPrice
|
||||
const change = ((currentPrice - pastPrice) / pastPrice) * 100
|
||||
const currentPrice = isPerp ? market.uiPrice : currentSpotPrice
|
||||
const change =
|
||||
dailyVolume > 0 || isPerp
|
||||
? ((currentPrice - pastPrice) / pastPrice) * 100
|
||||
: 0
|
||||
return change
|
||||
}, [marketsData, currentSpotPrice])
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import dayjs from 'dayjs'
|
||||
import { MouseEventHandler, ReactNode, forwardRef } from 'react'
|
||||
import { LinkButton } from './Button'
|
||||
import { ArrowSmallDownIcon } from '@heroicons/react/20/solid'
|
||||
import { SortConfig } from 'hooks/useSortableData'
|
||||
|
||||
export const Table = ({
|
||||
children,
|
||||
|
@ -101,3 +104,35 @@ export const TableDateDisplay = ({
|
|||
</p>
|
||||
</>
|
||||
)
|
||||
|
||||
export const SortableColumnHeader = ({
|
||||
sort,
|
||||
sortConfig,
|
||||
sortKey,
|
||||
title,
|
||||
titleClass,
|
||||
}: {
|
||||
sort: (key: string) => void
|
||||
sortConfig: SortConfig | null
|
||||
sortKey: string
|
||||
title: string
|
||||
titleClass?: string
|
||||
}) => {
|
||||
return (
|
||||
<LinkButton
|
||||
className="flex items-center font-normal"
|
||||
onClick={() => sort(sortKey)}
|
||||
>
|
||||
<span className={`text-th-fgd-3 ${titleClass}`}>{title}</span>
|
||||
<ArrowSmallDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === sortKey
|
||||
? sortConfig?.direction === 'ascending'
|
||||
? 'rotate-180'
|
||||
: 'rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,35 @@ interface GroupedDataItem extends PerpStatsItem {
|
|||
intervalStartMillis: number
|
||||
}
|
||||
|
||||
const groupByHourlyInterval = (
|
||||
data: PerpStatsItem[],
|
||||
intervalDurationHours: number,
|
||||
) => {
|
||||
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
|
||||
const groupedData = []
|
||||
let currentGroup: GroupedDataItem | null = null
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const obj = data[i]
|
||||
const date = new Date(obj.date_hour)
|
||||
const intervalStartMillis =
|
||||
Math.floor(date.getTime() / intervalMillis) * intervalMillis
|
||||
if (
|
||||
!currentGroup ||
|
||||
currentGroup.intervalStartMillis !== intervalStartMillis
|
||||
) {
|
||||
currentGroup = {
|
||||
...obj,
|
||||
intervalStartMillis: intervalStartMillis,
|
||||
}
|
||||
currentGroup.funding_rate_hourly = obj.funding_rate_hourly * 100
|
||||
groupedData.push(currentGroup)
|
||||
} else {
|
||||
currentGroup.funding_rate_hourly += obj.funding_rate_hourly * 100
|
||||
}
|
||||
}
|
||||
return groupedData
|
||||
}
|
||||
|
||||
const AverageFundingChart = ({
|
||||
loading,
|
||||
marketStats,
|
||||
|
@ -18,35 +47,6 @@ const AverageFundingChart = ({
|
|||
const { t } = useTranslation(['common', 'stats', 'trade'])
|
||||
const [daysToShow, setDaysToShow] = useState('30')
|
||||
|
||||
const groupByHourlyInterval = (
|
||||
data: PerpStatsItem[],
|
||||
intervalDurationHours: number,
|
||||
) => {
|
||||
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
|
||||
const groupedData = []
|
||||
let currentGroup: GroupedDataItem | null = null
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const obj = data[i]
|
||||
const date = new Date(obj.date_hour)
|
||||
const intervalStartMillis =
|
||||
Math.floor(date.getTime() / intervalMillis) * intervalMillis
|
||||
if (
|
||||
!currentGroup ||
|
||||
currentGroup.intervalStartMillis !== intervalStartMillis
|
||||
) {
|
||||
currentGroup = {
|
||||
...obj,
|
||||
intervalStartMillis: intervalStartMillis,
|
||||
}
|
||||
currentGroup.funding_rate_hourly = obj.funding_rate_hourly * 100
|
||||
groupedData.push(currentGroup)
|
||||
} else {
|
||||
currentGroup.funding_rate_hourly += obj.funding_rate_hourly * 100
|
||||
}
|
||||
}
|
||||
return groupedData
|
||||
}
|
||||
|
||||
const [interval, intervalString] = useMemo(() => {
|
||||
if (daysToShow === '30') {
|
||||
return [24, 'stats:daily']
|
||||
|
|
|
@ -1,23 +1,49 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import { COLORS } from '../../styles/colors'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import ContentBox from '../shared/ContentBox'
|
||||
import MarketLogos from '@components/trade/MarketLogos'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from '@components/shared/TableElements'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { floorToDecimal, getDecimalCount, numberCompacter } from 'utils/numbers'
|
||||
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||
import useListedMarketsWithMarketData, {
|
||||
SerumMarketWithMarketData,
|
||||
} from 'hooks/useListedMarketsWithMarketData'
|
||||
import { sortSpotMarkets } from 'utils/markets'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
import Change from '@components/shared/Change'
|
||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
type TableData = {
|
||||
baseBank: Bank | undefined
|
||||
change: number
|
||||
market: SerumMarketWithMarketData
|
||||
marketName: string
|
||||
price: number
|
||||
priceHistory:
|
||||
| {
|
||||
price: number
|
||||
time: string
|
||||
}[]
|
||||
| undefined
|
||||
quoteBank: Bank | undefined
|
||||
volume: number
|
||||
isUp: boolean
|
||||
}
|
||||
|
||||
const SpotMarketsTable = () => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -28,6 +54,64 @@ const SpotMarketsTable = () => {
|
|||
const { serumMarketsWithData, isLoading, isFetching } =
|
||||
useListedMarketsWithMarketData()
|
||||
|
||||
const formattedTableData = useCallback(
|
||||
(markets: SerumMarketWithMarketData[]) => {
|
||||
const formatted = []
|
||||
for (const m of markets) {
|
||||
const baseBank = group?.getFirstBankByTokenIndex(m.baseTokenIndex)
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(m.quoteTokenIndex)
|
||||
const market = group?.getSerum3ExternalMarket(m.serumMarketExternal)
|
||||
let price = 0
|
||||
if (baseBank && market && quoteBank) {
|
||||
price = floorToDecimal(
|
||||
baseBank.uiPrice / quoteBank.uiPrice,
|
||||
getDecimalCount(market.tickSize),
|
||||
).toNumber()
|
||||
}
|
||||
|
||||
const pastPrice = m?.marketData?.price_24h || 0
|
||||
|
||||
const priceHistory = m?.marketData?.price_history
|
||||
|
||||
const volume = m?.marketData?.quote_volume_24h || 0
|
||||
|
||||
const change = volume > 0 ? ((price - pastPrice) / pastPrice) * 100 : 0
|
||||
|
||||
const marketName = m.name
|
||||
|
||||
const isUp =
|
||||
price && priceHistory && priceHistory.length
|
||||
? price >= priceHistory[0].price
|
||||
: false
|
||||
|
||||
const data = {
|
||||
baseBank,
|
||||
change,
|
||||
market: m,
|
||||
marketName,
|
||||
price,
|
||||
priceHistory,
|
||||
quoteBank,
|
||||
volume,
|
||||
isUp,
|
||||
}
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted
|
||||
},
|
||||
[group],
|
||||
)
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(
|
||||
formattedTableData(
|
||||
sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h'),
|
||||
),
|
||||
)
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
|
||||
return (
|
||||
|
@ -36,142 +120,152 @@ const SpotMarketsTable = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th className="text-right">{t('price')}</Th>
|
||||
<Th className="text-right">{t('rolling-change')}</Th>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="marketName"
|
||||
sort={() => requestSort('marketName')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('market')}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="price"
|
||||
sort={() => requestSort('price')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('price')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="change"
|
||||
sort={() => requestSort('change')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('rolling-change')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="hidden text-right md:block"></Th>
|
||||
<Th className="text-right">{t('trade:24h-volume')}</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="volume"
|
||||
sort={() => requestSort('volume')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:24h-volume')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
|
||||
(mkt) => {
|
||||
const baseBank = group?.getFirstBankByTokenIndex(
|
||||
mkt.baseTokenIndex,
|
||||
)
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(
|
||||
mkt.quoteTokenIndex,
|
||||
)
|
||||
const market = group?.getSerum3ExternalMarket(
|
||||
mkt.serumMarketExternal,
|
||||
)
|
||||
let price
|
||||
if (baseBank && market && quoteBank) {
|
||||
price = floorToDecimal(
|
||||
baseBank.uiPrice / quoteBank.uiPrice,
|
||||
getDecimalCount(market.tickSize),
|
||||
).toNumber()
|
||||
}
|
||||
{tableData.map((data) => {
|
||||
const {
|
||||
baseBank,
|
||||
change,
|
||||
market,
|
||||
marketName,
|
||||
price,
|
||||
priceHistory,
|
||||
quoteBank,
|
||||
volume,
|
||||
isUp,
|
||||
} = data
|
||||
|
||||
const priceHistory = mkt?.marketData?.price_history
|
||||
|
||||
const volumeData = mkt?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = volumeData ? volumeData : 0
|
||||
|
||||
const isUp =
|
||||
price && priceHistory && priceHistory.length
|
||||
? price >= priceHistory[0].price
|
||||
: false
|
||||
|
||||
return (
|
||||
<TrBody key={mkt.publicKey.toString()}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={mkt} size="large" />
|
||||
<p className="font-body">{mkt.name}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{price ? (
|
||||
<>
|
||||
<FormatNumericValue
|
||||
value={price}
|
||||
isUsd={quoteBank?.name === 'USDC'}
|
||||
/>{' '}
|
||||
{quoteBank?.name !== 'USDC' ? (
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end">
|
||||
<MarketChange market={mkt} />
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{!loadingMarketData ? (
|
||||
priceHistory && priceHistory.length ? (
|
||||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
||||
}
|
||||
data={priceHistory}
|
||||
name={baseBank!.name + quoteBank!.name}
|
||||
xKey="time"
|
||||
yKey="price"
|
||||
/>
|
||||
</div>
|
||||
) : baseBank?.name === 'USDC' ||
|
||||
baseBank?.name === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">
|
||||
{t('unavailable')}
|
||||
</p>
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{volume ? (
|
||||
<span>
|
||||
{numberCompacter.format(volume)}{' '}
|
||||
return (
|
||||
<TrBody key={market.publicKey.toString()}>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} size="large" />
|
||||
<p className="font-body">{marketName}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{price ? (
|
||||
<>
|
||||
<FormatNumericValue
|
||||
value={price}
|
||||
isUsd={quoteBank?.name === 'USDC'}
|
||||
/>{' '}
|
||||
{quoteBank?.name !== 'USDC' ? (
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end">
|
||||
<Change change={change} suffix="%" />
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{!loadingMarketData ? (
|
||||
priceHistory && priceHistory.length ? (
|
||||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]}
|
||||
data={priceHistory}
|
||||
name={baseBank!.name + quoteBank!.name}
|
||||
xKey="time"
|
||||
yKey="price"
|
||||
/>
|
||||
</div>
|
||||
) : baseBank?.name === 'USDC' ||
|
||||
baseBank?.name === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{volume ? (
|
||||
<span>
|
||||
{numberCompacter.format(volume)}{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
0{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
0{' '}
|
||||
<span className="font-body text-th-fgd-4">
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
|
||||
(market) => {
|
||||
return (
|
||||
<MobileSpotMarketItem
|
||||
key={market.publicKey.toString()}
|
||||
loadingMarketData={loadingMarketData}
|
||||
market={market}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)}
|
||||
{tableData.map((data) => {
|
||||
return (
|
||||
<MobileSpotMarketItem
|
||||
key={data.market.publicKey.toString()}
|
||||
loadingMarketData={loadingMarketData}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</ContentBox>
|
||||
|
@ -181,37 +275,26 @@ const SpotMarketsTable = () => {
|
|||
export default SpotMarketsTable
|
||||
|
||||
const MobileSpotMarketItem = ({
|
||||
market,
|
||||
data,
|
||||
loadingMarketData,
|
||||
}: {
|
||||
market: SerumMarketWithMarketData
|
||||
data: TableData
|
||||
loadingMarketData: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const { theme } = useThemeWrapper()
|
||||
const baseBank = group?.getFirstBankByTokenIndex(market.baseTokenIndex)
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(market.quoteTokenIndex)
|
||||
const serumMarket = group?.getSerum3ExternalMarket(market.serumMarketExternal)
|
||||
|
||||
const price = useMemo(() => {
|
||||
if (!baseBank || !quoteBank || !serumMarket) return 0
|
||||
return floorToDecimal(
|
||||
baseBank.uiPrice / quoteBank.uiPrice,
|
||||
getDecimalCount(serumMarket.tickSize),
|
||||
).toNumber()
|
||||
}, [baseBank, quoteBank, serumMarket])
|
||||
|
||||
const priceHistory = market?.marketData?.price_history
|
||||
|
||||
const volueData = market?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = volueData ? volueData : 0
|
||||
|
||||
const isUp =
|
||||
price && priceHistory && priceHistory.length
|
||||
? price >= priceHistory[0].price
|
||||
: false
|
||||
const {
|
||||
baseBank,
|
||||
change,
|
||||
market,
|
||||
marketName,
|
||||
price,
|
||||
priceHistory,
|
||||
quoteBank,
|
||||
volume,
|
||||
isUp,
|
||||
} = data
|
||||
|
||||
return (
|
||||
<Disclosure>
|
||||
|
@ -225,7 +308,7 @@ const MobileSpotMarketItem = ({
|
|||
<div className="flex flex-shrink-0 items-center">
|
||||
<MarketLogos market={market} />
|
||||
</div>
|
||||
<p className="leading-none text-th-fgd-1">{market.name}</p>
|
||||
<p className="leading-none text-th-fgd-1">{marketName}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
{!loadingMarketData ? (
|
||||
|
@ -245,7 +328,7 @@ const MobileSpotMarketItem = ({
|
|||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
<MarketChange market={market} />
|
||||
<Change change={change} suffix="%" />
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
|
|
|
@ -8,13 +8,22 @@ import ContentBox from '../shared/ContentBox'
|
|||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Bank, toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from '@components/shared/TableElements'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
|
||||
import useBanksWithBalances from 'hooks/useBanksWithBalances'
|
||||
import Decimal from 'decimal.js'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import { useCallback } from 'react'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
|
||||
export const goToTokenPage = (token: string, router: NextRouter) => {
|
||||
const query = { ...router.query, ['token']: token }
|
||||
|
@ -29,45 +38,137 @@ const TokenOverviewTable = () => {
|
|||
const router = useRouter()
|
||||
const banks = useBanksWithBalances()
|
||||
|
||||
return group ? (
|
||||
const formattedTableData = useCallback(() => {
|
||||
const formatted = []
|
||||
for (const b of banks) {
|
||||
const bank: Bank = b.bank
|
||||
const deposits = bank.uiDeposits()
|
||||
const borrows = bank.uiBorrows()
|
||||
const availableVaultBalance = group
|
||||
? group.getTokenVaultBalanceByMintUi(bank.mint) -
|
||||
deposits * bank.minVaultToDepositsRatio
|
||||
: 0
|
||||
const available = Decimal.max(
|
||||
0,
|
||||
availableVaultBalance.toFixed(bank.mintDecimals),
|
||||
)
|
||||
const feesEarned = toUiDecimals(
|
||||
bank.collectedFeesNative,
|
||||
bank.mintDecimals,
|
||||
)
|
||||
const utilization =
|
||||
bank.uiDeposits() > 0 ? (bank.uiBorrows() / bank.uiDeposits()) * 100 : 0
|
||||
|
||||
const depositRate = bank.getDepositRateUi()
|
||||
const borrowRate = bank.getBorrowRateUi()
|
||||
const symbol = bank.name
|
||||
|
||||
const data = {
|
||||
available,
|
||||
bank,
|
||||
borrows,
|
||||
borrowRate,
|
||||
deposits,
|
||||
depositRate,
|
||||
feesEarned,
|
||||
symbol,
|
||||
utilization,
|
||||
}
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted
|
||||
}, [banks, group])
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(formattedTableData())
|
||||
|
||||
return (
|
||||
<ContentBox hideBorder hidePadding>
|
||||
{showTableView ? (
|
||||
<div className="thin-scroll overflow-x-auto">
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('token')}</Th>
|
||||
<Th className="text-right">{t('total-deposits')}</Th>
|
||||
<Th className="text-right">{t('total-borrows')}</Th>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="symbol"
|
||||
sort={() => requestSort('symbol')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('token')}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="deposits"
|
||||
sort={() => requestSort('deposits')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('total-deposits')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="borrows"
|
||||
sort={() => requestSort('borrows')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('total-borrows')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="text-right">
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The amount available to borrow">
|
||||
<span className="tooltip-underline">
|
||||
{t('available')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="available"
|
||||
sort={() => requestSort('available')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('available')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('token:fees-tooltip')}>
|
||||
<span className="tooltip-underline">{t('fees')}</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="feesEarned"
|
||||
sort={() => requestSort('feesEarned')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('fees')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The deposit rate (green) will automatically be paid on positive balances and the borrow rate (red) will automatically be charged on negative balances.">
|
||||
<span className="tooltip-underline">{t('rates')}</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="depositRate"
|
||||
sort={() => requestSort('depositRate')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('rates')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The percentage of deposits that have been lent out">
|
||||
<span className="tooltip-underline">
|
||||
{t('utilization')}
|
||||
</span>
|
||||
<SortableColumnHeader
|
||||
sortKey="utilization"
|
||||
sort={() => requestSort('utilization')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('utilization')}
|
||||
titleClass="tooltip-underline"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
|
@ -75,26 +176,23 @@ const TokenOverviewTable = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map((b) => {
|
||||
const bank: Bank = b.bank
|
||||
const deposits = bank.uiDeposits()
|
||||
const borrows = bank.uiBorrows()
|
||||
const availableVaultBalance =
|
||||
group.getTokenVaultBalanceByMintUi(bank.mint) -
|
||||
deposits * bank.minVaultToDepositsRatio
|
||||
const available = Decimal.max(
|
||||
0,
|
||||
availableVaultBalance.toFixed(bank.mintDecimals),
|
||||
)
|
||||
const feesEarned = toUiDecimals(
|
||||
bank.collectedFeesNative,
|
||||
bank.mintDecimals,
|
||||
)
|
||||
{tableData.map((data) => {
|
||||
const {
|
||||
available,
|
||||
bank,
|
||||
borrows,
|
||||
borrowRate,
|
||||
deposits,
|
||||
depositRate,
|
||||
feesEarned,
|
||||
symbol,
|
||||
utilization,
|
||||
} = data
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||
key={bank.name}
|
||||
key={symbol}
|
||||
onClick={() =>
|
||||
goToTokenPage(bank.name.split(' ')[0], router)
|
||||
}
|
||||
|
@ -104,7 +202,7 @@ const TokenOverviewTable = () => {
|
|||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<TokenLogo bank={bank} />
|
||||
</div>
|
||||
<p className="font-body">{bank.name}</p>
|
||||
<p className="font-body">{symbol}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
@ -151,32 +249,21 @@ const TokenOverviewTable = () => {
|
|||
<div className="flex justify-end space-x-1.5">
|
||||
<p className="text-th-up">
|
||||
<FormatNumericValue
|
||||
value={bank.getDepositRateUi()}
|
||||
value={depositRate}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
</p>
|
||||
<span className="text-th-fgd-4">|</span>
|
||||
<p className="text-th-down">
|
||||
<FormatNumericValue
|
||||
value={bank.getBorrowRateUi()}
|
||||
decimals={2}
|
||||
/>
|
||||
<FormatNumericValue value={borrowRate} decimals={2} />
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{bank.uiDeposits() > 0
|
||||
? (
|
||||
(bank.uiBorrows() / bank.uiDeposits()) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: '0.0'}
|
||||
%
|
||||
</p>
|
||||
<p>{utilization.toFixed(1)}%</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
@ -192,23 +279,21 @@ const TokenOverviewTable = () => {
|
|||
</div>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{banks.map((b, i) => {
|
||||
const bank = b.bank
|
||||
const deposits = bank.uiDeposits()
|
||||
const borrows = bank.uiBorrows()
|
||||
const availableVaultBalance =
|
||||
group.getTokenVaultBalanceByMintUi(bank.mint) -
|
||||
deposits * bank.minVaultToDepositsRatio
|
||||
const available = Decimal.max(
|
||||
0,
|
||||
availableVaultBalance.toFixed(bank.mintDecimals),
|
||||
)
|
||||
const feesEarned = toUiDecimals(
|
||||
bank.collectedFeesNative,
|
||||
bank.mintDecimals,
|
||||
)
|
||||
{tableData.map((data, i) => {
|
||||
const {
|
||||
available,
|
||||
bank,
|
||||
borrows,
|
||||
borrowRate,
|
||||
deposits,
|
||||
depositRate,
|
||||
feesEarned,
|
||||
symbol,
|
||||
utilization,
|
||||
} = data
|
||||
|
||||
return (
|
||||
<Disclosure key={bank.name}>
|
||||
<Disclosure key={symbol}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
|
@ -221,7 +306,7 @@ const TokenOverviewTable = () => {
|
|||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<TokenLogo bank={bank} />
|
||||
</div>
|
||||
<p className="text-th-fgd-1">{bank.name}</p>
|
||||
<p className="text-th-fgd-1">{symbol}</p>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
|
@ -286,7 +371,7 @@ const TokenOverviewTable = () => {
|
|||
<p className="space-x-2">
|
||||
<span className="font-mono text-th-up">
|
||||
<FormatNumericValue
|
||||
value={bank.getDepositRateUi()}
|
||||
value={depositRate}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
|
@ -296,7 +381,7 @@ const TokenOverviewTable = () => {
|
|||
</span>
|
||||
<span className="font-mono text-th-down">
|
||||
<FormatNumericValue
|
||||
value={bank.getBorrowRateUi()}
|
||||
value={borrowRate}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
|
@ -306,13 +391,7 @@ const TokenOverviewTable = () => {
|
|||
<div className="col-span-1">
|
||||
<p className="text-xs">{t('utilization')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{bank.uiDeposits() > 0
|
||||
? (
|
||||
(bank.uiBorrows() / bank.uiDeposits()) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: '0.0'}
|
||||
%
|
||||
{utilization}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
|
@ -337,7 +416,7 @@ const TokenOverviewTable = () => {
|
|||
</div>
|
||||
)}
|
||||
</ContentBox>
|
||||
) : null
|
||||
)
|
||||
}
|
||||
|
||||
export default TokenOverviewTable
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback, useMemo } from 'react'
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
|
@ -18,7 +18,7 @@ import Loading from '../shared/Loading'
|
|||
import { EnterBottomExitBottom } from '../shared/Transitions'
|
||||
import useQuoteRoutes from './useQuoteRoutes'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import { MANGO_MINT, USDC_MINT } from '../../utils/constants'
|
||||
import { MANGO_MINT, SWAP_MARGIN_KEY, USDC_MINT } from '../../utils/constants'
|
||||
import { useTokenMax } from './useTokenMax'
|
||||
import HealthImpact from '@components/shared/HealthImpact'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
@ -34,6 +34,7 @@ import TabUnderline from '@components/shared/TabUnderline'
|
|||
import MarketSwapForm from './MarketSwapForm'
|
||||
import LimitSwapForm from './LimitSwapForm'
|
||||
import Switch from '@components/forms/Switch'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -46,6 +47,10 @@ const SwapForm = () => {
|
|||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const [swapOrLimit, setSwapOrLimit] = useState('swap')
|
||||
const { group } = useMangoGroup()
|
||||
const [, setSavedSwapMargin] = useLocalStorageState<boolean>(
|
||||
SWAP_MARGIN_KEY,
|
||||
true,
|
||||
)
|
||||
const { ipAllowed, ipCountry } = useIpAddress()
|
||||
|
||||
const {
|
||||
|
@ -181,6 +186,10 @@ const SwapForm = () => {
|
|||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSavedSwapMargin(useMargin)
|
||||
}, [useMargin])
|
||||
|
||||
const limitOrderDisabled =
|
||||
!connected || !amountInFormValue || !amountOutFormValue
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useEffect, useMemo, useState } from 'react'
|
|||
import { TokenStatsItem } from 'types'
|
||||
import { formatYAxis } from 'utils/formatting'
|
||||
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
||||
import TokenRatesChart from './TokenRatesChart'
|
||||
|
||||
const ChartTabs = ({ bank }: { bank: Bank }) => {
|
||||
const { t } = useTranslation('token')
|
||||
|
@ -33,8 +34,6 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
|||
return tokenStats.reduce((a: TokenStatsItem[], c: TokenStatsItem) => {
|
||||
if (c.token_index === bank.tokenIndex) {
|
||||
const copy = { ...c }
|
||||
copy.deposit_apr = copy.deposit_apr * 100
|
||||
copy.borrow_apr = copy.borrow_apr * 100
|
||||
a.push(copy)
|
||||
}
|
||||
return a.sort(
|
||||
|
@ -69,25 +68,18 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
|||
loading={loadingTokenStats}
|
||||
small
|
||||
tickFormat={(x) => formatYAxis(x)}
|
||||
title={`${bank?.name} ${t('token:deposits')}`}
|
||||
title={`${t('token:deposits')}`}
|
||||
xKey="date_hour"
|
||||
yKey={'total_deposits'}
|
||||
/>
|
||||
) : (
|
||||
<DetailedAreaOrBarChart
|
||||
<TokenRatesChart
|
||||
data={statsHistory}
|
||||
dataKey="deposit_apr"
|
||||
daysToShow={depositRateDaysToShow}
|
||||
setDaysToShow={setDepositRateDaysToShow}
|
||||
heightClass="h-64"
|
||||
loaderHeightClass="h-[334px]"
|
||||
loading={loadingTokenStats}
|
||||
hideChange
|
||||
small
|
||||
suffix="%"
|
||||
tickFormat={(x) => `${x.toFixed(2)}%`}
|
||||
title={`${bank?.name} ${t('token:deposit-rates')} APR`}
|
||||
xKey="date_hour"
|
||||
yKey={'deposit_apr'}
|
||||
setDaysToShow={setDepositRateDaysToShow}
|
||||
title={`${t('token:average-deposit-rate')} (APR)`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -116,25 +108,18 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
|
|||
loading={loadingTokenStats}
|
||||
small
|
||||
tickFormat={(x) => formatYAxis(x)}
|
||||
title={`${bank?.name} ${t('token:borrows')}`}
|
||||
title={`${t('token:borrows')}`}
|
||||
xKey="date_hour"
|
||||
yKey={'total_borrows'}
|
||||
/>
|
||||
) : (
|
||||
<DetailedAreaOrBarChart
|
||||
<TokenRatesChart
|
||||
data={statsHistory}
|
||||
dataKey="borrow_apr"
|
||||
daysToShow={borrowRateDaysToShow}
|
||||
setDaysToShow={setBorrowRateDaysToShow}
|
||||
heightClass="h-64"
|
||||
loaderHeightClass="h-[334px]"
|
||||
loading={loadingTokenStats}
|
||||
small
|
||||
hideChange
|
||||
suffix="%"
|
||||
tickFormat={(x) => `${x.toFixed(2)}%`}
|
||||
title={`${bank?.name} ${t('token:borrow-rates')} APR`}
|
||||
xKey="date_hour"
|
||||
yKey={'borrow_apr'}
|
||||
setDaysToShow={setBorrowRateDaysToShow}
|
||||
title={`${t('token:average-borrow-rate')} (APR)`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TokenStatsItem } from 'types'
|
||||
|
||||
interface GroupedDataItem extends TokenStatsItem {
|
||||
intervalStartMillis: number
|
||||
}
|
||||
|
||||
const groupByHourlyInterval = (
|
||||
data: TokenStatsItem[],
|
||||
dataKey: 'borrow_apr' | 'deposit_apr',
|
||||
intervalDurationHours: number,
|
||||
) => {
|
||||
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
|
||||
const groupedData = []
|
||||
let currentGroup: GroupedDataItem | null = null
|
||||
let itemsInCurrentGroup = 0
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const obj = data[i]
|
||||
const date = new Date(obj.date_hour)
|
||||
const intervalStartMillis =
|
||||
Math.floor(date.getTime() / intervalMillis) * intervalMillis
|
||||
|
||||
if (
|
||||
!currentGroup ||
|
||||
currentGroup.intervalStartMillis !== intervalStartMillis
|
||||
) {
|
||||
if (currentGroup) {
|
||||
// calculate the average for the previous group
|
||||
currentGroup[dataKey] /= itemsInCurrentGroup
|
||||
groupedData.push(currentGroup)
|
||||
}
|
||||
|
||||
currentGroup = {
|
||||
...obj,
|
||||
intervalStartMillis: intervalStartMillis,
|
||||
}
|
||||
|
||||
// initialize the sum for the new group
|
||||
currentGroup[dataKey] = obj[dataKey] * 100
|
||||
itemsInCurrentGroup = 1
|
||||
} else {
|
||||
// add the value to the sum for the current group
|
||||
currentGroup[dataKey] += obj[dataKey] * 100
|
||||
itemsInCurrentGroup++
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the average for the last group (if it exists)
|
||||
if (currentGroup) {
|
||||
currentGroup[dataKey] /= itemsInCurrentGroup
|
||||
groupedData.push(currentGroup)
|
||||
}
|
||||
|
||||
return groupedData
|
||||
}
|
||||
|
||||
const TokenRatesChart = ({
|
||||
data,
|
||||
dataKey,
|
||||
daysToShow,
|
||||
loading,
|
||||
setDaysToShow,
|
||||
title,
|
||||
}: {
|
||||
data: TokenStatsItem[]
|
||||
dataKey: 'deposit_apr' | 'borrow_apr'
|
||||
daysToShow: string | undefined
|
||||
loading: boolean
|
||||
setDaysToShow: (x: string) => void
|
||||
title: string
|
||||
}) => {
|
||||
const { t } = useTranslation('stats')
|
||||
const [interval, intervalString] = useMemo(() => {
|
||||
if (daysToShow === '30') {
|
||||
return [24, 'stats:daily']
|
||||
} else if (daysToShow === '7') {
|
||||
return [6, 'stats:six-hourly']
|
||||
} else {
|
||||
return [1, 'stats:hourly']
|
||||
}
|
||||
}, [daysToShow])
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
if (!data || !data.length) return []
|
||||
const groupedData = groupByHourlyInterval(data, dataKey, interval)
|
||||
return groupedData
|
||||
}, [data, dataKey, daysToShow, interval])
|
||||
|
||||
return (
|
||||
<DetailedAreaOrBarChart
|
||||
chartType="bar"
|
||||
data={chartData}
|
||||
daysToShow={daysToShow}
|
||||
setDaysToShow={setDaysToShow}
|
||||
heightClass="h-64"
|
||||
loaderHeightClass="h-[334px]"
|
||||
loading={loading}
|
||||
small
|
||||
hideChange
|
||||
suffix="%"
|
||||
tickFormat={(x) => `${x.toFixed(2)}%`}
|
||||
title={`${t(intervalString)} ${title}`}
|
||||
xKey="date_hour"
|
||||
yKey={dataKey}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TokenRatesChart
|
|
@ -22,16 +22,19 @@ const OrderbookTooltip = () => {
|
|||
|
||||
const { averagePrice, cumulativeSize, cumulativeValue, side } =
|
||||
orderbookTooltip
|
||||
const isBid = side === 'buy'
|
||||
const isBuy = side === 'sell'
|
||||
const oppositeSide = side === 'buy' ? 'sell' : 'buy'
|
||||
const isPerp = serumOrPerpMarket instanceof PerpMarket
|
||||
return (
|
||||
<div
|
||||
className={`absolute max-w-[75%] w-full top-4 left-1/2 -translate-x-1/2 p-3 rounded-md bg-th-bkg-1 border text-center ${
|
||||
isBid ? 'border-th-up' : 'border-th-down'
|
||||
isBuy ? 'border-th-up' : 'border-th-down'
|
||||
}`}
|
||||
>
|
||||
<p>
|
||||
<span className={isBid ? 'text-th-up' : 'text-th-down'}>{t(side)}</span>
|
||||
<span className={isBuy ? 'text-th-up' : 'text-th-down'}>
|
||||
{t(oppositeSide)}
|
||||
</span>
|
||||
{` ${formatNumericValue(cumulativeSize, minOrderDecimals)} ${
|
||||
isPerp ? '' : baseSymbol
|
||||
} ${t('trade:for')} ${isPerp ? '$' : ''}${formatNumericValue(
|
||||
|
|
|
@ -5,6 +5,7 @@ import FormatNumericValue from '@components/shared/FormatNumericValue'
|
|||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
TableDateDisplay,
|
||||
Td,
|
||||
|
@ -29,6 +30,8 @@ import { breakpoints } from 'utils/theme'
|
|||
import MarketLogos from './MarketLogos'
|
||||
import PerpSideBadge from './PerpSideBadge'
|
||||
import TableMarketName from './TableMarketName'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
const TradeHistory = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
|
@ -44,6 +47,26 @@ const TradeHistory = () => {
|
|||
const { connected } = useWallet()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const formattedTableData = useCallback(() => {
|
||||
const formatted = []
|
||||
for (const trade of combinedTradeHistory) {
|
||||
const marketName = trade.market.name
|
||||
const value = trade.price * trade.size
|
||||
const sortTime = trade?.time
|
||||
? trade.time
|
||||
: dayjs().format('YYYY-MM-DDTHH:mm:ss')
|
||||
const data = { ...trade, marketName, value, sortTime }
|
||||
formatted.push(data)
|
||||
}
|
||||
return formatted
|
||||
}, [combinedTradeHistory])
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(formattedTableData())
|
||||
|
||||
if (!selectedMarket || !group) return null
|
||||
|
||||
return mangoAccountAddress &&
|
||||
|
@ -54,18 +77,71 @@ const TradeHistory = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</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>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="marketName"
|
||||
sort={() => requestSort('marketName')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('market')}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="size"
|
||||
sort={() => requestSort('size')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('trade:size')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="price"
|
||||
sort={() => requestSort('price')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('price')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="value"
|
||||
sort={() => requestSort('value')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('value')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="feeCost"
|
||||
sort={() => requestSort('feeCost')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('fee')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="sortTime"
|
||||
sort={() => requestSort('sortTime')}
|
||||
sortConfig={sortConfig}
|
||||
title={t('date')}
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{combinedTradeHistory.map((trade, index: number) => {
|
||||
const { side, price, market, size, feeCost, liquidity } = trade
|
||||
{tableData.map((trade, index: number) => {
|
||||
const { side, price, market, size, feeCost, liquidity, value } =
|
||||
trade
|
||||
return (
|
||||
<TrBody
|
||||
key={`${side}${size}${price}${index}`}
|
||||
|
@ -88,11 +164,7 @@ const TradeHistory = () => {
|
|||
<FormatNumericValue value={price} />
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<FormatNumericValue
|
||||
value={price * size}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
<FormatNumericValue value={value} decimals={2} isUsd />
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
<span className="font-mono">
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
type Direction = 'ascending' | 'descending'
|
||||
|
||||
export interface SortConfig {
|
||||
key: string
|
||||
direction: Direction
|
||||
}
|
||||
|
||||
export function useSortableData<T extends Record<string, any>>(
|
||||
items: T[],
|
||||
config: SortConfig | null = null,
|
||||
): {
|
||||
items: T[]
|
||||
requestSort: (key: string) => void
|
||||
sortConfig: SortConfig | null
|
||||
} {
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig | null>(config)
|
||||
|
||||
const sortedItems = useMemo(() => {
|
||||
const sortableItems = items ? [...items] : []
|
||||
if (sortConfig !== null) {
|
||||
sortableItems.sort((a, b) => {
|
||||
if (!isNaN(a[sortConfig.key])) {
|
||||
return sortConfig.direction === 'ascending'
|
||||
? a[sortConfig.key] - b[sortConfig.key]
|
||||
: b[sortConfig.key] - a[sortConfig.key]
|
||||
}
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) {
|
||||
return sortConfig.direction === 'ascending' ? -1 : 1
|
||||
}
|
||||
if (a[sortConfig.key] > b[sortConfig.key]) {
|
||||
return sortConfig.direction === 'ascending' ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
return sortableItems
|
||||
}, [items, sortConfig])
|
||||
|
||||
const requestSort = (key: string) => {
|
||||
let direction: Direction = 'ascending'
|
||||
if (
|
||||
sortConfig &&
|
||||
sortConfig.key === key &&
|
||||
sortConfig.direction === 'ascending'
|
||||
) {
|
||||
direction = 'descending'
|
||||
}
|
||||
setSortConfig({ key, direction })
|
||||
}
|
||||
|
||||
return { items: sortedItems, requestSort, sortConfig }
|
||||
}
|
|
@ -22,7 +22,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-feeds": "0.1.7",
|
||||
"@blockworks-foundation/mango-v4": "^0.17.27",
|
||||
"@blockworks-foundation/mango-v4": "^0.18.3",
|
||||
"@headlessui/react": "1.6.6",
|
||||
"@heroicons/react": "2.0.10",
|
||||
"@metaplex-foundation/js": "0.19.4",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"@solana/wallet-adapter-react": "0.15.32",
|
||||
"@solana/wallet-adapter-wallets": "0.19.18",
|
||||
"@solflare-wallet/pfp": "0.0.6",
|
||||
"@switchboard-xyz/solana.js": "2.2.0",
|
||||
"@switchboard-xyz/solana.js": "2.4.7",
|
||||
"@tanstack/react-query": "4.10.1",
|
||||
"@tippyjs/react": "4.2.6",
|
||||
"@types/howler": "2.2.7",
|
||||
|
@ -71,14 +71,14 @@
|
|||
"react-i18next": "13.0.2",
|
||||
"react-nice-dates": "3.1.0",
|
||||
"react-number-format": "4.9.2",
|
||||
"react-responsive-pagination": "^2.1.0",
|
||||
"react-tsparticles": "2.2.4",
|
||||
"react-window": "1.8.7",
|
||||
"recharts": "2.5.0",
|
||||
"tsparticles": "2.2.4",
|
||||
"walktour": "5.1.1",
|
||||
"webpack-node-externals": "3.0.0",
|
||||
"zustand": "4.1.3",
|
||||
"react-responsive-pagination": "^2.1.0"
|
||||
"zustand": "4.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@project-serum/anchor": "0.25.0",
|
||||
|
|
|
@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, [
|
||||
'account',
|
||||
'common',
|
||||
'notifications',
|
||||
'onboarding',
|
||||
|
|
|
@ -15,6 +15,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, [
|
||||
'account',
|
||||
'common',
|
||||
'notifications',
|
||||
'onboarding',
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"refresh-balance": "Refresh Balance",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"average-borrow-rate": "Average Borrow Rate",
|
||||
"average-deposit-rate": "Average Deposit Rate",
|
||||
"borrowing": "Borrowing",
|
||||
"borrows": "Borrows",
|
||||
"borrow-rates": "Borrow Rates",
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"init-health": "Init Health",
|
||||
"maint-health": "Maint Health",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
|
@ -17,6 +19,7 @@
|
|||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"refresh-balance": "Refresh Balance",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
|
@ -29,5 +32,6 @@
|
|||
"total-funding-earned": "Total Funding Earned",
|
||||
"volume-chart": "Volume Chart",
|
||||
"week-starting": "Week starting {{week}}",
|
||||
"zero-collateral": "Zero Collateral"
|
||||
"zero-collateral": "Zero Collateral",
|
||||
"zero-balances": "Show Zero Balances"
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"average-borrow-rate": "Average Borrow Rate",
|
||||
"average-deposit-rate": "Average Deposit Rate",
|
||||
"borrowing": "Borrowing",
|
||||
"borrows": "Borrows",
|
||||
"borrow-rates": "Borrow Rates",
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"init-health": "Init Health",
|
||||
"maint-health": "Maint Health",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
|
@ -17,6 +19,7 @@
|
|||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"refresh-balance": "Refresh Balance",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
|
@ -29,5 +32,6 @@
|
|||
"total-funding-earned": "Total Funding Earned",
|
||||
"volume-chart": "Volume Chart",
|
||||
"week-starting": "Week starting {{week}}",
|
||||
"zero-collateral": "Zero Collateral"
|
||||
"zero-collateral": "Zero Collateral",
|
||||
"zero-balances": "Show Zero Balances"
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"all-time-high": "All-time High",
|
||||
"all-time-low": "All-time Low",
|
||||
"average-borrow-rate": "Average Borrow Rate",
|
||||
"average-deposit-rate": "Average Deposit Rate",
|
||||
"borrowing": "Borrowing",
|
||||
"borrows": "Borrows",
|
||||
"borrow-rates": "Borrow Rates",
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"init-health": "Init Health",
|
||||
"maint-health": "Maint Health",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
|
@ -17,6 +19,7 @@
|
|||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"refresh-balance": "Refresh Balance",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
|
@ -29,5 +32,6 @@
|
|||
"total-funding-earned": "Total Funding Earned",
|
||||
"volume-chart": "Volume Chart",
|
||||
"week-starting": "Week starting {{week}}",
|
||||
"zero-collateral": "Zero Collateral"
|
||||
"zero-collateral": "Zero Collateral",
|
||||
"zero-balances": "Show Zero Balances"
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"all-time-high": "历史高价",
|
||||
"all-time-low": "历史低价",
|
||||
"average-borrow-rate": "Average Borrow Rate",
|
||||
"average-deposit-rate": "Average Deposit Rate",
|
||||
"borrow-rates": "借贷利率",
|
||||
"borrow-upkeep-rate": "维持借贷的费用",
|
||||
"borrowing": "借入",
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"init-health": "Init Health",
|
||||
"maint-health": "Maint Health",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
|
@ -17,6 +19,7 @@
|
|||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"refresh-balance": "Refresh Balance",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
|
@ -29,5 +32,6 @@
|
|||
"total-funding-earned": "Total Funding Earned",
|
||||
"volume-chart": "Volume Chart",
|
||||
"week-starting": "Week starting {{week}}",
|
||||
"zero-collateral": "Zero Collateral"
|
||||
"zero-collateral": "Zero Collateral",
|
||||
"zero-balances": "Show Zero Balances"
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"all-time-high": "歷史高價",
|
||||
"all-time-low": "歷史低價",
|
||||
"average-borrow-rate": "Average Borrow Rate",
|
||||
"average-deposit-rate": "Average Deposit Rate",
|
||||
"borrow-rates": "借貸利率",
|
||||
"borrow-upkeep-rate": "維持借貸的費用",
|
||||
"borrowing": "借入",
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
PAGINATION_PAGE_LENGTH,
|
||||
PRIORITY_FEE_KEY,
|
||||
RPC_PROVIDER_KEY,
|
||||
SWAP_MARGIN_KEY,
|
||||
} from '../utils/constants'
|
||||
import {
|
||||
ActivityFeed,
|
||||
|
@ -270,12 +271,17 @@ export type MangoStore = {
|
|||
const mangoStore = create<MangoStore>()(
|
||||
subscribeWithSelector((_set, get) => {
|
||||
let rpcUrl = ENDPOINT.url
|
||||
let swapMargin = true
|
||||
|
||||
if (typeof window !== 'undefined' && CLUSTER === 'mainnet-beta') {
|
||||
const urlFromLocalStorage = localStorage.getItem(RPC_PROVIDER_KEY)
|
||||
const swapMarginFromLocalStorage = localStorage.getItem(SWAP_MARGIN_KEY)
|
||||
rpcUrl = urlFromLocalStorage
|
||||
? JSON.parse(urlFromLocalStorage)
|
||||
: ENDPOINT.url
|
||||
swapMargin = swapMarginFromLocalStorage
|
||||
? JSON.parse(swapMarginFromLocalStorage)
|
||||
: true
|
||||
}
|
||||
|
||||
let connection: Connection
|
||||
|
@ -370,7 +376,7 @@ const mangoStore = create<MangoStore>()(
|
|||
outputBank: undefined,
|
||||
inputTokenInfo: undefined,
|
||||
outputTokenInfo: undefined,
|
||||
margin: true,
|
||||
margin: swapMargin,
|
||||
slippage: 0.5,
|
||||
swapMode: 'ExactIn',
|
||||
amountIn: '',
|
||||
|
|
|
@ -53,6 +53,8 @@ export const PRIORITY_FEE_KEY = 'priorityFeeKey-0.1'
|
|||
|
||||
export const SHOW_ORDER_LINES_KEY = 'showOrderLines-0.1'
|
||||
|
||||
export const SWAP_MARGIN_KEY = 'swapMargin-0.1'
|
||||
|
||||
export const SHOW_SWAP_INTRO_MODAL = 'showSwapModal-0.1'
|
||||
|
||||
export const ACCEPT_TERMS_KEY = 'termsOfUseAccepted-0.1'
|
||||
|
|
96
yarn.lock
96
yarn.lock
|
@ -12,7 +12,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@apocentre/alias-sampling/-/alias-sampling-0.5.3.tgz#897ff181b48ad7b2bcb4ecf29400214888244f08"
|
||||
integrity sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA==
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.5":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.5", "@babel/runtime@^7.22.6":
|
||||
version "7.22.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
|
||||
integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
|
||||
|
@ -26,10 +26,10 @@
|
|||
dependencies:
|
||||
ws "^8.13.0"
|
||||
|
||||
"@blockworks-foundation/mango-v4@^0.17.27":
|
||||
version "0.17.27"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.17.27.tgz#70998e21e8fa2ae340fec0c36a7f8c07400c6fd4"
|
||||
integrity sha512-oXvA08MqZ3dvd1ODHwx/XjMGyFvdC+6MTfzNL3vhylJA2JLlck48Fscl0mHL1h9kOdFIHJJPrnCkIW5z5g9pIQ==
|
||||
"@blockworks-foundation/mango-v4@^0.18.3":
|
||||
version "0.18.3"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.18.3.tgz#a3d41cfb85f9b7121469d2ac3d52a7915f677701"
|
||||
integrity sha512-j45GXiLPncKaSnBPLg0JLYEW2kSRRCWklE0usmhPlPD99KcX/iKqwFEzWtcOuGC5sQJTn4cblTOj5V6iiUiGAw==
|
||||
dependencies:
|
||||
"@coral-xyz/anchor" "^0.27.0"
|
||||
"@project-serum/serum" "0.13.65"
|
||||
|
@ -89,7 +89,7 @@
|
|||
eventemitter3 "^4.0.7"
|
||||
uuid "^8.3.2"
|
||||
|
||||
"@coral-xyz/anchor@^0.26.0", "@coral-xyz/anchor@^0.27.0":
|
||||
"@coral-xyz/anchor@^0.26.0", "@coral-xyz/anchor@^0.27.0", "@coral-xyz/anchor@^0.28.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.27.0.tgz#621e5ef123d05811b97e49973b4ed7ede27c705c"
|
||||
integrity sha512-+P/vPdORawvg3A9Wj02iquxb4T0C5m4P6aZBVYysKl4Amk+r6aMPZkUhilBkD6E4Nuxnoajv3CFykUfkGE0n5g==
|
||||
|
@ -118,6 +118,14 @@
|
|||
bn.js "^5.1.2"
|
||||
buffer-layout "^1.2.0"
|
||||
|
||||
"@coral-xyz/borsh@^0.28.0":
|
||||
version "0.28.0"
|
||||
resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.28.0.tgz#fa368a2f2475bbf6f828f4657f40a52102e02b6d"
|
||||
integrity sha512-/u1VTzw7XooK7rqeD7JLUSwOyRSesPUk0U37BV9zK0axJc1q0nRbKFGFLYCQ16OtdOJTTwGfGp11Lx9B45bRCQ==
|
||||
dependencies:
|
||||
bn.js "^5.1.2"
|
||||
buffer-layout "^1.2.0"
|
||||
|
||||
"@eslint/eslintrc@^1.2.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
|
||||
|
@ -1449,7 +1457,7 @@
|
|||
bs58 "^4.0.1"
|
||||
superstruct "^0.15.2"
|
||||
|
||||
"@solana/spl-token@0.3.7", "@solana/spl-token@^0.3.5", "@solana/spl-token@^0.3.6":
|
||||
"@solana/spl-token@0.3.7":
|
||||
version "0.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.7.tgz#6f027f9ad8e841f792c32e50920d9d2e714fc8da"
|
||||
integrity sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==
|
||||
|
@ -1470,6 +1478,15 @@
|
|||
buffer-layout "^1.2.0"
|
||||
dotenv "10.0.0"
|
||||
|
||||
"@solana/spl-token@^0.3.5", "@solana/spl-token@^0.3.6", "@solana/spl-token@^0.3.8":
|
||||
version "0.3.8"
|
||||
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf"
|
||||
integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg==
|
||||
dependencies:
|
||||
"@solana/buffer-layout" "^4.0.0"
|
||||
"@solana/buffer-layout-utils" "^0.2.0"
|
||||
buffer "^6.0.3"
|
||||
|
||||
"@solana/wallet-adapter-alpha@^0.1.9":
|
||||
version "0.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-alpha/-/wallet-adapter-alpha-0.1.9.tgz#863ae3f7108046c9e022c80023bb1b0877a6dec5"
|
||||
|
@ -1922,12 +1939,12 @@
|
|||
"@wallet-standard/app" "^1.0.1"
|
||||
"@wallet-standard/base" "^1.0.1"
|
||||
|
||||
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.22.0", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.50.1", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.73.0", "@solana/web3.js@^1.73.2":
|
||||
version "1.77.3"
|
||||
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.77.3.tgz#2cbeaa1dd24f8fa386ac924115be82354dfbebab"
|
||||
integrity sha512-PHaO0BdoiQRPpieC1p31wJsBaxwIOWLh8j2ocXNKX8boCQVldt26Jqm2tZE4KlrvnCIV78owPLv1pEUgqhxZ3w==
|
||||
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.22.0", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.50.1", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.66.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.73.2", "@solana/web3.js@^1.78.0":
|
||||
version "1.78.1"
|
||||
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.78.1.tgz#1b1023f81aa98f53ef45eaec642be11c0a0877f3"
|
||||
integrity sha512-r0WZAYwCfVElfONP/dmWkEfw6wufL+u7lWojEsNecn9PyIIYq+r4eb0h2MRiJ3xkctvTN76G0T6FTGcTJhXh3Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@babel/runtime" "^7.22.6"
|
||||
"@noble/curves" "^1.0.0"
|
||||
"@noble/hashes" "^1.3.0"
|
||||
"@solana/buffer-layout" "^4.0.0"
|
||||
|
@ -1939,7 +1956,7 @@
|
|||
buffer "6.0.3"
|
||||
fast-stable-stringify "^1.0.0"
|
||||
jayson "^4.1.0"
|
||||
node-fetch "^2.6.7"
|
||||
node-fetch "^2.6.12"
|
||||
rpc-websockets "^7.5.1"
|
||||
superstruct "^0.14.2"
|
||||
|
||||
|
@ -2122,18 +2139,19 @@
|
|||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@switchboard-xyz/common@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@switchboard-xyz/common/-/common-2.2.3.tgz#f4d39cea8cea9354ad369f749462fa37152c4ec9"
|
||||
integrity sha512-E4NQf9aXdOiul+sySAbFPAW9k0qz4wRTfqrU7cEa8nRIvUkg6VIZ+5JfajHv/VfK9UOD+6ZfMBxq2+dHkiz9zw==
|
||||
"@switchboard-xyz/common@^2.2.8":
|
||||
version "2.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@switchboard-xyz/common/-/common-2.2.9.tgz#a9e71bb63eb55c8d47295ed10ba35921beb99079"
|
||||
integrity sha512-ZhGLT8YobM3A3GKv8UMXQHUgW3bthyk+fsqEDWhO0Ej1xcOjGOS3c++K1/9Z8/xl1xekgQ5kj2CKC8AWxr+U+w==
|
||||
dependencies:
|
||||
"@solana/web3.js" "^1.66.2"
|
||||
"@coral-xyz/borsh" "^0.28.0"
|
||||
"@types/big.js" "^6.1.6"
|
||||
"@types/bn.js" "^5.1.1"
|
||||
big.js "^6.2.1"
|
||||
bn.js "^5.2.1"
|
||||
bs58 "^5.0.0"
|
||||
decimal.js "^10.4.3"
|
||||
lodash "^4.17.21"
|
||||
protobufjs "^7.2.3"
|
||||
yaml "^2.2.1"
|
||||
|
||||
|
@ -2145,17 +2163,18 @@
|
|||
"@project-serum/anchor" "^0.24.2"
|
||||
big.js "^6.1.1"
|
||||
|
||||
"@switchboard-xyz/solana.js@2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@switchboard-xyz/solana.js/-/solana.js-2.2.0.tgz#5108cfbbf0ca6e48297ae8c6e8c11f39e04ac32a"
|
||||
integrity sha512-UzAyKDY1wq1UO50PsKc/6huF6xYX/3B5kA0lmEnZMb+5L6M3YtDckbxk6mD4kG7J0curvvX6Alu9cO6uGqnI3A==
|
||||
"@switchboard-xyz/solana.js@2.4.7":
|
||||
version "2.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@switchboard-xyz/solana.js/-/solana.js-2.4.7.tgz#48013db5a4166a57dbfbf45adbd7a4561a841eb8"
|
||||
integrity sha512-eISKIkM29HRfs3hS+6Y1r7+gl3KsuHxT9EvwNvU921QL5RUzPnZtRinUQrNm0z4BKGJTzs4HZOUzNfV1acseKw==
|
||||
dependencies:
|
||||
"@coral-xyz/anchor" "^0.27.0"
|
||||
"@coral-xyz/borsh" "^0.27.0"
|
||||
"@solana/spl-token" "^0.3.6"
|
||||
"@solana/web3.js" "^1.73.0"
|
||||
"@switchboard-xyz/common" "^2.2.3"
|
||||
dotenv "^16.0.3"
|
||||
"@coral-xyz/anchor" "^0.28.0"
|
||||
"@coral-xyz/borsh" "^0.28.0"
|
||||
"@solana/spl-token" "^0.3.8"
|
||||
"@solana/web3.js" "^1.78.0"
|
||||
"@switchboard-xyz/common" "^2.2.8"
|
||||
cron-validator "^1.3.1"
|
||||
dotenv "^16.3.1"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@tanstack/query-core@4.10.1":
|
||||
|
@ -3992,6 +4011,11 @@ create-hmac@1.1.7, create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
|||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
cron-validator@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.3.1.tgz#8f2fe430f92140df77f91178ae31fc1e3a48a20e"
|
||||
integrity sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==
|
||||
|
||||
cross-fetch@^3.1.4, cross-fetch@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||
|
@ -4400,10 +4424,10 @@ dotenv@10.0.0:
|
|||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
dotenv@^16.0.3:
|
||||
version "16.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
|
||||
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
|
||||
dotenv@^16.0.3, dotenv@^16.3.1:
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||
|
||||
drbg.js@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
@ -6555,10 +6579,10 @@ node-fetch@2.6.7:
|
|||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.6.1, node-fetch@^2.6.7:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
|
||||
integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
|
||||
node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7:
|
||||
version "2.6.12"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
|
||||
integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
|
|
Loading…
Reference in New Issue