diff --git a/components/AccountNameModal.tsx b/components/AccountNameModal.tsx index de8fa5c0..9073b30e 100644 --- a/components/AccountNameModal.tsx +++ b/components/AccountNameModal.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, useState } from 'react' +import { FunctionComponent, useEffect, useState } from 'react' import useMangoStore, { mangoClient } from '../stores/useMangoStore' import { ExclamationCircleIcon, @@ -27,6 +27,20 @@ const AccountNameModal: FunctionComponent = ({ const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current) const actions = useMangoStore((s) => s.actions) + const setMangoStore = useMangoStore((s) => s.set) + + useEffect(() => { + setMangoStore((state) => { + state.blurBackground = true + }) + }, []) + + const handleClose = () => { + setMangoStore((state) => { + state.blurBackground = false + }) + onClose() + } const submitName = async () => { const wallet = useMangoStore.getState().wallet.current @@ -72,7 +86,7 @@ const AccountNameModal: FunctionComponent = ({ } return ( - +
Name your Account diff --git a/components/AccountsModal.tsx b/components/AccountsModal.tsx index f71ed799..180c4a12 100644 --- a/components/AccountsModal.tsx +++ b/components/AccountsModal.tsx @@ -1,11 +1,7 @@ import React, { FunctionComponent, useEffect, useState } from 'react' import { RadioGroup } from '@headlessui/react' import { CheckCircleIcon } from '@heroicons/react/solid' -import { - ChevronLeftIcon, - CurrencyDollarIcon, - PlusCircleIcon, -} from '@heroicons/react/outline' +import { ChevronLeftIcon, PlusCircleIcon } from '@heroicons/react/outline' import useMangoStore from '../stores/useMangoStore' import { MangoAccount, @@ -53,6 +49,19 @@ const AccountsModal: FunctionComponent = ({ onClose() } + useEffect(() => { + setMangoStore((state) => { + state.blurBackground = true + }) + }, []) + + const handleClose = () => { + setMangoStore((state) => { + state.blurBackground = false + }) + onClose() + } + useEffect(() => { if (newAccPublicKey) { setMangoStore((state) => { @@ -76,7 +85,7 @@ const AccountsModal: FunctionComponent = ({ } return ( - + {mangoAccounts.length > 0 ? ( !showNewAccountForm ? ( <> @@ -126,7 +135,6 @@ const AccountsModal: FunctionComponent = ({
-
{account?.name || @@ -142,6 +150,15 @@ const AccountsModal: FunctionComponent = ({
) : null}
+ {mangoGroup ? ( +
+ +
+ ) : null}
diff --git a/components/AlphaModal.tsx b/components/AlphaModal.tsx index f809a971..9a718570 100644 --- a/components/AlphaModal.tsx +++ b/components/AlphaModal.tsx @@ -1,7 +1,8 @@ -import React from 'react' +import React, { useEffect } from 'react' import Modal from './Modal' import Button from './Button' import useLocalStorageState from '../hooks/useLocalStorageState' +import useMangoStore from '../stores/useMangoStore' const AlphaModal = ({ isOpen, @@ -14,6 +15,20 @@ const AlphaModal = ({ 'mangoAlphaAccepted-2.0', false ) + const setMangoStore = useMangoStore((s) => s.set) + + useEffect(() => { + setMangoStore((state) => { + state.blurBackground = true + }) + }, []) + + const handleAccept = () => { + setAlphaAccepted(true) + setMangoStore((state) => { + state.blurBackground = false + }) + } return ( @@ -55,7 +70,7 @@ const AlphaModal = ({
Mango Markets is unaudited software. Use at your own risk.
-
diff --git a/components/DepositModal.tsx b/components/DepositModal.tsx index 496a17b4..0a39c8ea 100644 --- a/components/DepositModal.tsx +++ b/components/DepositModal.tsx @@ -47,6 +47,7 @@ const DepositModal: FunctionComponent = ({ const [maxButtonTransition, setMaxButtonTransition] = useState(false) const walletTokens = useMangoStore((s) => s.wallet.tokens) const actions = useMangoStore((s) => s.actions) + const setMangoStore = useMangoStore((s) => s.set) const [selectedAccount, setSelectedAccount] = useState(walletTokens[0]) // const prices = [] //useMangoStore((s) => s.selectedMangoGroup.prices) @@ -65,6 +66,19 @@ const DepositModal: FunctionComponent = ({ } }, [tokenSymbol, walletTokens]) + useEffect(() => { + setMangoStore((state) => { + state.blurBackground = true + }) + }, []) + + const handleClose = () => { + setMangoStore((state) => { + state.blurBackground = false + }) + onClose() + } + /* TODO: simulation useEffect(() => { if (!selectedMangoGroup || !selectedMangoAccount || !selectedAccount) @@ -243,7 +257,7 @@ const DepositModal: FunctionComponent = ({ }, [maxButtonTransition]) return ( - + {!showSimulation ? ( <> diff --git a/components/MarketsModal.tsx b/components/MarketsModal.tsx index 17462abb..4db64e70 100644 --- a/components/MarketsModal.tsx +++ b/components/MarketsModal.tsx @@ -1,9 +1,10 @@ -import React from 'react' +import React, { useEffect } from 'react' import styled from '@emotion/styled' import Link from 'next/link' import { EyeIcon, EyeOffIcon } from '@heroicons/react/outline' import Modal from './Modal' import useLocalStorageState from '../hooks/useLocalStorageState' +import useMangoStore from '../stores/useMangoStore' import { LinkButton } from './Button' const StyledColumnHeader = styled.span` @@ -17,12 +18,26 @@ const MarketsModal = ({ }: { isOpen: boolean markets: Array - onClose?: (x) => void + onClose?: (x?) => void }) => { const [hiddenMarkets, setHiddenMarkets] = useLocalStorageState( 'hiddenMarkets', [] ) + const setMangoStore = useMangoStore((s) => s.set) + + useEffect(() => { + setMangoStore((state) => { + state.blurBackground = true + }) + }, []) + + const handleClose = () => { + setMangoStore((state) => { + state.blurBackground = false + }) + onClose() + } const handleHideShowMarket = (asset) => { if (hiddenMarkets.includes(asset)) { @@ -33,7 +48,7 @@ const MarketsModal = ({ } return ( - +
Markets
= ({ const [maxButtonTransition, setMaxButtonTransition] = useState(false) const actions = useMangoStore((s) => s.actions) + const setMangoStore = useMangoStore((s) => s.set) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const selectedMangoAccount = useMangoStore( (s) => s.selectedMangoAccount.current @@ -351,6 +352,19 @@ const WithdrawModal: FunctionComponent = ({ } }, [maxButtonTransition]) + useEffect(() => { + setMangoStore((state) => { + state.blurBackground = true + }) + }, []) + + const handleClose = () => { + setMangoStore((state) => { + state.blurBackground = false + }) + onClose() + } + // turn on borrow toggle when asset balance is zero // useEffect(() => { // if (withdrawTokenSymbol && getDepositsForSelectedAsset() === 0) { @@ -361,7 +375,7 @@ const WithdrawModal: FunctionComponent = ({ if (!withdrawTokenSymbol) return null return ( - + <> {!showSimulation ? ( <> diff --git a/components/account-page/AccountOverview.tsx b/components/account-page/AccountOverview.tsx index daccdbc9..001e215b 100644 --- a/components/account-page/AccountOverview.tsx +++ b/components/account-page/AccountOverview.tsx @@ -1,29 +1,30 @@ -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useCallback, useState } from 'react' import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table' import styled from '@emotion/styled' import { Menu } from '@headlessui/react' +import Link from 'next/link' import { ArrowSmDownIcon, + ChartBarIcon, CurrencyDollarIcon, + ExclamationIcon, DotsHorizontalIcon, HeartIcon, XIcon, } from '@heroicons/react/outline' -import { - getTokenBySymbol, - getMarketByPublicKey, - I80F48, - nativeI80F48ToUi, - PerpMarket, -} from '@blockworks-foundation/mango-client' -import useMangoStore from '../../stores/useMangoStore' +import { getTokenBySymbol, I80F48 } from '@blockworks-foundation/mango-client' +import useMangoStore, { mangoClient } from '../../stores/useMangoStore' import { useBalances } from '../../hooks/useBalances' import { useSortableData } from '../../hooks/useSortableData' -import { usdFormatter, tokenPrecision } from '../../utils' +import { sleep, usdFormatter, tokenPrecision } from '../../utils' +import { notify } from '../../utils/notifications' +import { Market } from '@project-serum/serum' import SideBadge from '../SideBadge' import Button, { LinkButton } from '../Button' import Switch from '../Switch' import PositionsTable from '../PositionsTable' +import DepositModal from '../DepositModal' +import WithdrawModal from '../WithdrawModal' const StyledAccountValue = styled.div` font-size: 1.8rem; @@ -32,11 +33,14 @@ const StyledAccountValue = styled.div` export default function AccountOverview() { const [spotPortfolio, setSpotPortfolio] = useState([]) - const [perpPositions, setPerpPositions] = useState([]) + const [unsettled, setUnsettled] = useState([]) const [filteredSpotPortfolio, setFilteredSpotPortfolio] = useState([]) const [showZeroBalances, setShowZeroBalances] = useState(false) - const allMarkets = useMangoStore((s) => s.selectedMangoGroup.markets) + const [showDepositModal, setShowDepositModal] = useState(false) + const [showWithdrawModal, setShowWithdrawModal] = useState(false) + const [actionSymbol, setActionSymbol] = useState('') const balances = useBalances() + const actions = useMangoStore((s) => s.actions) const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) @@ -45,62 +49,13 @@ export default function AccountOverview() { filteredSpotPortfolio ) - 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(() => { - let positions = [] - 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 - ) { - positions.push({ - market: marketConfig.name, - balance: perpMarket.baseLotsToNumber(acc.basePosition), - price: mangoGroup.getPrice(marketConfig.marketIndex, mangoCache), - symbol: marketConfig.baseSymbol, - value: +nativeI80F48ToUi( - acc.quotePosition, - marketConfig.quoteDecimals - ), - type: - perpMarket.baseLotsToNumber(acc.basePosition) > 0 - ? 'Long' - : 'Short', - }) - } - }) - setPerpPositions(positions.sort((a, b) => b.value - a.value)) - }, []) - useEffect(() => { const spotPortfolio = [] + const unsettled = [] balances.forEach((b) => { const token = getTokenBySymbol(groupConfig, b.symbol) const tokenIndex = mangoGroup.getTokenIndex(token.mintKey) - if (+b.marginDeposits > 0) { + if (+b.marginDeposits > 0 || b.orders > 0) { spotPortfolio.push({ market: b.symbol, balance: +b.marginDeposits + b.orders + b.unsettled, @@ -148,6 +103,16 @@ export default function AccountOverview() { type: '–', }) } + if (b.unsettled > 0) { + unsettled.push({ + market: b.symbol, + balance: b.unsettled, + symbol: b.symbol, + value: + b.unsettled * + mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), + }) + } }) setSpotPortfolio(spotPortfolio.sort((a, b) => b.value - a.value)) setFilteredSpotPortfolio( @@ -155,6 +120,7 @@ export default function AccountOverview() { .filter((pos) => pos.balance > 0) .sort((a, b) => b.value - a.value) ) + setUnsettled(unsettled) }, []) const handleShowZeroBalances = (checked) => { @@ -166,20 +132,75 @@ export default function AccountOverview() { setShowZeroBalances(checked) } + async function handleSettleAll() { + const mangoAccount = useMangoStore.getState().selectedMangoAccount.current + const mangoGroup = useMangoStore.getState().selectedMangoGroup.current + const markets = useMangoStore.getState().selectedMangoGroup.markets + const wallet = useMangoStore.getState().wallet.current + + try { + const spotMarkets = Object.values(markets).filter( + (mkt) => mkt instanceof Market + ) as Market[] + await mangoClient.settleAll(mangoGroup, mangoAccount, spotMarkets, wallet) + notify({ title: 'Successfully settled funds' }) + await sleep(250) + actions.fetchMangoAccounts() + } catch (e) { + console.warn('Error settling all:', e) + if (e.message === 'No unsettled funds') { + notify({ + title: 'There are no unsettled funds', + type: 'error', + }) + } else { + notify({ + title: 'Error settling funds', + description: e.message, + txid: e.txid, + type: 'error', + }) + } + } + } + + const handleOpenDepositModal = useCallback((symbol) => { + setActionSymbol(symbol) + setShowDepositModal(true) + }, []) + + const handleOpenWithdrawModal = useCallback((symbol) => { + setActionSymbol(symbol) + setShowWithdrawModal(true) + }, []) + return mangoAccount ? ( <> -
-
Account Value
-
- - - {usdFormatter.format( - +mangoAccount.computeValue(mangoGroup, mangoCache).toFixed(2) - )} - +
+
+
Account Value
+
+ + + {usdFormatter.format( + +mangoAccount.computeValue(mangoGroup, mangoCache).toFixed(2) + )} + +
+
+
+
PNL
+
+ + + {usdFormatter.format( + +mangoAccount.computeValue(mangoGroup, mangoCache).toFixed(2) + )} + +
-
+
Positions
@@ -225,6 +246,40 @@ export default function AccountOverview() {
+ {unsettled.length > 0 ? ( +
+
+
+ + Unsettled Balances +
+ +
+ {unsettled.map((a) => ( +
+
+ +
{a.symbol}
+
+ {a.balance.toFixed(tokenPrecision[a.symbol])} +
+ ))} +
+ ) : null}
Perp Positions
@@ -244,9 +299,9 @@ export default function AccountOverview() { - - - - - - -
+ requestSort('market')} > Asset @@ -261,9 +316,9 @@ export default function AccountOverview() { /> + requestSort('type')} > Type @@ -278,9 +333,9 @@ export default function AccountOverview() { /> + requestSort('balance')} > Balance @@ -295,9 +350,9 @@ export default function AccountOverview() { /> + requestSort('price')} > Price @@ -312,9 +367,9 @@ export default function AccountOverview() { /> + requestSort('value')} > Value @@ -329,9 +384,9 @@ export default function AccountOverview() { /> + requestSort('depositRate')} > Deposit Rate @@ -346,9 +401,9 @@ export default function AccountOverview() { /> + requestSort('borrowRate')} > Borrow Rate @@ -445,18 +500,24 @@ export default function AccountOverview() { /> {pos.symbol} + {pos.symbol !== 'USDC' ? ( + + + +
Trade
+
+ +
+ ) : null} - - - @@ -464,19 +525,13 @@ export default function AccountOverview() { - - - )} @@ -488,6 +543,20 @@ export default function AccountOverview() {
) : null} + {showDepositModal && ( + setShowDepositModal(false)} + tokenSymbol={actionSymbol} + /> + )} + {showWithdrawModal && ( + setShowWithdrawModal(false)} + tokenSymbol={actionSymbol} + /> + )} ) : null } diff --git a/pages/_app.tsx b/pages/_app.tsx index 818a3fe9..8c2cf8a5 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -5,11 +5,13 @@ import '../node_modules/react-resizable/css/styles.css' import '../styles/index.css' import useWallet from '../hooks/useWallet' import useHydrateStore from '../hooks/useHydrateStore' +import useMangoStore from '../stores/useMangoStore' import Notifications from '../components/Notification' function App({ Component, pageProps }) { useHydrateStore() useWallet() + const blurBackground = useMangoStore((s) => s.blurBackground) return ( <> @@ -47,7 +49,15 @@ function App({ Component, pageProps }) { - +
+
+ +
+
diff --git a/pages/account.tsx b/pages/account.tsx index e552c8dd..4058b9d5 100644 --- a/pages/account.tsx +++ b/pages/account.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { CurrencyDollarIcon, DuplicateIcon, @@ -6,14 +6,7 @@ import { LinkIcon, PencilIcon, } 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, copyToClipboard } from '../utils' import PageBodyContainer from '../components/PageBodyContainer' import TopBar from '../components/TopBar' @@ -28,9 +21,9 @@ import Button from '../components/Button' import EmptyState from '../components/EmptyState' const TABS = [ - 'Overview', - 'Assets', - 'Borrows', + 'Portfolio', + // 'Assets', + // 'Borrows', // 'Stats', // 'Positions', 'Orders', @@ -40,16 +33,10 @@ const TABS = [ 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 [showNameModal, setShowNameModal] = useState(false) const [isCopied, setIsCopied] = useState(false) 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) const wallet = useMangoStore((s) => s.wallet.current) const handleTabChange = (tabName) => { @@ -59,78 +46,6 @@ 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]) - const handleCopyPublicKey = (code) => { setIsCopied(true) copyToClipboard(code) @@ -181,7 +96,7 @@ export default function Account() {
setShowAccountsModal(true)} > -
- - Accounts -
+ Accounts
@@ -264,7 +176,7 @@ export default function Account() { const TabContent = ({ activeTab }) => { switch (activeTab) { - case 'Overview': + case 'Portfolio': return case 'Assets': return diff --git a/stores/useMangoStore.tsx b/stores/useMangoStore.tsx index 6d4554ff..53544f2a 100644 --- a/stores/useMangoStore.tsx +++ b/stores/useMangoStore.tsx @@ -158,6 +158,7 @@ interface MangoStore extends State { uiLocked: boolean } tradeHistory: any[] + blurBackground: boolean set: (x: any) => void actions: { [key: string]: (args?) => void @@ -213,6 +214,7 @@ const useMangoStore = create((set, get) => ({ uiLocked: true, }, tradeHistory: [], + blurBackground: false, set: (fn) => set(produce(fn)), actions: { async fetchWalletTokens() { diff --git a/styles/index.css b/styles/index.css index 3872a798..a9142e19 100644 --- a/styles/index.css +++ b/styles/index.css @@ -78,7 +78,7 @@ button { } .default-transition { - @apply transition-all duration-500; + @apply transition-all duration-300; } .tiny-text {