@@ -142,7 +155,7 @@ const ManagePosition: React.FC = () => {
data-testid="trade-open-position-button"
noOutline
fullWidth
- disabled={placeOrderDisabled}
+ disabled={!!placeOrderDisabledReason}
onClick={() => setConfirmationModalOpen(true)}
>
{status === 'fetching' ?
: t(placeOrderTranslationKey)}
@@ -161,7 +174,7 @@ const ManagePosition: React.FC = () => {
? PositionSide.SHORT
: PositionSide.LONG;
setLeverageSide(newLeverageSide);
- onTradeAmountChange(newTradeSize.toString(), 'native');
+ onTradeAmountChange(newTradeSize.toString(), tradePrice, 'native');
setConfirmationModalOpen(true);
} else {
setCancelModalOpen(true);
diff --git a/sections/futures/Trade/MarketActions.tsx b/sections/futures/Trade/MarketActions.tsx
index 87f9675111..f2808369db 100644
--- a/sections/futures/Trade/MarketActions.tsx
+++ b/sections/futures/Trade/MarketActions.tsx
@@ -9,8 +9,7 @@ import useIsL2 from 'hooks/useIsL2';
import { balancesState, marketInfoState, positionState } from 'store/futures';
import { zeroBN } from 'utils/formatters/number';
-import DepositMarginModal from './DepositMarginModal';
-import WithdrawMarginModal from './WithdrawMarginModal';
+import TransferIsolatedMarginModal from './TransferIsolatedMarginModal';
const MarketActions: React.FC = () => {
const { t } = useTranslation();
@@ -48,10 +47,20 @@ const MarketActions: React.FC = () => {
{openModal === 'deposit' && (
-
setOpenModal(null)} />
+ setOpenModal(null)}
+ />
)}
- {openModal === 'withdraw' && setOpenModal(null)} />}
+ {openModal === 'withdraw' && (
+ setOpenModal(null)}
+ />
+ )}
>
);
};
diff --git a/sections/futures/Trade/TradeIsolatedMargin.tsx b/sections/futures/Trade/TradeIsolatedMargin.tsx
index bab7a8e2b0..5779a2d8c0 100644
--- a/sections/futures/Trade/TradeIsolatedMargin.tsx
+++ b/sections/futures/Trade/TradeIsolatedMargin.tsx
@@ -1,73 +1,39 @@
-import { useMemo, useState } from 'react';
+import { useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
-import styled, { useTheme } from 'styled-components';
+import styled from 'styled-components';
-import DepositArrow from 'assets/svg/futures/deposit-arrow.svg';
-import WithdrawArrow from 'assets/svg/futures/withdraw-arrow.svg';
import SegmentedControl from 'components/SegmentedControl';
import { ISOLATED_MARGIN_ORDER_TYPES } from 'constants/futures';
-import {
- balancesState,
- leverageSideState,
- marketInfoState,
- orderTypeState,
- positionState,
-} from 'store/futures';
-import { zeroBN } from 'utils/formatters/number';
+import { balancesState, leverageSideState, orderTypeState } from 'store/futures';
import FeeInfoBox from '../FeeInfoBox';
import LeverageInput from '../LeverageInput';
import MarketInfoBox from '../MarketInfoBox';
import OrderSizing from '../OrderSizing';
import PositionButtons from '../PositionButtons';
-import DepositMarginModal from './DepositMarginModal';
import ManagePosition from './ManagePosition';
-import MarketsDropdown from './MarketsDropdown';
import NextPrice from './NextPrice';
import TradePanelHeader from './TradePanelHeader';
-import WithdrawMarginModal from './WithdrawMarginModal';
+import TransferIsolatedMarginModal from './TransferIsolatedMarginModal';
type Props = {
isMobile?: boolean;
};
const TradeIsolatedMargin = ({ isMobile }: Props) => {
- const { colors } = useTheme();
-
const [leverageSide, setLeverageSide] = useRecoilState(leverageSideState);
- const position = useRecoilValue(positionState);
- const marketInfo = useRecoilValue(marketInfoState);
const { susdWalletBalance } = useRecoilValue(balancesState);
const [orderType, setOrderType] = useRecoilState(orderTypeState);
- const [openModal, setOpenModal] = useState<'deposit' | 'withdraw' | null>(null);
-
- const headerButtons = useMemo(() => {
- const transferButtons = !marketInfo?.isSuspended
- ? [
- {
- i18nTitle: 'futures.market.trade.button.deposit',
- icon: ,
- onClick: () => setOpenModal('deposit'),
- },
- ]
- : [];
-
- if (position?.remainingMargin?.gt(zeroBN) && !marketInfo?.isSuspended) {
- transferButtons.push({
- i18nTitle: 'futures.market.trade.button.withdraw',
- icon: ,
- onClick: () => setOpenModal('withdraw'),
- });
- }
- return transferButtons;
- }, [position?.remainingMargin, marketInfo?.isSuspended, colors.selectedTheme.yellow]);
+ const [openTransferModal, setOpenTransferModal] = useState(false);
return (
- {!isMobile && }
-
-
+ setOpenTransferModal(true)}
+ balance={susdWalletBalance}
+ accountType={'isolated_margin'}
+ />
{!isMobile && }
@@ -91,11 +57,13 @@ const TradeIsolatedMargin = ({ isMobile }: Props) => {
- {openModal === 'deposit' && (
- setOpenModal(null)} />
+ {openTransferModal && (
+ setOpenTransferModal(false)}
+ />
)}
-
- {openModal === 'withdraw' && setOpenModal(null)} />}
);
};
diff --git a/sections/futures/Trade/TradePanelHeader.tsx b/sections/futures/Trade/TradePanelHeader.tsx
index 5f7188b0e6..6f6aa6a3d5 100644
--- a/sections/futures/Trade/TradePanelHeader.tsx
+++ b/sections/futures/Trade/TradePanelHeader.tsx
@@ -1,26 +1,23 @@
-import { ReactNode } from 'react';
+import Wei from '@synthetixio/wei';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import HelpIcon from 'assets/svg/app/question-mark.svg';
-import Button from 'components/Button';
-import { ButtonVariant } from 'components/Button/Button';
+import SwitchAssetArrows from 'assets/svg/futures/deposit-withdraw-arrows.svg';
import FuturesIcon from 'components/Nav/FuturesIcon';
+import { NumberDiv } from 'components/Text/NumberLabel';
import { EXTERNAL_LINKS } from 'constants/links';
import { FuturesAccountType } from 'queries/futures/subgraph';
-import { BorderedPanel } from 'styles/common';
+import { BorderedPanel, YellowIconButton } from 'styles/common';
+import { formatDollars } from 'utils/formatters/number';
type Props = {
accountType: FuturesAccountType;
- buttons?: {
- onClick: (() => void) | undefined;
- variant?: ButtonVariant;
- i18nTitle: string;
- icon?: ReactNode;
- }[];
+ balance: Wei;
+ onManageBalance: () => void;
};
-export default function TradePanelHeader({ accountType, buttons }: Props) {
+export default function TradePanelHeader({ accountType, onManageBalance, balance }: Props) {
const { t } = useTranslation();
return (
@@ -38,35 +35,22 @@ export default function TradePanelHeader({ accountType, buttons }: Props) {
)}
-
- {buttons &&
- buttons.map(({ icon, i18nTitle, variant, onClick }) => (
-
-
- {icon && {icon}}
-
- ))}
-
+
+ {formatDollars(balance)}
+
+
+
+
);
}
-const StyledFuturesIcon = styled(FuturesIcon)`
- margin-right: 6px;
-`;
-
const Container = styled(BorderedPanel)`
display: flex;
justify-content: space-between;
- padding: 10px 10px;
+ padding: 10px 12px;
margin-bottom: 16px;
+ height: 55px;
`;
const Title = styled.div`
@@ -77,33 +61,29 @@ const Title = styled.div`
cursor: default;
`;
-const FAQLink = styled.div`
- &:hover {
- opacity: 0.5;
- }
- cursor: pointer;
- margin-left: 5px;
+const StyledFuturesIcon = styled(FuturesIcon)`
+ margin-right: 6px;
`;
-const Buttons = styled.div`
+const BalanceRow = styled.div`
display: flex;
+ gap: 8px;
+ align-items: center;
`;
-const HeaderButton = styled(Button)`
- margin-left: 10px;
- font-size: 11px;
-`;
-
-const Label = styled.span`
- @media (max-width: 1550px) {
- display: none;
+const BalanceButton = styled(YellowIconButton)`
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ &:hover {
+ opacity: 0.7;
}
`;
-const IconContainer = styled.span`
- margin-left: 5px;
-
- @media (max-width: 1550px) {
- margin-left: 0;
+const FAQLink = styled.div`
+ &:hover {
+ opacity: 0.5;
}
+ cursor: pointer;
+ margin-left: 5px;
`;
diff --git a/sections/futures/Trade/DepositMarginModal.tsx b/sections/futures/Trade/TransferIsolatedMarginModal.tsx
similarity index 68%
rename from sections/futures/Trade/DepositMarginModal.tsx
rename to sections/futures/Trade/TransferIsolatedMarginModal.tsx
index 7eeb7d6cba..603b57cf21 100644
--- a/sections/futures/Trade/DepositMarginModal.tsx
+++ b/sections/futures/Trade/TransferIsolatedMarginModal.tsx
@@ -9,6 +9,8 @@ import BaseModal from 'components/BaseModal';
import Button from 'components/Button';
import Error from 'components/Error';
import CustomInput from 'components/Input/CustomInput';
+import SegmentedControl from 'components/SegmentedControl';
+import Spacer from 'components/Spacer';
import { MIN_MARGIN_AMOUNT } from 'constants/futures';
import { NO_VALUE } from 'constants/placeholder';
import TransactionNotifier from 'containers/TransactionNotifier';
@@ -20,14 +22,15 @@ import { FlexDivRowCentered } from 'styles/common';
import { formatDollars, zeroBN } from 'utils/formatters/number';
import { getDisplayAsset } from 'utils/futures';
-type DepositMarginModalProps = {
+type Props = {
onDismiss(): void;
+ defaultTab: 'deposit' | 'withdraw';
sUSDBalance: Wei;
};
const PLACEHOLDER = '$0.00';
-const DepositMarginModal: React.FC = ({ onDismiss, sUSDBalance }) => {
+const TransferIsolatedMarginModal: React.FC = ({ onDismiss, sUSDBalance, defaultTab }) => {
const { t } = useTranslation();
const { monitorTransaction } = TransactionNotifier.useContainer();
const { useEthGasPriceQuery, useSynthetixTxn } = useSynthetixQueries();
@@ -44,21 +47,35 @@ const DepositMarginModal: React.FC = ({ onDismiss, sUSD
}, [position?.accessibleMargin]);
const [amount, setAmount] = useState('');
+ const [transferType, setTransferType] = useState(defaultTab === 'deposit' ? 0 : 1);
const ethGasPriceQuery = useEthGasPriceQuery();
const { handleRefetch } = useRefetchContext();
const gasPrice = ethGasPriceQuery.data != null ? ethGasPriceQuery.data[gasSpeed] : null;
+ const susdBal = transferType === 0 ? sUSDBalance : position?.accessibleMargin || zeroBN;
+ const accessibleMargin = useMemo(() => position?.accessibleMargin ?? zeroBN, [
+ position?.accessibleMargin,
+ ]);
+
const isDisabled = useMemo(() => {
if (!amount) {
return true;
}
const amtWei = wei(amount);
- if (amtWei.eq(0) || amtWei.gt(sUSDBalance) || amtWei.lt(minDeposit)) {
+ if (amtWei.eq(0) || amtWei.gt(susdBal) || (transferType === 0 && amtWei.lt(minDeposit))) {
return true;
}
return false;
- }, [amount, sUSDBalance, minDeposit]);
+ }, [amount, susdBal, minDeposit, transferType]);
+
+ const computedWithdrawAmount = useMemo(
+ () =>
+ accessibleMargin.eq(wei(amount || 0))
+ ? accessibleMargin.mul(wei(-1)).toBN()
+ : wei(-amount).toBN(),
+ [amount, accessibleMargin]
+ );
const depositTxn = useSynthetixTxn(
`FuturesMarket${getDisplayAsset(market)}`,
@@ -68,12 +85,21 @@ const DepositMarginModal: React.FC = ({ onDismiss, sUSD
{ enabled: !!market && !!amount && !isDisabled }
);
- const transactionFee = estimateSnxTxGasCost(depositTxn);
+ const withdrawTxn = useSynthetixTxn(
+ `FuturesMarket${market?.[0] === 's' ? market?.substring(1) : market}`,
+ 'transferMargin',
+ [computedWithdrawAmount],
+ gasPrice || undefined,
+ { enabled: !!market && !!amount }
+ );
+
+ const transactionFee = estimateSnxTxGasCost(transferType === 0 ? depositTxn : withdrawTxn);
useEffect(() => {
- if (depositTxn.hash) {
+ const hash = depositTxn.hash ?? withdrawTxn.hash;
+ if (hash) {
monitorTransaction({
- txHash: depositTxn.hash,
+ txHash: hash,
onTxConfirmed: () => {
handleRefetch('margin-change');
onDismiss();
@@ -82,11 +108,20 @@ const DepositMarginModal: React.FC = ({ onDismiss, sUSD
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [depositTxn.hash]);
+ }, [depositTxn.hash, withdrawTxn.hash]);
const handleSetMax = useCallback(() => {
- setAmount(sUSDBalance.toString());
- }, [sUSDBalance]);
+ if (transferType === 0) {
+ setAmount(susdBal.toString());
+ } else {
+ setAmount(accessibleMargin.toString());
+ }
+ }, [susdBal, accessibleMargin, transferType]);
+
+ const onChangeTab = (selection: number) => {
+ setTransferType(selection);
+ setAmount('');
+ };
return (
= ({ onDismiss, sUSD
isOpen
onDismiss={onDismiss}
>
+
{t('futures.market.trade.margin.modal.balance')}:
- {formatDollars(sUSDBalance)} sUSD
+ {formatDollars(susdBal)} sUSD
= ({ onDismiss, sUSD
{t('futures.market.trade.margin.modal.max')}
}
/>
-
-
- {t('futures.market.trade.margin.modal.deposit.disclaimer')}
-
+ {transferType === 0 ? (
+
+ {t('futures.market.trade.margin.modal.deposit.disclaimer')}
+
+ ) : (
+
+ )}
depositTxn.mutate()}
+ onClick={transferType === 0 ? () => depositTxn.mutate() : () => withdrawTxn.mutate()}
>
- {t('futures.market.trade.margin.modal.deposit.button')}
+ {transferType === 0
+ ? t('futures.market.trade.margin.modal.deposit.button')
+ : t('futures.market.trade.margin.modal.withdraw.button')}
@@ -190,4 +235,8 @@ export const GasFeeContainer = styled(FlexDivRowCentered)`
}
`;
-export default DepositMarginModal;
+const StyledSegmentedControl = styled(SegmentedControl)`
+ margin-bottom: 16px;
+`;
+
+export default TransferIsolatedMarginModal;
diff --git a/sections/futures/Trade/WithdrawMarginModal.tsx b/sections/futures/Trade/WithdrawMarginModal.tsx
deleted file mode 100644
index e0b5ac3589..0000000000
--- a/sections/futures/Trade/WithdrawMarginModal.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import useSynthetixQueries from '@synthetixio/queries';
-import { wei } from '@synthetixio/wei';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { useRecoilValue } from 'recoil';
-
-import Error from 'components/Error';
-import CustomInput from 'components/Input/CustomInput';
-import Spacer from 'components/Spacer';
-import { NO_VALUE } from 'constants/placeholder';
-import TransactionNotifier from 'containers/TransactionNotifier';
-import { useRefetchContext } from 'contexts/RefetchContext';
-import useEstimateGasCost from 'hooks/useEstimateGasCost';
-import { currentMarketState, positionState } from 'store/futures';
-import { gasSpeedState } from 'store/wallet';
-import { formatDollars } from 'utils/formatters/number';
-
-import {
- StyledBaseModal,
- BalanceContainer,
- BalanceText,
- GasFeeContainer,
- MaxButton,
- MarginActionButton,
-} from './DepositMarginModal';
-
-type WithdrawMarginModalProps = {
- onDismiss(): void;
-};
-
-const PLACEHOLDER = '$0.00';
-const ZERO_WEI = wei(0);
-
-const WithdrawMarginModal: React.FC = ({ onDismiss }) => {
- const { t } = useTranslation();
- const { monitorTransaction } = TransactionNotifier.useContainer();
- const gasSpeed = useRecoilValue(gasSpeedState);
- const market = useRecoilValue(currentMarketState);
- const { useEthGasPriceQuery, useSynthetixTxn } = useSynthetixQueries();
- const [amount, setAmount] = React.useState('');
- const [isDisabled, setDisabled] = React.useState(true);
- const [isMax, setMax] = React.useState(false);
-
- const ethGasPriceQuery = useEthGasPriceQuery();
- const { estimateSnxTxGasCost } = useEstimateGasCost();
-
- const { handleRefetch } = useRefetchContext();
-
- const gasPrice = ethGasPriceQuery.data != null ? ethGasPriceQuery.data[gasSpeed] : null;
-
- const position = useRecoilValue(positionState);
- const accessibleMargin = position?.accessibleMargin ?? ZERO_WEI;
-
- const computedAmount = React.useMemo(
- () =>
- accessibleMargin.eq(wei(amount || 0))
- ? accessibleMargin.mul(wei(-1)).toBN()
- : wei(-amount).toBN(),
- [amount, accessibleMargin]
- );
-
- const withdrawTxn = useSynthetixTxn(
- `FuturesMarket${market?.[0] === 's' ? market?.substring(1) : market}`,
- isMax ? 'withdrawAllMargin' : 'transferMargin',
- isMax ? [] : [computedAmount],
- gasPrice || undefined,
- { enabled: !!market && !!amount }
- );
-
- const transactionFee = estimateSnxTxGasCost(withdrawTxn);
-
- React.useEffect(() => {
- if (withdrawTxn.hash) {
- monitorTransaction({
- txHash: withdrawTxn.hash,
- onTxConfirmed: () => {
- handleRefetch('margin-change');
- onDismiss();
- },
- });
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [withdrawTxn.hash]);
-
- React.useEffect(() => {
- if (!amount) {
- setDisabled(true);
- return;
- }
-
- const amtWei = wei(amount);
-
- if (amtWei.gt(ZERO_WEI) && amtWei.lte(accessibleMargin)) {
- setDisabled(false);
- } else {
- setDisabled(true);
- }
- }, [amount, isDisabled, accessibleMargin, setDisabled]);
-
- const handleSetMax = React.useCallback(() => {
- setMax(true);
- setAmount(accessibleMargin.toString());
- }, [accessibleMargin]);
-
- return (
-
-
- {t('futures.market.trade.margin.modal.balance')}:
-
- {formatDollars(accessibleMargin)} sUSD
-
-
-
- {
- if (isMax) setMax(false);
- setAmount(v);
- }}
- right={
-
- {t('futures.market.trade.margin.modal.max')}
-
- }
- />
-
-
- withdrawTxn.mutate()}
- >
- {t('futures.market.trade.margin.modal.withdraw.button')}
-
-
-
- {t('futures.market.trade.margin.modal.gas-fee')}:
-
-
- {transactionFee ? formatDollars(transactionFee, { maxDecimals: 1 }) : NO_VALUE}
-
-
-
-
- {withdrawTxn.errorMessage && (
-
- )}
-
- );
-};
-
-export default WithdrawMarginModal;
diff --git a/sections/futures/TradeCrossMargin/CrossMarginInfoBox.tsx b/sections/futures/TradeCrossMargin/CrossMarginInfoBox.tsx
index 6305dd13f1..c933cdd40b 100644
--- a/sections/futures/TradeCrossMargin/CrossMarginInfoBox.tsx
+++ b/sections/futures/TradeCrossMargin/CrossMarginInfoBox.tsx
@@ -1,7 +1,7 @@
import Wei, { wei } from '@synthetixio/wei';
import React, { useCallback, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
-import styled, { useTheme } from 'styled-components';
+import styled from 'styled-components';
import WithdrawArrow from 'assets/svg/futures/withdraw-arrow.svg';
import InfoBox from 'components/InfoBox';
@@ -20,6 +20,7 @@ import {
futuresOrderPriceState,
crossMarginAccountOverviewState,
} from 'store/futures';
+import { PillButtonSpan } from 'styles/common';
import {
formatCurrency,
formatDollars,
@@ -37,7 +38,6 @@ type Props = {
function MarginInfoBox({ editingLeverage }: Props) {
const { selectedLeverage } = useFuturesContext();
- const { colors } = useTheme();
const position = useRecoilValue(positionState);
const marketInfo = useRecoilValue(marketInfoState);
@@ -54,12 +54,9 @@ function MarginInfoBox({ editingLeverage }: Props) {
const [openModal, setOpenModal] = useState<'leverage' | 'keeper-deposit' | null>(null);
const totalMargin = position?.remainingMargin.add(crossMarginFreeMargin) ?? zeroBN;
- const availableMargin = position?.accessibleMargin.add(crossMarginFreeMargin) ?? zeroBN;
- const currentSize = position?.position?.size ?? zeroBN;
+ const remainingMargin = position?.remainingMargin ?? zeroBN;
- const marginUsage = availableMargin.gt(zeroBN)
- ? totalMargin.sub(availableMargin).div(totalMargin)
- : zeroBN;
+ const marginUsage = totalMargin.gt(zeroBN) ? remainingMargin.div(totalMargin) : zeroBN;
const previewTotalMargin = useMemo(() => {
const remainingMargin = crossMarginFreeMargin.sub(marginDelta);
@@ -70,6 +67,8 @@ function MarginInfoBox({ editingLeverage }: Props) {
(previewTrade: FuturesPotentialTradeDetails | null, marketMaxLeverage: Wei | undefined) => {
let inaccessible;
+ if (!marketMaxLeverage) return zeroBN;
+
inaccessible = previewTrade?.notionalValue.div(marketMaxLeverage).abs() ?? zeroBN;
// If the user has a position open, we'll enforce a min initial margin requirement.
@@ -140,35 +139,35 @@ function MarginInfoBox({ editingLeverage }: Props) {
- {potentialTrade.status === 'fetching' ? (
-
- ) : (
- formatDollars(previewTradeData.freeAccountMargin)
- )}
-
- ),
- },
- 'Market Margin': !editingLeverage
+ 'Free Account Margin': editingLeverage
? {
- value: formatDollars(position?.remainingMargin || 0),
+ value: formatDollars(crossMarginFreeMargin),
valueNode: (
-
+
{potentialTrade.status === 'fetching' ? (
) : (
- formatDollars(previewTradeData.totalMargin)
+ formatDollars(previewTradeData.freeAccountMargin)
)}
),
}
: null,
+ 'Market Margin': {
+ value: formatDollars(position?.remainingMargin || 0),
+ valueNode: (
+
+ {potentialTrade.status === 'fetching' ? (
+
+ ) : (
+ formatDollars(previewTradeData.totalMargin)
+ )}
+
+ ),
+ },
'Margin Usage': {
value: formatPercent(marginUsage),
valueNode: (
@@ -187,16 +186,12 @@ function MarginInfoBox({ editingLeverage }: Props) {
valueNode: (
<>
{keeperEthBal.gt(0) && (
- setOpenModal('keeper-deposit')}
>
-
-
+
+
)}
>
),
@@ -205,9 +200,17 @@ function MarginInfoBox({ editingLeverage }: Props) {
Leverage: {
value: (
<>
- {formatNumber(selectedLeverage, { maxDecimals: 2 })}x
+ {formatNumber(
+ editingLeverage
+ ? position?.position?.leverage ?? selectedLeverage
+ : selectedLeverage,
+ {
+ maxDecimals: 2,
+ }
+ )}
+ x
{!editingLeverage && (
- setOpenModal('leverage')}>Edit
+ setOpenModal('leverage')}>Edit
)}
>
),
@@ -221,25 +224,13 @@ function MarginInfoBox({ editingLeverage }: Props) {
),
},
- 'Position Size': editingLeverage
- ? {
- value: formatCurrency(marketInfo?.asset || '', currentSize),
- valueNode: (
-
- {potentialTrade.status === 'fetching' ? (
-
- ) : (
- formatCurrency(marketInfo?.asset || '', previewTradeData.size)
- )}
-
- ),
- }
- : null,
}}
disabled={marketInfo?.isSuspended}
/>
- {openModal === 'leverage' && setOpenModal(null)} />}
+ {openModal === 'leverage' && (
+ setOpenModal(null)} />
+ )}
{openModal === 'keeper-deposit' && (
setOpenModal(null)} />
)}
@@ -255,28 +246,4 @@ const StyledInfoBox = styled(InfoBox)`
}
`;
-const Button = styled.span`
- transition: all 0.1s ease-in-out;
- &:hover {
- opacity: 0.7;
- }
-`;
-
-const ActionButton = styled(Button)<{ padding?: string }>`
- margin-left: 8px;
- cursor: pointer;
- font-size: 10px;
- font-family: ${(props) => props.theme.fonts.black};
- font-variant: all-small-caps;
- border: 1px solid ${(props) => props.theme.colors.selectedTheme.yellow};
- color: ${(props) => props.theme.colors.selectedTheme.button.pill.background};
- border-radius: 10px;
- padding: ${(props) => props.padding ?? '3px 5px'};
- &:hover {
- background-color: ${(props) => props.theme.colors.selectedTheme.button.pill.background};
- color: ${(props) => props.theme.colors.selectedTheme.button.pill.hover};
- opacity: unset;
- }
-`;
-
export default React.memo(MarginInfoBox);
diff --git a/sections/futures/TradeCrossMargin/EditLeverageModal.tsx b/sections/futures/TradeCrossMargin/EditLeverageModal.tsx
index 2e47e812a5..120e959f52 100644
--- a/sections/futures/TradeCrossMargin/EditLeverageModal.tsx
+++ b/sections/futures/TradeCrossMargin/EditLeverageModal.tsx
@@ -39,9 +39,10 @@ import MarginInfoBox from './CrossMarginInfoBox';
type DepositMarginModalProps = {
onDismiss(): void;
+ editMode: 'existing_position' | 'next_trade';
};
-export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps) {
+export default function EditLeverageModal({ onDismiss, editMode }: DepositMarginModalProps) {
const { t } = useTranslation();
const { monitorTransaction } = TransactionNotifier.useContainer();
const { handleRefetch, refetchUntilUpdate } = useRefetchContext();
@@ -50,6 +51,7 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
onLeverageChange,
resetTradeState,
submitCrossMarginOrder,
+ onChangeOpenPosLeverage,
} = useFuturesContext();
const market = useRecoilValue(marketInfoState);
@@ -62,14 +64,18 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
const [preferredLeverage, setPreferredLeverage] = usePersistedRecoilState(preferredLeverageState);
- const [leverage, setLeverage] = useState(Number(Number(selectedLeverage).toFixed(2)));
+ const [leverage, setLeverage] = useState(
+ editMode === 'existing_position' && position?.position
+ ? Number(position.position.leverage.toNumber().toFixed(2))
+ : Number(Number(selectedLeverage).toFixed(2))
+ );
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);
const maxLeverage = Number((market?.maxLeverage || wei(DEFAULT_LEVERAGE)).toString(2));
useEffect(() => {
- if (orderType !== 'market') {
+ if (editMode === 'existing_position' && orderType !== 'market') {
setOrderType('market');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -97,7 +103,9 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
const previewPositionChange = useCallback(
debounce((leverage: number) => {
if (leverage >= 1) {
- onLeverageChange(leverage);
+ editMode === 'existing_position'
+ ? onChangeOpenPosLeverage(leverage)
+ : onLeverageChange(leverage);
}
}, 200),
[onLeverageChange]
@@ -105,7 +113,7 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
const onConfirm = useCallback(async () => {
setError(null);
- if (position?.position) {
+ if (editMode === 'existing_position' && position?.position) {
try {
setSubmitting(true);
const tx = await submitCrossMarginOrder(true);
@@ -147,6 +155,7 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
leverage,
position?.position,
preferredLeverage,
+ editMode,
setSubmitting,
resetTradeState,
t,
@@ -175,7 +184,10 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- const errorMessage = error || previewError;
+ const errorMessage = useMemo(
+ () => error || previewError || (previewData?.showStatus && previewData?.statusMessage),
+ [error, previewError, previewData?.showStatus, previewData?.statusMessage]
+ );
return (
-
-
-
-
+ {editMode === 'next_trade' && (
+
+
+
+
+ )}
- {position?.position && (
+ {position?.position && editMode === 'existing_position' && (
<>
@@ -230,7 +244,11 @@ export default function EditLeverageModal({ onDismiss }: DepositMarginModalProps
)}
{
const invalidLabel = orderPriceInvalidLabel(price, leverageSide, marketAssetRate, orderType);
+ setOrderPrice(price);
if (!invalidLabel || !price) {
onTradeOrderPriceChange(price);
}
- setOrderPrice(price);
},
[onTradeOrderPriceChange, setOrderPrice, leverageSide, marketAssetRate, orderType]
);
- const headerButtons = walletAddress
- ? [
- {
- i18nTitle: 'futures.market.trade.button.deposit',
- icon: ,
- onClick: () => setOpenTransferModal('deposit'),
- },
- {
- i18nTitle: 'futures.market.trade.button.withdraw',
- icon: ,
- onClick: () => setOpenTransferModal('withdraw'),
- },
- ]
- : [];
-
if (!showOnboard && (status === 'refetching' || status === 'initial-fetch')) return ;
return (
@@ -93,9 +72,11 @@ export default function TradeCrossMargin({ isMobile }: Props) {
setShowOnboard(true)} />
) : (
<>
- {!isMobile && }
-
-
+ setOpenTransferModal('deposit')}
+ />
{
const type = CROSS_MARGIN_ORDER_TYPES[index];
setOrderType(type as FuturesOrderType);
- const decimals = suggestedDecimals(marketAssetRate);
- const offset = 1 / 10 ** (decimals ?? 1);
- const price =
- (type === 'limit' && leverageSide === 'long') ||
- (type === 'stop market' && leverageSide === 'short')
- ? floorNumber(marketAssetRate, decimals) - offset
- : (type === 'stop market' && leverageSide === 'long') ||
- (type === 'limit' && leverageSide === 'short')
- ? ceilNumber(marketAssetRate, decimals) + offset
- : '';
- onChangeOrderPrice(String(price));
+ setOrderPrice('');
}}
/>
diff --git a/sections/futures/UserInfo/UserInfo.tsx b/sections/futures/UserInfo/UserInfo.tsx
index 56dcc34129..318c9df912 100644
--- a/sections/futures/UserInfo/UserInfo.tsx
+++ b/sections/futures/UserInfo/UserInfo.tsx
@@ -4,10 +4,10 @@ import { useRecoilValue } from 'recoil';
import styled from 'styled-components';
import CalculatorIcon from 'assets/svg/futures/calculator-icon.svg';
+import TransfersIcon from 'assets/svg/futures/deposit-withdraw-arrows.svg';
import OpenPositionsIcon from 'assets/svg/futures/icon-open-positions.svg';
import OrderHistoryIcon from 'assets/svg/futures/icon-order-history.svg';
import PositionIcon from 'assets/svg/futures/icon-position.svg';
-import TransfersIcon from 'assets/svg/futures/icon-transfers.svg';
import UploadIcon from 'assets/svg/futures/upload-icon.svg';
import TabButton from 'components/Button/TabButton';
import { TabPanel } from 'components/Tab';
@@ -139,7 +139,7 @@ const UserInfo: React.FC = () => {
badge: undefined,
disabled: false, // leave this until we determine a disbaled state
active: activeTab === FuturesTab.TRANSFERS,
- icon: ,
+ icon: ,
onClick: () =>
router.push(ROUTES.Markets.Transfers(marketAsset, accountType), undefined, {
scroll: false,
diff --git a/styles/common.tsx b/styles/common.tsx
index 6aa6d97d98..43b7e6627b 100644
--- a/styles/common.tsx
+++ b/styles/common.tsx
@@ -355,3 +355,63 @@ export const BorderedPanel = styled.div`
border-radius: 10px;
color: ${(props) => props.theme.colors.selectedTheme.text.value};
`;
+
+const PillButtonCss = css<{ padding?: string }>`
+ transition: all 0.1s ease-in-out;
+ margin-left: 8px;
+ cursor: pointer;
+ font-size: 10px;
+ line-height: 12px;
+ font-family: ${(props) => props.theme.fonts.black};
+ font-variant: all-small-caps;
+ border: 1px solid ${(props) => props.theme.colors.selectedTheme.yellow};
+ color: ${(props) => props.theme.colors.selectedTheme.button.pill.background};
+ border-radius: 10px;
+ padding: ${(props) => props.padding ?? '3px 5px'};
+ svg {
+ path {
+ ${(props) =>
+ css`
+ fill: ${props.theme.colors.selectedTheme.yellow};
+ `}
+ }
+ }
+ &:hover {
+ background-color: ${(props) => props.theme.colors.selectedTheme.button.pill.background};
+ color: ${(props) => props.theme.colors.selectedTheme.button.pill.hover};
+ opacity: 0.7;
+ svg {
+ path {
+ ${(props) =>
+ css`
+ fill: ${props.theme.colors.selectedTheme.button.pill.hover};
+ `}
+ }
+ }
+ }
+`;
+
+export const PillButtonSpan = styled.span<{ padding?: string }>`
+ ${PillButtonCss}
+`;
+
+export const PillButtonDiv = styled.div<{ padding?: string }>`
+ ${PillButtonCss}
+`;
+
+export const YellowIconButton = styled.div`
+ transition: all 0.1s ease-in-out;
+ cursor: pointer;
+ color: ${(props) => props.theme.colors.selectedTheme.yellow};
+ svg {
+ path {
+ ${(props) =>
+ css`
+ fill: ${props.theme.colors.selectedTheme.yellow};
+ `}
+ }
+ }
+ &:hover {
+ opacity: 0.7;
+ }
+`;
diff --git a/styles/theme/colors/dark.ts b/styles/theme/colors/dark.ts
index 391c657288..63ac71287a 100644
--- a/styles/theme/colors/dark.ts
+++ b/styles/theme/colors/dark.ts
@@ -108,6 +108,7 @@ const darkTheme = {
icon: {
fill: '#787878',
hover: '#ECE8E3',
+ hoverReverse: common.dark.black,
},
openInterestBar: {
border: '1px solid #2b2a2a',
diff --git a/styles/theme/colors/light.ts b/styles/theme/colors/light.ts
index ea22400376..82a475184a 100644
--- a/styles/theme/colors/light.ts
+++ b/styles/theme/colors/light.ts
@@ -105,6 +105,7 @@ const lightTheme = {
icon: {
fill: '#515151',
hover: '#171002',
+ hoverReverse: common.dark.white,
},
openInterestBar: {
border: '1px solid #F2F2F2',
diff --git a/translations/en.json b/translations/en.json
index 26d872054e..359ab6a274 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -750,7 +750,7 @@
},
"edit-leverage": {
"failed": "Transaction failed",
- "insufficient-margin": "Leverage cannot be decreased, please deposit more margin or reduce open position sizes"
+ "insufficient-margin": "Leverage cannot be modified, please deposit more margin or reduce open position sizes"
}
},
"history": {
diff --git a/utils/futures.ts b/utils/futures.ts
index 6062ba00b5..7113666637 100644
--- a/utils/futures.ts
+++ b/utils/futures.ts
@@ -299,15 +299,58 @@ export const orderPriceInvalidLabel = (
return null;
};
+const getPositionChangeState = (existingSize: Wei, newSize: Wei) => {
+ if (existingSize.eq(newSize)) return 'edit_leverage';
+ if (existingSize.eq(0)) return 'increase_size';
+ if ((existingSize.gt(0) && newSize.lt(0)) || (existingSize.lt(0) && newSize.gt(0)))
+ return 'flip_side';
+ if (
+ (existingSize.gt(0) && newSize.gt(existingSize)) ||
+ (existingSize.lt(0) && newSize.lt(existingSize))
+ )
+ return 'increase_size';
+ return 'reduce_size';
+};
+
export const calculateMarginDelta = (
nextTrade: FuturesTradeInputs,
fees: TradeFees,
position: FuturesPosition | null
) => {
if (nextTrade.nativeSizeDelta.add(position?.position?.size || 0).eq(zeroBN)) return zeroBN;
- let currentSize = position?.position?.notionalValue || zeroBN;
- currentSize = position?.position?.side === 'long' ? currentSize : currentSize.neg();
- const newNotionalValue = currentSize.add(nextTrade.susdSizeDelta).abs();
- const fullMargin = newNotionalValue.abs().div(nextTrade.leverage);
- return fullMargin.sub(position?.remainingMargin || '0').add(fees.total);
+
+ const existingSize = position?.position
+ ? position?.position?.side === 'long'
+ ? position?.position?.size
+ : position?.position?.size.neg()
+ : zeroBN;
+
+ const newSize = existingSize.add(nextTrade.nativeSizeDelta);
+ const newSizeAbs = newSize.abs();
+ const posChangeState = getPositionChangeState(existingSize, newSize);
+
+ switch (posChangeState) {
+ case 'edit_leverage':
+ const nextMargin = position?.position?.notionalValue.div(nextTrade.leverage) ?? zeroBN;
+ const delta = nextMargin.sub(position?.remainingMargin);
+ return delta.add(fees.total);
+ case 'reduce_size':
+ // When a position is reducing we keep the leverage the same as the existing position
+ let marginDiff = nextTrade.susdSizeDelta.div(position?.position?.leverage ?? zeroBN);
+ return nextTrade.susdSizeDelta.gt(0)
+ ? marginDiff.neg().add(fees.total)
+ : marginDiff.add(fees.total);
+ case 'increase_size':
+ // When a position is increasing we calculate margin for selected leverage
+ return nextTrade.susdSizeDelta.abs().div(nextTrade.leverage).add(fees.total);
+ case 'flip_side':
+ // When flipping sides we calculate the margin required for selected leverage
+ const newNotionalSize = newSizeAbs.mul(nextTrade.orderPrice);
+ const newMargin = newNotionalSize.div(nextTrade.leverage);
+ const remainingMargin =
+ position?.position?.size.mul(nextTrade.orderPrice).div(position?.position?.leverage) ??
+ zeroBN;
+ const marginDelta = newMargin.sub(remainingMargin ?? zeroBN);
+ return marginDelta.add(fees.total);
+ }
};