From d95c2843c4beb1f9985eccaa3f533dea31ff3cb7 Mon Sep 17 00:00:00 2001 From: saml33 Date: Sun, 25 Jul 2021 23:54:25 +1000 Subject: [PATCH 1/2] add portfolio view --- components/account-page/AccountAssets.tsx | 8 +- components/account-page/AccountOverview.tsx | 233 ++++++++++++++++++++ pages/account.tsx | 150 ++++++++----- utils/index.ts | 5 + 4 files changed, 340 insertions(+), 56 deletions(-) create mode 100644 components/account-page/AccountOverview.tsx diff --git a/components/account-page/AccountAssets.tsx b/components/account-page/AccountAssets.tsx index 9254d5c4..fd847d28 100644 --- a/components/account-page/AccountAssets.tsx +++ b/components/account-page/AccountAssets.tsx @@ -162,10 +162,10 @@ export default function AccountAssets() { {balances.map((bal, i) => { const token = getTokenBySymbol(groupConfig, bal.symbol) const tokenIndex = mangoGroup.getTokenIndex(token.mintKey) - console.log( - 'price cache', - mangoCache.priceCache[tokenIndex] - ) + // console.log( + // 'price cache', + // mangoCache.priceCache[tokenIndex] + // ) return ( s.selectedMangoGroup.markets) + const balances = useBalances() + const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) + const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current) + const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) + const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache) + + const perpMarkets = useMemo( + () => + mangoGroup + ? groupConfig.perpMarkets.map( + (m) => mangoGroup.perpMarkets[m.marketIndex] + ) + : [], + [mangoGroup] + ) + + const perpAccounts = useMemo( + () => + mangoAccount + ? groupConfig.perpMarkets.map( + (m) => mangoAccount.perpAccounts[m.marketIndex] + ) + : [], + [mangoAccount] + ) + + useEffect(() => { + const portfolio = [] + perpAccounts.forEach((acc, index) => { + const market = perpMarkets[index] + const marketConfig = getMarketByPublicKey(groupConfig, market.perpMarket) + const perpMarket = allMarkets[ + marketConfig.publicKey.toString() + ] as PerpMarket + if ( + +nativeI80F48ToUi(acc.quotePosition, marketConfig.quoteDecimals) > 0 + ) { + portfolio.push({ + market: marketConfig.name, + balance: perpMarket.baseLotsToNumber(acc.basePosition), + symbol: marketConfig.baseSymbol, + value: +nativeI80F48ToUi( + acc.quotePosition, + marketConfig.quoteDecimals + ), + type: + perpMarket.baseLotsToNumber(acc.basePosition) > 0 + ? 'Long' + : 'Short', + }) + } + }) + balances.forEach((b) => { + const token = getTokenBySymbol(groupConfig, b.symbol) + const tokenIndex = mangoGroup.getTokenIndex(token.mintKey) + if (+b.marginDeposits > 0) { + portfolio.push({ + market: b.symbol, + balance: +b.marginDeposits + b.orders + b.unsettled, + symbol: b.symbol, + value: + (+b.marginDeposits + b.orders + b.unsettled) * + mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), + type: 'Deposits', + }) + } + if (+b.borrows > 0) { + portfolio.push({ + market: b.symbol, + balance: +b.borrows, + value: b.borrows.mul(mangoGroup.getPrice(tokenIndex, mangoCache)), + type: 'Borrows', + }) + } + }) + setPortfolio(portfolio.sort((a, b) => b.value - a.value)) + }, [perpAccounts]) + + return mangoAccount ? ( + <> +
+
Account Value
+
+ + + {usdFormatter.format( + +mangoAccount.computeValue(mangoGroup, mangoCache).toFixed(2) + )} + +
+
+
+
+
Positions
+
+ +
+ {portfolio.length > 0 + ? usdFormatter.format( + portfolio.reduce( + (acc, d) => d.market.includes('PERP') && acc + d.value, + 0 + ) + ) + : 0} +
+
+
+
+
Deposits
+
+ +
+ {usdFormatter.format( + +mangoAccount.getAssetsVal(mangoGroup, mangoCache) + )} +
+
+
+
+
Borrows
+
+ +
+ {usdFormatter.format( + +mangoAccount.getLiabsVal(mangoGroup, mangoCache) + )} +
+
+
+
+
Health Ratio
+
+ +
+ {mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')}% +
+
+
+
+ {portfolio.length > 0 ? ( + <> +
Portfolio
+ + + + + + + + + + + {portfolio.map((pos, i) => ( + + + + + + + ))} + +
+ Asset/Market + + Type + + Size + + Value +
+
+ +
{pos.market}
+
+
+ {pos.type === 'Long' || pos.type === 'Short' ? ( + + ) : ( + pos.type + )} + + {pos.balance.toFixed(tokenPrecision[pos.symbol])} + + {usdFormatter.format(pos.value)} +
+ + ) : null} + + ) : null +} diff --git a/pages/account.tsx b/pages/account.tsx index 90cf4422..1840203e 100644 --- a/pages/account.tsx +++ b/pages/account.tsx @@ -1,12 +1,17 @@ -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { CurrencyDollarIcon, - ChartBarIcon, ExternalLinkIcon, - ChartPieIcon, LinkIcon, } from '@heroicons/react/outline' +import { + getTokenBySymbol, + getMarketByPublicKey, + nativeI80F48ToUi, + PerpMarket, +} from '@blockworks-foundation/mango-client' import useMangoStore from '../stores/useMangoStore' +import { useBalances } from '../hooks/useBalances' import { abbreviateAddress } from '../utils' import PageBodyContainer from '../components/PageBodyContainer' import TopBar from '../components/TopBar' @@ -15,21 +20,27 @@ import AccountBorrows from '../components/account-page/AccountBorrows' import AccountOrders from '../components/account-page/AccountOrders' import AccountHistory from '../components/account-page/AccountHistory' import AccountsModal from '../components/AccountsModal' +import AccountOverview from '../components/account-page/AccountOverview' import EmptyState from '../components/EmptyState' const TABS = [ + 'Overview', 'Assets', 'Borrows', // 'Stats', // 'Positions', 'Orders', - 'History', + 'Activity', ] export default function Account() { const [activeTab, setActiveTab] = useState(TABS[0]) const [showAccountsModal, setShowAccountsModal] = useState(false) + const [portfolio, setPortfolio] = useState([]) + const allMarkets = useMangoStore((s) => s.selectedMangoGroup.markets) + const balances = useBalances() const connected = useMangoStore((s) => s.wallet.connected) + const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache) @@ -42,6 +53,78 @@ export default function Account() { setShowAccountsModal(false) }, []) + const perpMarkets = useMemo( + () => + mangoGroup + ? groupConfig.perpMarkets.map( + (m) => mangoGroup.perpMarkets[m.marketIndex] + ) + : [], + [mangoGroup] + ) + + const perpAccounts = useMemo( + () => + mangoAccount + ? groupConfig.perpMarkets.map( + (m) => mangoAccount.perpAccounts[m.marketIndex] + ) + : [], + [mangoAccount] + ) + + useEffect(() => { + const portfolio = [] + perpAccounts.forEach((acc, index) => { + const market = perpMarkets[index] + const marketConfig = getMarketByPublicKey(groupConfig, market.perpMarket) + const perpMarket = allMarkets[ + marketConfig.publicKey.toString() + ] as PerpMarket + if ( + +nativeI80F48ToUi(acc.quotePosition, marketConfig.quoteDecimals) > 0 + ) { + portfolio.push({ + market: marketConfig.name, + balance: perpMarket.baseLotsToNumber(acc.basePosition), + symbol: marketConfig.baseSymbol, + value: +nativeI80F48ToUi( + acc.quotePosition, + marketConfig.quoteDecimals + ), + type: + perpMarket.baseLotsToNumber(acc.basePosition) > 0 + ? 'Long' + : 'Short', + }) + } + }) + balances.forEach((b) => { + const token = getTokenBySymbol(groupConfig, b.symbol) + const tokenIndex = mangoGroup.getTokenIndex(token.mintKey) + if (+b.marginDeposits > 0) { + portfolio.push({ + market: b.symbol, + balance: +b.marginDeposits + b.orders + b.unsettled, + symbol: b.symbol, + value: + (+b.marginDeposits + b.orders + b.unsettled) * + mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), + type: 'Deposits', + }) + } + if (+b.borrows > 0) { + portfolio.push({ + market: b.symbol, + balance: +b.borrows, + value: b.borrows.mul(mangoGroup.getPrice(tokenIndex, mangoCache)), + type: 'Borrows', + }) + } + }) + setPortfolio(portfolio.sort((a, b) => b.value - a.value)) + }, [perpAccounts]) + return (
@@ -82,65 +165,26 @@ export default function Account() {
{mangoAccount ? ( <> -
Overview
-
-
-
- Account Value -
-
- -
- $ - {mangoAccount - .computeValue(mangoGroup, mangoCache) - .toFixed(2)} -
-
-
-
-
Total PnL
-
- -
$0.00
-
-
-
-
- Health Ratio -
-
- -
- {mangoAccount.getHealthRatio( - mangoGroup, - mangoCache, - 'Maint' - )} - % -
-
-
-
-
+ + ) : connected ? ( @@ -173,6 +217,8 @@ export default function Account() { const TabContent = ({ activeTab }) => { switch (activeTab) { + case 'Overview': + return case 'Assets': return case 'Borrows': @@ -186,6 +232,6 @@ const TabContent = ({ activeTab }) => { case 'History': return default: - return + return } } diff --git a/utils/index.ts b/utils/index.ts index 3d132bd0..555afcbb 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -298,3 +298,8 @@ export async function getOrderBookAccountInfos( return await getMultipleAccounts(DEFAULT_CONNECTION, orderBookPks) } + +export const usdFormatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', +}) From e810c5c21506297aafe4813c7862041c012f87f9 Mon Sep 17 00:00:00 2001 From: saml33 Date: Thu, 29 Jul 2021 23:19:32 +1000 Subject: [PATCH 2/2] improve theme and misc styling --- components/BalancesTable.tsx | 14 +- components/Button.tsx | 23 +- components/ConnectWalletButton.tsx | 4 +- components/DepositWithdraw.tsx | 11 +- components/ManualRefresh.tsx | 8 +- components/MarginInfo.tsx | 15 +- components/MarketMenuItem.tsx | 2 +- components/MarketSelect.tsx | 14 +- components/OpenOrdersTable.tsx | 29 +- components/PositionsTable.tsx | 40 +-- components/ResetLayout.tsx | 8 +- components/Switch.tsx | 2 +- components/ThemeSwitch.tsx | 5 +- components/TopBar.tsx | 6 +- components/TradeForm.tsx | 28 +- components/TradingView/index.tsx | 7 +- components/UiLock.tsx | 8 +- components/account-page/AccountOverview.tsx | 336 +++++++++++++++++--- pages/account.tsx | 2 +- styles/index.css | 3 + tailwind.config.js | 72 +++-- 21 files changed, 466 insertions(+), 171 deletions(-) diff --git a/components/BalancesTable.tsx b/components/BalancesTable.tsx index 11abf4cc..dc0558a2 100644 --- a/components/BalancesTable.tsx +++ b/components/BalancesTable.tsx @@ -81,7 +81,7 @@ const BalancesTable = () => { > - + diff --git a/components/Button.tsx b/components/Button.tsx index 6ff72888..2df02813 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -17,8 +17,8 @@ const Button: FunctionComponent = ({ ) } + +export const IconButton: FunctionComponent = ({ + children, + onClick, + disabled = false, + className, + ...props +}) => { + return ( + + ) +} diff --git a/components/ConnectWalletButton.tsx b/components/ConnectWalletButton.tsx index 8f9289a5..3a01c892 100644 --- a/components/ConnectWalletButton.tsx +++ b/components/ConnectWalletButton.tsx @@ -44,8 +44,8 @@ const ConnectWalletButton = () => { {connected && wallet?.publicKey ? (
- - + + diff --git a/components/DepositWithdraw.tsx b/components/DepositWithdraw.tsx index 69deb0bd..f5d14100 100644 --- a/components/DepositWithdraw.tsx +++ b/components/DepositWithdraw.tsx @@ -8,7 +8,7 @@ import useMangoStore from '../stores/useMangoStore' import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' // import BorrowModal from './BorrowModal' -import Button from './Button' +import Button, { IconButton } from './Button' import AccountsModal from './AccountsModal' export default function MarginBalances() { @@ -48,11 +48,10 @@ export default function MarginBalances() { Mango Account
- - + + + + diff --git a/components/ManualRefresh.tsx b/components/ManualRefresh.tsx index 0a327e25..f46dab07 100644 --- a/components/ManualRefresh.tsx +++ b/components/ManualRefresh.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { RefreshClockwiseIcon } from './icons' import useMangoStore from '../stores/useMangoStore' import Tooltip from './Tooltip' +import { IconButton } from './Button' const ManualRefresh = ({ className = '' }) => { const [spin, setSpin] = useState(false) @@ -18,14 +19,11 @@ const ManualRefresh = ({ className = '' }) => { return (
- +
) diff --git a/components/MarginInfo.tsx b/components/MarginInfo.tsx index bc1eb541..8d3b2283 100644 --- a/components/MarginInfo.tsx +++ b/components/MarginInfo.tsx @@ -106,11 +106,14 @@ export default function MarginInfo() {
*/} -
+
-
{maintHealth.toFixed(2)}%
@@ -121,7 +124,13 @@ export default function MarginInfo() { style={{ width: `${maintHealth}%`, }} - className="flex rounded bg-th-primary" + className={`flex rounded ${ + maintHealth > 50 + ? 'bg-th-green' + : maintHealth <= 50 && maintHealth > 24 + ? 'bg-th-orange' + : 'bg-th-red' + }`} >
diff --git a/components/MarketMenuItem.tsx b/components/MarketMenuItem.tsx index d7c33a28..b9c2433d 100644 --- a/components/MarketMenuItem.tsx +++ b/components/MarketMenuItem.tsx @@ -80,7 +80,7 @@ export default function MarketMenuItem({ menuTitle = '', linksArray = [] }) { className="flex flex-col h-10" > handleClick(open)} > diff --git a/components/MarketSelect.tsx b/components/MarketSelect.tsx index 2201f910..570986d5 100644 --- a/components/MarketSelect.tsx +++ b/components/MarketSelect.tsx @@ -15,16 +15,16 @@ const StyledMarketSelectWrapper = styled.div` } ` -const StyledMarketTypeToggleWrapper = styled.div` - background: rgba(255, 255, 255, 0.12); -` +// const StyledMarketTypeToggleWrapper = styled.div` +// background: rgba(255, 255, 255, 0.12); +// ` const StyledArrow = styled.div` width: 0; height: 0; border-top: 20px solid transparent; border-bottom: 20px solid transparent; - border-left: 20px solid rgba(255, 255, 255, 0.12); + // border-left: 20px solid rgba(255, 255, 255, 0.12); padding-right: 0.5rem; ` @@ -54,15 +54,15 @@ const MarketSelect = () => { return ( <> - +
setShowMarketsModal(true)} > MARKETS - - +
+
{sortedMarkets diff --git a/components/OpenOrdersTable.tsx b/components/OpenOrdersTable.tsx index 2122c9a0..04ea49a2 100644 --- a/components/OpenOrdersTable.tsx +++ b/components/OpenOrdersTable.tsx @@ -12,6 +12,7 @@ import SideBadge from './SideBadge' import { useSortableData } from '../hooks/useSortableData' import { Order, Market } from '@project-serum/serum/lib/market' import { PerpOrder, PerpMarket } from '@blockworks-foundation/mango-client' +import { usdFormatter } from '../utils' const OpenOrdersTable = () => { const { asPath } = useRouter() @@ -76,9 +77,9 @@ const OpenOrdersTable = () => {
{ `} > { {balance.symbol} {balance.marginDeposits.toFixed(tokenConfig.decimals)} {balance.borrows.toFixed(tokenConfig.decimals)} {balance.orders} {balance.unsettled} {balance.net.toFixed(tokenConfig.decimals)}
- - - - -
+ requestSort('marketName')} > Market @@ -93,9 +94,9 @@ const OpenOrdersTable = () => { /> + requestSort('side')} > Side @@ -110,9 +111,9 @@ const OpenOrdersTable = () => { /> + requestSort('size')} > Size @@ -127,9 +128,9 @@ const OpenOrdersTable = () => { /> + requestSort('price')} > Price @@ -158,7 +159,7 @@ const OpenOrdersTable = () => { `} >
{
{order.size} - {order.price} + {usdFormatter.format(order.price)} +