Merge branch 'tif' into main

This commit is contained in:
tjs 2022-02-28 12:52:08 -05:00
commit 6ff5517045
17 changed files with 274 additions and 185 deletions

View File

@ -59,6 +59,7 @@ export interface Balances extends BalancesBase {
value?: I80F48 | null | undefined value?: I80F48 | null | undefined
depositRate?: I80F48 | null | undefined depositRate?: I80F48 | null | undefined
borrowRate?: I80F48 | null | undefined borrowRate?: I80F48 | null | undefined
decimals?: number | null | undefined
} }
export interface OpenOrdersBalances extends BalancesBase { export interface OpenOrdersBalances extends BalancesBase {

View File

@ -1,31 +1,29 @@
import { useCallback } from 'react' import { useCallback, useState } from 'react'
import { useBalances } from '../hooks/useBalances' import { useBalances } from '../hooks/useBalances'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
import Button, { LinkButton } from '../components/Button' import Button, { LinkButton } from '../components/Button'
import { notify } from '../utils/notifications' import { notify } from '../utils/notifications'
import { ArrowSmDownIcon, ExclamationIcon } from '@heroicons/react/outline' import { ArrowSmDownIcon, ExclamationIcon } from '@heroicons/react/outline'
import { Market } from '@project-serum/serum' import { Market } from '@project-serum/serum'
import { import { getTokenBySymbol } from '@blockworks-foundation/mango-client'
getMarketIndexBySymbol,
getTokenBySymbol,
} from '@blockworks-foundation/mango-client'
import { useState } from 'react'
import Loading from './Loading' import Loading from './Loading'
import { useViewport } from '../hooks/useViewport' import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid' import { breakpoints } from './TradePageGrid'
import { floorToDecimal, formatUsdValue } from '../utils' import { floorToDecimal, formatUsdValue, getPrecisionDigits } from '../utils'
import { Table, Td, Th, TrBody, TrHead } from './TableElements' import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements'
import { useSortableData } from '../hooks/useSortableData' import { useSortableData } from '../hooks/useSortableData'
import DepositModal from './DepositModal' import DepositModal from './DepositModal'
import WithdrawModal from './WithdrawModal' import WithdrawModal from './WithdrawModal'
import { ExpandableRow } from './TableElements'
import MobileTableHeader from './mobile/MobileTableHeader' import MobileTableHeader from './mobile/MobileTableHeader'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { TransactionSignature } from '@solana/web3.js' import { TransactionSignature } from '@solana/web3.js'
import Link from 'next/link'
import { useRouter } from 'next/router'
const BalancesTable = ({ const BalancesTable = ({
showZeroBalances = false, showZeroBalances = false,
showDepositWithdraw = false, showDepositWithdraw = false,
clickToPopulateTradeForm = false,
}) => { }) => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const [showDepositModal, setShowDepositModal] = useState(false) const [showDepositModal, setShowDepositModal] = useState(false)
@ -59,34 +57,42 @@ const BalancesTable = ({
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current) const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const wallet = useMangoStore((s) => s.wallet.current) const wallet = useMangoStore((s) => s.wallet.current)
const canWithdraw = mangoAccount?.owner.equals(wallet.publicKey) const canWithdraw = mangoAccount?.owner.equals(wallet.publicKey)
const { asPath } = useRouter()
const handleSizeClick = (size, symbol) => { const handleSizeClick = (size, symbol) => {
const step = selectedMarket.minOrderSize const minOrderSize = selectedMarket.minOrderSize
const marketIndex = getMarketIndexBySymbol( const sizePrecisionDigits = getPrecisionDigits(minOrderSize)
mangoGroupConfig, const marketIndex = marketConfig.marketIndex
marketConfig.baseSymbol
)
const priceOrDefault = price const priceOrDefault = price
? price ? price
: mangoGroup.getPrice(marketIndex, mangoGroupCache).toNumber() : mangoGroup.getPriceUi(marketIndex, mangoGroupCache)
if (symbol === 'USDC') {
const baseSize = Math.floor(size / priceOrDefault / step) * step
setMangoStore((state) => {
state.tradeForm.baseSize = baseSize
state.tradeForm.quoteSize = baseSize * priceOrDefault
state.tradeForm.side = 'buy'
})
} else {
const roundedSize = Math.round(size / step) * step
const quoteSize = roundedSize * priceOrDefault
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = 'sell'
})
}
}
let roundedSize, side
if (symbol === 'USDC') {
roundedSize = parseFloat(
(
Math.abs(size) / priceOrDefault +
(size < 0 ? minOrderSize / 2 : -minOrderSize / 2)
) // round up so neg USDC gets cleared
.toFixed(sizePrecisionDigits)
)
side = size > 0 ? 'buy' : 'sell'
} else {
roundedSize = parseFloat(
(
Math.abs(size) + (size < 0 ? minOrderSize / 2 : -minOrderSize / 2)
).toFixed(sizePrecisionDigits)
)
side = size > 0 ? 'sell' : 'buy'
}
const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2))
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = side
})
}
const handleOpenDepositModal = useCallback((symbol) => { const handleOpenDepositModal = useCallback((symbol) => {
setActionSymbol(symbol) setActionSymbol(symbol)
setShowDepositModal(true) setShowDepositModal(true)
@ -158,6 +164,7 @@ const BalancesTable = ({
</div> </div>
{unsettledBalances.map((bal) => { {unsettledBalances.map((bal) => {
const tokenConfig = getTokenBySymbol(mangoGroupConfig, bal.symbol) const tokenConfig = getTokenBySymbol(mangoGroupConfig, bal.symbol)
return ( return (
<div <div
className="border-b border-th-bkg-4 flex items-center justify-between py-4 last:border-b-0 last:pb-0" className="border-b border-th-bkg-4 flex items-center justify-between py-4 last:border-b-0 last:pb-0"
@ -356,20 +363,46 @@ const BalancesTable = ({
className={`mr-2.5`} className={`mr-2.5`}
/> />
{balance.symbol} {balance.symbol === 'USDC' ||
decodeURIComponent(asPath).includes(
`${balance.symbol}/USDC`
) ? (
<span>{balance.symbol}</span>
) : (
<Link
href={{
pathname: '/',
query: { name: `${balance.symbol}/USDC` },
}}
shallow={true}
>
<a className="text-th-fgd-1 underline hover:no-underline hover:text-th-fgd-1">
{balance.symbol}
</a>
</Link>
)}
</div> </div>
</Td> </Td>
<Td>{balance.deposits.toFixed()}</Td> <Td>
<Td>{balance.borrows.toFixed()}</Td> {balance.deposits.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</Td>
<Td>
{balance.borrows.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</Td>
<Td>{balance.orders}</Td> <Td>{balance.orders}</Td>
<Td>{balance.unsettled}</Td> <Td>{balance.unsettled}</Td>
<Td> <Td>
{marketConfig.kind === 'spot' && {marketConfig.kind === 'spot' &&
marketConfig.name.includes(balance.symbol) && marketConfig.name.includes(balance.symbol) &&
selectedMarket ? ( selectedMarket &&
clickToPopulateTradeForm ? (
<span <span
className={ className={
balance.net.toNumber() > 0 balance.net.toNumber() != 0
? 'cursor-pointer underline hover:no-underline' ? 'cursor-pointer underline hover:no-underline'
: '' : ''
} }
@ -377,10 +410,14 @@ const BalancesTable = ({
handleSizeClick(balance.net, balance.symbol) handleSizeClick(balance.net, balance.symbol)
} }
> >
{balance.net.toFixed()} {balance.net.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</span> </span>
) : ( ) : (
balance.net.toFixed() balance.net.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})
)} )}
</Td> </Td>
<Td>{formatUsdValue(balance.value.toNumber())}</Td> <Td>{formatUsdValue(balance.value.toNumber())}</Td>
@ -464,7 +501,9 @@ const BalancesTable = ({
{balance.symbol} {balance.symbol}
</div> </div>
<div className="text-th-fgd-1 text-right"> <div className="text-th-fgd-1 text-right">
{balance.net.toFixed()} {balance.net.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</div> </div>
</div> </div>
} }
@ -477,25 +516,33 @@ const BalancesTable = ({
<div className="pb-0.5 text-th-fgd-3 text-xs"> <div className="pb-0.5 text-th-fgd-3 text-xs">
{t('deposits')} {t('deposits')}
</div> </div>
{balance.deposits.toFixed()} {balance.deposits.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</div> </div>
<div className="text-left"> <div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs"> <div className="pb-0.5 text-th-fgd-3 text-xs">
{t('borrows')} {t('borrows')}
</div> </div>
{balance.borrows.toFixed()} {balance.borrows.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</div> </div>
<div className="text-left"> <div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs"> <div className="pb-0.5 text-th-fgd-3 text-xs">
{t('in-orders')} {t('in-orders')}
</div> </div>
{balance.orders.toFixed()} {balance.orders.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</div> </div>
<div className="text-left"> <div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs"> <div className="pb-0.5 text-th-fgd-3 text-xs">
{t('unsettled')} {t('unsettled')}
</div> </div>
{balance.unsettled.toFixed()} {balance.unsettled.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})}
</div> </div>
<div className="text-left"> <div className="text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs"> <div className="pb-0.5 text-th-fgd-3 text-xs">
@ -544,7 +591,9 @@ const BalancesTable = ({
tokenSymbol={actionSymbol} tokenSymbol={actionSymbol}
repayAmount={ repayAmount={
balance.borrows.toNumber() > 0 balance.borrows.toNumber() > 0
? balance.borrows.toFixed() ? balance.borrows.toLocaleString(undefined, {
maximumFractionDigits: balance.decimals,
})
: '' : ''
} }
/> />

View File

@ -1,11 +1,8 @@
import { ElementTitle } from './styles' import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
import { i80f48ToPercent, floorToDecimal } from '../utils/index' import { getPrecisionDigits, i80f48ToPercent } from '../utils'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import { import { nativeI80F48ToUi } from '@blockworks-foundation/mango-client'
getMarketIndexBySymbol,
nativeI80F48ToUi,
} from '@blockworks-foundation/mango-client'
import { useViewport } from '../hooks/useViewport' import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid' import { breakpoints } from './TradePageGrid'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
@ -27,30 +24,38 @@ export default function MarketBalances() {
const isMobile = width ? width < breakpoints.sm : false const isMobile = width ? width < breakpoints.sm : false
const handleSizeClick = (size, symbol) => { const handleSizeClick = (size, symbol) => {
const step = selectedMarket.minOrderSize const minOrderSize = selectedMarket.minOrderSize
const marketIndex = getMarketIndexBySymbol( const sizePrecisionDigits = getPrecisionDigits(minOrderSize)
mangoGroupConfig, const marketIndex = marketConfig.marketIndex
marketConfig.baseSymbol
)
const priceOrDefault = price const priceOrDefault = price
? price ? price
: mangoGroup.getPrice(marketIndex, mangoGroupCache).toNumber() : mangoGroup.getPriceUi(marketIndex, mangoGroupCache)
let roundedSize, side
if (symbol === 'USDC') { if (symbol === 'USDC') {
const baseSize = Math.floor(size / priceOrDefault / step) * step roundedSize = parseFloat(
setMangoStore((state) => { (
state.tradeForm.baseSize = baseSize Math.abs(size) / priceOrDefault +
state.tradeForm.quoteSize = baseSize * priceOrDefault (size < 0 ? minOrderSize / 2 : -minOrderSize / 2)
state.tradeForm.side = 'buy' ) // round up so neg USDC gets cleared
}) .toFixed(sizePrecisionDigits)
)
side = size > 0 ? 'buy' : 'sell'
} else { } else {
const roundedSize = Math.round(size / step) * step roundedSize = parseFloat(
const quoteSize = roundedSize * priceOrDefault (
setMangoStore((state) => { Math.abs(size) + (size < 0 ? minOrderSize / 2 : -minOrderSize / 2)
state.tradeForm.baseSize = roundedSize ).toFixed(sizePrecisionDigits)
state.tradeForm.quoteSize = quoteSize )
state.tradeForm.side = 'sell' side = size > 0 ? 'sell' : 'buy'
})
} }
const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2))
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = side
})
} }
if (!mangoGroup || !selectedMarket) return null if (!mangoGroup || !selectedMarket) return null
@ -67,31 +72,22 @@ export default function MarketBalances() {
.reverse() .reverse()
.map(({ decimals, symbol, mintKey }) => { .map(({ decimals, symbol, mintKey }) => {
const tokenIndex = mangoGroup.getTokenIndex(mintKey) const tokenIndex = mangoGroup.getTokenIndex(mintKey)
const deposit = mangoAccount const balance = mangoAccount
? mangoAccount.getUiDeposit( ? nativeI80F48ToUi(
mangoGroupCache.rootBankCache[tokenIndex], mangoAccount.getNet(
mangoGroup, mangoGroupCache.rootBankCache[tokenIndex],
tokenIndex tokenIndex
),
decimals
) )
: null : 0
const borrow = mangoAccount
? mangoAccount.getUiBorrow(
mangoGroupCache.rootBankCache[tokenIndex],
mangoGroup,
tokenIndex
)
: null
const availableBalance = mangoAccount const availableBalance = mangoAccount
? floorToDecimal( ? nativeI80F48ToUi(
nativeI80F48ToUi( mangoAccount.getAvailableBalance(
mangoAccount.getAvailableBalance( mangoGroup,
mangoGroup, mangoGroupCache,
mangoGroupCache, tokenIndex
tokenIndex ),
),
decimals
).toNumber(),
decimals decimals
) )
: 0 : 0
@ -115,19 +111,20 @@ export default function MarketBalances() {
<div className="pb-0.5 text-th-fgd-3 text-xs"> <div className="pb-0.5 text-th-fgd-3 text-xs">
{t('balance')} {t('balance')}
</div> </div>
<div className={`text-th-fgd-1`}> <div
className={`text-th-fgd-1 ${
balance != 0
? 'cursor-pointer underline hover:no-underline'
: ''
}`}
onClick={() => handleSizeClick(balance, symbol)}
>
{isLoading ? ( {isLoading ? (
<DataLoader /> <DataLoader />
) : mangoAccount ? (
deposit.gt(borrow) ? (
deposit.toFixed()
) : borrow.toNumber() > 0 ? (
`-${borrow.toFixed()}`
) : (
0
)
) : ( ) : (
0 balance.toLocaleString(undefined, {
maximumFractionDigits: decimals,
})
)} )}
</div> </div>
</div> </div>
@ -147,10 +144,10 @@ export default function MarketBalances() {
> >
{isLoading ? ( {isLoading ? (
<DataLoader /> <DataLoader />
) : mangoAccount ? (
availableBalance
) : ( ) : (
0 availableBalance.toLocaleString(undefined, {
maximumFractionDigits: decimals,
})
)} )}
</div> </div>
</div> </div>

View File

@ -59,20 +59,20 @@ const MarketCloseModal: FunctionComponent<MarketCloseModalProps> = ({
// hard coded for now; market orders are very dangerous and fault prone // hard coded for now; market orders are very dangerous and fault prone
const maxSlippage: number | undefined = 0.025 const maxSlippage: number | undefined = 0.025
const txid = await mangoClient.placePerpOrder( const txid = await mangoClient.placePerpOrder2(
mangoGroup, mangoGroup,
mangoAccount, mangoAccount,
mangoGroup.mangoCache,
market, market,
wallet, wallet,
side, side,
referencePrice * (1 + (side === 'buy' ? 1 : -1) * maxSlippage), referencePrice * (1 + (side === 'buy' ? 1 : -1) * maxSlippage),
size, size,
'ioc', {
0, // client order id orderType: 'ioc',
side === 'buy' ? askInfo : bidInfo, bookSideInfo: side === 'buy' ? askInfo : bidInfo,
true, // reduce only reduceOnly: true,
referrerPk ? referrerPk : undefined referrerMangoAccountPk: referrerPk ? referrerPk : undefined,
}
) )
await sleep(500) await sleep(500)
actions.reloadMangoAccount() actions.reloadMangoAccount()

View File

@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import useMangoStore from '../stores/useMangoStore' import useMangoStore, { SECONDS } from '../stores/useMangoStore'
import usePrevious from '../hooks/usePrevious' import usePrevious from '../hooks/usePrevious'
import useInterval from '../hooks/useInterval' import useInterval from '../hooks/useInterval'
import ChartApi from '../utils/chartDataConnector' import ChartApi from '../utils/chartDataConnector'
@ -7,10 +7,10 @@ import UiLock from './UiLock'
import ManualRefresh from './ManualRefresh' import ManualRefresh from './ManualRefresh'
import useOraclePrice from '../hooks/useOraclePrice' import useOraclePrice from '../hooks/useOraclePrice'
import DayHighLow from './DayHighLow' import DayHighLow from './DayHighLow'
import { useEffect } from 'react'
import { import {
getDecimalCount, getPrecisionDigits,
patchInternalMarketName, patchInternalMarketName,
perpContractPrecision,
usdFormatter, usdFormatter,
} from '../utils' } from '../utils'
import { PerpMarket } from '@blockworks-foundation/mango-client' import { PerpMarket } from '@blockworks-foundation/mango-client'
@ -20,7 +20,6 @@ import { breakpoints } from './TradePageGrid'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import SwitchMarketDropdown from './SwitchMarketDropdown' import SwitchMarketDropdown from './SwitchMarketDropdown'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import { SECONDS } from '../stores/useMangoStore'
export function calculateFundingRate(perpStats, perpMarket) { export function calculateFundingRate(perpStats, perpMarket) {
const oldestStat = perpStats[perpStats.length - 1] const oldestStat = perpStats[perpStats.length - 1]
@ -190,7 +189,11 @@ const MarketDetails = () => {
</div> </div>
<div className="text-th-fgd-1 md:text-xs"> <div className="text-th-fgd-1 md:text-xs">
{oraclePrice && selectedMarket {oraclePrice && selectedMarket
? oraclePrice.toFixed(getDecimalCount(selectedMarket.tickSize)) ? oraclePrice.toNumber().toLocaleString(undefined, {
maximumFractionDigits: getPrecisionDigits(
selectedMarket.tickSize
),
})
: '--'} : '--'}
</div> </div>
</div> </div>
@ -255,7 +258,9 @@ const MarketDetails = () => {
{selectedMarket ? ( {selectedMarket ? (
`${parseOpenInterest( `${parseOpenInterest(
selectedMarket as PerpMarket selectedMarket as PerpMarket
)} ${baseSymbol}` ).toLocaleString(undefined, {
maximumFractionDigits: perpContractPrecision[baseSymbol],
})} ${baseSymbol}`
) : ( ) : (
<MarketDataLoader /> <MarketDataLoader />
)} )}

View File

@ -1,7 +1,11 @@
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { ElementTitle } from './styles' import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
import { formatUsdValue } from '../utils/index' import {
formatUsdValue,
getPrecisionDigits,
perpContractPrecision,
} from '../utils'
import Button, { LinkButton } from './Button' import Button, { LinkButton } from './Button'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import PerpSideBadge from './PerpSideBadge' import PerpSideBadge from './PerpSideBadge'
@ -92,18 +96,17 @@ export default function MarketPosition() {
perpAccount = mangoAccount.perpAccounts[marketIndex] perpAccount = mangoAccount.perpAccounts[marketIndex]
} }
const handleSizeClick = (size, side) => { const handleSizeClick = (size) => {
const step = selectedMarket.minOrderSize const sizePrecisionDigits = getPrecisionDigits(selectedMarket.minOrderSize)
const priceOrDefault = price const priceOrDefault = price
? price ? price
: mangoGroup.getPrice(marketIndex, mangoCache).toNumber() : mangoGroup.getPriceUi(marketIndex, mangoCache)
const roundedSize = Math.round(size / step) * step const roundedSize = parseFloat(Math.abs(size).toFixed(sizePrecisionDigits))
const quoteSize = roundedSize * priceOrDefault const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2))
setMangoStore((state) => { setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = side === 'buy' ? 'sell' : 'buy' state.tradeForm.side = size > 0 ? 'sell' : 'buy'
}) })
} }
@ -177,14 +180,11 @@ export default function MarketPosition() {
) : basePosition ? ( ) : basePosition ? (
<span <span
className="cursor-pointer underline hover:no-underline" className="cursor-pointer underline hover:no-underline"
onClick={() => onClick={() => handleSizeClick(basePosition)}
handleSizeClick(
Math.abs(basePosition),
basePosition > 0 ? 'buy' : 'sell'
)
}
> >
{`${Math.abs(basePosition)} ${baseSymbol}`} {`${Math.abs(basePosition).toLocaleString(undefined, {
maximumFractionDigits: perpContractPrecision[baseSymbol],
})} ${baseSymbol}`}
</span> </span>
) : ( ) : (
`0 ${baseSymbol}` `0 ${baseSymbol}`

View File

@ -99,7 +99,11 @@ const DesktopTable = ({
</Td> </Td>
{editOrderIndex !== index ? ( {editOrderIndex !== index ? (
<> <>
<Td className="w-[14.286%]">{order.size}</Td> <Td className="w-[14.286%]">
{order.size.toLocaleString(undefined, {
maximumFractionDigits: 4,
})}
</Td>
<Td className="w-[14.286%]"> <Td className="w-[14.286%]">
{usdFormatter(order.price, decimals)} {usdFormatter(order.price, decimals)}
</Td> </Td>

View File

@ -9,7 +9,11 @@ import Button from '../components/Button'
import { useViewport } from '../hooks/useViewport' import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid' import { breakpoints } from './TradePageGrid'
import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements' import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements'
import { formatUsdValue } from '../utils' import {
formatUsdValue,
getPrecisionDigits,
perpContractPrecision,
} from '../utils'
import Loading from './Loading' import Loading from './Loading'
import MarketCloseModal from './MarketCloseModal' import MarketCloseModal from './MarketCloseModal'
import PerpSideBadge from './PerpSideBadge' import PerpSideBadge from './PerpSideBadge'
@ -40,15 +44,15 @@ const PositionsTable = () => {
setShowMarketCloseModal(false) setShowMarketCloseModal(false)
}, []) }, [])
const handleSizeClick = (size, side, indexPrice) => { const handleSizeClick = (size, indexPrice) => {
const step = selectedMarket.minOrderSize const sizePrecisionDigits = getPrecisionDigits(selectedMarket.minOrderSize)
const priceOrDefault = price ? price : indexPrice const priceOrDefault = price ? price : indexPrice
const roundedSize = Math.round(size / step) * step const roundedSize = parseFloat(Math.abs(size).toFixed(sizePrecisionDigits))
const quoteSize = roundedSize * priceOrDefault const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2))
setMangoStore((state) => { setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = side === 'buy' ? 'sell' : 'buy' state.tradeForm.side = size > 0 ? 'sell' : 'buy'
}) })
} }
@ -129,6 +133,12 @@ const PositionsTable = () => {
breakEvenPrice, breakEvenPrice,
unrealizedPnl, unrealizedPnl,
}) => { }) => {
const basePositionUi = Math.abs(
basePosition
).toLocaleString(undefined, {
maximumFractionDigits:
perpContractPrecision[marketConfig.baseSymbol],
})
return ( return (
<TrBody key={`${marketConfig.marketIndex}`}> <TrBody key={`${marketConfig.marketIndex}`}>
<Td> <Td>
@ -169,22 +179,14 @@ const PositionsTable = () => {
<span <span
className="cursor-pointer underline hover:no-underline" className="cursor-pointer underline hover:no-underline"
onClick={() => onClick={() =>
handleSizeClick( handleSizeClick(basePosition, indexPrice)
Math.abs(basePosition),
basePosition > 0 ? 'buy' : 'sell',
indexPrice
)
} }
> >
{`${Math.abs(basePosition)} ${ {`${basePositionUi} ${marketConfig.baseSymbol}`}
marketConfig.baseSymbol
}`}
</span> </span>
) : ( ) : (
<span> <span>
{`${Math.abs(basePosition)} ${ {`${basePositionUi} ${marketConfig.baseSymbol}`}
marketConfig.baseSymbol
}`}
</span> </span>
)} )}
</Td> </Td>

View File

@ -55,7 +55,7 @@ const TabContent = ({ activeTab }) => {
case 'Orders': case 'Orders':
return <OpenOrdersTable /> return <OpenOrdersTable />
case 'Balances': case 'Balances':
return <BalancesTable /> return <BalancesTable clickToPopulateTradeForm />
case 'Trade History': case 'Trade History':
return <TradeHistoryTable numTrades={100} /> return <TradeHistoryTable numTrades={100} />
case 'Positions': case 'Positions':
@ -63,7 +63,7 @@ const TabContent = ({ activeTab }) => {
case 'Fee Discount': case 'Fee Discount':
return <FeeDiscountsTable /> return <FeeDiscountsTable />
default: default:
return <BalancesTable /> return <BalancesTable clickToPopulateTradeForm />
} }
} }

View File

@ -14,7 +14,11 @@ import {
InformationCircleIcon, InformationCircleIcon,
} from '@heroicons/react/outline' } from '@heroicons/react/outline'
import { notify } from '../../utils/notifications' import { notify } from '../../utils/notifications'
import { calculateTradePrice, getDecimalCount } from '../../utils' import {
calculateTradePrice,
getDecimalCount,
percentFormat,
} from '../../utils'
import { floorToDecimal } from '../../utils/index' import { floorToDecimal } from '../../utils/index'
import useMangoStore, { Orderbook } from '../../stores/useMangoStore' import useMangoStore, { Orderbook } from '../../stores/useMangoStore'
import Button, { LinkButton } from '../Button' import Button, { LinkButton } from '../Button'
@ -658,20 +662,21 @@ export default function AdvancedTradeForm({
) )
actions.reloadOrders() actions.reloadOrders()
} else { } else {
txid = await mangoClient.placePerpOrder( txid = await mangoClient.placePerpOrder2(
mangoGroup, mangoGroup,
mangoAccount, mangoAccount,
mangoGroup.mangoCache,
market, market,
wallet, wallet,
side, side,
perpOrderPrice, perpOrderPrice,
baseSize, baseSize,
perpOrderType, {
Date.now(), orderType: perpOrderType,
side === 'buy' ? askInfo : bidInfo, // book side used for ConsumeEvents clientOrderId: Date.now(),
reduceOnly, bookSideInfo: side === 'buy' ? askInfo : bidInfo, // book side used for ConsumeEvents
referrerPk ? referrerPk : undefined reduceOnly,
referrerMangoAccountPk: referrerPk ? referrerPk : undefined,
}
) )
} }
} }
@ -1108,12 +1113,12 @@ export default function AdvancedTradeForm({
) : ( ) : (
<div className="flex flex-col md:flex-row text-xs text-th-fgd-4 px-6 mt-2.5 items-center justify-center"> <div className="flex flex-col md:flex-row text-xs text-th-fgd-4 px-6 mt-2.5 items-center justify-center">
<div> <div>
{t('maker-fee')}: {(makerFee * 100).toFixed(2)}%{' '} {t('maker-fee')}: {percentFormat.format(makerFee)}{' '}
</div> </div>
<span className="hidden md:block md:px-1">|</span> <span className="hidden md:block md:px-1">|</span>
<div> <div>
{' '} {' '}
{t('taker-fee')}: {(takerFee * 100).toFixed(3)}% {t('taker-fee')}: {percentFormat.format(takerFee)}
</div> </div>
</div> </div>
)} )}

View File

@ -316,18 +316,18 @@ export default function SimpleTradeForm({ initLeverage }) {
orderType orderType
) )
} else { } else {
txid = await mangoClient.placePerpOrder( txid = await mangoClient.placePerpOrder2(
mangoGroup, mangoGroup,
mangoAccount, mangoAccount,
mangoGroup.mangoCache,
market, market,
wallet, wallet,
side, side,
orderPrice, orderPrice,
baseSize, baseSize,
orderType, {
0, orderType,
side === 'buy' ? askInfo : bidInfo bookSideInfo: side === 'buy' ? askInfo : bidInfo,
}
) )
} }
notify({ title: t('successfully-placed'), txid }) notify({ title: t('successfully-placed'), txid })

View File

@ -107,6 +107,7 @@ export function useBalances(): Balances[] {
value: value(nativeBaseLocked, tokenIndex), value: value(nativeBaseLocked, tokenIndex),
depositRate: i80f48ToPercent(mangoGroup.getDepositRate(tokenIndex)), depositRate: i80f48ToPercent(mangoGroup.getDepositRate(tokenIndex)),
borrowRate: i80f48ToPercent(mangoGroup.getBorrowRate(tokenIndex)), borrowRate: i80f48ToPercent(mangoGroup.getBorrowRate(tokenIndex)),
decimals: mangoGroup.tokens[tokenIndex].decimals,
}, },
{ {
market: null, market: null,
@ -134,6 +135,7 @@ export function useBalances(): Balances[] {
value: value(nativeQuoteLocked, quoteCurrencyIndex), value: value(nativeQuoteLocked, quoteCurrencyIndex),
depositRate: i80f48ToPercent(mangoGroup.getDepositRate(tokenIndex)), depositRate: i80f48ToPercent(mangoGroup.getDepositRate(tokenIndex)),
borrowRate: i80f48ToPercent(mangoGroup.getBorrowRate(tokenIndex)), borrowRate: i80f48ToPercent(mangoGroup.getBorrowRate(tokenIndex)),
decimals: mangoGroup.tokens[quoteCurrencyIndex].decimals,
}, },
] ]
balances.push(marketPair) balances.push(marketPair)
@ -172,6 +174,7 @@ export function useBalances(): Balances[] {
value, value,
depositRate, depositRate,
borrowRate, borrowRate,
decimals: mangoGroup.tokens[QUOTE_INDEX].decimals,
}, },
].concat(baseBalances) ].concat(baseBalances)
} }

View File

@ -1,8 +1,8 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { AccountInfo, PublicKey } from '@solana/web3.js' import { AccountInfo, PublicKey } from '@solana/web3.js'
import useMangoStore, { programId } from '../stores/useMangoStore' import useMangoStore, { programId, SECONDS } from '../stores/useMangoStore'
import useInterval from './useInterval' import useInterval from './useInterval'
import { Orderbook as SpotOrderBook, Market } from '@project-serum/serum' import { Market, Orderbook as SpotOrderBook } from '@project-serum/serum'
import { import {
BookSide, BookSide,
BookSideLayout, BookSideLayout,
@ -19,7 +19,6 @@ import {
marketSelector, marketSelector,
marketsSelector, marketsSelector,
} from '../stores/selectors' } from '../stores/selectors'
import { SECONDS } from '../stores/useMangoStore'
function decodeBook(market, accInfo: AccountInfo<Buffer>): number[][] { function decodeBook(market, accInfo: AccountInfo<Buffer>): number[][] {
if (market && accInfo?.data) { if (market && accInfo?.data) {
@ -33,7 +32,7 @@ function decodeBook(market, accInfo: AccountInfo<Buffer>): number[][] {
market, market,
BookSideLayout.decode(accInfo.data) BookSideLayout.decode(accInfo.data)
) )
return book.getL2(depth).map(([price, size]) => [price, size]) return book.getL2Ui(depth)
} }
} else { } else {
return [] return []

View File

@ -250,7 +250,7 @@
"open-interest": "Open Interest", "open-interest": "Open Interest",
"open-orders": "Open Orders", "open-orders": "Open Orders",
"optional": "(Optional)", "optional": "(Optional)",
"oracle-price": "Oracle price", "oracle-price": "Oracle Price",
"order-error": "Error placing order", "order-error": "Error placing order",
"orderbook": "Orderbook", "orderbook": "Orderbook",
"orderbook-animation": "Orderbook Animation", "orderbook-animation": "Orderbook Animation",

View File

@ -56,7 +56,7 @@ module.exports = {
'fgd-2': '#C8C8C8', 'fgd-2': '#C8C8C8',
'fgd-3': '#B3B3B3', 'fgd-3': '#B3B3B3',
'fgd-4': '#878787', 'fgd-4': '#878787',
'bkg-button': '#52514E', 'bkg-button': '#4E5152',
}, },
'mango-theme': { 'mango-theme': {
yellow: { yellow: {

View File

@ -12,8 +12,8 @@ export async function sleep(ms) {
export const percentFormat = new Intl.NumberFormat(undefined, { export const percentFormat = new Intl.NumberFormat(undefined, {
style: 'percent', style: 'percent',
minimumFractionDigits: 2, minimumFractionDigits: 1,
maximumFractionDigits: 2, maximumFractionDigits: 3,
}) })
export function floorToDecimal( export function floorToDecimal(
@ -41,6 +41,9 @@ export function roundToDecimal(
return decimals ? Math.round(value * 10 ** decimals) / 10 ** decimals : value return decimals ? Math.round(value * 10 ** decimals) / 10 ** decimals : value
} }
export function getPrecisionDigits(x: number): number {
return -Math.round(Math.log10(x))
}
export function getDecimalCount(value): number { export function getDecimalCount(value): number {
if ( if (
!isNaN(value) && !isNaN(value) &&

View File

@ -1659,13 +1659,14 @@
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.1.tgz#0937807e807e8332aa708cfef4bcb6cbb88b4129" resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.1.tgz#0937807e807e8332aa708cfef4bcb6cbb88b4129"
integrity sha512-2TuBmGUn9qeYz6sJINJlElrBuPsaUAtYyUsJ3XplEBf1pczrANAgs5ceJUFzdiqGEWLn+84ObSdBeChT/AXYFA== integrity sha512-2TuBmGUn9qeYz6sJINJlElrBuPsaUAtYyUsJ3XplEBf1pczrANAgs5ceJUFzdiqGEWLn+84ObSdBeChT/AXYFA==
dependencies: dependencies:
"@project-serum/borsh" "^0.2.2" "@project-serum/borsh" "^0.2.4"
"@solana/web3.js" "^1.17.0" "@solana/web3.js" "^1.17.0"
base64-js "^1.5.1" base64-js "^1.5.1"
bn.js "^5.1.2" bn.js "^5.1.2"
bs58 "^4.0.1" bs58 "^4.0.1"
buffer-layout "^1.2.2" buffer-layout "^1.2.2"
camelcase "^5.3.1" camelcase "^5.3.1"
cross-fetch "^3.1.5"
crypto-hash "^1.3.0" crypto-hash "^1.3.0"
eventemitter3 "^4.0.7" eventemitter3 "^4.0.7"
find "^0.3.0" find "^0.3.0"
@ -1825,7 +1826,27 @@
buffer-layout "^1.2.0" buffer-layout "^1.2.0"
dotenv "10.0.0" dotenv "10.0.0"
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.31.0": "@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0":
version "1.35.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.35.0.tgz#a2d09add241f48a370470a5c4db8596cb13f0dc6"
integrity sha512-eKf2rPoWEyVq7QsgAQKNqxODvPsb0vqSwwg2xRY1e49Fn5Qh29m2FiLcYHRS/xhPu/7b/5gsD+RzO3BWozOeZQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@ethersproject/sha2" "^5.5.0"
"@solana/buffer-layout" "^3.0.0"
bn.js "^5.0.0"
borsh "^0.4.0"
bs58 "^4.0.1"
buffer "6.0.1"
cross-fetch "^3.1.4"
jayson "^3.4.4"
js-sha3 "^0.8.0"
rpc-websockets "^7.4.2"
secp256k1 "^4.0.2"
superstruct "^0.14.2"
tweetnacl "^1.0.0"
"@solana/web3.js@^1.31.0":
version "1.34.0" version "1.34.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.34.0.tgz#33becf2c7e87497d73406374185e54e0b7bc235d" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.34.0.tgz#33becf2c7e87497d73406374185e54e0b7bc235d"
integrity sha512-6QvqN2DqEELvuV+5yUQM8P9fRiSG+6SzQ58HjumJqODu14r7eu5HXVWEymvKAvMLGME+0TmAdJHjw9xD5NgUWA== integrity sha512-6QvqN2DqEELvuV+5yUQM8P9fRiSG+6SzQ58HjumJqODu14r7eu5HXVWEymvKAvMLGME+0TmAdJHjw9xD5NgUWA==
@ -2206,14 +2227,14 @@
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
"@types/node@*": "@types/node@*":
version "17.0.17" version "17.0.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.17.tgz#a8ddf6e0c2341718d74ee3dc413a13a042c45a0c" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074"
integrity sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw== integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==
"@types/node@^12.12.54": "@types/node@^12.12.54":
version "12.20.45" version "12.20.46"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.45.tgz#f4980d177999299d99cd4b290f7f39366509a44f" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.46.tgz#7e49dee4c54fd19584e6a9e0da5f3dc2e9136bc7"
integrity sha512-1Jg2Qv5tuxBqgQV04+wO5u+wmSHbHgpORCJdeCLM+E+YdPElpdHhgywU+M1V1InL8rfOtpqtOjswk+uXTKwx7w== integrity sha512-cPjLXj8d6anFPzFvOPxS3fvly3Shm5nTfl6g8X5smexixbuGUf7hfr21J5tX9JW+UPStp/5P5R8qrKL5IyVJ+A==
"@types/node@^14.14.25": "@types/node@^14.14.25":
version "14.18.11" version "14.18.11"