Merge branch 'main' into kierangillen/wallet-updates

This commit is contained in:
tjshipe 2022-03-24 11:20:23 -04:00 committed by GitHub
commit 587949e9e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 294 additions and 135 deletions

View File

@ -359,101 +359,107 @@ const BalancesTable = ({
</TrHead> </TrHead>
</thead> </thead>
<tbody> <tbody>
{items.map((balance, index) => ( {items.map((balance, index) => {
<TrBody key={`${balance.symbol}${index}`}> console.log('balance', balance)
<Td> if (!balance) {
<div className="flex items-center"> return null
<img }
alt="" return (
width="20" <TrBody key={`${balance.symbol}${index}`}>
height="20"
src={`/assets/icons/${balance.symbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
{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:text-th-fgd-1 hover:no-underline">
{balance.symbol}
</a>
</Link>
)}
</div>
</Td>
<Td>{balance.deposits.toFormat(balance.decimals)}</Td>
<Td>{balance.borrows.toFormat(balance.decimals)}</Td>
<Td>{balance.orders}</Td>
<Td>{balance.unsettled}</Td>
<Td>
{marketConfig.kind === 'spot' &&
marketConfig.name.includes(balance.symbol) &&
selectedMarket &&
clickToPopulateTradeForm ? (
<span
className={
balance.net.toNumber() != 0
? 'cursor-pointer underline hover:no-underline'
: ''
}
onClick={() =>
handleSizeClick(balance.net, balance.symbol)
}
>
{balance.net.toFormat(balance.decimals)}
</span>
) : (
balance.net.toFormat(balance.decimals)
)}
</Td>
<Td>{formatUsdValue(balance.value.toNumber())}</Td>
<Td>
<span className="text-th-green">
{balance.depositRate.toFixed(2)}%
</span>
</Td>
<Td>
<span className="text-th-red">
{balance.borrowRate.toFixed(2)}%
</span>
</Td>
{showDepositWithdraw ? (
<Td> <Td>
<div className="flex justify-end"> <div className="flex items-center">
<Button <img
className="h-7 pt-0 pb-0 pl-3 pr-3 text-xs" alt=""
onClick={() => width="20"
handleOpenDepositModal(balance.symbol) height="20"
} src={`/assets/icons/${balance.symbol.toLowerCase()}.svg`}
> className={`mr-2.5`}
{balance.borrows.toNumber() > 0 />
? t('repay')
: t('deposit')} {balance.symbol === 'USDC' ||
</Button> decodeURIComponent(asPath).includes(
<Button `${balance.symbol}/USDC`
className="ml-4 h-7 pt-0 pb-0 pl-3 pr-3 text-xs" ) ? (
onClick={() => <span>{balance.symbol}</span>
handleOpenWithdrawModal(balance.symbol) ) : (
} <Link
disabled={!canWithdraw} href={{
> pathname: '/',
{t('withdraw')} query: { name: `${balance.symbol}/USDC` },
</Button> }}
shallow={true}
>
<a className="text-th-fgd-1 underline hover:text-th-fgd-1 hover:no-underline">
{balance.symbol}
</a>
</Link>
)}
</div> </div>
</Td> </Td>
) : null} <Td>{balance.deposits.toFormat(balance.decimals)}</Td>
</TrBody> <Td>{balance.borrows.toFormat(balance.decimals)}</Td>
))} <Td>{balance.orders}</Td>
<Td>{balance.unsettled}</Td>
<Td>
{marketConfig.kind === 'spot' &&
marketConfig.name.includes(balance.symbol) &&
selectedMarket &&
clickToPopulateTradeForm ? (
<span
className={
balance.net.toNumber() != 0
? 'cursor-pointer underline hover:no-underline'
: ''
}
onClick={() =>
handleSizeClick(balance.net, balance.symbol)
}
>
{balance.net.toFormat(balance.decimals)}
</span>
) : (
balance.net.toFormat(balance.decimals)
)}
</Td>
<Td>{formatUsdValue(balance.value.toNumber())}</Td>
<Td>
<span className="text-th-green">
{balance.depositRate.toFixed(2)}%
</span>
</Td>
<Td>
<span className="text-th-red">
{balance.borrowRate.toFixed(2)}%
</span>
</Td>
{showDepositWithdraw ? (
<Td>
<div className="flex justify-end">
<Button
className="h-7 pt-0 pb-0 pl-3 pr-3 text-xs"
onClick={() =>
handleOpenDepositModal(balance.symbol)
}
>
{balance.borrows.toNumber() > 0
? t('repay')
: t('deposit')}
</Button>
<Button
className="ml-4 h-7 pt-0 pb-0 pl-3 pr-3 text-xs"
onClick={() =>
handleOpenWithdrawModal(balance.symbol)
}
disabled={!canWithdraw}
>
{t('withdraw')}
</Button>
</div>
</Td>
) : null}
</TrBody>
)
})}
</tbody> </tbody>
{showDepositModal && ( {showDepositModal && (
<DepositModal <DepositModal

View File

@ -38,6 +38,12 @@ const MarketNavItem: FunctionComponent<MarketNavItemProps> = ({
mangoGroupConfig, mangoGroupConfig,
market.baseSymbol market.baseSymbol
) )
// The following if statement is for markets not on devnet
if (!mangoGroup.spotMarkets[marketIndex]) {
return 1
}
const ws = getWeights(mangoGroup, marketIndex, 'Init') const ws = getWeights(mangoGroup, marketIndex, 'Init')
const w = market.name.includes('PERP') const w = market.name.includes('PERP')
? ws.perpAssetWeight ? ws.perpAssetWeight
@ -85,7 +91,7 @@ const MarketNavItem: FunctionComponent<MarketNavItemProps> = ({
</div> </div>
) : null} ) : null}
</button> </button>
<div className="ml-2"> <div className="ml-1">
<FavoriteMarketButton market={market} /> <FavoriteMarketButton market={market} />
</div> </div>
</div> </div>

View File

@ -22,6 +22,50 @@ import { useTranslation } from 'next-i18next'
import useMangoAccount from '../hooks/useMangoAccount' import useMangoAccount from '../hooks/useMangoAccount'
import { useWallet, Wallet } from '@solana/wallet-adapter-react' import { useWallet, Wallet } from '@solana/wallet-adapter-react'
export const settlePosPnl = async (
perpMarkets: PerpMarket[],
perpAccount: PerpAccount,
t,
mangoAccounts: MangoAccount[] | undefined
) => {
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
const mangoCache = useMangoStore.getState().selectedMangoGroup.cache
const wallet = useMangoStore.getState().wallet.current
const actions = useMangoStore.getState().actions
const mangoClient = useMangoStore.getState().connection.client
try {
const txids = await mangoClient.settlePosPnl(
mangoGroup,
mangoCache,
mangoAccount,
perpMarkets,
mangoGroup.rootBankAccounts[QUOTE_INDEX],
wallet,
mangoAccounts
)
actions.reloadMangoAccount()
for (const txid of txids) {
if (txid) {
notify({
title: t('pnl-success'),
description: '',
txid,
})
}
}
} catch (e) {
console.log('Error settling PNL: ', `${e}`, `${perpAccount}`)
notify({
title: t('pnl-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
}
export const settlePnl = async ( export const settlePnl = async (
perpMarket: PerpMarket, perpMarket: PerpMarket,
perpAccount: PerpAccount, perpAccount: PerpAccount,

View File

@ -278,9 +278,7 @@ const MarketsTable = ({ isPerpMarket }) => {
<Td> <Td>
{funding1h ? ( {funding1h ? (
<> <>
<span>{`${funding1h.toLocaleString(undefined, { <span>{`${funding1h.toFixed(4)}%`}</span>{' '}
maximumSignificantDigits: 3,
})}%`}</span>{' '}
<span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span> <span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span>
</> </>
) : ( ) : (

View File

@ -0,0 +1,122 @@
import { ChevronDownIcon } from '@heroicons/react/solid'
import React, { Fragment, useState } from 'react'
import { settlePnl, settlePosPnl } from 'components/MarketPosition'
import Button from 'components/Button'
import { Transition } from '@headlessui/react'
import { useTranslation } from 'next-i18next'
import Loading from 'components/Loading'
import useMangoStore from 'stores/useMangoStore'
const MenuButton: React.FC<{
onClick: () => void
text: string
disabled?: boolean
}> = ({ onClick, text, disabled }) => {
return (
<div
className={`default-transition flex items-center justify-end whitespace-nowrap pb-2.5 text-xs tracking-wider hover:cursor-pointer hover:text-th-primary ${
disabled ? 'pointer-events-none text-th-fgd-4' : 'text-th-fgd-1'
}`}
onClick={disabled ? null : onClick}
>
{text}
</div>
)
}
export const RedeemDropdown: React.FC = () => {
const { t } = useTranslation('common')
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
const [settling, setSettling] = useState(false)
const [settlingPosPnl, setSettlingPosPnl] = useState(false)
const [open, setOpen] = useState(false)
const unsettledPositions =
useMangoStore.getState().selectedMangoAccount.unsettledPerpPositions
const unsettledPositivePositions = useMangoStore
.getState()
.selectedMangoAccount.unsettledPerpPositions?.filter(
(p) => p.unsettledPnl > 0
)
const loading = settling || settlingPosPnl
const handleSettleAll = async () => {
setOpen(false)
setSettling(true)
for (const p of unsettledPositions) {
await settlePnl(p.perpMarket, p.perpAccount, t, undefined)
}
reloadMangoAccount()
setSettling(false)
}
const handleSettlePosPnl = async () => {
setOpen(false)
setSettlingPosPnl(true)
for (const p of unsettledPositivePositions) {
console.log('settlePosPnl', p)
await settlePosPnl([p.perpMarket], p.perpAccount, t, null)
}
setSettlingPosPnl(false)
}
const buttons = [
{ onClick: handleSettleAll, disabled: false, text: t('redeem-all') },
{
onClick: handleSettlePosPnl,
disabled: !unsettledPositivePositions?.length,
text: t('redeem-positive'),
},
]
return (
<div
className="relative"
onMouseOver={() => setOpen(true)}
onMouseOut={() => setOpen(false)}
>
<Button
className="flex h-8 w-full items-center justify-center rounded-full bg-th-bkg-button pt-0 pb-0 pl-3 pr-2 text-xs font-bold hover:brightness-[1.1] hover:filter sm:w-auto"
disabled={!unsettledPositions?.length}
>
{loading ? (
<Loading />
) : (
<>
{t('redeem-pnl')}
<ChevronDownIcon
className={`default-transition h-5 w-5 ${
open ? 'rotate-180 transform' : 'rotate-360 transform'
}`}
/>
</>
)}
</Button>
<Transition
appear={true}
show={open}
as={Fragment}
enter="transition-all ease-in duration-200"
enterFrom="opacity-0 transform scale-75"
enterTo="opacity-100 transform scale-100"
leave="transition ease-out duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute right-0 rounded-md bg-th-bkg-3 px-4 pt-2.5">
{buttons.map((b) => {
return (
<MenuButton
key={b.text}
onClick={b.onClick}
text={b.text}
disabled={b.disabled}
/>
)
})}
</div>
</Transition>
</div>
)
}

View File

@ -0,0 +1 @@
export * from './RedeemDropdown'

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next'
import { ExclamationIcon } from '@heroicons/react/outline' import { ExclamationIcon } from '@heroicons/react/outline'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
import Button, { LinkButton } from '../components/Button' import { LinkButton } 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'
@ -17,11 +17,10 @@ import PnlText from './PnlText'
import { settlePnl } from './MarketPosition' import { settlePnl } from './MarketPosition'
import MobileTableHeader from './mobile/MobileTableHeader' import MobileTableHeader from './mobile/MobileTableHeader'
import { useWallet } from '@solana/wallet-adapter-react' import { useWallet } from '@solana/wallet-adapter-react'
import { RedeemDropdown } from 'components/PerpPositions'
const PositionsTable: React.FC = () => { const PositionsTable: React.FC = () => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
const [settling, setSettling] = useState(false)
const [settleSinglePos, setSettleSinglePos] = useState(null) const [settleSinglePos, setSettleSinglePos] = useState(null)
const { wallet } = useWallet() const { wallet } = useWallet()
const selectedMarket = useMangoStore((s) => s.selectedMarket.current) const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
@ -54,16 +53,6 @@ const PositionsTable: React.FC = () => {
}) })
} }
const handleSettleAll = async () => {
setSettling(true)
for (const p of unsettledPositions) {
await settlePnl(p.perpMarket, p.perpAccount, t, undefined, wallet)
}
reloadMangoAccount()
setSettling(false)
}
const handleSettlePnl = async (perpMarket, perpAccount, index) => { const handleSettlePnl = async (perpMarket, perpAccount, index) => {
setSettleSinglePos(index) setSettleSinglePos(index)
await settlePnl(perpMarket, perpAccount, t, undefined, wallet) await settlePnl(perpMarket, perpAccount, t, undefined, wallet)
@ -80,12 +69,7 @@ const PositionsTable: React.FC = () => {
<h3>{t('unsettled-positions')}</h3> <h3>{t('unsettled-positions')}</h3>
</div> </div>
<Button <RedeemDropdown />
className="h-8 whitespace-nowrap pt-0 pb-0 pl-3 pr-3 text-xs"
onClick={handleSettleAll}
>
{settling ? <Loading /> : t('redeem-all')}
</Button>
</div> </div>
<div className="grid grid-flow-row grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3"> <div className="grid grid-flow-row grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{unsettledPositions.map((p, index) => { {unsettledPositions.map((p, index) => {

View File

@ -23,22 +23,14 @@ import usePagination from '../hooks/usePagination'
import { useEffect } from 'react' import { useEffect } from 'react'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
const renderTradeDateTime = (timestamp: BN | string) => { const formatTradeDateTime = (timestamp: BN | string) => {
let date
// don't compare to BN because of npm maddness // don't compare to BN because of npm maddness
// prototypes can be different due to multiple versions being imported // prototypes can be different due to multiple versions being imported
if (typeof timestamp === 'string') { if (typeof timestamp === 'string') {
date = new Date(timestamp) return timestamp
} else { } else {
date = new Date(timestamp.toNumber() * 1000) return (timestamp.toNumber() * 1000).toString()
} }
return (
<>
<div>{date.toLocaleDateString()}</div>
<div className="text-xs text-th-fgd-3">{date.toLocaleTimeString()}</div>
</>
)
} }
const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => { const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
@ -280,7 +272,9 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
<Td className="!py-2"> <Td className="!py-2">
{trade.loadTimestamp || trade.timestamp ? ( {trade.loadTimestamp || trade.timestamp ? (
<TableDateDisplay <TableDateDisplay
date={trade.loadTimestamp || trade.timestamp} date={formatTradeDateTime(
trade.loadTimestamp || trade.timestamp
)}
/> />
) : ( ) : (
t('recent') t('recent')
@ -333,11 +327,15 @@ const TradeHistoryTable = ({ numTrades }: { numTrades?: number }) => {
<> <>
<div className="text-fgd-1 flex w-full items-center justify-between"> <div className="text-fgd-1 flex w-full items-center justify-between">
<div className="text-left"> <div className="text-left">
{trade.loadTimestamp || trade.timestamp {trade.loadTimestamp || trade.timestamp ? (
? renderTradeDateTime( <TableDateDisplay
date={formatTradeDateTime(
trade.loadTimestamp || trade.timestamp trade.loadTimestamp || trade.timestamp
) )}
: t('recent')} />
) : (
t('recent')
)}
</div> </div>
<div> <div>
<div className="text-right"> <div className="text-right">

View File

@ -240,14 +240,14 @@ export const FavoriteMarketButton = ({ market }) => {
return favoriteMarkets.find((mkt) => mkt.name === market.name) ? ( return favoriteMarkets.find((mkt) => mkt.name === market.name) ? (
<button <button
className="default-transition text-th-primary hover:text-th-fgd-3" className="default-transition flex items-center justify-center text-th-primary hover:text-th-fgd-3"
onClick={() => removeFromFavorites(market)} onClick={() => removeFromFavorites(market)}
> >
<FilledStarIcon className="h-5 w-5" /> <FilledStarIcon className="h-5 w-5" />
</button> </button>
) : ( ) : (
<button <button
className="default-transition text-th-fgd-4 hover:text-th-primary" className="default-transition flex items-center justify-center text-th-fgd-4 hover:text-th-primary"
onClick={() => addToFavorites(market)} onClick={() => addToFavorites(market)}
> >
<StarIcon className="h-5 w-5" /> <StarIcon className="h-5 w-5" />

View File

@ -91,8 +91,7 @@ const usePerpPositions = () => {
const mangoGroup = useMangoStore(mangoGroupSelector) const mangoGroup = useMangoStore(mangoGroupSelector)
const mangoCache = useMangoStore(mangoCacheSelector) const mangoCache = useMangoStore(mangoCacheSelector)
const allMarkets = useMangoStore(marketsSelector) const allMarkets = useMangoStore(marketsSelector)
const tradeHistory = useMangoStore.getState().tradeHistory.parsed const tradeHistory = useMangoStore((s) => s.tradeHistory.parsed)
useEffect(() => { useEffect(() => {
if ( if (
mangoAccount && mangoAccount &&

View File

@ -300,6 +300,7 @@
"redeem-all": "Redeem All", "redeem-all": "Redeem All",
"redeem-failure": "Error redeeming MNGO", "redeem-failure": "Error redeeming MNGO",
"redeem-pnl": "Redeem", "redeem-pnl": "Redeem",
"redeem-positive": "Redeem positive",
"redeem-success": "Successfully redeemed MNGO", "redeem-success": "Successfully redeemed MNGO",
"referrals": "Referrals", "referrals": "Referrals",
"refresh": "Refresh", "refresh": "Refresh",