Merge remote-tracking branch 'origin/main' into stats-page

This commit is contained in:
Maximilian Schneider 2021-06-16 12:07:13 -04:00
commit d31eb4b119
19 changed files with 2073 additions and 737 deletions

View File

@ -12,6 +12,7 @@
],
"plugins": [
"babel-plugin-macros",
"@babel/plugin-proposal-logical-assignment-operators",
["styled-components", { "ssr": true }],
"@emotion/babel-plugin"
]

View File

@ -139,13 +139,17 @@ const AccountSelect = ({
return (
<Listbox.Option
disabled={getBalanceForAccount(account) === '0'}
key={account?.publicKey.toString()}
value={account?.publicKey.toString()}
>
{({ selected }) => (
{({ disabled, selected }) => (
<div
className={`p-2 hover:bg-th-bkg-2 hover:cursor-pointer ${
selected && `text-th-primary`
} ${
disabled &&
'opacity-50 hover:bg-th-bkg-1 hover:cursor-not-allowed'
}`}
>
<div className={`flex items-center text-th-fgd-1`}>

View File

@ -62,7 +62,7 @@ const BalancesTable = () => {
({ borrows, marginDeposits }) => borrows > 0 && marginDeposits > 0
)) ? (
<div
className={`flex items-center justify-between p-4 mb-2 rounded-md bg-th-bkg-1`}
className={`flex items-center justify-between px-4 py-2 mb-2 rounded-md bg-th-bkg-1`}
>
<div className="flex items-center text-fgd-1 font-semibold pr-4">
You have unsettled funds
@ -86,37 +86,37 @@ const BalancesTable = () => {
<Tr className="text-th-fgd-3">
<Th
scope="col"
className={`px-6 py-3 text-left font-normal`}
className={`px-6 py-2 text-left font-normal`}
>
Coin
</Th>
<Th
scope="col"
className={`px-6 py-3 text-left font-normal`}
className={`px-6 py-2 text-left font-normal`}
>
Deposits
</Th>
<Th
scope="col"
className={`px-6 py-3 text-left font-normal`}
className={`px-6 py-2 text-left font-normal`}
>
Borrows
</Th>
<Th
scope="col"
className={`px-6 py-3 text-left font-normal`}
className={`px-6 py-2 text-left font-normal`}
>
In Orders
</Th>
<Th
scope="col"
className={`px-6 py-3 text-left font-normal`}
className={`px-6 py-2 text-left font-normal`}
>
Unsettled
</Th>
<Th
scope="col"
className={`px-6 py-3 text-left font-normal`}
className={`px-6 py-2 text-left font-normal`}
>
Net
</Th>

View File

@ -267,8 +267,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
}
}
const validateAmountInput = (e) => {
const amount = e.target.value
const validateAmountInput = (amount) => {
if (Number(amount) <= 0) {
setInvalidAmountMessage('Enter an amount to deposit')
}
@ -289,9 +288,14 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
const onChangeSlider = async (percentage) => {
const max = getBalanceForAccount(selectedAccount)
const amount = (percentage / 100) * max
setInputAmount(trimDecimals(amount, DECIMALS[symbol]))
if (percentage === 100) {
setInputAmount(amount)
} else {
setInputAmount(trimDecimals(amount, DECIMALS[symbol]))
}
setSliderPercentage(percentage)
setInvalidAmountMessage('')
validateAmountInput(amount)
}
// turn off slider transition for dragging slider handle interaction
@ -344,7 +348,7 @@ const DepositModal: FunctionComponent<DepositModalProps> = ({
className={`border border-th-fgd-4 flex-grow pr-11`}
placeholder="0.00"
error={!!invalidAmountMessage}
onBlur={validateAmountInput}
onBlur={(e) => validateAmountInput(e.target.value)}
value={inputAmount}
onChange={(e) => onChangeAmountInput(e.target.value)}
suffix={symbol}

View File

@ -1,6 +1,8 @@
import { percentFormat } from '../utils/index'
import useSrmAccount from '../hooks/useSrmAccount'
import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
import Tooltip from './Tooltip'
import { InformationCircleIcon } from '@heroicons/react/outline'
const FeeDiscountsTable = () => {
const { totalSrm, rates } = useSrmAccount()
@ -27,9 +29,24 @@ const FeeDiscountsTable = () => {
</div>
</div>
<div className="px-4 mt-4 sm:mt-0">
<div>Taker Fee</div>
<div className="flex items-center">
<div>Taker Fee</div>
<div className="flex items-center">
<Tooltip
content={`Taker fee is ${percentFormat.format(
rates.taker
)} before the 20% GUI hoster fee is rebated.`}
>
<div>
<InformationCircleIcon
className={`h-5 w-5 ml-2 text-th-fgd-4 cursor-help`}
/>
</div>
</Tooltip>
</div>
</div>
<div className="text-th-fgd-1 text-lg font-semibold">
{rates ? percentFormat.format(rates.taker) : null}
{rates ? percentFormat.format(rates.taker * 0.8) : null}
</div>
</div>
</div>

View File

@ -20,6 +20,8 @@ const MarketHeader = () => {
const volume = ohlcv ? ohlcv.v[0] : '--'
const fetchOhlcv = useCallback(async () => {
if (!selectedMarketName) return
// calculate from and to date (0:00UTC to 23:59:59UTC)
const date = new Date()
const utcFrom = new Date(

View File

@ -111,8 +111,7 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
})
}
const validateAmountInput = (e) => {
const amount = e.target.value
const validateAmountInput = (amount) => {
if (Number(amount) <= 0) {
setInvalidAmountMessage('Enter an amount to deposit')
}
@ -133,9 +132,14 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
const onChangeSlider = async (percentage) => {
const max = getBalanceForAccount(selectedAccount)
const amount = (percentage / 100) * max
setInputAmount(trimDecimals(amount, DECIMALS[symbol]))
if (percentage === 100) {
setInputAmount(amount)
} else {
setInputAmount(trimDecimals(amount, DECIMALS[symbol]))
}
setSliderPercentage(percentage)
setInvalidAmountMessage('')
validateAmountInput(amount)
}
// turn off slider transition for dragging slider handle interaction
@ -173,7 +177,7 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
className={`border border-th-fgd-4 flex-grow pr-11`}
placeholder="0.00"
error={!!invalidAmountMessage}
onBlur={validateAmountInput}
onBlur={(e) => validateAmountInput(e.target.value)}
value={inputAmount}
onChange={(e) => onChangeAmountInput(e.target.value)}
suffix={symbol}
@ -187,7 +191,6 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
) : null}
<div className="pt-3 pb-4">
<Slider
disabled={null}
value={sliderPercentage}
onChange={(v) => onChangeSlider(v)}
step={1}
@ -195,7 +198,14 @@ const NewAccount: FunctionComponent<NewAccountProps> = ({
/>
</div>
<div className={`pt-8 flex justify-center`}>
<Button onClick={handleNewAccountDeposit} className="w-full">
<Button
disabled={
inputAmount <= 0 ||
inputAmount > getBalanceForAccount(selectedAccount)
}
onClick={handleNewAccountDeposit}
className="w-full"
>
<div className={`flex items-center justify-center`}>
{submitting && <Loading className="-ml-1 mr-3" />}
Create Account

View File

@ -13,6 +13,8 @@ export default function PublicTrades() {
const [trades, setTrades] = useState([])
const fetchTradesForChart = useCallback(async () => {
if (!marketAddress) return
const newTrades = await ChartApi.getRecentTrades(marketAddress)
if (!newTrades) return null
if (newTrades.length && trades.length === 0) {

View File

@ -75,16 +75,18 @@ const StyledSliderButton = styled.button<StyledSliderButtonProps>`
`
type SliderProps = {
onChange: (...args: any[]) => any
onChange: (x) => void
onAfterChange?: (x) => void
step: number
value: number
disabled: boolean
disabled?: boolean
max?: number
maxButtonTransition?: boolean
}
const AmountSlider: FunctionComponent<SliderProps> = ({
onChange,
onAfterChange,
step,
value,
disabled,
@ -120,6 +122,7 @@ const AmountSlider: FunctionComponent<SliderProps> = ({
max={max}
value={value || 0}
onChange={onChange}
onAfterChange={onAfterChange}
step={step}
enableTransition={enableTransition}
disabled={disabled}

View File

@ -332,7 +332,11 @@ export default function TradeForm() {
'border-th-green hover:border-th-green-dark'
} text-th-green hover:text-th-fgd-1 hover:bg-th-green-dark flex-grow`}
>
{`${baseSize > 0 ? 'Buy ' + baseSize : 'Set BUY bid >= ' + market?.minOrderSize} ${baseCurrency}`}
{`${
baseSize > 0
? 'Buy ' + baseSize
: 'Set BUY bid >= ' + market?.minOrderSize
} ${baseCurrency}`}
</Button>
) : (
<Button
@ -343,7 +347,11 @@ export default function TradeForm() {
'border-th-red hover:border-th-red-dark'
} text-th-red hover:text-th-fgd-1 hover:bg-th-red-dark flex-grow`}
>
{`${baseSize > 0 ? 'Sell ' + baseSize : 'Set SELL bid >= ' + market?.minOrderSize} ${baseCurrency}`}
{`${
baseSize > 0
? 'Sell ' + baseSize
: 'Set SELL bid >= ' + market?.minOrderSize
} ${baseCurrency}`}
</Button>
)
) : (

View File

@ -25,7 +25,7 @@ export const defaultLayouts = {
{ i: 'tradeForm', x: 9, y: 0, w: 3, h: 12 },
{ i: 'marketTrades', x: 6, y: 1, w: 3, h: 13 },
{ i: 'balanceInfo', x: 9, y: 1, w: 3, h: 15 },
{ i: 'userInfo', x: 0, y: 2, w: 9, h: 18 },
{ i: 'userInfo', x: 0, y: 2, w: 9, h: 19 },
{ i: 'marginInfo', x: 9, y: 2, w: 3, h: 13 },
],
lg: [
@ -35,7 +35,7 @@ export const defaultLayouts = {
{ i: 'orderbook', x: 0, y: 2, w: 4, h: 17, minW: 2 },
{ i: 'tradeForm', x: 4, y: 2, w: 4, h: 17, minW: 3 },
{ i: 'marketTrades', x: 8, y: 2, w: 4, h: 17, minW: 2 },
{ i: 'userInfo', x: 0, y: 3, w: 12, h: 18, minW: 6 },
{ i: 'userInfo', x: 0, y: 3, w: 12, h: 19, minW: 6 },
],
md: [
{ i: 'tvChart', x: 0, y: 0, w: 8, h: 26, minW: 2 },
@ -44,7 +44,7 @@ export const defaultLayouts = {
{ i: 'orderbook', x: 0, y: 2, w: 4, h: 17, minW: 2 },
{ i: 'tradeForm', x: 4, y: 2, w: 4, h: 17, minW: 3 },
{ i: 'marketTrades', x: 8, y: 2, w: 4, h: 17, minW: 2 },
{ i: 'userInfo', x: 0, y: 3, w: 12, h: 18, minW: 6 },
{ i: 'userInfo', x: 0, y: 3, w: 12, h: 19, minW: 6 },
],
sm: [
{ i: 'tvChart', x: 0, y: 0, w: 12, h: 25, minW: 6 },
@ -53,7 +53,7 @@ export const defaultLayouts = {
{ i: 'tradeForm', x: 0, y: 2, w: 12, h: 13, minW: 3 },
{ i: 'orderbook', x: 0, y: 3, w: 6, h: 17, minW: 3 },
{ i: 'marketTrades', x: 6, y: 3, w: 6, h: 17, minW: 2 },
{ i: 'userInfo', x: 0, y: 4, w: 12, h: 18, minW: 6 },
{ i: 'userInfo', x: 0, y: 4, w: 12, h: 19, minW: 6 },
],
xs: [
{ i: 'tvChart', x: 0, y: 0, w: 0, h: 0, minW: 6 },
@ -62,11 +62,11 @@ export const defaultLayouts = {
{ i: 'tradeForm', x: 0, y: 3, w: 12, h: 13, minW: 3 },
{ i: 'orderbook', x: 0, y: 4, w: 6, h: 17, minW: 3 },
{ i: 'marketTrades', x: 0, y: 5, w: 6, h: 17, minW: 2 },
{ i: 'userInfo', x: 0, y: 6, w: 12, h: 18, minW: 6 },
{ i: 'userInfo', x: 0, y: 6, w: 12, h: 19, minW: 6 },
],
}
export const GRID_LAYOUT_KEY = 'mangoSavedLayouts-2.1'
export const GRID_LAYOUT_KEY = 'mangoSavedLayouts-2.2'
const TradePageGrid = () => {
const { uiLocked } = useMangoStore((s) => s.settings)

View File

@ -290,22 +290,39 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
const onChangeSlider = async (percentage) => {
const amount = (percentage / 100) * maxAmount
setInputAmount(trimDecimals(amount, DECIMALS[withdrawTokenSymbol] + 2))
if (percentage === 100) {
setInputAmount(trimDecimals(maxAmount, DECIMALS[withdrawTokenSymbol] + 4))
} else {
setInputAmount(trimDecimals(amount, DECIMALS[withdrawTokenSymbol] + 2))
}
setSliderPercentage(percentage)
setInvalidAmountMessage('')
validateAmountInput(amount)
}
const validateAmountInput = (e) => {
const amount = e.target.value
if (Number(amount) <= 0) {
setInvalidAmountMessage('Withdrawal amount must be greater than 0')
const validateAmountInput = (amount) => {
if (
(Number(amount) <= 0 && getMaxForSelectedAsset() > 0) ||
(Number(amount) <= 0 && includeBorrow)
) {
setInvalidAmountMessage('Enter an amount to withdraw')
}
if (simulation.collateralRatio < 1.2) {
if (
(getMaxForSelectedAsset() === 0 ||
Number(amount) > getMaxForSelectedAsset()) &&
!includeBorrow
) {
setInvalidAmountMessage('Insufficient balance. Borrow funds to withdraw')
}
}
useEffect(() => {
if (simulation && simulation.collateralRatio < 1.2 && includeBorrow) {
setInvalidAmountMessage(
'Leverage too high. Reduce the amount to withdraw'
)
}
}
}, [simulation])
const trimDecimals = (n, digits) => {
const step = Math.pow(10, digits || 0)
@ -414,8 +431,9 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
<div className="flex justify-between pb-2 pt-4">
<div className="text-th-fgd-1">Amount</div>
<div className="flex space-x-4">
<div
className="text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline"
<button
className="font-normal text-th-fgd-1 underline cursor-pointer default-transition hover:text-th-primary hover:no-underline focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!includeBorrow && getMaxForSelectedAsset() === 0}
onClick={
includeBorrow
? setMaxBorrowForSelectedAsset
@ -423,7 +441,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
}
>
Max
</div>
</button>
</div>
</div>
<div className="flex">
@ -435,7 +453,7 @@ const WithdrawModal: FunctionComponent<WithdrawModalProps> = ({
error={!!invalidAmountMessage}
placeholder="0.00"
value={inputAmount}
onBlur={validateAmountInput}
onBlur={(e) => validateAmountInput(e.target.value)}
onChange={(e) => onChangeAmountInput(e.target.value)}
suffix={withdrawTokenSymbol}
/>

View File

@ -8,18 +8,15 @@ import {
floorToDecimal,
} from '../utils'
import useAllMarkets from './useAllMarkets'
import { sumBy } from 'lodash'
export function useBalances(): Balances[] {
let balances = []
const balances = []
const markets = useAllMarkets()
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const marginAccount = useMangoStore((s) => s.selectedMarginAccount.current)
const { symbols } = useMarketList()
let nativeQuoteFree = 0
let nativeQuoteLocked = 0
let nativeQuoteUnsettled = 0
for (const { market, baseCurrency, quoteCurrency } of markets) {
if (!marginAccount || !mangoGroup || !market) {
return []
@ -44,24 +41,23 @@ export function useBalances(): Balances[] {
}
const nativeBaseFree = openOrders?.baseTokenFree || 0
nativeQuoteFree += openOrders?.quoteTokenFree || 0
const nativeQuoteFree =
(openOrders?.quoteTokenFree || 0) +
(openOrders?.['referrerRebatesAccrued'].toNumber() || 0)
const nativeBaseLocked = openOrders
? openOrders.baseTokenTotal - nativeBaseFree
? openOrders.baseTokenTotal - openOrders?.baseTokenFree
: 0
nativeQuoteLocked += openOrders
? openOrders?.quoteTokenTotal - nativeQuoteFree
const nativeQuoteLocked = openOrders
? openOrders?.quoteTokenTotal - (openOrders?.quoteTokenFree || 0)
: 0
const nativeBaseUnsettled = openOrders?.baseTokenFree || 0
nativeQuoteUnsettled += openOrders?.quoteTokenFree || 0
const tokenIndex = marketIndex
const net = (borrows, currencyIndex) => {
const net = (locked, currencyIndex) => {
const amount =
marginAccount.getNativeDeposit(mangoGroup, currencyIndex) +
borrows -
locked -
marginAccount.getNativeBorrow(mangoGroup, currencyIndex)
return floorToDecimal(
@ -89,9 +85,8 @@ export function useBalances(): Balances[] {
nativeBaseLocked,
mangoGroup.mintDecimals[tokenIndex]
),
openOrders,
unsettled: nativeToUi(
nativeBaseUnsettled,
nativeBaseFree,
mangoGroup.mintDecimals[tokenIndex]
),
net: net(nativeBaseLocked, tokenIndex),
@ -110,26 +105,41 @@ export function useBalances(): Balances[] {
mangoGroup,
quoteCurrencyIndex
),
openOrders,
orders: nativeToUi(
nativeQuoteLocked,
mangoGroup.mintDecimals[quoteCurrencyIndex]
),
unsettled: nativeToUi(
nativeQuoteUnsettled,
nativeQuoteFree,
mangoGroup.mintDecimals[quoteCurrencyIndex]
),
net: net(nativeQuoteLocked, quoteCurrencyIndex),
},
]
balances = balances.concat(marketPair)
balances.push(marketPair)
}
balances.sort((a, b) => (a.coin > b.coin ? 1 : -1))
const baseBalances = balances.map((b) => b[0])
const quoteBalances = balances.map((b) => b[1])
const quoteMeta = quoteBalances[0]
const quoteInOrders = sumBy(quoteBalances, 'orders')
const unsettled = sumBy(quoteBalances, 'unsettled')
const net =
quoteMeta.marginDeposits + unsettled - quoteMeta.borrows - quoteInOrders
const quoteCurrencyIndex = Object.entries(symbols).findIndex(
(x) => x[0] === quoteMeta.coin
)
balances = balances.filter((elem, index, self) => {
return index === self.map((a) => a.coin).indexOf(elem.coin)
})
return balances
return baseBalances.concat([
{
market: null,
key: `${quoteMeta.coin}${quoteMeta.coin}`,
coin: quoteMeta.coin,
marginDeposits: quoteMeta.marginDeposits,
borrows: quoteMeta.borrows,
orders: quoteInOrders,
unsettled,
net: floorToDecimal(net, mangoGroup.mintDecimals[quoteCurrencyIndex]),
},
])
}

View File

@ -26,6 +26,7 @@ const SANCTIONED_COUNTRIES = [
{ country: 'Venezuela', code: 'VE' },
{ country: 'Yemen', code: 'YE' },
{ country: 'Zimbabwe', code: 'ZW' },
{ country: 'United States', code: 'US' },
]
const SANCTIONED_COUNTRY_CODES = SANCTIONED_COUNTRIES.map(({ code }) => code)

View File

@ -16,6 +16,7 @@ export default function useOraclePrice() {
const fetchOraclePrice = useCallback(() => {
if (selectedMangoGroup) {
setOraclePrice(null)
const marketIndex = getMarketIndex(marketAddress)
selectedMangoGroup.getPrices(connection).then((prices) => {
const oraclePriceForMarket = prices[marketIndex]

View File

@ -8,7 +8,7 @@
"build": "next build",
"start": "next start",
"type-check": "tsc --pretty --noEmit",
"format": "prettier --check .",
"format": "prettier --write .",
"lint": "eslint . --ext ts --ext tsx --ext js --quiet",
"test": "jest",
"test-all": "yarn lint && yarn type-check && yarn test"
@ -31,7 +31,7 @@
}
},
"dependencies": {
"@blockworks-foundation/mango-client": "^2.0.0",
"@blockworks-foundation/mango-client": "2.1.0",
"@emotion/react": "^11.1.5",
"@emotion/styled": "^11.1.5",
"@headlessui/react": "^1.2.0",
@ -47,6 +47,7 @@
"dayjs": "^1.10.4",
"immer": "^9.0.1",
"immutable-tuple": "^0.4.10",
"lodash-es": "^4.17.21",
"next": "^10.1.3",
"next-themes": "^0.0.14",
"postcss-preset-env": "^6.7.0",

View File

@ -24,8 +24,8 @@ export const ENDPOINTS: EndpointInfo[] = [
},
{
name: 'devnet',
url: 'https://devnet.solana.com',
websocket: 'https://devnet.solana.com',
url: 'https://api.devnet.solana.com',
websocket: 'https://api.devnet.solana.com',
custom: false,
},
]

View File

@ -856,13 +856,15 @@ export async function placeAndSettle(
)
const rates = getFeeRates(feeTier)
const maxQuoteQuantity = new BN(
spotMarket['_decoded'].quoteLotSize.toNumber() * (1 + rates.taker)
).mul(
spotMarket
.baseSizeNumberToLots(size)
.mul(spotMarket.priceNumberToLots(price))
maxBaseQuantity
.mul(limitPrice)
.mul(spotMarket['_decoded'].quoteLotSize)
.toNumber() *
(1 + rates.taker)
)
console.log(maxBaseQuantity.toString(), maxQuoteQuantity.toString())
if (maxBaseQuantity.lte(new BN(0))) {
throw new Error('size too small')
}
@ -917,6 +919,26 @@ export async function placeAndSettle(
}
}
// Only send a pre-settle instruction if open orders account already exists
if (!marginAccount.openOrders[marketIndex].equals(zeroKey)) {
const settleFundsInstr = makeSettleFundsInstruction(
programId,
mangoGroup.publicKey,
wallet.publicKey,
marginAccount.publicKey,
spotMarket.programId,
spotMarket.publicKey,
openOrdersKeys[marketIndex],
mangoGroup.signerKey,
spotMarket['_decoded'].baseVault,
spotMarket['_decoded'].quoteVault,
mangoGroup.vaults[marketIndex],
mangoGroup.vaults[NUM_TOKENS - 1],
dexSigner
)
transaction.add(settleFundsInstr)
}
const keys = [
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
@ -1281,14 +1303,18 @@ export async function settleAll(
if (openOrdersAccount === undefined) {
continue
} else if (
openOrdersAccount.quoteTokenFree.toNumber() === 0 &&
openOrdersAccount.quoteTokenFree.toNumber() +
openOrdersAccount['referrerRebatesAccrued'].toNumber() ===
0 &&
openOrdersAccount.baseTokenFree.toNumber() === 0
) {
continue
}
assetGains[i] += openOrdersAccount.baseTokenFree.toNumber()
assetGains[NUM_TOKENS - 1] += openOrdersAccount.quoteTokenFree.toNumber()
assetGains[NUM_TOKENS - 1] +=
openOrdersAccount.quoteTokenFree.toNumber() +
openOrdersAccount['referrerRebatesAccrued'].toNumber()
const spotMarket = markets[i]
const dexSigner = await PublicKey.createProgramAddress(
@ -1338,6 +1364,7 @@ export async function settleAll(
data,
programId,
})
transaction.add(settleFundsInstruction)
}

2547
yarn.lock

File diff suppressed because it is too large Load Diff