From 250f688673ef05d41291cfa0fd5afb618c8c3872 Mon Sep 17 00:00:00 2001 From: pietro-maximoff Date: Mon, 2 May 2022 08:56:16 +0300 Subject: [PATCH 01/39] [Menu] - remove unnecessary components --- src/app/components/Form/AssetSelect/index.tsx | 39 ----- .../components/Form/AssetSelect/renderers.tsx | 45 ------ src/app/components/Form/Select/index.tsx | 119 ---------------- src/app/components/Form/Select/renderers.tsx | 134 ------------------ src/app/components/Form/Select/types.ts | 7 - .../components/Form/Select1/arrow-down.svg | 3 - .../components/Form/Select1/index.module.scss | 21 --- src/app/components/Form/Select1/index.tsx | 96 ------------- src/app/components/Form/Select1/renderers.tsx | 84 ----------- src/app/components/Form/Select1/types.ts | 7 - src/app/components/PageHeader/index.tsx | 50 ------- 11 files changed, 605 deletions(-) delete mode 100644 src/app/components/Form/AssetSelect/index.tsx delete mode 100644 src/app/components/Form/AssetSelect/renderers.tsx delete mode 100644 src/app/components/Form/Select/index.tsx delete mode 100644 src/app/components/Form/Select/renderers.tsx delete mode 100644 src/app/components/Form/Select/types.ts delete mode 100644 src/app/components/Form/Select1/arrow-down.svg delete mode 100644 src/app/components/Form/Select1/index.module.scss delete mode 100644 src/app/components/Form/Select1/index.tsx delete mode 100644 src/app/components/Form/Select1/renderers.tsx delete mode 100644 src/app/components/Form/Select1/types.ts delete mode 100644 src/app/components/PageHeader/index.tsx diff --git a/src/app/components/Form/AssetSelect/index.tsx b/src/app/components/Form/AssetSelect/index.tsx deleted file mode 100644 index f3e429ef8..000000000 --- a/src/app/components/Form/AssetSelect/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useMemo } from 'react'; -import { Nullable } from 'types'; -import { Asset } from 'types/asset'; -import { Select } from 'app/components/Form/Select'; -import { Option } from 'app/components/Form/Select/types'; -import { AssetsDictionary } from 'utils/dictionaries/assets-dictionary'; -import { - renderItem, - valueRenderer, -} from 'app/components/Form/AssetSelect/renderers'; - -interface IAssetSelectProps { - value: Nullable; - onChange: (value: Asset, item: Option) => void; - options: Asset[]; -} - -export const AssetSelect: React.FC = ({ - value, - onChange, - options, -}) => { - const selectOptions = useMemo(() => { - return options.map(item => { - const asset = AssetsDictionary.get(item); - return { key: item, label: asset.symbol, data: asset.logoSvg }; - }); - }, [options]); - - return ( - - - - ) : ( - <> - - `${toNumberFormat(steps[value], 1, 0)}x`} - type={SliderType.gradient} - /> - - + - + > +
+ {manual || sliderValue < 0 ? ( + <> + + + + + ) : ( + <> + + + `${toNumberFormat(steps[value], 1, 0)}x` + } + type={SliderType.gradient} + /> + {!disabled && ( + + )} + + )} +
+ + ); }; diff --git a/src/app/pages/PerpetualPage/components/NewPositionCard/components/ConnectFormStep.tsx b/src/app/pages/PerpetualPage/components/NewPositionCard/components/ConnectFormStep.tsx index 98a8005d9..522c88e40 100644 --- a/src/app/pages/PerpetualPage/components/NewPositionCard/components/ConnectFormStep.tsx +++ b/src/app/pages/PerpetualPage/components/NewPositionCard/components/ConnectFormStep.tsx @@ -1,9 +1,12 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useWalletContext } from '@sovryn/react-wallet'; import { translations } from '../../../../../../locales/i18n'; import { TransitionStep } from '../../../../../containers/TransitionSteps'; import { NewPositionCardStep } from '../types'; +import { PerpetualPageModals } from 'app/pages/PerpetualPage/types'; +import { actions } from '../../../slice'; +import { useDispatch } from 'react-redux'; export const ConnectFormStep: TransitionStep = ({ changeTo, @@ -11,19 +14,35 @@ export const ConnectFormStep: TransitionStep = ({ const { t } = useTranslation(); const { connect } = useWalletContext(); + const dispatch = useDispatch(); + + const onViewAccount = useCallback( + () => dispatch(actions.setModal(PerpetualPageModals.ACCOUNT_BALANCE)), + [dispatch], + ); + return (

{t(translations.perpetualPage.tradeForm.text.welcome1)}

-

- {t(translations.perpetualPage.tradeForm.text.welcome2)} -

    - ]} - /> +
  • + + Fund your wallet + , + ]} + /> +
  • +
  • + {t(translations.perpetualPage.tradeForm.text.welcome3)} +

= ({ changeTo, }) => { const { t } = useTranslation(); - const currentPairId = usePerpetual_getCurrentPairId(); + const { pairType } = useSelector(selectPerpetualPage); const { perpetuals } = useContext(PerpetualQueriesContext); - const { - ammState, - traderState, - perpetualParameters: perpParameters, - averagePrice, - } = perpetuals[currentPairId]; - const { trade, onChangeTrade } = useContext(NewPositionCardContext); + const { trade, setTrade } = useContext(NewPositionCardContext); - const pair = useMemo(() => PerpetualPairDictionary.get(trade.pairType), [ - trade.pairType, - ]); + const { + estimatedLiquidationPrice: liquidationPrice, + } = usePerpetual_calculateResultingPosition(trade); + + const pair = useMemo( + () => PerpetualPairDictionary.get(trade.pairType || pairType), + [trade.pairType, pairType], + ); + const { averagePrice } = perpetuals[pair.id]; const onCloseSlippage = useCallback( () => changeTo(NewPositionCardStep.trade, TransitionAnimation.slideRight), [changeTo], ); const onChangeSlippage = useCallback( - slippage => onChangeTrade({ ...trade, slippage }), - [trade, onChangeTrade], + slippage => setTrade({ ...trade, slippage }), + [trade, setTrade], ); const minEntryPrice = useMemo( @@ -65,33 +60,6 @@ export const SlippageFormStep: TransitionStep = ({ [averagePrice, trade.position, trade.slippage], ); - const minLiquidationPrice = useMemo(() => { - const amount = getSignedAmount(trade.position, trade.amount); - const margin = getRequiredMarginCollateral( - trade.leverage, - traderState.marginAccountPositionBC + amount, - perpParameters, - ammState, - traderState, - trade.slippage, - ); - return calculateApproxLiquidationPrice( - traderState, - ammState, - perpParameters, - amount, - margin, - ); - }, [ - trade.amount, - trade.position, - trade.leverage, - trade.slippage, - traderState, - ammState, - perpParameters, - ]); - return (

@@ -133,7 +101,7 @@ export const SlippageFormStep: TransitionStep = ({ minDecimals={2} maxDecimals={2} mode={AssetValueMode.auto} - value={minLiquidationPrice} + value={liquidationPrice} assetString={pair.quoteAsset} />

diff --git a/src/app/pages/PerpetualPage/components/NewPositionCard/components/TradeFormStep.tsx b/src/app/pages/PerpetualPage/components/NewPositionCard/components/TradeFormStep.tsx index fc04622dd..5abb2510b 100644 --- a/src/app/pages/PerpetualPage/components/NewPositionCard/components/TradeFormStep.tsx +++ b/src/app/pages/PerpetualPage/components/NewPositionCard/components/TradeFormStep.tsx @@ -1,65 +1,79 @@ -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import { NewPositionCardContext } from '..'; import { TransitionStep } from '../../../../../containers/TransitionSteps'; import { TradeForm } from '../../TradeForm'; import { NewPositionCardStep } from '../types'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { translations } from '../../../../../../locales/i18n'; +import { selectPerpetualPage } from 'app/pages/PerpetualPage/selectors'; +import { useSelector } from 'react-redux'; export const TradeFormStep: TransitionStep = ({ changeTo, }) => { const { t } = useTranslation(); - const { - hasEmptyBalance, - hasOpenPosition, - trade, - onSubmit, - onChangeTrade, - } = useContext(NewPositionCardContext); + const { isAddressWhitelisted } = useSelector(selectPerpetualPage); + + const { hasEmptyBalance, trade, onSubmit, setTrade } = useContext( + NewPositionCardContext, + ); const onOpenSlippage = useCallback( () => changeTo(NewPositionCardStep.slippage), [changeTo], ); + const isDisabled = useMemo(() => !isAddressWhitelisted || hasEmptyBalance, [ + hasEmptyBalance, + isAddressWhitelisted, + ]); + return (
- {(hasOpenPosition || hasEmptyBalance) && ( + {isDisabled && (
- {hasOpenPosition ? ( - <> -
- {t( - translations.perpetualPage.tradeForm.disabledState - .explanation1, - )} -
-
- {t( +
+ {isAddressWhitelisted ? ( + - - ) : ( -
- {t( + .whitelistExplanation + } + components={[ + + sovryn.app + , + + Twitter + , + ]} + /> + ) : ( + t( translations.perpetualPage.tradeForm.disabledState .emptyBalanceExplanation, - )} -
- )} + ) + )} +
)}
); diff --git a/src/app/pages/PerpetualPage/components/NewPositionCard/index.tsx b/src/app/pages/PerpetualPage/components/NewPositionCard/index.tsx index f543fe4c3..c0210272d 100644 --- a/src/app/pages/PerpetualPage/components/NewPositionCard/index.tsx +++ b/src/app/pages/PerpetualPage/components/NewPositionCard/index.tsx @@ -25,7 +25,7 @@ import { TradeFormStep } from './components/TradeFormStep'; import { ConnectFormStep } from './components/ConnectFormStep'; import { noop } from '../../../../constants'; import { PERPETUAL_SLIPPAGE_DEFAULT } from '../../types'; -import { PerpetualTxMethods } from '../TradeDialog/types'; +import { PerpetualTxMethod, PerpetualTx } from '../../types'; import { usePerpetual_accountBalance } from '../../hooks/usePerpetual_accountBalance'; import debounce from 'lodash.debounce'; @@ -40,12 +40,10 @@ export const NewPositionCardContext = React.createContext< position: TradingPosition.LONG, tradeType: PerpetualTradeType.MARKET, amount: '0', - limit: '0', leverage: 1, slippage: PERPETUAL_SLIPPAGE_DEFAULT, - entryPrice: 0, }, - onChangeTrade: noop, + setTrade: trade => trade, onSubmit: noop, }); @@ -91,32 +89,57 @@ export const NewPositionCard: React.FC = () => { position: TradingPosition.LONG, tradeType: PerpetualTradeType.MARKET, amount: '0', - limit: '0', leverage: 1, slippage: PERPETUAL_SLIPPAGE_DEFAULT, - entryPrice: 0, }); - const onSubmit = useCallback(() => { - dispatch( - actions.setModal(PerpetualPageModals.TRADE_REVIEW, { - origin: PerpetualPageModals.NONE, - trade, - transactions: [ - { - pair: pairType, - method: PerpetualTxMethods.trade, - amount: trade.amount, - tradingPosition: trade.position, - slippage: trade.slippage, - leverage: trade.leverage, - tx: null, - approvalTx: null, - }, - ], - }), - ); - }, [dispatch, trade, pairType]); + const onSubmit = useCallback( + (trade: PerpetualTrade) => { + if (!trade.averagePrice || trade.averagePrice === '0') { + return; + } + + const transactions: PerpetualTx[] = []; + if (trade.tradeType === PerpetualTradeType.MARKET) { + transactions.push({ + method: PerpetualTxMethod.trade, + pair: trade.pairType, + amount: trade.amount, + price: trade.averagePrice, + tradingPosition: trade.position, + slippage: trade.slippage, + leverage: trade.leverage, + keepPositionLeverage: trade.keepPositionLeverage, + isClosePosition: trade.isClosePosition, + tx: null, + approvalTx: null, + }); + } else { + transactions.push({ + method: PerpetualTxMethod.createLimitOrder, + pair: trade.pairType, + amount: trade.amount, + tradingPosition: trade.position, + leverage: trade.leverage, + limit: trade.limit || '0', + trigger: trade.trigger || '0', + expiry: trade.expiry || 30, + created: Date.now(), + tx: null, + approvalTx: null, + }); + } + + dispatch( + actions.setModal(PerpetualPageModals.TRADE_REVIEW, { + origin: PerpetualPageModals.NONE, + trade, + transactions, + }), + ); + }, + [dispatch], + ); const pair = useMemo(() => PerpetualPairDictionary.get(pairType), [pairType]); @@ -128,7 +151,12 @@ export const NewPositionCard: React.FC = () => { useEffect(() => { if (pairType !== trade.pairType) { - dispatch(actions.setPairType(pairType)); + setTrade(trade => ({ + ...trade, + pairType, + limit: '0', + trigger: '0', + })); } }, [dispatch, pairType, trade.pairType]); @@ -138,7 +166,7 @@ export const NewPositionCard: React.FC = () => { hasEmptyBalance, trade, onSubmit, - onChangeTrade: setTrade, + setTrade, }), [trade, onSubmit, hasOpenPosition, hasEmptyBalance], ); diff --git a/src/app/pages/PerpetualPage/components/NewPositionCard/types.tsx b/src/app/pages/PerpetualPage/components/NewPositionCard/types.tsx index ff7ea089b..08a00d034 100644 --- a/src/app/pages/PerpetualPage/components/NewPositionCard/types.tsx +++ b/src/app/pages/PerpetualPage/components/NewPositionCard/types.tsx @@ -1,4 +1,5 @@ import { PerpetualTrade } from '../../types'; +import { SetStateAction, Dispatch } from 'react'; export enum NewPositionCardStep { unconnected = 'unconnected', @@ -10,6 +11,6 @@ export type NewPositionCardContextType = { hasOpenPosition: boolean; hasEmptyBalance: boolean; trade: PerpetualTrade; - onChangeTrade: (trade: PerpetualTrade) => void; - onSubmit: () => void; + setTrade: Dispatch>; + onSubmit: (trade: PerpetualTrade) => void; }; diff --git a/src/app/pages/PerpetualPage/components/OpenOrdersTable/components/OpenOrderRow.tsx b/src/app/pages/PerpetualPage/components/OpenOrdersTable/components/OpenOrderRow.tsx new file mode 100644 index 000000000..68a0d5a59 --- /dev/null +++ b/src/app/pages/PerpetualPage/components/OpenOrdersTable/components/OpenOrderRow.tsx @@ -0,0 +1,165 @@ +import React, { useCallback, useMemo } from 'react'; +import { PerpetualPairDictionary } from '../../../../../../utils/dictionaries/perpetual-pair-dictionary'; +import classNames from 'classnames'; +import { AssetValue } from '../../../../../components/AssetValue'; +import { useTranslation } from 'react-i18next'; +import { AssetValueMode } from '../../../../../components/AssetValue/types'; +import { getCollateralName } from 'app/pages/PerpetualPage/utils/renderUtils'; +import { OpenOrderEntry } from 'app/pages/PerpetualPage/hooks/usePerpetual_OpenOrders'; +import { + DisplayDate, + SeparatorType, +} from 'app/components/ActiveUserLoanContainer/components/DisplayDate'; +import { + PerpetualTradeType, + PERPETUAL_CHAIN_ID, + PerpetualPageModals, +} from 'app/pages/PerpetualPage/types'; +import { LinkToExplorer } from 'app/components/LinkToExplorer'; +import { prettyTx } from 'utils/helpers'; +import { translations } from 'locales/i18n'; +import { TableRowAction } from '../../TableRowAction'; +import { actions } from '../../../slice'; +import { useDispatch } from 'react-redux'; +import { PerpetualTxMethod, PerpetualTxCancelLimitOrder } from '../../../types'; +import { toWei } from '../../../../../../utils/blockchain/math-helpers'; +import { TradingPosition } from '../../../../../../types/trading-position'; + +type OpenOrderRowProps = { + item: OpenOrderEntry; +}; + +export const OpenOrderRow: React.FC = ({ item }) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const pair = useMemo(() => PerpetualPairDictionary.get(item.pairType), [ + item.pairType, + ]); + + const collateralName = useMemo( + () => getCollateralName(pair.collateralAsset), + [pair.collateralAsset], + ); + + const onCancelOrder = useCallback(() => { + const transactions: PerpetualTxCancelLimitOrder[] = [ + { + method: PerpetualTxMethod.cancelLimitOrder, + pair: item.pairType, + digest: item.id, + tx: null, + }, + ]; + + dispatch( + actions.setModal(PerpetualPageModals.TRADE_REVIEW, { + origin: PerpetualPageModals.CANCEL_ORDER, + trade: { + id: item.id, + pairType: item.pairType, + tradeType: item.orderType, + position: + item.orderSize > 0 ? TradingPosition.LONG : TradingPosition.SHORT, + amount: toWei(Math.abs(item.orderSize)), + entryPrice: toWei(item.limitPrice), + limit: toWei(item.limitPrice), + collateral: pair.collateralAsset, + leverage: -1, + slippage: 0, + }, + transactions: transactions, + }), + ); + }, [pair, item, dispatch]); + + const orderTypeTranslation = useMemo(() => { + const tradeDirection = t( + translations.perpetualPage.openOrdersTable.orderTypes[ + item?.orderSize > 0 ? 'buy' : 'sell' + ], + ); + + if (item.orderType === PerpetualTradeType.STOP) { + return `${t( + translations.perpetualPage.openOrdersTable.orderTypes.stop, + )} ${tradeDirection}`; + } + + return `${t( + translations.perpetualPage.openOrdersTable.orderTypes.limit, + )} ${tradeDirection}`; + }, [item, t]); + + return ( + + + + + {pair.name} + 0, + 'tw-text-trade-short': item.orderSize < 0, + })} + > + {orderTypeTranslation} + + {collateralName} + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/app/pages/PerpetualPage/components/OpenOrdersTable/index.tsx b/src/app/pages/PerpetualPage/components/OpenOrdersTable/index.tsx new file mode 100644 index 000000000..86e974043 --- /dev/null +++ b/src/app/pages/PerpetualPage/components/OpenOrdersTable/index.tsx @@ -0,0 +1,138 @@ +import React, { useCallback, useState } from 'react'; +import { SkeletonRow } from 'app/components/Skeleton/SkeletonRow'; +import { OpenOrderRow } from './components/OpenOrderRow'; +import { useTranslation } from 'react-i18next'; +import { translations } from '../../../../../locales/i18n'; +import { usePerpetual_OpenOrders } from '../../hooks/usePerpetual_OpenOrders'; +import { Tooltip } from '@blueprintjs/core'; +import { Pagination } from 'app/components/Pagination'; + +type OpenOrdersTableProps = { + perPage: number; +}; + +export const OpenOrdersTable: React.FC = ({ + perPage, +}) => { + const { t } = useTranslation(); + const [page, setPage] = useState(1); + + const { data: items, loading, totalCount } = usePerpetual_OpenOrders( + page, + perPage, + ); + + const onPageChanged = useCallback(data => { + setPage(data.currentPage); + }, []); + + const isEmpty = !loading && !items?.length; + const showLoading = loading && !items?.length; + + return ( + <> + + + + + + + + + + + + + + + + {isEmpty && ( + + + + )} + {showLoading && ( + + + + )} + {items?.map(item => ( + + ))} + +
+ {t(translations.perpetualPage.openOrdersTable.dateTime)} + + {t(translations.perpetualPage.openOrdersTable.symbol)} + + + {t(translations.perpetualPage.openOrdersTable.orderType)} + + + + {t(translations.perpetualPage.openOrdersTable.collateral)} + + + + {t(translations.perpetualPage.openOrdersTable.orderSize)} + + + + {t(translations.perpetualPage.openOrdersTable.limitPrice)} + + + + {t(translations.perpetualPage.openOrdersTable.triggerPrice)} + + + {t(translations.perpetualPage.openOrdersTable.timeInForce)} + + {t(translations.perpetualPage.openOrdersTable.transactionId)} +
{t(translations.openPositionTable.noData)}
+ +
+ + {items && totalCount > 0 && ( + + )} + + ); +}; diff --git a/src/app/pages/PerpetualPage/components/OpenPositionsTable/components/OpenPositionRow.tsx b/src/app/pages/PerpetualPage/components/OpenPositionsTable/components/OpenPositionRow.tsx index cb4e7cb8f..0c20b458d 100644 --- a/src/app/pages/PerpetualPage/components/OpenPositionsTable/components/OpenPositionRow.tsx +++ b/src/app/pages/PerpetualPage/components/OpenPositionsTable/components/OpenPositionRow.tsx @@ -19,8 +19,8 @@ import { AssetValueMode } from '../../../../../components/AssetValue/types'; import { toWei } from '../../../../../../utils/blockchain/math-helpers'; import { ErrorBadge } from 'app/components/Form/ErrorBadge'; import { usePerpetual_isTradingInMaintenance } from '../../../hooks/usePerpetual_isTradingInMaintenance'; -import { RowAction } from './RowAction'; import { getCollateralName } from 'app/pages/PerpetualPage/utils/renderUtils'; +import { TableRowAction } from '../../TableRowAction'; type OpenPositionRowProps = { item: OpenPositionEntry; @@ -52,18 +52,14 @@ export const OpenPositionRow: React.FC = ({ item }) => { amount: item.amount ? toWei(Math.abs(item.amount)) : '0', collateral: pair.collateralAsset, leverage: item.leverage || 0, - entryPrice: item.entryPrice || 0, + entryPrice: item.entryPrice ? toWei(item.entryPrice) : '0', + averagePrice: item.averagePrice ? toWei(item.averagePrice) : '0', }; dispatch(actions.setModal(modal, trade)); }, [item, pair, dispatch], ); - const onEditSize = useCallback( - () => onOpenTradeModal(PerpetualPageModals.EDIT_POSITION_SIZE), - [onOpenTradeModal], - ); - const onEditLeverage = useCallback( () => onOpenTradeModal(PerpetualPageModals.EDIT_LEVERAGE), [onOpenTradeModal], @@ -203,17 +199,7 @@ export const OpenPositionRow: React.FC = ({ item }) => { /> ) : ( <> - - = ({ item }) => { )} onClick={onEditLeverage} /> - = ({ item }) => { onClick={onEditMargin} /> - = ({ tradeType, datetime, pair, - execSize, execPrice, orderId, orderSize, orderState, + triggerPrice, limitPrice, }, }) => { @@ -62,6 +67,24 @@ export const OrderHistoryRow: React.FC = ({ )}`; }, [t, tradeType, position]); + const shouldHideExecPrice = useMemo( + () => + ([PerpetualTradeType.LIMIT, PerpetualTradeType.STOP].includes( + tradeType, + ) && + [OrderState.Opened, OrderState.Cancelled].includes(orderState)) || + !execPrice, + [execPrice, orderState, tradeType], + ); + + const shouldHideTriggerPrice = useMemo( + () => + tradeType !== PerpetualTradeType.STOP || + !triggerPrice || + triggerPrice === '0', + [tradeType, triggerPrice], + ); + return ( @@ -85,6 +108,17 @@ export const OrderHistoryRow: React.FC = ({ mode={AssetValueMode.auto} /> + + {shouldHideTriggerPrice ? ( + + ) : ( + + )} + {limitPrice && ( = ({ )} - - - - {execPrice && ( + {shouldHideExecPrice ? ( + + ) : ( diff --git a/src/app/pages/PerpetualPage/components/OrderHistoryTable/index.tsx b/src/app/pages/PerpetualPage/components/OrderHistoryTable/index.tsx index f270a6126..d69ed2f05 100644 --- a/src/app/pages/PerpetualPage/components/OrderHistoryTable/index.tsx +++ b/src/app/pages/PerpetualPage/components/OrderHistoryTable/index.tsx @@ -52,6 +52,9 @@ export const OrderHistoryTable: React.FC = ({ {t(translations.perpetualPage.orderHistoryTable.orderSize)} + + {t(translations.perpetualPage.orderHistoryTable.triggerPrice)} + = ({ {t(translations.perpetualPage.orderHistoryTable.limitPrice)} - - {t(translations.perpetualPage.orderHistoryTable.execSize)} - {t(translations.perpetualPage.orderHistoryTable.execPrice)} diff --git a/src/app/pages/PerpetualPage/components/PairSelector/PairSelectorButton.tsx b/src/app/pages/PerpetualPage/components/PairSelector/PairSelectorButton.tsx index ee77c59ae..ad23d3e1c 100644 --- a/src/app/pages/PerpetualPage/components/PairSelector/PairSelectorButton.tsx +++ b/src/app/pages/PerpetualPage/components/PairSelector/PairSelectorButton.tsx @@ -67,7 +67,7 @@ export const PairSelectorButton: React.FC = ({ > {pair.name}
-
+
{t(translations.perpetualPage.pairSelector.markPrice)}
= ({ {trades && trades.map(item => ( = ({ +export const TableRowAction: React.FC = ({ tooltip, label, onClick, }) => (
@@ -174,6 +187,8 @@ const getTranslations = ( current: number, count: number, ) => { + const wallet = detectWeb3Wallet(); + if (rejected) { return { title: t( @@ -204,6 +219,12 @@ const getTranslations = ( count, }, ), - text:

{t(translations.perpetualPage.processTrade.texts.confirm)}

, + text: ( +

+ {t(translations.perpetualPage.processTrade.texts.confirm, { + wallet: getWalletName(wallet), + })} +

+ ), }; }; diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/components/ResultPosition.tsx b/src/app/pages/PerpetualPage/components/TradeDialog/components/ResultPosition.tsx index e8771176d..f646a30e5 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/components/ResultPosition.tsx +++ b/src/app/pages/PerpetualPage/components/TradeDialog/components/ResultPosition.tsx @@ -7,14 +7,19 @@ import { useTranslation } from 'react-i18next'; import { translations } from '../../../../../../locales/i18n'; import { toNumberFormat } from '../../../../../../utils/display-text/format'; import { PerpetualPair } from '../../../../../../utils/models/perpetual-pair'; -import { PerpetualPageModals } from '../../../types'; -import { TradeAnalysis } from '../types'; +import { + PerpetualPageModals, + PerpetualTradeAnalysis, + PerpetualTrade, + PerpetualTradeType, +} from '../../../types'; import { getCollateralName } from 'app/pages/PerpetualPage/utils/renderUtils'; type ResultPositionProps = { origin?: PerpetualPageModals; pair: PerpetualPair; - analysis: TradeAnalysis; + trade?: PerpetualTrade; + analysis: PerpetualTradeAnalysis; lotSize: number; lotPrecision: number; }; @@ -22,6 +27,7 @@ type ResultPositionProps = { export const ResultPosition: React.FC = ({ origin, pair, + trade, analysis, lotSize, lotPrecision, @@ -43,8 +49,15 @@ export const ResultPosition: React.FC = ({ ); if ( - origin === PerpetualPageModals.CLOSE_POSITION && - Math.abs(marginTarget) < lotSize + origin === PerpetualPageModals.CANCEL_ORDER || + trade?.tradeType !== PerpetualTradeType.MARKET + ) { + return null; + } + + if ( + origin === PerpetualPageModals.CLOSE_POSITION || + Math.abs(amountTarget) < lotSize ) { return (
diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/components/ReviewStep.tsx b/src/app/pages/PerpetualPage/components/TradeDialog/components/ReviewStep.tsx index 82abccad2..0a104de60 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/components/ReviewStep.tsx +++ b/src/app/pages/PerpetualPage/components/TradeDialog/components/ReviewStep.tsx @@ -18,18 +18,19 @@ import { usePerpetual_isTradingInMaintenance } from 'app/pages/PerpetualPage/hoo import { useMaintenance } from '../../../../../hooks/useMaintenance'; import { useSelector } from 'react-redux'; import { selectPerpetualPage } from '../../../selectors'; +import { ValidationHint } from '../../ValidationHint/ValidationHint'; const titleMap = { [PerpetualPageModals.NONE]: translations.perpetualPage.reviewTrade.titles.newOrder, - [PerpetualPageModals.EDIT_POSITION_SIZE]: - translations.perpetualPage.reviewTrade.titles.newOrder, [PerpetualPageModals.EDIT_LEVERAGE]: translations.perpetualPage.reviewTrade.titles.editLeverage, [PerpetualPageModals.EDIT_MARGIN]: translations.perpetualPage.reviewTrade.titles.editMargin, [PerpetualPageModals.CLOSE_POSITION]: translations.perpetualPage.reviewTrade.titles.close, + [PerpetualPageModals.CANCEL_ORDER]: + translations.perpetualPage.reviewTrade.titles.cancelOrder, }; export const ReviewStep: TransitionStep = ({ changeTo }) => { @@ -52,6 +53,12 @@ export const ReviewStep: TransitionStep = ({ changeTo }) => { ); const isTradingInMaintenance = usePerpetual_isTradingInMaintenance(); + // We don't want to display ValidationHint in case of Cancel limit orders + const shouldDisplayValidationHint = useMemo( + () => !origin || (origin && origin !== PerpetualPageModals.CANCEL_ORDER), + [origin], + ); + const onSubmit = useCallback(async () => { let nonce = await bridgeNetwork.nonce(Chain.BSC); @@ -62,12 +69,12 @@ export const ReviewStep: TransitionStep = ({ changeTo }) => { }); changeTo( - analysis.marginChange > 0 + analysis.requiredAllowance > 0 ? TradeDialogStep.approval - : TradeDialogStep.confirmation, + : TradeDialogStep.confirmationEven, TransitionAnimation.slideLeft, ); - }, [analysis.marginChange, setCurrentTransaction, changeTo]); + }, [analysis.requiredAllowance, setCurrentTransaction, changeTo]); return ( <> @@ -82,10 +89,17 @@ export const ReviewStep: TransitionStep = ({ changeTo }) => { + {shouldDisplayValidationHint && ( + + )}
{isTradingInMaintenance || (useMetaTransactions && isGsnInMaintenance) ? ( @@ -114,7 +128,7 @@ export const ReviewStep: TransitionStep = ({ changeTo }) => { diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/components/TradeSummary.tsx b/src/app/pages/PerpetualPage/components/TradeDialog/components/TradeSummary.tsx index a367843df..29ddd4a10 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/components/TradeSummary.tsx +++ b/src/app/pages/PerpetualPage/components/TradeDialog/components/TradeSummary.tsx @@ -11,6 +11,7 @@ import { PerpetualPageModals, PerpetualTrade, PerpetualTradeType, + PerpetualTradeAnalysis, } from '../../../types'; import { Transaction, @@ -20,9 +21,10 @@ import { import { LinkToExplorer } from '../../../../../components/LinkToExplorer'; import { RecentTradesContext } from '../../../contexts/RecentTradesContext'; import { RecentTradesDataEntry } from '../../RecentTradesTable/types'; -import { TradeAnalysis } from '../types'; import { getCollateralName } from 'app/pages/PerpetualPage/utils/renderUtils'; import { StatusComponent } from 'app/components/Dialogs/StatusComponent'; +import { PerpetualQueriesContext } from '../../../contexts/PerpetualQueriesContext'; +import { PerpetualPairDictionary } from '../../../../../../utils/dictionaries/perpetual-pair-dictionary'; const TxTypeLabels = { [TxType.APPROVE]: translations.perpetualPage.processTrade.labels.approvalTx, @@ -34,21 +36,39 @@ const TxTypeLabels = { translations.perpetualPage.processTrade.labels.marginTx, }; +const TradeTypeLabels: Record = { + [PerpetualTradeType.MARKET]: translations.perpetualPage.reviewTrade.market, + [PerpetualTradeType.LIMIT]: translations.perpetualPage.reviewTrade.limit, + [PerpetualTradeType.STOP]: translations.perpetualPage.reviewTrade.stop, + [PerpetualTradeType.LIQUIDATION]: undefined, +}; + type TradeSummaryProps = { origin?: PerpetualPageModals; trade?: PerpetualTrade; pair: PerpetualPair; - analysis: TradeAnalysis; + analysis: PerpetualTradeAnalysis; transactions?: Transaction[]; }; export const TradeSummary: React.FC = ({ origin = PerpetualPageModals.NONE, trade, - pair, + pair: currentPair, analysis, transactions, }) => { + const { perpetuals } = useContext(PerpetualQueriesContext); + const pair = useMemo( + () => + trade?.pairType + ? PerpetualPairDictionary.get(trade?.pairType) + : currentPair, + [trade?.pairType, currentPair], + ); + + const { lotPrecision } = perpetuals[pair.id]; + const { amountChange, marginChange, @@ -73,16 +93,15 @@ export const TradeSummary: React.FC = ({ showMarginText, showAmountText, showCloseText, + showOrderText, isBuy, } = useMemo(() => { switch (origin) { case PerpetualPageModals.CLOSE_POSITION: return { - title: `${ - trade?.tradeType === PerpetualTradeType.MARKET - ? t(translations.perpetualPage.reviewTrade.market) - : t(translations.perpetualPage.reviewTrade.limit) - } ${t(translations.perpetualPage.reviewTrade.close)}`, + title: `${t( + TradeTypeLabels[trade?.tradeType || PerpetualTradeType.MARKET], + )} ${t(translations.perpetualPage.reviewTrade.close)}`, showAmountText: true, showCloseText: true, isBuy: amountChange > 0, @@ -105,27 +124,36 @@ export const TradeSummary: React.FC = ({ showMarginText: true, isBuy: marginChange > 0, }; - case PerpetualPageModals.EDIT_POSITION_SIZE: + case PerpetualPageModals.CANCEL_ORDER: return { - title: `${ - trade?.tradeType === PerpetualTradeType.MARKET - ? t(translations.perpetualPage.reviewTrade.market) - : t(translations.perpetualPage.reviewTrade.limit) - } ${ + title: `${t(translations.perpetualPage.reviewTrade.cancel)} ${t( + TradeTypeLabels[trade?.tradeType || PerpetualTradeType.MARKET], + )} ${ amountChange > 0 ? t(translations.perpetualPage.reviewTrade.buy) : t(translations.perpetualPage.reviewTrade.sell) }`, - showAmountText: true, + showOrderText: true, + isBuy: amountChange > 0, + }; + case PerpetualPageModals.NONE: + return { + title: `${toNumberFormat(trade?.leverage || leverageTarget, 2)}x ${t( + TradeTypeLabels[trade?.tradeType || PerpetualTradeType.MARKET], + )} ${ + amountChange > 0 + ? t(translations.perpetualPage.reviewTrade.buy) + : t(translations.perpetualPage.reviewTrade.sell) + }`, + showAmountText: trade?.tradeType === PerpetualTradeType.MARKET, + showOrderText: trade?.tradeType !== PerpetualTradeType.MARKET, isBuy: amountChange > 0, }; default: return { - title: `${toNumberFormat(leverageTarget, 2)}x ${ - trade?.tradeType === PerpetualTradeType.MARKET - ? t(translations.perpetualPage.reviewTrade.market) - : t(translations.perpetualPage.reviewTrade.limit) - } ${ + title: `${toNumberFormat(leverageTarget, 2)}x ${t( + TradeTypeLabels[trade?.tradeType || PerpetualTradeType.MARKET], + )} ${ amountChange > 0 ? t(translations.perpetualPage.reviewTrade.buy) : t(translations.perpetualPage.reviewTrade.sell) @@ -134,7 +162,15 @@ export const TradeSummary: React.FC = ({ isBuy: amountChange > 0, }; } - }, [t, origin, marginChange, amountChange, leverageTarget, trade?.tradeType]); + }, [ + origin, + trade?.tradeType, + trade?.leverage, + t, + amountChange, + leverageTarget, + marginChange, + ]); const totalToReceive = -marginChange + partialUnrealizedPnL; @@ -159,8 +195,8 @@ export const TradeSummary: React.FC = ({ )} {showAmountText && (
- {toNumberFormat(Math.abs(amountChange), 3)} {pair.baseAsset} @{' '} - {toNumberFormat(entryPrice, 2)} {pair.quoteAsset} + {toNumberFormat(Math.abs(amountChange), lotPrecision)}{' '} + {pair.baseAsset} @ {toNumberFormat(entryPrice, 2)} {pair.quoteAsset}
)} {showCloseText && ( @@ -206,13 +242,69 @@ export const TradeSummary: React.FC = ({ maxDecimals={4} mode={AssetValueMode.auto} value={totalToReceive} - assetString={pair.baseAsset} + assetString={collateralName} showPositiveSign />
)} + {showOrderText && ( +
+
+ + {t(translations.perpetualPage.reviewTrade.labels.orderSize)} + + + + +
+
+ + {t(translations.perpetualPage.reviewTrade.labels.limitPrice)} + + + + +
+ {trade?.trigger && trade.trigger !== '0' && ( +
+ + {t( + translations.perpetualPage.reviewTrade.labels.triggerPrice, + )} + + + + +
+ )} +
+ )}
diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/components/TransactionStep.tsx b/src/app/pages/PerpetualPage/components/TradeDialog/components/TransactionStep.tsx index 05a50c3c2..e6e4cc069 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/components/TransactionStep.tsx +++ b/src/app/pages/PerpetualPage/components/TradeDialog/components/TransactionStep.tsx @@ -1,6 +1,6 @@ import React, { useContext, useMemo, useCallback } from 'react'; import { TransitionStep } from '../../../../../containers/TransitionSteps'; -import { TradeDialogStep, PerpetualTxMethods } from '../types'; +import { TradeDialogStep } from '../types'; import { TradeDialogContext } from '../index'; import styles from '../index.module.scss'; import { translations } from '../../../../../../locales/i18n'; @@ -13,7 +13,7 @@ import { import { useSelector, useDispatch } from 'react-redux'; import { selectTransactions } from '../../../../../../store/global/transactions-store/selectors'; import { actions } from '../../../slice'; -import { PerpetualPageModals } from '../../../types'; +import { PerpetualPageModals, PerpetualTxMethod } from '../../../types'; import { RecentTradesContext } from '../../../contexts/RecentTradesContext'; import { StatusComponent } from 'app/components/Dialogs/StatusComponent'; @@ -46,8 +46,8 @@ export const TransactionStep: TransitionStep = ({ () => transactions.reduce((acc, transaction) => { if ( - (transaction.method === PerpetualTxMethods.deposit || - transaction.method === PerpetualTxMethods.trade) && + (transaction.method === PerpetualTxMethod.deposit || + transaction.method === PerpetualTxMethod.trade) && transaction.approvalTx && transactionsMap[transaction.approvalTx] ) { diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/index.module.scss b/src/app/pages/PerpetualPage/components/TradeDialog/index.module.scss index d246eab3b..e03a3e44d 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/index.module.scss +++ b/src/app/pages/PerpetualPage/components/TradeDialog/index.module.scss @@ -21,6 +21,10 @@ tw-transition-colors tw-duration-300 hover:tw-bg-primary-75; min-height: 3.125rem; + + &[disabled] { + @apply tw-bg-primary-50; + } } .ghostButton { @@ -30,6 +34,10 @@ tw-transition-colors tw-duration-300 hover:tw-bg-primary-25; min-height: 3.125rem; + + &[disabled] { + @apply tw-text-primary-50 tw-border-primary-50; + } } .positionSizeBuy { @@ -37,5 +45,5 @@ } .positionSizeSell { - @apply tw-font-medium tw-text-trade-long; + @apply tw-font-medium tw-text-trade-short; } diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/index.tsx b/src/app/pages/PerpetualPage/components/TradeDialog/index.tsx index 9c7312fe7..0fea46100 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/index.tsx +++ b/src/app/pages/PerpetualPage/components/TradeDialog/index.tsx @@ -1,26 +1,21 @@ -import React, { - useCallback, - useMemo, - useState, - useEffect, - useContext, -} from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Dialog } from '../../../../containers/Dialog'; import { selectPerpetualPage } from '../../selectors'; import { actions } from '../../slice'; -import { PerpetualPageModals, isPerpetualTradeReview } from '../../types'; +import { + PerpetualPageModals, + isPerpetualTradeReview, + PerpetualTx, +} from '../../types'; import { PerpetualPairDictionary, PerpetualPairType, } from 'utils/dictionaries/perpetual-pair-dictionary'; -import { getSignedAmount } from '../../utils/contractUtils'; import { - TradeAnalysis, TradeDialogContextType, TradeDialogStep, TradeDialogCurrentTransaction, - PerpetualTx, } from './types'; import { noop } from '../../../../constants'; import { TransitionSteps } from '../../../../containers/TransitionSteps'; @@ -29,19 +24,7 @@ import { ReviewStep } from './components/ReviewStep'; import { ApprovalStep } from './components/ApprovalStep'; import { ConfirmationStep } from './components/ConfirmationStep'; import { TransactionStep } from './components/TransactionStep'; -import { PerpetualQueriesContext } from '../../contexts/PerpetualQueriesContext'; -import { numberFromWei } from '../../../../../utils/blockchain/math-helpers'; -import { perpUtils } from '@sovryn/perpetual-swap'; - -const { - calculateApproxLiquidationPrice, - getRequiredMarginCollateral, - getTradingFee, - getTraderPnLInCC, - calculateSlippagePriceFromMidPrice, - getPrice, - calculateLeverage, -} = perpUtils; +import { usePerpetual_analyseTrade } from '../../hooks/usePerpetual_analyseTrade'; const tradeDialogContextDefault: TradeDialogContextType = { pair: PerpetualPairDictionary.get(PerpetualPairType.BTCUSD), @@ -56,7 +39,9 @@ const tradeDialogContextDefault: TradeDialogContextType = { limitPrice: 0, liquidationPrice: 0, orderCost: 0, + requiredAllowance: 0, tradingFee: 0, + loading: true, }, transactions: [], setTransactions: noop, @@ -71,15 +56,16 @@ export const TradeDialogContext = React.createContext( const TradeDialogStepComponents = { [TradeDialogStep.review]: ReviewStep, [TradeDialogStep.approval]: ApprovalStep, - [TradeDialogStep.confirmation]: ConfirmationStep, + [TradeDialogStep.confirmationEven]: ConfirmationStep, + [TradeDialogStep.confirmationOdd]: ConfirmationStep, [TradeDialogStep.transaction]: TransactionStep, }; export const TradeDialog: React.FC = () => { const dispatch = useDispatch(); - const { modal, modalOptions } = useSelector(selectPerpetualPage); - const { perpetuals } = useContext(PerpetualQueriesContext); + const { modal, modalOptions, pairType } = useSelector(selectPerpetualPage); + const { origin, trade, transactions: requestedTransactions } = useMemo( () => isPerpetualTradeReview(modalOptions) @@ -97,19 +83,13 @@ export const TradeDialog: React.FC = () => { >(); const pair = useMemo( - () => - PerpetualPairDictionary.get(trade?.pairType || PerpetualPairType.BTCUSD), - [trade], + () => PerpetualPairDictionary.get(trade?.pairType || pairType), + [pairType, trade?.pairType], ); - const { - ammState, - traderState, - perpetualParameters: perpParameters, - } = perpetuals[pair.id]; - - const [analysis, setAnalysis] = useState( - tradeDialogContextDefault.analysis, + const analysis = usePerpetual_analyseTrade( + trade, + currentTransaction !== undefined, ); const onClose = useCallback( @@ -133,88 +113,11 @@ export const TradeDialog: React.FC = () => { ); useEffect(() => { + // reset current transaction state when modal is closed or called again setCurrentTransaction(undefined); setTransactions(requestedTransactions); }, [origin, requestedTransactions]); - useEffect(() => { - if (!trade) { - setAnalysis(tradeDialogContextDefault.analysis); - return; - } - - if (currentTransaction) { - return; - } - - const amountTarget = getSignedAmount(trade.position, trade.amount); - const amountChange = amountTarget - traderState.marginAccountPositionBC; - - const entryPrice = getPrice(amountChange, perpParameters, ammState); - const limitPrice = calculateSlippagePriceFromMidPrice( - perpParameters, - ammState, - trade.slippage, - Math.sign(amountChange), - ); - - const marginTarget = trade.margin - ? numberFromWei(trade.margin) - : Math.abs(amountTarget) / trade.leverage; - - const orderCost = getRequiredMarginCollateral( - trade.leverage, - amountTarget, - perpParameters, - ammState, - traderState, - trade.slippage, - true, - ); - - const marginChange = marginTarget - traderState.availableCashCC; - - const partialUnrealizedPnL = - getTraderPnLInCC(traderState, ammState, perpParameters, limitPrice) * - Math.abs(-marginChange / traderState.availableCashCC); - - const liquidationPrice = calculateApproxLiquidationPrice( - traderState, - ammState, - perpParameters, - amountChange, - marginChange, - ); - - const tradingFee = getTradingFee( - Math.abs(amountChange), - perpParameters, - ammState, - ); - - const leverageTarget = calculateLeverage( - amountTarget, - marginTarget, - traderState, - ammState, - perpParameters, - ); - - setAnalysis({ - amountChange, - amountTarget, - marginChange, - partialUnrealizedPnL, - marginTarget, - leverageTarget, - liquidationPrice, - entryPrice, - limitPrice, - orderCost, - tradingFee, - }); - }, [currentTransaction, trade, traderState, perpParameters, ammState]); - if (!trade) { return null; } diff --git a/src/app/pages/PerpetualPage/components/TradeDialog/types.tsx b/src/app/pages/PerpetualPage/components/TradeDialog/types.tsx index bcfb17305..90a33b0ba 100644 --- a/src/app/pages/PerpetualPage/components/TradeDialog/types.tsx +++ b/src/app/pages/PerpetualPage/components/TradeDialog/types.tsx @@ -1,89 +1,20 @@ -import { TradingPosition } from '../../../../../types/trading-position'; -import { PerpetualPageModals, PerpetualTrade } from '../../types'; +import { + PerpetualPageModals, + PerpetualTrade, + PerpetualTradeAnalysis, + PerpetualTx, +} from '../../types'; import { PerpetualPair } from '../../../../../utils/models/perpetual-pair'; -import { Nullable } from '../../../../../types'; import { Dispatch, SetStateAction } from 'react'; -import { PerpetualPairType } from '../../../../../utils/dictionaries/perpetual-pair-dictionary'; export enum TradeDialogStep { review = 'review', approval = 'approval', - confirmation = 'confirmation', + confirmationEven = 'confirmationEven', + confirmationOdd = 'confirmationOdd', transaction = 'transaction', } -export type TradeAnalysis = { - amountChange: number; - amountTarget: number; - marginChange: number; - marginTarget: number; - partialUnrealizedPnL: number; - leverageTarget: number; - entryPrice: number; - limitPrice: number; - liquidationPrice: number; - orderCost: number; - tradingFee: number; -}; - -export enum PerpetualTxMethods { - trade = 'trade', - deposit = 'deposit', - withdraw = 'withdraw', - withdrawAll = 'withdrawAll', -} - -interface PerpetualTxBase { - pair: PerpetualPairType; - method: PerpetualTxMethods; - tx: Nullable; - origin?: PerpetualPageModals; - index?: number; - count?: number; - target?: { - leverage: number; - }; -} - -export interface PerpetualTxTrade extends PerpetualTxBase { - method: PerpetualTxMethods.trade; - /** amount as wei string */ - amount: string; - leverage?: number; - slippage?: number; - tradingPosition?: TradingPosition; - isClosePosition?: boolean; - - approvalTx: Nullable; -} - -export interface PerpetualTxDepositMargin extends PerpetualTxBase { - method: PerpetualTxMethods.deposit; - /** amount as wei string */ - amount: string; - - approvalTx: Nullable; -} - -export interface PerpetualTxWithdrawMargin extends PerpetualTxBase { - method: PerpetualTxMethods.withdraw; - /** amount as wei string */ - amount: string; -} - -export interface PerpetualTxWithdrawAllMargin extends PerpetualTxBase { - method: PerpetualTxMethods.withdrawAll; -} - -export type PerpetualTx = - | PerpetualTxTrade - | PerpetualTxDepositMargin - | PerpetualTxWithdrawMargin - | PerpetualTxWithdrawAllMargin; - -export const isPerpetualTx = (x: any): x is PerpetualTx => - x && typeof x === 'object' && PerpetualTxMethods[x.method] !== undefined; - export enum PerpetualTxStage { reviewed = 'reviewed', approved = 'approved', @@ -100,7 +31,7 @@ export type TradeDialogContextType = { pair: PerpetualPair; origin?: PerpetualPageModals; trade?: PerpetualTrade; - analysis: TradeAnalysis; + analysis: PerpetualTradeAnalysis; transactions: PerpetualTx[]; currentTransaction?: TradeDialogCurrentTransaction; setTransactions: Dispatch>; @@ -109,23 +40,3 @@ export type TradeDialogContextType = { >; onClose: () => void; }; - -export const isTrade = ( - transaction: PerpetualTx, -): transaction is PerpetualTxTrade => - transaction.method === PerpetualTxMethods.trade; - -export const isDepositMargin = ( - transaction: PerpetualTx, -): transaction is PerpetualTxDepositMargin => - transaction.method === PerpetualTxMethods.deposit; - -export const isWithdrawMargin = ( - transaction: PerpetualTx, -): transaction is PerpetualTxWithdrawMargin => - transaction.method === PerpetualTxMethods.withdraw; - -export const isWithdrawAllMargin = ( - transaction: PerpetualTx, -): transaction is PerpetualTxWithdrawAllMargin => - transaction.method === PerpetualTxMethods.withdrawAll; diff --git a/src/app/pages/PerpetualPage/components/TradeForm/components/ExpiryDateInput.tsx b/src/app/pages/PerpetualPage/components/TradeForm/components/ExpiryDateInput.tsx new file mode 100644 index 000000000..7f2eb2a30 --- /dev/null +++ b/src/app/pages/PerpetualPage/components/TradeForm/components/ExpiryDateInput.tsx @@ -0,0 +1,49 @@ +import { + AmountSelectorButton, + AmountSelectorButtonPadding, +} from 'app/components/Form/AmountInput'; +import { Input } from 'app/components/Input'; +import React, { useCallback } from 'react'; + +type ExpiryDateInputProps = { + value: string; + onChange: (value: string) => void; + step?: number; +}; + +const buttonValues = [1, 7, 30]; + +export const ExpiryDateInput: React.FC = ({ + value, + onChange, + step = 1, +}) => { + const onInputChange = useCallback( + (value: string) => onChange(String(Math.floor(Number(value)))), + [onChange], + ); + + return ( +
+ + +
+ {buttonValues.map(buttonValue => ( + onChange(String(buttonValue))} + padding={AmountSelectorButtonPadding.sm} + dataActionId="perpetuals-expiryDate" + /> + ))} +
+
+ ); +}; diff --git a/src/app/pages/PerpetualPage/components/TradeForm/components/ResultingPosition.tsx b/src/app/pages/PerpetualPage/components/TradeForm/components/ResultingPosition.tsx new file mode 100644 index 000000000..5ee854f45 --- /dev/null +++ b/src/app/pages/PerpetualPage/components/TradeForm/components/ResultingPosition.tsx @@ -0,0 +1,91 @@ +import { AssetValue } from 'app/components/AssetValue'; +import { AssetValueMode } from 'app/components/AssetValue/types'; +import { selectPerpetualPage } from 'app/pages/PerpetualPage/selectors'; +import { PerpetualTrade } from 'app/pages/PerpetualPage/types'; +import { translations } from 'locales/i18n'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { TradingPosition } from 'types/trading-position'; +import { PerpetualPairDictionary } from 'utils/dictionaries/perpetual-pair-dictionary'; +import { toNumberFormat } from 'utils/display-text/format'; +import { LeverageViewer } from '../../LeverageViewer'; +import { usePerpetual_calculateResultingPosition } from '../../../hooks/usePerpetual_calculateResultingPosition'; + +type ResultingPositionProps = { + trade: PerpetualTrade; + minLeverage: number; + maxLeverage: number; + limitOrderPrice: number; +}; + +export const ResultingPosition: React.FC = ({ + trade, + minLeverage, + maxLeverage, + limitOrderPrice, +}) => { + const { t } = useTranslation(); + + const { + estimatedLiquidationPrice: liquidationPrice, + leverage, + } = usePerpetual_calculateResultingPosition(trade); + + const { pairType } = useSelector(selectPerpetualPage); + const pair = useMemo( + () => PerpetualPairDictionary.get(trade.pairType || pairType), + [trade.pairType, pairType], + ); + + return ( + <> + + +
+ + +
+ + +
+ +
+ + +
+
+ + ); +}; diff --git a/src/app/pages/PerpetualPage/components/TradeForm/index.tsx b/src/app/pages/PerpetualPage/components/TradeForm/index.tsx index 685b3b40a..fc935a592 100644 --- a/src/app/pages/PerpetualPage/components/TradeForm/index.tsx +++ b/src/app/pages/PerpetualPage/components/TradeForm/index.tsx @@ -4,6 +4,8 @@ import React, { useEffect, useMemo, useState, + Dispatch, + SetStateAction, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { ErrorBadge } from 'app/components/Form/ErrorBadge'; @@ -18,68 +20,73 @@ import { toNumberFormat, } from '../../../../../utils/display-text/format'; import classNames from 'classnames'; -import { PerpetualTrade, PerpetualTradeType } from '../../types'; +import { + PerpetualTrade, + PerpetualTradeType, + PERPETUAL_EXPIRY_DEFAULT, +} from '../../types'; import { AssetSymbolRenderer } from '../../../../components/AssetSymbolRenderer'; import { Input } from '../../../../components/Input'; import { PopoverPosition, Tooltip } from '@blueprintjs/core'; import { AssetValue } from '../../../../components/AssetValue'; import { AssetValueMode } from '../../../../components/AssetValue/types'; -import { LeverageViewer } from '../LeverageViewer'; -import { - getSignedAmount, - getTradeDirection, - validatePositionChange, -} from '../../utils/contractUtils'; +import { getSignedAmount, getTradeDirection } from '../../utils/contractUtils'; import { PerpetualQueriesContext } from '../../contexts/PerpetualQueriesContext'; import { usePerpetual_isTradingInMaintenance } from '../../hooks/usePerpetual_isTradingInMaintenance'; -import { numberFromWei, toWei } from 'utils/blockchain/math-helpers'; +import { + numberFromWei, + toWei, + isValidNumerishValue, +} from 'utils/blockchain/math-helpers'; import { useSelector } from 'react-redux'; import { selectPerpetualPage } from '../../selectors'; import { getCollateralName } from '../../utils/renderUtils'; import { TxType } from '../../../../../store/global/transactions-store/types'; import { perpMath, perpUtils } from '@sovryn/perpetual-swap'; -import { getRequiredMarginCollateralWithGasFees } from '../../utils/perpUtils'; -import { usePerpetual_getCurrentPairId } from '../../hooks/usePerpetual_getCurrentPairId'; +import { bignumber } from 'mathjs'; +import { ExpiryDateInput } from './components/ExpiryDateInput'; +import { ResultingPosition } from './components/ResultingPosition'; +import { Checkbox } from 'app/components/Checkbox'; +import { usePerpetual_analyseTrade } from '../../hooks/usePerpetual_analyseTrade'; +import { getTraderPnLInCC } from '@sovryn/perpetual-swap/dist/scripts/utils/perpUtils'; +import { ValidationHint } from '../ValidationHint/ValidationHint'; const { shrinkToLot } = perpMath; const { getTradingFee, getQuote2CollateralFX, - calculateApproxLiquidationPrice, - calculateSlippagePrice, + getTraderLeverage, calculateLeverage, getMaxInitialLeverage, - getMaximalTradeSizeInPerpetualWithCurrentMargin, getPrice, getSignedMaxAbsPositionForTrader, } = perpUtils; interface ITradeFormProps { trade: PerpetualTrade; - isNewTrade?: boolean; disabled?: boolean; - onChange: (trade: PerpetualTrade) => void; - onSubmit: () => void; + setTrade: Dispatch>; + onSubmit: (trade: PerpetualTrade) => void; onOpenSlippage: () => void; } export const TradeForm: React.FC = ({ trade, - isNewTrade, disabled, - onChange, + setTrade, onSubmit, onOpenSlippage, }) => { - const { t } = useTranslation(); + const pair = useMemo(() => PerpetualPairDictionary.get(trade.pairType), [ + trade.pairType, + ]); - const { pairType, collateral } = useSelector(selectPerpetualPage); + const { t } = useTranslation(); const inMaintenance = usePerpetual_isTradingInMaintenance(); const { useMetaTransactions } = useSelector(selectPerpetualPage); - const currentPairId = usePerpetual_getCurrentPairId(); const { perpetuals } = useContext(PerpetualQueriesContext); const { ammState, @@ -90,48 +97,31 @@ export const TradeForm: React.FC = ({ lotSize, lotPrecision, availableBalance, - } = perpetuals[currentPairId]; + } = perpetuals[pair.id]; - const pair = useMemo(() => PerpetualPairDictionary.get(pairType), [pairType]); - const collateralName = useMemo(() => getCollateralName(collateral), [ - collateral, - ]); + const collateralName = useMemo( + () => getCollateralName(pair.collateralAsset), + [pair.collateralAsset], + ); const maxTradeSize = useMemo(() => { - let maxTradeSize; - if (isNewTrade) { - maxTradeSize = shrinkToLot( - Math.abs( - getSignedMaxAbsPositionForTrader( - getTradeDirection(trade.position), - numberFromWei(availableBalance), - perpParameters, - traderState, - ammState, - liqPoolState, - trade.slippage, - ), - ), - lotSize, - ); - } else { - maxTradeSize = shrinkToLot( - Math.abs( - getMaximalTradeSizeInPerpetualWithCurrentMargin( - getTradeDirection(trade.position), - perpParameters, - traderState, - ammState, - liqPoolState, - ), + const maxTradeSize = shrinkToLot( + Math.abs( + getSignedMaxAbsPositionForTrader( + getTradeDirection(trade.position), + numberFromWei(availableBalance), + perpParameters, + traderState, + ammState, + liqPoolState, + trade.slippage, ), - lotSize, - ); - } + ), + lotSize, + ); return Number.isFinite(maxTradeSize) ? maxTradeSize : 0; }, [ - isNewTrade, trade.position, trade.slippage, availableBalance, @@ -142,6 +132,13 @@ export const TradeForm: React.FC = ({ lotSize, ]); + const isMarketOrder = useMemo( + () => trade.tradeType === PerpetualTradeType.MARKET, + [trade.tradeType], + ); + + const hasOpenTrades = traderState?.marginAccountPositionBC !== 0; + const [minLeverage, maxLeverage] = useMemo(() => { const amountChange = getSignedAmount(trade.position, trade.amount); const amountTarget = traderState.marginAccountPositionBC + amountChange; @@ -150,11 +147,17 @@ export const TradeForm: React.FC = ({ traderState.availableCashCC - getTradingFee(amountChange, perpParameters, ammState); if (useMetaTransactions) { - possibleMargin -= - gasLimit.deposit_collateral + gasLimit[TxType.PERPETUAL_TRADE]; + possibleMargin -= gasLimit[TxType.PERPETUAL_TRADE]; } - const maxLeverage = getMaxInitialLeverage(amountTarget, perpParameters); + // Reduce possible margin slightly to account for calculation + possibleMargin += getTraderPnLInCC(traderState, ammState, perpParameters); + // max leverage for limit and stop orders should equal to max leverage for huge positions + // and not the actual target amount so we don't allow users to place multiple high leverage small positions + const position = isMarketOrder ? amountTarget : 1000000000; + + const maxLeverage = getMaxInitialLeverage(position, perpParameters); + const minLeverage = Math.min( maxLeverage, Math.max( @@ -165,6 +168,7 @@ export const TradeForm: React.FC = ({ traderState, ammState, perpParameters, + trade.slippage, ), ), ); @@ -173,12 +177,14 @@ export const TradeForm: React.FC = ({ }, [ trade.position, trade.amount, - pair, - availableBalance, - useMetaTransactions, + trade.slippage, traderState, + availableBalance, perpParameters, ammState, + useMetaTransactions, + isMarketOrder, + pair.config.leverage.min, ]); const [amount, setAmount] = useState( @@ -186,8 +192,11 @@ export const TradeForm: React.FC = ({ ); useEffect(() => { - if (amount > maxTradeSize) { - setAmount(maxTradeSize); + if ( + isValidNumerishValue(amount) && + bignumber(amount).greaterThan(maxTradeSize) + ) { + setAmount(String(maxTradeSize)); } }, [amount, maxTradeSize, trade.position]); @@ -200,79 +209,94 @@ export const TradeForm: React.FC = ({ ).toFixed(lotPrecision), ); setAmount(amount); - const amountChange = roundedAmount * getTradeDirection(trade.position); - const targetAmount = traderState.marginAccountPositionBC + amountChange; - - let newTrade = { ...trade, amount: toWei(roundedAmount) }; - - if (isNewTrade) { - newTrade.leverage = Math.max( - minLeverage, - Math.min(maxLeverage, newTrade.leverage), - ); - } else { - newTrade.leverage = calculateLeverage( - targetAmount, - traderState.availableCashCC, - traderState, - ammState, - perpParameters, - ); - } - onChange(newTrade); + setTrade(trade => ({ + ...trade, + amount: toWei(roundedAmount), + leverage: Math.max(minLeverage, Math.min(maxLeverage, trade.leverage)), + })); }, - [ - onChange, - trade, - lotSize, - lotPrecision, - maxTradeSize, - minLeverage, - maxLeverage, - isNewTrade, - traderState, - ammState, - perpParameters, - ], + [lotSize, lotPrecision, maxTradeSize, minLeverage, maxLeverage, setTrade], ); const onBlurOrderAmount = useCallback(() => { setAmount(numberFromWei(trade.amount).toFixed(lotPrecision)); }, [lotPrecision, trade.amount]); - const [limit, setLimit] = useState(trade.limit); + const entryPrice = useMemo( + () => + getPrice( + getSignedAmount(trade.position, trade.amount), + perpParameters, + ammState, + ), + [trade.position, trade.amount, perpParameters, ammState], + ); + + const [limit, setLimit] = useState(); const onChangeOrderLimit = useCallback( (limit: string) => { setLimit(limit); - onChange({ ...trade, limit: toWei(limit) }); + setTrade(trade => ({ ...trade, limit: toWei(limit) })); + }, + [setTrade], + ); + + const [triggerPrice, setTriggerPrice] = useState(); + const onChangeTriggerPrice = useCallback( + (triggerPrice: string) => { + setTriggerPrice(triggerPrice); + setTrade(trade => ({ ...trade, trigger: toWei(triggerPrice) })); + }, + [setTrade], + ); + + useEffect(() => { + if (entryPrice && (limit === undefined || triggerPrice === undefined)) { + setTrade(trade => ({ + ...trade, + limit: limit === undefined ? toWei(entryPrice) : trade.limit, + trigger: triggerPrice === undefined ? toWei(entryPrice) : trade.trigger, + })); + } + }, [entryPrice, limit, triggerPrice, setTrade]); + + const [expiry, setExpiry] = useState(String(PERPETUAL_EXPIRY_DEFAULT)); + const onChangeExpiry = useCallback( + (expiry: string) => { + setExpiry(expiry); + setTrade(trade => ({ ...trade, expiry: Number(expiry) })); }, - [onChange, trade], + [setTrade], ); const onChangeLeverage = useCallback( (leverage: number) => { - onChange({ ...trade, leverage }); + setTrade({ ...trade, leverage }); }, - [onChange, trade], + [setTrade, trade], ); const bindSelectPosition = useCallback( - (position: TradingPosition) => () => onChange({ ...trade, position }), - [trade, onChange], + (position: TradingPosition) => () => + setTrade(trade => ({ ...trade, position })), + [setTrade], ); const bindSelectTradeType = useCallback( - (tradeType: PerpetualTradeType) => () => onChange({ ...trade, tradeType }), - [trade, onChange], + (tradeType: PerpetualTradeType) => () => + setTrade(trade => ({ ...trade, tradeType: tradeType })), + [setTrade], ); const tradeButtonLabel = useMemo(() => { const i18nKey = { LONG_LIMIT: translations.perpetualPage.tradeForm.buttons.buyLimit, LONG_MARKET: translations.perpetualPage.tradeForm.buttons.buyMarket, + LONG_STOP: translations.perpetualPage.tradeForm.buttons.buyStop, SHORT_LIMIT: translations.perpetualPage.tradeForm.buttons.sellLimit, SHORT_MARKET: translations.perpetualPage.tradeForm.buttons.sellMarket, + SHORT_STOP: translations.perpetualPage.tradeForm.buttons.sellStop, }[`${trade.position}_${trade.tradeType}`]; return i18nKey && t(i18nKey); @@ -283,119 +307,125 @@ export const TradeForm: React.FC = ({ [ammState], ); - const requiredCollateral = useMemo(() => { - const amount = getSignedAmount(trade.position, trade.amount); - return getRequiredMarginCollateralWithGasFees( - trade.leverage, - traderState.marginAccountPositionBC + amount, - perpParameters, - ammState, - traderState, - trade.slippage, - useMetaTransactions, + const { + orderCost, + tradingFee, + limitPrice, + amountTarget, + validation, + } = usePerpetual_analyseTrade(trade); + + const buttonDisabled = useMemo( + () => + disabled || + Number(amount) <= 0 || + (validation && !validation.valid && !validation.isWarning), + [disabled, amount, validation], + ); + + // clamp leverage + useEffect(() => { + const leverage = Math.max( + minLeverage, + Math.min( + maxLeverage, + trade.keepPositionLeverage + ? getTraderLeverage(traderState, ammState) + : trade.leverage || 1, + ), ); - }, [perpParameters, ammState, traderState, trade, useMetaTransactions]); + if (Number.isFinite(leverage) && trade.leverage !== leverage) { + setTrade({ + ...trade, + leverage, + }); + } + }, [trade, minLeverage, maxLeverage, traderState, ammState, setTrade]); + + const signedAmount = useMemo( + () => getSignedAmount(trade.position, trade.amount), + [trade.amount, trade.position], + ); - const tradingFee = useMemo( - () => getTradingFee(numberFromWei(trade.amount), perpParameters, ammState), - [trade.amount, perpParameters, ammState], + const resultingPositionSize = useMemo( + () => traderState.marginAccountPositionBC + signedAmount, + [signedAmount, traderState.marginAccountPositionBC], + ); + + const [keepPositionLeverage, setKeepPositionLeverage] = useState(false); + + const onChangeKeepPositionLeverage = useCallback( + (keepPositionLeverage: boolean) => { + setKeepPositionLeverage(keepPositionLeverage); + setTrade(trade => ({ ...trade, keepPositionLeverage })); + }, + [setTrade], ); - const liquidationPrice = useMemo( + const isLeverageDisabled = useMemo( () => - calculateApproxLiquidationPrice( - traderState, - ammState, - perpParameters, - Number(amount) * getTradeDirection(trade.position), - requiredCollateral, - ), + isMarketOrder && + (keepPositionLeverage || + (Number(amount) !== 0 && Math.abs(resultingPositionSize) < lotSize)), [ + isMarketOrder, + keepPositionLeverage, amount, - trade.position, - traderState, - ammState, - perpParameters, - requiredCollateral, + resultingPositionSize, + lotSize, ], ); - const limitPrice = useMemo( - () => - calculateSlippagePrice( - averagePrice, - trade.slippage, - getTradeDirection(trade.position), - ), - [averagePrice, trade.slippage, trade.position], - ); + const onSubmitWrapper = useCallback(() => { + const completeTrade = { + ...trade, + averagePrice: toWei(averagePrice), + entryPrice: toWei(entryPrice), + isClosePosition: isMarketOrder && Math.abs(amountTarget) < lotSize, + }; + + if (trade.tradeType !== PerpetualTradeType.MARKET) { + if ( + !completeTrade.limit || + bignumber(completeTrade.limit).lessThanOrEqualTo(0) + ) { + completeTrade.limit = limit ? toWei(limit) : '0'; + } - const entryPrice = useMemo( - () => - getPrice( - getSignedAmount(trade.position, trade.amount), - perpParameters, - ammState, - ), - [trade.position, trade.amount, perpParameters, ammState], - ); + if (completeTrade.tradeType === PerpetualTradeType.STOP) { + if ( + !completeTrade.trigger || + bignumber(completeTrade.trigger).lessThanOrEqualTo(0) + ) { + completeTrade.trigger = triggerPrice ? toWei(triggerPrice) : '0'; + } + } else { + completeTrade.trigger = '0'; + } - const validation = useMemo(() => { - const signedAmount = getSignedAmount(trade.position, trade.amount); - const marginChange = isNewTrade ? requiredCollateral : 0; - return signedAmount !== 0 || marginChange !== 0 - ? validatePositionChange( - signedAmount, - marginChange, - trade.leverage, - trade.slippage, - numberFromWei(availableBalance), - traderState, - perpParameters, - ammState, - useMetaTransactions, - ) - : undefined; + completeTrade.slippage = 0; + completeTrade.keepPositionLeverage = false; + } + + onSubmit(completeTrade); }, [ - isNewTrade, trade, - requiredCollateral, - availableBalance, - traderState, - perpParameters, - ammState, - useMetaTransactions, - ]); - - const buttonDisabled = useMemo( - () => - disabled || - Number(amount) <= 0 || - (validation && !validation.valid && !validation.isWarning), - [disabled, amount, validation], - ); - - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => onChange({ ...trade, entryPrice: averagePrice }), [ averagePrice, - onChange, + entryPrice, + isMarketOrder, + amountTarget, + lotSize, + onSubmit, + limit, + triggerPrice, ]); - // clamp leverage useEffect(() => { - if (isNewTrade) { - const leverage = Math.max( - minLeverage, - Math.min(maxLeverage, trade.leverage || 1), - ); - if (Number.isFinite(leverage) && trade.leverage !== leverage) { - onChange({ - ...trade, - leverage, - }); - } + // resets trade.keepPositionLeverage in case we flip the sign + if (trade.tradeType !== PerpetualTradeType.MARKET && keepPositionLeverage) { + onChangeKeepPositionLeverage(false); } - }, [isNewTrade, trade, minLeverage, maxLeverage, onChange]); + }, [keepPositionLeverage, onChangeKeepPositionLeverage, trade.tradeType]); return (
= ({ > {t(translations.perpetualPage.tradeForm.buttons.market)} - - - -
- - -
+ {t(translations.perpetualPage.tradeForm.buttons.limit)} + + +
+ = ({ onBlur={onBlurOrderAmount} />
+ +
+ + +
+
@@ -503,12 +544,53 @@ export const TradeForm: React.FC = ({
+ + {(trade.tradeType === PerpetualTradeType.LIMIT || + trade.tradeType === PerpetualTradeType.STOP) && ( + <> +
+ +
+ +
+ +
+ +
+ + + {t(translations.perpetualPage.tradeForm.labels.days)} + + +
+ + )} +
= ({ minDecimals={8} maxDecimals={8} mode={AssetValueMode.auto} - value={requiredCollateral} + value={orderCost} assetString={collateralName} /> - + {(!pair.isQuanto || isMarketOrder) && ( + + )} } > @@ -547,12 +631,19 @@ export const TradeForm: React.FC = ({ minDecimals={4} maxDecimals={4} mode={AssetValueMode.auto} - value={requiredCollateral} + value={orderCost} assetString={collateralName} />
-
+
= ({ />
- {isNewTrade && ( - - )} -
- -
- {isNewTrade && ( -
- - -
- )} - {!isNewTrade && ( -
- -
+ {(trade.tradeType === PerpetualTradeType.LIMIT || + trade.tradeType === PerpetualTradeType.STOP) && ( +
+ + + + + + + } + > -
+ +
+ )} -
- - + +
+
-
+ + + )} - {validation && !validation.valid && validation.errors.length > 0 && ( -
- {validation.errorMessages} -
+ + {Number(amount) > 0 && ( + )}
{!inMaintenance ? ( @@ -695,13 +807,15 @@ export const TradeForm: React.FC = ({ ? 'tw-opacity-25 tw-cursor-not-allowed' : 'tw-opacity-100 hover:tw-opacity-75', )} - onClick={onSubmit} + onClick={onSubmitWrapper} disabled={buttonDisabled} > {tradeButtonLabel} {weiToNumberFormat(trade.amount, lotPrecision)} @{' '} - {toNumberFormat(entryPrice, 2)} + {isMarketOrder + ? toNumberFormat(entryPrice, 2) + : toNumberFormat(limitPrice, 2)} ) : ( diff --git a/src/app/pages/PerpetualPage/components/ValidationHint/ValidationHint.tsx b/src/app/pages/PerpetualPage/components/ValidationHint/ValidationHint.tsx new file mode 100644 index 000000000..392c41b9e --- /dev/null +++ b/src/app/pages/PerpetualPage/components/ValidationHint/ValidationHint.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Validation } from '../../utils/contractUtils'; +import classNames from 'classnames'; + +type ValidationHintProps = { + validation?: Validation; + className?: string; +}; + +export const ValidationHint: React.FC = ({ + validation, + className, +}) => { + if (!validation || validation.valid || validation.errors.length === 0) { + return null; + } + + return ( +
+ {validation.errorMessages} +
+ ); +}; diff --git a/src/app/pages/PerpetualPage/contexts/GraphQLProvider.tsx b/src/app/pages/PerpetualPage/contexts/GraphQLProvider.tsx index 154d54be4..f673d61ca 100644 --- a/src/app/pages/PerpetualPage/contexts/GraphQLProvider.tsx +++ b/src/app/pages/PerpetualPage/contexts/GraphQLProvider.tsx @@ -137,15 +137,15 @@ const evaluateEndpoint = async (endpoint: GraphQLEndpoint) => { // TODO: add Perpetual GraphQL mainnet urls // fallback endpoint has to always be the last entry. const config: GraphQLEndpoint[] = [ - { - graph: - 'https://api.thegraph.com/subgraphs/name/omerzam/perpetual-swaps-compete', - index: 'https://api.thegraph.com/index-node/graphql', - }, + // { + // graph: + // 'https://api.thegraph.com/subgraphs/name/omerzam/perpetual-swaps-compete', + // index: 'https://api.thegraph.com/index-node/graphql', + // }, { graph: 'https://graphql.sovryn.app/subgraphs/name/DistributedCollective/Sovryn-perpetual-swaps-subgraph', - isFallback: true, + // isFallback: true, }, ]; @@ -170,22 +170,22 @@ export const GraphQLProvider: React.FC = ({ let bestIndex = active; if (bestEvaluation.score > LACKING_TEST_SCORE) { - console.info( - `GraphQL endpoint is healthy and synced! [score:${bestEvaluation.score}] Continuing to use "${activeEndpoint.graph}".`, - ); + // console.info( + // `GraphQL endpoint is healthy and synced! [score:${bestEvaluation.score}] Continuing to use "${activeEndpoint.graph}".`, + // ); return; } else if (activeEndpoint.isFallback) { // TODO: add index to private fallback graph to be able to properly evaluate it. bestEvaluation.score = bestEvaluation.score > 0 ? LACKING_TEST_SCORE : 0; } - console.info( - `GraphQL endpoint is ${ - activeEndpoint.isFallback ? 'the fallback' : 'lacking' - }!`, - JSON.stringify(bestEvaluation), - `Evaluating alternatives!`, - ); + // console.info( + // `GraphQL endpoint is ${ + // activeEndpoint.isFallback ? 'the fallback' : 'lacking' + // }!`, + // JSON.stringify(bestEvaluation), + // `Evaluating alternatives!`, + // ); for (let index = 0; index < config.length; index++) { if (index === active) { diff --git a/src/app/pages/PerpetualPage/contexts/PerpetualQueriesContext.tsx b/src/app/pages/PerpetualPage/contexts/PerpetualQueriesContext.tsx index 0243912fd..96db175d3 100644 --- a/src/app/pages/PerpetualPage/contexts/PerpetualQueriesContext.tsx +++ b/src/app/pages/PerpetualPage/contexts/PerpetualQueriesContext.tsx @@ -90,7 +90,7 @@ type PerpetualValue = { availableBalance: string; }; -type PerpetualQueriesContextValue = { +export type PerpetualQueriesContextValue = { perpetuals: { [key: string]: PerpetualValue; }; @@ -203,8 +203,8 @@ export const PerpetualQueriesContextProvider: React.FC { - firstRefetch(); - secondRefetch(); + firstRefetch().catch(console.error); + secondRefetch().catch(console.error); }, [firstRefetch, secondRefetch]); const refetchDebounced = useMemo( diff --git a/src/app/pages/PerpetualPage/hooks/graphql/useGetTraderEvents.ts b/src/app/pages/PerpetualPage/hooks/graphql/useGetTraderEvents.ts index 4579d27d6..2520633e2 100644 --- a/src/app/pages/PerpetualPage/hooks/graphql/useGetTraderEvents.ts +++ b/src/app/pages/PerpetualPage/hooks/graphql/useGetTraderEvents.ts @@ -85,6 +85,7 @@ export enum Event { UPDATE_MARGIN_ACCOUNT = 'UPDATE_MARGIN_ACCOUNT', POSITION = 'POSITION', FUNDING_RATE = 'FUNDING_RATE', + LIMIT_ORDER = 'LIMIT_ORDER', } const totalCountFields = { @@ -115,6 +116,7 @@ class EventDictionary { 'price', 'limitPrice', 'isClose', + 'orderDigest', ...genericFields, ]), ], @@ -198,6 +200,20 @@ class EventDictionary { 'fundingPayment{ position{ perpetual{ id } } }', ]), ], + [ + Event.LIMIT_ORDER, + new EventDetails('limitOrders', [ + 'id', + 'perpetual{ id }', + 'limitPrice', + 'triggerPrice', + 'tradeAmount', + 'state', + 'deadline', + 'createdTimestamp', + 'createdTransactionHash', + ]), + ], ], ); public static get(event: Event): EventDetails { diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_ClosedPositions.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_ClosedPositions.ts index 0f0134337..6cbfe9c47 100644 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_ClosedPositions.ts +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_ClosedPositions.ts @@ -11,9 +11,11 @@ import { import { RecentTradesContext } from '../contexts/RecentTradesContext'; import debounce from 'lodash.debounce'; import { perpUtils } from '@sovryn/perpetual-swap'; -import { usePerpetual_getCurrentPairId } from './usePerpetual_getCurrentPairId'; import { PerpetualPair } from 'utils/models/perpetual-pair'; -import { PerpetualPairDictionary } from 'utils/dictionaries/perpetual-pair-dictionary'; +import { + PerpetualPairDictionary, + PerpetualPairType, +} from 'utils/dictionaries/perpetual-pair-dictionary'; const { getQuote2CollateralFX } = perpUtils; @@ -36,12 +38,8 @@ export const usePerpetual_ClosedPositions = ( page: number, perPage: number, ): ClosedPositionHookResult => { - const address = useAccount(); - - const currentPairId = usePerpetual_getCurrentPairId(); + const account = useAccount(); const { perpetuals } = useContext(PerpetualQueriesContext); - const { ammState } = perpetuals[currentPairId]; - const { latestTradeByUser } = useContext(RecentTradesContext); const eventQuery = useMemo( @@ -63,7 +61,7 @@ export const usePerpetual_ClosedPositions = ( previousData: previousPositions, refetch, loading, - } = useGetTraderEvents(address.toLowerCase(), eventQuery); + } = useGetTraderEvents(account.toLowerCase(), eventQuery); const data: ClosedPositionEntry[] = useMemo(() => { const currentPositions = @@ -74,10 +72,15 @@ export const usePerpetual_ClosedPositions = ( if (currentPositions?.length > 0) { data = currentPositions.map(item => { const realizedPnlCC = ABK64x64ToFloat(BigNumber.from(item.totalPnLCC)); + const pair = + PerpetualPairDictionary.getById(item?.perpetual?.id) || + PerpetualPairDictionary.get(PerpetualPairType.BTCUSD); + + const { ammState } = perpetuals[pair.id]; return { id: item.id, - pair: PerpetualPairDictionary.getById(item?.perpetual?.id), + pair, datetime: item.endDate, positionSizeMin: ABK64x64ToFloat(BigNumber.from(item.lowestSizeBC)), positionSizeMax: ABK64x64ToFloat(BigNumber.from(item.highestSizeBC)), @@ -90,7 +93,7 @@ export const usePerpetual_ClosedPositions = ( } return data; }, [ - ammState, + perpetuals, positions?.trader?.positions, previousPositions?.trader?.positions, ]); diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenOrders.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenOrders.ts new file mode 100644 index 000000000..378e6e17e --- /dev/null +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenOrders.ts @@ -0,0 +1,147 @@ +import { + PerpetualPairType, + PerpetualPairDictionary, +} from '../../../../utils/dictionaries/perpetual-pair-dictionary'; +import { PerpetualTradeType, LimitOrderEvent } from '../types'; + +import { useMemo, useEffect, useContext } from 'react'; + +import debounce from 'lodash.debounce'; +import { ABK64x64ToFloat } from '@sovryn/perpetual-swap/dist/scripts/utils/perpMath'; +import { BigNumber } from 'ethers'; +import { + Event, + EventQuery, + OrderDirection, + useGetTraderEvents, +} from './graphql/useGetTraderEvents'; +import { usePerpetual_completedTransactions } from './usePerpetual_completedTransactions'; +import { TxType } from '../../../../store/global/transactions-store/types'; +import { RecentTradesContext } from '../contexts/RecentTradesContext'; +import { useAccount } from 'app/hooks/useAccount'; + +export type OpenOrderEntry = { + id: string; + pairType: PerpetualPairType; + orderType: PerpetualTradeType; + limitPrice: number; + triggerPrice: number; + createdAt: string; + orderSize: number; + expiry: string; + createdTransactionHash: string; +}; + +type OpenOrdersHookResult = { + loading: boolean; + data?: OpenOrderEntry[]; + totalCount: number; +}; + +export const usePerpetual_OpenOrders = ( + page: number, + perPage: number, +): OpenOrdersHookResult => { + const account = useAccount(); + const completedTransactions = usePerpetual_completedTransactions(); + const completeLimitOrderTxCount = useMemo( + () => + Object.values(completedTransactions).reduce( + (count, transaction) => + [ + TxType.PERPETUAL_CREATE_LIMIT_ORDER, + TxType.PERPETUAL_CANCEL_LIMIT_ORDER, + ].includes(transaction.type) + ? count + 1 + : count, + 0, + ), + [completedTransactions], + ); + const { latestTradeByUser } = useContext(RecentTradesContext); + + const eventQuery = useMemo( + (): EventQuery[] => [ + { + event: Event.LIMIT_ORDER, + whereCondition: `state: Active`, + orderBy: 'createdTimestamp', + orderDirection: OrderDirection.desc, + page: 1, // TODO: Add a proper pagination once we have a total limit orders field in the subgraph + perPage: 1000, + }, + ], + [], + ); + + const { + data: tradeEvents, + previousData: previousTradeEvents, + refetch, + loading, + } = useGetTraderEvents(account.toLowerCase(), eventQuery); + + const data = useMemo(() => { + const currentPositions: LimitOrderEvent[] | undefined = + tradeEvents?.trader?.limitOrders || + previousTradeEvents?.trader?.limitOrders; + + if (!currentPositions) { + return; + } + + const result: OpenOrderEntry[] = currentPositions.reduce( + (acc, position) => { + const pair = PerpetualPairDictionary.getById(position.perpetual.id); + + const triggerPrice = ABK64x64ToFloat( + BigNumber.from(position.triggerPrice), + ); + + acc.push({ + id: position.id, + createdAt: position.createdTimestamp, + pairType: pair?.pairType, + orderType: + triggerPrice > 0 + ? PerpetualTradeType.STOP + : PerpetualTradeType.LIMIT, + triggerPrice, + limitPrice: ABK64x64ToFloat(BigNumber.from(position.limitPrice)), + orderSize: ABK64x64ToFloat(BigNumber.from(position.tradeAmount)), + expiry: position.deadline, + createdTransactionHash: position.createdTransactionHash, + }); + + return acc; + }, + [] as any, + ); + + return result; + }, [ + previousTradeEvents?.trader?.limitOrders, + tradeEvents?.trader?.limitOrders, + ]); + + const refetchDebounced = useMemo( + () => + debounce(refetch, 1000, { + leading: false, + trailing: true, + maxWait: 1000, + }), + [refetch], + ); + + useEffect(() => { + refetchDebounced(); + }, [refetchDebounced, completeLimitOrderTxCount, latestTradeByUser]); + + const paginatedData = useMemo( + () => data && data.slice((page - 1) * perPage, page * perPage), + [data, page, perPage], + ); + + return { data: paginatedData, loading, totalCount: data?.length || 0 }; +}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenPositions.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenPositions.ts index 7f5655cb0..0b2e1cba5 100644 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenPositions.ts +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_OpenPositions.ts @@ -25,6 +25,7 @@ export type OpenPositionEntry = { position?: TradingPosition; amount?: number; entryPrice?: number; + averagePrice?: number; liquidationPrice?: number; margin: number; leverage?: number; @@ -89,6 +90,7 @@ export const usePerpetual_OpenPositions = ( ammState, traderState, perpetualParameters: perpParameters, + averagePrice, } = perpetuals[position.perpetual.id]; const base2quote = getBase2QuoteFX(ammState, true); @@ -102,11 +104,12 @@ export const usePerpetual_OpenPositions = ( const margin = traderState.availableCashCC; if (traderState.marginAccountPositionBC === 0) { - return { + acc.push({ id: pair.id, pairType: pair.pairType, margin, - }; + }); + return acc; } const tradeAmount = traderState.marginAccountPositionBC; @@ -149,6 +152,7 @@ export const usePerpetual_OpenPositions = ( tradeAmount > 0 ? TradingPosition.LONG : TradingPosition.SHORT, amount: tradeAmount, entryPrice, + averagePrice, liquidationPrice, leverage, margin, diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_OrderHistory.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_OrderHistory.ts index 7aea62036..9321f20a6 100644 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_OrderHistory.ts +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_OrderHistory.ts @@ -5,6 +5,8 @@ import { PerpetualTradeType, PerpetualTradeEvent, PerpetualLiquidationEvent, + LimitOrderEvent, + LimitOrderState, } from '../types'; import { Event, @@ -19,11 +21,10 @@ import debounce from 'lodash.debounce'; import { PerpetualPair } from 'utils/models/perpetual-pair'; import { PerpetualPairDictionary } from 'utils/dictionaries/perpetual-pair-dictionary'; -// TODO: Finalize this enum once we know what possible order states we can have -enum OrderState { +export enum OrderState { Filled = 'Filled', - Open = 'Open', - Failed = 'Failed', + Opened = 'Opened', + Cancelled = 'Cancelled', } export type OrderHistoryEntry = { @@ -34,8 +35,8 @@ export type OrderHistoryEntry = { tradeType: PerpetualTradeType; orderState: OrderState; orderSize: string; + triggerPrice?: string; limitPrice?: string; - execSize: string; execPrice?: string; orderId?: string; }; @@ -72,6 +73,13 @@ export const usePerpetual_OrderHistory = ( page: 1, perPage: 1000, }, + { + event: Event.LIMIT_ORDER, + orderBy: 'createdTimestamp', + orderDirection: OrderDirection.desc, + page: 1, + perPage: 1000, + }, ], [], ); @@ -87,16 +95,28 @@ export const usePerpetual_OrderHistory = ( const currentTradeEvents: PerpetualTradeEvent[] = tradeEvents?.trader?.trades || previousTradeEvents?.trader?.trades || []; const currentTradeEventsLength = currentTradeEvents.length; + const currentLiquidationEvents: PerpetualLiquidationEvent[] = tradeEvents?.trader?.liquidates || previousTradeEvents?.trader?.liquidates || []; + + const currentLimitOrderEvents: LimitOrderEvent[] = + tradeEvents?.trader?.limitOrders || + previousTradeEvents?.trader?.limitOrders || + []; + let entries: OrderHistoryEntry[] = []; if (currentTradeEventsLength > 0) { entries = currentTradeEvents.map(item => { const tradeAmount = BigNumber.from(item.tradeAmountBC); const tradeAmountWei = ABK64x64ToWei(tradeAmount); + const tradeType = getFilledOrderTradeType( + item.orderDigest, + currentLimitOrderEvents, + ); + return { id: item.id, pair: PerpetualPairDictionary.getById(item?.perpetual?.id), @@ -104,12 +124,18 @@ export const usePerpetual_OrderHistory = ( position: tradeAmount.isNegative() ? TradingPosition.SHORT : TradingPosition.LONG, - tradeType: PerpetualTradeType.MARKET, + tradeType, orderState: OrderState.Filled, orderSize: tradeAmountWei, limitPrice: ABK64x64ToWei(BigNumber.from(item.limitPrice)), - execSize: tradeAmountWei, execPrice: ABK64x64ToWei(BigNumber.from(item.price)), + triggerPrice: + tradeType === PerpetualTradeType.STOP + ? getFilledOrderTriggerPrice( + item.orderDigest!, + currentLimitOrderEvents, + ) + : undefined, orderId: item.transaction.id, }; }); @@ -134,20 +160,56 @@ export const usePerpetual_OrderHistory = ( orderState: OrderState.Filled, orderSize: tradeAmountWei, limitPrice: undefined, - execSize: tradeAmountWei, execPrice: ABK64x64ToWei(BigNumber.from(item.liquidationPrice)), orderId: item.transaction.id, }; }), ); + } + + if (currentLimitOrderEvents.length > 0) { + entries.push( + ...currentLimitOrderEvents.map(item => { + const tradeAmount = BigNumber.from(item.tradeAmount); + const tradeAmountWei = ABK64x64ToWei(tradeAmount); + const triggerPrice = BigNumber.from(item.triggerPrice); + const triggerPriceWei = ABK64x64ToWei(triggerPrice); + const limitPrice = ABK64x64ToWei(BigNumber.from(item.limitPrice)); + const orderState = getOrderState(item.state); + + return { + id: item.id, + pair: PerpetualPairDictionary.getById(item?.perpetual?.id), + datetime: item.createdTimestamp, + position: tradeAmount.isNegative() + ? TradingPosition.SHORT + : TradingPosition.LONG, + tradeType: triggerPrice.gt(0) + ? PerpetualTradeType.STOP + : PerpetualTradeType.LIMIT, + orderState, + triggerPrice: triggerPriceWei, + orderSize: tradeAmountWei, + limitPrice: limitPrice, + execPrice: limitPrice, + orderId: item.createdTransactionHash, + }; + }), + ); + } + + if (entries.length > 1) { entries.sort((a, b) => Number(b.datetime) - Number(a.datetime)); } + return entries; }, [ - previousTradeEvents?.trader?.trades, tradeEvents?.trader?.trades, - previousTradeEvents?.trader?.liquidates, tradeEvents?.trader?.liquidates, + tradeEvents?.trader?.limitOrders, + previousTradeEvents?.trader?.trades, + previousTradeEvents?.trader?.liquidates, + previousTradeEvents?.trader?.limitOrders, ]); const paginatedData = useMemo( @@ -177,3 +239,39 @@ export const usePerpetual_OrderHistory = ( totalCount: data.length, }; }; + +const getOrderState = (state: LimitOrderState): OrderState => { + switch (state) { + case LimitOrderState.ACTIVE: + case LimitOrderState.FILLED: + return OrderState.Opened; + default: + return OrderState.Cancelled; + } +}; + +const getFilledOrderTradeType = ( + orderDigest: string | undefined, + limitOrders: LimitOrderEvent[], +): PerpetualTradeType => { + const limitOrder = limitOrders.find(item => item.id === orderDigest); + + if (!orderDigest || !limitOrder) { + return PerpetualTradeType.MARKET; + } + + if (!limitOrder.triggerPrice || limitOrder.triggerPrice === '0') { + return PerpetualTradeType.LIMIT; + } + + return PerpetualTradeType.STOP; +}; + +const getFilledOrderTriggerPrice = ( + orderDigest: string, + limitOrders: LimitOrderEvent[], +) => { + const limitOrder = limitOrders.find(order => order.id === orderDigest); + + return ABK64x64ToWei(BigNumber.from(limitOrder?.triggerPrice)); +}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_analyseTrade.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_analyseTrade.ts new file mode 100644 index 000000000..081b0452c --- /dev/null +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_analyseTrade.ts @@ -0,0 +1,297 @@ +import { PerpetualTrade, PerpetualTradeType, PERPETUAL_CHAIN } from '../types'; +import { useRef, useMemo, useContext, useEffect, useState } from 'react'; +import { useAccount } from '../../../hooks/useAccount'; +import { PerpetualTradeAnalysis } from '../types'; +import { PerpetualQueriesContext } from '../contexts/PerpetualQueriesContext'; +import { useSelector } from 'react-redux'; +import { selectPerpetualPage } from '../selectors'; +import { PerpetualPairDictionary } from '../../../../utils/dictionaries/perpetual-pair-dictionary'; +import { + getSignedAmount, + validatePositionChange, +} from '../utils/contractUtils'; +import { numberFromWei } from '../../../../utils/blockchain/math-helpers'; +import { + getPrice, + calculateSlippagePriceFromMidPrice, + getEstimatedMarginCollateralForLimitOrder, + getTraderPnLInCC, + getTradingFee, + getEstimatedMarginCollateralForTrader, +} from '@sovryn/perpetual-swap/dist/scripts/utils/perpUtils'; +import { ABK64x64ToFloat } from '@sovryn/perpetual-swap/dist/scripts/utils/perpMath'; +import { usePerpetual_calculateResultingPosition } from './usePerpetual_calculateResultingPosition'; +import { getContract } from '../../../../utils/blockchain/contract-helpers'; +import { bridgeNetwork } from '../../BridgeDepositPage/utils/bridge-network'; +import { BigNumber } from 'ethers'; +import { getRequiredMarginCollateralWithGasFees } from '../utils/perpUtils'; +import { usePerpetual_OpenOrders } from './usePerpetual_OpenOrders'; + +const defaultAnalysis: PerpetualTradeAnalysis = { + amountChange: 0, + amountTarget: 0, + marginChange: 0, + marginTarget: 0, + partialUnrealizedPnL: 0, + leverageTarget: 0, + entryPrice: 0, + limitPrice: 0, + liquidationPrice: 0, + orderCost: 0, + tradingFee: 0, + requiredAllowance: 0, + loading: true, +}; + +export const usePerpetual_analyseTrade = ( + trade?: PerpetualTrade, + lockedIn?: boolean, +): PerpetualTradeAnalysis => { + const account = useAccount(); + const { perpetuals } = useContext(PerpetualQueriesContext); + + const { totalCount: limitOrdersCount } = usePerpetual_OpenOrders(1, 1000); + + const { pairType, useMetaTransactions } = useSelector(selectPerpetualPage); + + const pair = useMemo( + () => PerpetualPairDictionary.get(trade?.pairType || pairType), + [pairType, trade?.pairType], + ); + + const ref = useRef(defaultAnalysis); + + const openOrders = useRef(); + const [requiredAllowance, setRequiredAllowance] = useState(); + + const { + ammState, + traderState, + perpetualParameters: perpParameters, + availableBalance, + } = perpetuals[pair.id]; + + const { + leverage: leverageTarget, + size: amountTarget, + estimatedLiquidationPrice, + estimatedMargin: estimatedMarginTarget, + } = usePerpetual_calculateResultingPosition(trade); + + const analysis = useMemo(() => { + if (lockedIn) { + // Do not recompute + return ref.current; + } + + // reset analysis + if (!trade || !account) { + openOrders.current = []; + return defaultAnalysis; + } + + const isLimitOrder = + [PerpetualTradeType.LIMIT, PerpetualTradeType.STOP].includes( + trade?.tradeType, + ) && trade.limit; + + const amountChange = getSignedAmount(trade.position, trade.amount); + + const entryPrice = isLimitOrder + ? numberFromWei(trade.limit) + : getPrice(amountChange, perpParameters, ammState); + + const limitPrice = isLimitOrder + ? numberFromWei(trade.limit) + : calculateSlippagePriceFromMidPrice( + perpParameters, + ammState, + trade.slippage, + Math.sign(amountChange), + ); + + const marginChange = estimatedMarginTarget - traderState.availableCashCC; + + let orderCost = Math.max(0, marginChange); + + if (isLimitOrder) { + orderCost = getEstimatedMarginCollateralForLimitOrder( + perpParameters, + ammState, + trade.leverage, + amountChange, + numberFromWei(trade.limit), + trade.trigger ? numberFromWei(trade.trigger) : undefined, + ); + } else if (amountChange !== 0) { + orderCost = getRequiredMarginCollateralWithGasFees( + leverageTarget, + amountTarget, + perpParameters, + ammState, + traderState, + trade.slippage, + useMetaTransactions, + true, + true, + ); + } + + const partialUnrealizedPnL = + getTraderPnLInCC(traderState, ammState, perpParameters, limitPrice) * + Math.abs(-marginChange / traderState.availableCashCC); + + const tradingFee = getTradingFee(amountChange, perpParameters, ammState); + + const analysis: PerpetualTradeAnalysis = { + amountChange, + amountTarget, + marginChange, + marginTarget: estimatedMarginTarget, + partialUnrealizedPnL, + leverageTarget, + liquidationPrice: estimatedLiquidationPrice, + entryPrice, + limitPrice, + orderCost, + tradingFee, + requiredAllowance: requiredAllowance || 0, + loading: requiredAllowance === undefined, + }; + + analysis.validation = validatePositionChange( + analysis, + numberFromWei(availableBalance), + traderState, + perpParameters, + ammState, + trade.tradeType, + limitOrdersCount, + ); + + return analysis; + }, [ + lockedIn, + trade, + account, + perpParameters, + ammState, + estimatedMarginTarget, + traderState, + amountTarget, + leverageTarget, + estimatedLiquidationPrice, + requiredAllowance, + availableBalance, + limitOrdersCount, + useMetaTransactions, + ]); + + useEffect(() => { + // reset cached orders result + openOrders.current = undefined; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [account, origin]); + + useEffect(() => { + if (lockedIn) { + return; + } + + if (!trade) { + setRequiredAllowance(undefined); + return; + } + + const fetchRequiredAllowance = async () => { + const contract = getContract(pair.limitOrderBook); + + let orders: any[]; + if (openOrders.current) { + orders = openOrders.current; + } else { + orders = await bridgeNetwork.call( + PERPETUAL_CHAIN, + contract.address, + contract.abi, + 'getOrders', + [account.toLowerCase(), 0, 1000], + ); + orders = Array.isArray(orders) + ? orders + // filter empty placeholders + .filter(entry => entry?.traderAddr === account) + // resolve all BigNumber objects to float + .map(entry => + Object.entries(entry).reduce((acc, [key, value]) => { + acc[key] = + typeof value === 'object' + ? (acc[key] = ABK64x64ToFloat(BigNumber.from(value))) + : value; + return acc; + }, {}), + ) + : []; + openOrders.current = orders; + } + + const requiredAllowance = + getEstimatedMarginCollateralForTrader( + analysis.amountChange, + trade.leverage, + orders, + perpParameters, + ammState, + traderState, + trade.slippage, + trade.limit ? numberFromWei(trade.limit) : null, + trade.trigger ? numberFromWei(trade.trigger) : null, + ) || 0; + + if (analysis.amountChange === 0) { + // only margin is being changed, + // so take the open orders allowance and add the order cost. + return requiredAllowance + analysis.orderCost; + } + return requiredAllowance; + }; + + const isAllowanceRequired = analysis.marginChange > 0; + + let cancelled = false; + if (isAllowanceRequired) { + fetchRequiredAllowance() + .then(requiredAllowance => { + if (cancelled) { + return; + } + // add 1% allowance to account for estimation accuracy + const requiredAllowanceWithMargin = requiredAllowance * 1.01; + setRequiredAllowance(requiredAllowanceWithMargin); + }) + .catch(console.error); + } else { + setRequiredAllowance(0); + } + + return () => { + cancelled = true; + }; + }, [ + lockedIn, + analysis, + account, + ammState, + perpParameters, + traderState, + pair.id, + pair.limitOrderBook, + trade, + ]); + + useEffect(() => { + ref.current = analysis; + }, [ref, analysis]); + + return analysis; +}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_calculateResultingPosition.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_calculateResultingPosition.ts new file mode 100644 index 000000000..68a5f5e01 --- /dev/null +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_calculateResultingPosition.ts @@ -0,0 +1,155 @@ +import { useContext, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { + getTraderLeverage, + calculateResultingPositionLeverage, + calculateApproxLiquidationPrice, + calculateLeverage, + getRequiredMarginCollateral, +} from '@sovryn/perpetual-swap/dist/scripts/utils/perpUtils'; +import { roundToLot } from '@sovryn/perpetual-swap/dist/scripts/utils/perpMath'; +import { numberFromWei } from 'utils/blockchain/math-helpers'; +import { PerpetualPairDictionary } from 'utils/dictionaries/perpetual-pair-dictionary'; +import { PerpetualQueriesContext } from '../contexts/PerpetualQueriesContext'; +import { selectPerpetualPage } from '../selectors'; +import { PerpetualTrade, PerpetualTradeType } from '../types'; +import { getSignedAmount } from '../utils/contractUtils'; + +type ResultingPositionData = { + leverage: number; + size: number; + estimatedLiquidationPrice: number; + estimatedMargin: number; +}; + +export const usePerpetual_calculateResultingPosition = ( + trade?: PerpetualTrade, +): ResultingPositionData => { + const { pairType } = useSelector(selectPerpetualPage); + const pair = useMemo( + () => PerpetualPairDictionary.get(trade?.pairType || pairType), + [trade?.pairType, pairType], + ); + + const signedOrderSize = useMemo( + () => (trade?.amount ? getSignedAmount(trade.position, trade.amount) : 0), + [trade?.amount, trade?.position], + ); + + const { perpetuals } = useContext(PerpetualQueriesContext); + const { + ammState, + traderState, + perpetualParameters: perpParameters, + lotSize, + } = perpetuals[pair.id]; + + const size = useMemo( + () => + roundToLot( + signedOrderSize + traderState.marginAccountPositionBC, + lotSize || 1, + ), + [signedOrderSize, traderState.marginAccountPositionBC, lotSize], + ); + + const leverage = useMemo(() => { + if (!trade) { + return 0; + } + + const isLimitOrder = + [PerpetualTradeType.LIMIT, PerpetualTradeType.STOP].includes( + trade.tradeType, + ) && trade.limit !== undefined; + + // trader doesn't have an open position or is flipping their position direction + if (!traderState.marginAccountPositionBC || isLimitOrder) { + return trade.leverage; + } + + // trader wants to keep their position leverage and is reducing their position + if ( + trade.keepPositionLeverage && + Math.sign(signedOrderSize) !== + Math.sign(traderState.marginAccountPositionBC) && + Math.abs(signedOrderSize) <= + Math.abs(traderState.marginAccountPositionBC) && + trade.tradeType === PerpetualTradeType.MARKET + ) { + return getTraderLeverage(traderState, ammState); + } + + if (trade.margin) { + return calculateLeverage( + size, + traderState.availableCashCC + numberFromWei(trade.margin), + traderState, + ammState, + perpParameters, + trade.slippage, + ); + } + + return calculateResultingPositionLeverage( + traderState, + ammState, + perpParameters, + signedOrderSize, + trade.leverage, + trade.slippage, + trade.keepPositionLeverage, + ); + }, [trade, signedOrderSize, traderState, ammState, perpParameters, size]); + + const estimatedMargin = useMemo(() => { + if (trade?.margin && trade?.margin !== '0') { + return numberFromWei(trade.margin) + traderState.availableCashCC; + } + + return getRequiredMarginCollateral( + leverage, + size, + perpParameters, + ammState, + traderState, + trade?.slippage, + false, + false, + ); + }, [ + trade?.margin, + leverage, + size, + traderState, + perpParameters, + ammState, + trade?.slippage, + ]); + + const estimatedLiquidationPrice = useMemo(() => { + return size === 0 + ? 0 + : calculateApproxLiquidationPrice( + traderState, + ammState, + perpParameters, + signedOrderSize, + estimatedMargin - traderState.availableCashCC, + ); + }, [ + size, + signedOrderSize, + estimatedMargin, + ammState, + perpParameters, + traderState, + ]); + + return { + leverage, + size, + estimatedLiquidationPrice, + estimatedMargin, + }; +}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_completedTransactions.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_completedTransactions.ts index 0e31d528d..afe144152 100644 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_completedTransactions.ts +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_completedTransactions.ts @@ -21,6 +21,8 @@ export const usePerpetual_completedTransactions = () => { TxType.APPROVE, TxType.PERPETUAL_DEPOSIT_COLLATERAL, TxType.PERPETUAL_WITHDRAW_COLLATERAL, + TxType.PERPETUAL_CREATE_LIMIT_ORDER, + TxType.PERPETUAL_CANCEL_LIMIT_ORDER, TxType.PERPETUAL_TRADE, ].includes(transaction.type) && transaction.asset === Asset.BTCS && diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_depositMarginToken.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_depositMarginToken.ts deleted file mode 100644 index 0d3b3ac5a..000000000 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_depositMarginToken.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useAccount } from 'app/hooks/useAccount'; -import { gasLimit } from 'utils/classifiers'; -import { TxType } from 'store/global/transactions-store/types'; -import { weiToABK64x64 } from '../utils/contractUtils'; -import { - PerpetualPairType, - PerpetualPairDictionary, -} from '../../../../utils/dictionaries/perpetual-pair-dictionary'; -import { - PERPETUAL_GAS_PRICE_DEFAULT, - PERPETUAL_PAYMASTER, - PERPETUAL_CHAIN, -} from '../types'; -import { PerpetualTx } from '../components/TradeDialog/types'; -import { useGsnSendTx } from '../../../hooks/useGsnSendTx'; - -export const usePerpetual_depositMarginToken = (useGSN: boolean) => { - const account = useAccount(); - - const { send, ...rest } = useGsnSendTx( - PERPETUAL_CHAIN, - 'perpetualManager', - 'deposit', - PERPETUAL_PAYMASTER, - useGSN, - ); - - return { - deposit: async ( - amount: string, - nonce?: number, - customData?: PerpetualTx, - ) => { - const pair = PerpetualPairDictionary.get( - customData?.pair || PerpetualPairType.BTCUSD, - ); - await send( - [pair.id, weiToABK64x64(amount)], - { - from: account, - gas: gasLimit[TxType.PERPETUAL_DEPOSIT_COLLATERAL], - gasPrice: PERPETUAL_GAS_PRICE_DEFAULT, - nonce: nonce, - }, - { - type: TxType.PERPETUAL_DEPOSIT_COLLATERAL, - asset: pair.collateralAsset, - customData, - }, - ); - }, - txData: rest.txData, - txHash: rest.txHash, - loading: rest.loading, - status: rest.status, - reset: rest.reset, - }; -}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_executeTransaction.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_executeTransaction.ts deleted file mode 100644 index f25f7d320..000000000 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_executeTransaction.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { usePerpetual_openTrade } from './usePerpetual_openTrade'; -import { usePerpetual_depositMarginToken } from './usePerpetual_depositMarginToken'; -import { usePerpetual_withdrawAll } from './usePerpetual_withdrawAll'; -import { usePerpetual_withdrawMarginToken } from './usePerpetual_withdrawMarginToken'; -import { useMemo, useState } from 'react'; -import { - PerpetualTx, - PerpetualTxTrade, - PerpetualTxDepositMargin, - PerpetualTxWithdrawMargin, - PerpetualTxMethods, -} from '../components/TradeDialog/types'; -import { ResetTxResponseInterface } from '../../../hooks/useSendContractTx'; - -export const usePerpetual_executeTransaction = (useGSN: boolean) => { - // TODO: find a nicer solution only this hooks should ever be used anyway - const { trade, ...tradeRest } = usePerpetual_openTrade(useGSN); - const { deposit, ...depositRest } = usePerpetual_depositMarginToken(useGSN); - const { withdraw, ...withdrawRest } = usePerpetual_withdrawMarginToken( - useGSN, - ); - const { - withdraw: withdrawAll, - ...withdrawAllRest - } = usePerpetual_withdrawAll(useGSN); - - const [transaction, setTransaction] = useState(); - - return useMemo(() => { - let rest: ResetTxResponseInterface | undefined; - - switch (transaction?.method) { - case PerpetualTxMethods.trade: - rest = tradeRest; - break; - case PerpetualTxMethods.deposit: - rest = depositRest; - break; - case PerpetualTxMethods.withdraw: - rest = withdrawRest; - break; - case PerpetualTxMethods.withdrawAll: - rest = withdrawAllRest; - break; - } - - return { - execute: (transaction: PerpetualTx, nonce?: number) => { - setTransaction(transaction); - switch (transaction?.method) { - case PerpetualTxMethods.trade: - const tradeTx: PerpetualTxTrade = transaction; - return trade( - tradeTx.isClosePosition, - tradeTx.amount, - tradeTx.leverage, - tradeTx.slippage, - tradeTx.tradingPosition, - nonce, - transaction, - ); - case PerpetualTxMethods.deposit: - const depositTx: PerpetualTxDepositMargin = transaction; - return deposit(depositTx.amount, nonce, transaction); - case PerpetualTxMethods.withdraw: - const withdrawTx: PerpetualTxWithdrawMargin = transaction; - return withdraw(withdrawTx.amount, nonce, transaction); - case PerpetualTxMethods.withdrawAll: - return withdrawAll(nonce, transaction); - } - }, - txData: rest?.txData, - txHash: rest?.txHash, - loading: rest?.loading, - status: rest?.status, - reset: rest?.reset, - }; - }, [ - trade, - tradeRest, - deposit, - depositRest, - withdraw, - withdrawRest, - withdrawAll, - withdrawAllRest, - transaction, - ]); -}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_isAddressWhitelisted.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_isAddressWhitelisted.ts new file mode 100644 index 000000000..196d3a477 --- /dev/null +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_isAddressWhitelisted.ts @@ -0,0 +1,30 @@ +import { useAccount } from 'app/hooks/useAccount'; +import { bridgeNetwork } from 'app/pages/BridgeDepositPage/utils/bridge-network'; +import { useEffect, useMemo, useState } from 'react'; +import { Chain } from 'types'; +import { getContract } from 'utils/blockchain/contract-helpers'; + +export const usePerpetual_isAddressWhitelisted = (): boolean => { + const [isAddressWhitelisted, setIsAddressWhitelisted] = useState(false); + const account = useAccount(); + + const perpetualManagerContract = useMemo( + () => getContract('perpetualManager'), + [], + ); + + useEffect(() => { + bridgeNetwork + .call( + Chain.BSC, + perpetualManagerContract.address, + perpetualManagerContract.abi, + 'isAddrWhitelisted', + [account], + ) + .then(result => setIsAddressWhitelisted(result)) + .catch(e => console.log(e)); + }, [account, perpetualManagerContract.abi, perpetualManagerContract.address]); + + return isAddressWhitelisted; +}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_openTrade.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_openTrade.ts deleted file mode 100644 index bf6a137c5..000000000 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_openTrade.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { useAccount } from 'app/hooks/useAccount'; -import { useContext } from 'react'; -import { TxType } from 'store/global/transactions-store/types'; -import { TradingPosition } from 'types/trading-position'; -import { ethGenesisAddress, gasLimit } from 'utils/classifiers'; -import { PerpetualQueriesContext } from '../contexts/PerpetualQueriesContext'; -import { floatToABK64x64, getSignedAmount } from '../utils/contractUtils'; -import { - PERPETUAL_SLIPPAGE_DEFAULT, - PERPETUAL_GAS_PRICE_DEFAULT, - PERPETUAL_CHAIN, - PERPETUAL_PAYMASTER, -} from '../types'; -import { - PerpetualPairType, - PerpetualPairDictionary, -} from '../../../../utils/dictionaries/perpetual-pair-dictionary'; -import { PerpetualTx } from '../components/TradeDialog/types'; -import { useGsnSendTx } from '../../../hooks/useGsnSendTx'; -import { perpUtils } from '@sovryn/perpetual-swap'; - -const { calculateSlippagePrice } = perpUtils; - -const MASK_MARKET_ORDER = 0x40000000; -const MASK_CLOSE_ONLY = 0x80000000; - -export const usePerpetual_openTrade = (useGSN: boolean) => { - const account = useAccount(); - - const { perpetuals } = useContext(PerpetualQueriesContext); - - const { send, ...rest } = useGsnSendTx( - PERPETUAL_CHAIN, - 'perpetualManager', - 'trade', - PERPETUAL_PAYMASTER, - useGSN, - ); - - return { - trade: async ( - isClosePosition: boolean | undefined = false, - /** amount as wei string */ - amount: string = '0', - leverage: number | undefined = 1, - slippage: number | undefined = PERPETUAL_SLIPPAGE_DEFAULT, - tradingPosition: TradingPosition | undefined = TradingPosition.LONG, - nonce?: number, - customData?: PerpetualTx, - ) => { - const signedAmount = getSignedAmount(tradingPosition, amount); - - const pair = PerpetualPairDictionary.get( - customData?.pair || PerpetualPairType.BTCUSD, - ); - - const { averagePrice } = perpetuals[pair.id]; - - let tradeDirection = Math.sign(signedAmount); - - const limitPrice = calculateSlippagePrice( - averagePrice, - slippage, - tradeDirection, - ); - - const deadline = Math.round(Date.now() / 1000) + 86400; // 1 day - const timeNow = Math.round(Date.now() / 1000); - const order = [ - pair.id, - account, - floatToABK64x64(signedAmount), - floatToABK64x64(limitPrice), - 0, // TODO: this is fTriggerPrice, it will need to be adjusted once we have limit orders functionality, it's 0 for market orders - deadline, - ethGenesisAddress, - isClosePosition ? MASK_CLOSE_ONLY : MASK_MARKET_ORDER, - floatToABK64x64(leverage), - timeNow, - ]; - - await send( - [order], - { - from: account, - gas: gasLimit[TxType.PERPETUAL_TRADE], - gasPrice: PERPETUAL_GAS_PRICE_DEFAULT, - nonce, - }, - { - type: TxType.PERPETUAL_TRADE, - asset: pair.collateralAsset, - customData, - }, - ); - }, - txData: rest.txData, - txHash: rest.txHash, - loading: rest.loading, - status: rest.status, - reset: rest.reset, - }; -}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_queryLiqPoolId.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_queryLiqPoolId.ts index ca053d487..56a9a5375 100644 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_queryLiqPoolId.ts +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_queryLiqPoolId.ts @@ -3,6 +3,7 @@ import { bridgeNetwork } from 'app/pages/BridgeDepositPage/utils/bridge-network' import { Chain } from 'types'; import { getContract } from 'utils/blockchain/contract-helpers'; import perpetualManagerAbi from 'utils/blockchain/abi/PerpetualManager.json'; +import { BigNumber } from 'ethers'; export const usePerpetual_queryLiqPoolId = (perpetualId: string) => { const [poolId, setPoolId] = useState(); @@ -16,7 +17,7 @@ export const usePerpetual_queryLiqPoolId = (perpetualId: string) => { 'getPoolIdByPerpetualId', [perpetualId], ) - .then(setPoolId); + .then(id => id && setPoolId(BigNumber.from(id).toString())); }, [perpetualId]); useEffect(fetch, [fetch]); diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_transaction.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_transaction.ts new file mode 100644 index 000000000..8feebe62f --- /dev/null +++ b/src/app/pages/PerpetualPage/hooks/usePerpetual_transaction.ts @@ -0,0 +1,99 @@ +import { useMemo, useCallback, useState } from 'react'; +import { useGsnSendTx } from '../../../hooks/useGsnSendTx'; +import { + PerpetualPairDictionary, + PerpetualPairType, +} from '../../../../utils/dictionaries/perpetual-pair-dictionary'; +import { + PERPETUAL_CHAIN, + PERPETUAL_PAYMASTER, + PERPETUAL_GAS_PRICE_DEFAULT, + PerpetualTx, + PerpetualTxMethod, +} from '../types'; +import { + perpetualTransactionArgs, + getPerpetualTxContractName, +} from '../utils/contractUtils'; +import { useAccount } from '../../../hooks/useAccount'; +import { TxType } from '../../../../store/global/transactions-store/types'; +import { gasLimit } from '../../../../utils/classifiers'; + +const PerpetualTxMethodMap: { [key in PerpetualTxMethod]: string } = { + [PerpetualTxMethod.trade]: 'trade', + [PerpetualTxMethod.createLimitOrder]: 'createLimitOrder', + [PerpetualTxMethod.cancelLimitOrder]: 'cancelLimitOrder', + [PerpetualTxMethod.deposit]: 'deposit', + [PerpetualTxMethod.withdraw]: 'withdraw', + [PerpetualTxMethod.withdrawAll]: 'withdrawAll', +}; + +const PerpetualTxMethodTypeMap: { [key in PerpetualTxMethod]: TxType } = { + [PerpetualTxMethod.trade]: TxType.PERPETUAL_TRADE, + [PerpetualTxMethod.createLimitOrder]: TxType.PERPETUAL_CREATE_LIMIT_ORDER, + [PerpetualTxMethod.cancelLimitOrder]: TxType.PERPETUAL_CREATE_LIMIT_ORDER, + [PerpetualTxMethod.deposit]: TxType.PERPETUAL_DEPOSIT_COLLATERAL, + [PerpetualTxMethod.withdraw]: TxType.PERPETUAL_WITHDRAW_COLLATERAL, + [PerpetualTxMethod.withdrawAll]: TxType.PERPETUAL_WITHDRAW_COLLATERAL, +}; + +export const usePerpetual_transaction = ( + transaction: PerpetualTx | undefined, + useGSN: boolean, +) => { + const account = useAccount(); + + const pair = useMemo( + () => + PerpetualPairDictionary.get( + transaction?.pair || PerpetualPairType.BTCUSD, + ), + [transaction?.pair], + ); + + const { send, ...rest } = useGsnSendTx( + PERPETUAL_CHAIN, + getPerpetualTxContractName(transaction), + transaction ? PerpetualTxMethodMap[transaction.method] : '', + PERPETUAL_PAYMASTER, + useGSN, + ); + + const [sending, setSending] = useState(false); + + const execute = useCallback( + async (nonce?: number) => { + if (!transaction) { + throw Error('No transaction given to execute!'); + } + + setSending(true); + + const txType: TxType = PerpetualTxMethodTypeMap[transaction.method]; + + return send( + await perpetualTransactionArgs(pair, account, transaction), + { + from: account, + gas: gasLimit[txType], + gasPrice: PERPETUAL_GAS_PRICE_DEFAULT, + nonce: nonce, + }, + { + type: txType, + asset: pair.collateralAsset, + customData: transaction, + }, + ).finally(() => setSending(false)); + }, + [transaction, account, pair, send], + ); + + return useMemo(() => { + return { + execute, + loading: rest.loading || sending, + ...rest, + }; + }, [execute, rest, sending]); +}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_withdrawAll.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_withdrawAll.ts deleted file mode 100644 index 23bb390c8..000000000 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_withdrawAll.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useAccount } from 'app/hooks/useAccount'; -import { TxType } from 'store/global/transactions-store/types'; -import { gasLimit } from 'utils/classifiers'; -import { - PerpetualPairType, - PerpetualPairDictionary, -} from '../../../../utils/dictionaries/perpetual-pair-dictionary'; -import { useMemo } from 'react'; -import { - PERPETUAL_GAS_PRICE_DEFAULT, - PERPETUAL_CHAIN, - PERPETUAL_PAYMASTER, -} from '../types'; -import { PerpetualTx } from '../components/TradeDialog/types'; -import { useGsnSendTx } from '../../../hooks/useGsnSendTx'; - -export const usePerpetual_withdrawAll = (useGSN: boolean) => { - const account = useAccount(); - - const { send, ...rest } = useGsnSendTx( - PERPETUAL_CHAIN, - 'perpetualManager', - 'withdrawAll', - PERPETUAL_PAYMASTER, - useGSN, - ); - - return useMemo( - () => ({ - withdraw: async (nonce?: number, customData?: PerpetualTx) => { - const pair = PerpetualPairDictionary.get( - customData?.pair || PerpetualPairType.BTCUSD, - ); - send( - [pair.id], - { - from: account, - gas: gasLimit[TxType.PERPETUAL_WITHDRAW_COLLATERAL], - gasPrice: PERPETUAL_GAS_PRICE_DEFAULT, - nonce, - }, - { - type: TxType.PERPETUAL_WITHDRAW_COLLATERAL, - asset: pair.collateralAsset, - customData, - }, - ); - }, - txData: rest.txData, - txHash: rest.txHash, - loading: rest.loading, - status: rest.status, - reset: rest.reset, - }), - [account, send, rest], - ); -}; diff --git a/src/app/pages/PerpetualPage/hooks/usePerpetual_withdrawMarginToken.ts b/src/app/pages/PerpetualPage/hooks/usePerpetual_withdrawMarginToken.ts deleted file mode 100644 index 48643f7e7..000000000 --- a/src/app/pages/PerpetualPage/hooks/usePerpetual_withdrawMarginToken.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useAccount } from 'app/hooks/useAccount'; -import { gasLimit } from 'utils/classifiers'; -import { TxType } from 'store/global/transactions-store/types'; -import { weiToABK64x64 } from '../utils/contractUtils'; -import { - PerpetualPairType, - PerpetualPairDictionary, -} from '../../../../utils/dictionaries/perpetual-pair-dictionary'; -import { - PERPETUAL_GAS_PRICE_DEFAULT, - PERPETUAL_CHAIN, - PERPETUAL_PAYMASTER, -} from '../types'; -import { PerpetualTx } from '../components/TradeDialog/types'; -import { useGsnSendTx } from '../../../hooks/useGsnSendTx'; - -export const usePerpetual_withdrawMarginToken = (useGSN: boolean) => { - const account = useAccount(); - - const { send, ...rest } = useGsnSendTx( - PERPETUAL_CHAIN, - 'perpetualManager', - 'withdraw', - PERPETUAL_PAYMASTER, - useGSN, - ); - - return { - withdraw: async ( - amount: string, - nonce?: number, - customData?: PerpetualTx, - ) => { - const pair = PerpetualPairDictionary.get( - customData?.pair || PerpetualPairType.BTCUSD, - ); - send( - [pair.id, weiToABK64x64(amount)], - { - from: account, - gas: gasLimit[TxType.PERPETUAL_WITHDRAW_COLLATERAL], - gasPrice: PERPETUAL_GAS_PRICE_DEFAULT, - nonce, - }, - { - type: TxType.PERPETUAL_WITHDRAW_COLLATERAL, - asset: pair.collateralAsset, - customData, - }, - ); - }, - txData: rest.txData, - txHash: rest.txHash, - loading: rest.loading, - status: rest.status, - reset: rest.reset, - }; -}; diff --git a/src/app/pages/PerpetualPage/slice.ts b/src/app/pages/PerpetualPage/slice.ts index b207245d5..8598e9429 100644 --- a/src/app/pages/PerpetualPage/slice.ts +++ b/src/app/pages/PerpetualPage/slice.ts @@ -17,6 +17,7 @@ export const initialState: ContainerState = { showRecentTrades: true, showTradeForm: true, showTables: true, + isAddressWhitelisted: false, }; const perpetualPageSlice = createSlice({ @@ -75,6 +76,9 @@ const perpetualPageSlice = createSlice({ setShowTables(state, { payload }: PayloadAction) { state.showTables = payload; }, + setIsAddressWhitelisted(state, { payload }: PayloadAction) { + state.isAddressWhitelisted = payload; + }, }, }); diff --git a/src/app/pages/PerpetualPage/types.ts b/src/app/pages/PerpetualPage/types.ts index 61a0112fa..0b6597478 100644 --- a/src/app/pages/PerpetualPage/types.ts +++ b/src/app/pages/PerpetualPage/types.ts @@ -2,20 +2,22 @@ import { Asset, Nullable, Chain, ChainId } from '../../../types'; import { TradingPosition } from '../../../types/trading-position'; import { PerpetualPairType } from '../../../utils/dictionaries/perpetual-pair-dictionary'; import { Transaction } from 'ethers'; -import { PerpetualTx } from './components/TradeDialog/types'; import { CheckAndApproveResult } from '../../../utils/sovryn/contract-writer'; import { getBridgeChainId } from '../BridgeDepositPage/utils/helpers'; import { isMainnet } from '../../../utils/classifiers'; import { toWei } from '../../../utils/blockchain/math-helpers'; +import { Validation } from './utils/contractUtils'; export const PERPETUAL_SLIPPAGE_DEFAULT = 0.005; +export const PERPETUAL_EXPIRY_DEFAULT = 30; export const PERPETUAL_MAX_LEVERAGE_DEFAULT = 15; export const PERPETUAL_CHAIN = Chain.BSC; export const PERPETUAL_CHAIN_ID = getBridgeChainId(Chain.BSC) || ChainId.BSC_MAINNET; + export const PERPETUAL_PAYMASTER = isMainnet ? '' // TODO: add mainnet paymaster address - : '0x71fEE3eF1305D7879d8d7Bd5CcbF04b66c3a604E'; + : '0x516181Fe2053B3b5CfD547f1220Fa9cdD38e7f9B'; export const PERPETUAL_GAS_PRICE_DEFAULT = isMainnet ? undefined @@ -24,20 +26,18 @@ export const PERPETUAL_GAS_PRICE_DEFAULT = isMainnet export enum PerpetualTradeType { MARKET = 'MARKET', LIMIT = 'LIMIT', + STOP = 'STOP', LIQUIDATION = 'LIQUIDATION', } export enum PerpetualPageModals { NONE = 'NONE', ACCOUNT_BALANCE = 'ACCOUNT_BALANCE', - FASTBTC_DEPOSIT = 'FASTBTC_DEPOSIT', - FASTBTC_WITHDRAW = 'FASTBTC_WITHDRAW', - FASTBTC_TRANSFER = 'FASTBTC_TRANSFER', TRADE_REVIEW = 'TRADE_REVIEW', - EDIT_POSITION_SIZE = 'EDIT_POSITION_SIZE', EDIT_LEVERAGE = 'EDIT_LEVERAGE', EDIT_MARGIN = 'EDIT_MARGIN', CLOSE_POSITION = 'CLOSE_POSITION', + CANCEL_ORDER = 'CANCEL_ORDER', } export type PerpetualTradeEvent = { @@ -89,21 +89,47 @@ export type PerpetualPositionEvent = { tradesCount?: number; }; +export enum LimitOrderState { + ACTIVE = 'Active', + CANCELLED = 'Cancelled', + FILLED = 'Filled', +} + +export type LimitOrderEvent = { + id: string; + perpetual: { id: string }; + triggerPrice: string; + limitPrice: string; + tradeAmount: string; + state: LimitOrderState; + deadline: string; + createdTimestamp: string; + createdTransactionHash: string; +}; + export type PerpetualTrade = { id?: string; pairType: PerpetualPairType; collateral: Asset; tradeType: PerpetualTradeType; position: TradingPosition; - /** wei string */ + /** base value as wei string */ amount: string; - /** wei string */ + /** limit quote price as wei string */ limit?: string; - /** wei string */ + /** trigger quote price as wei string */ + trigger?: string; + /** collateral value wei string */ margin?: string; + /** expected entry price as wei string */ + entryPrice?: string; + /** current average price as wei string */ + averagePrice?: string; + expiry?: number; leverage: number; slippage: number; - entryPrice: number; + keepPositionLeverage?: boolean; + isClosePosition?: boolean; }; export type PerpetualTradeReview = { @@ -124,9 +150,15 @@ export const isPerpetualTrade = (x: any): x is PerpetualTrade => typeof x.amount === 'string' && typeof x.leverage === 'number' && typeof x.slippage === 'number' && - typeof x.entryPrice === 'number' && + (x.entryPrice === undefined || typeof x.entryPrice === 'string') && + (x.averagePrice === undefined || typeof x.averagePrice === 'string') && (x.limit === undefined || typeof x.limit === 'string') && - (x.margin === undefined || typeof x.margin === 'string'); + (x.trigger === undefined || typeof x.trigger === 'string') && + (x.margin === undefined || typeof x.margin === 'string') && + (x.keepPositionLeverage === undefined || + typeof x.keepPositionLeverage === 'boolean') && + (x.isClosingPosition === undefined || + typeof x.isClosingPosition === 'boolean'); export const isPerpetualTradeReview = (x: any): x is PerpetualTradeReview => x && @@ -147,6 +179,7 @@ export type PerpetualPageState = { showRecentTrades: boolean; showTradeForm: boolean; showTables: boolean; + isAddressWhitelisted: boolean; }; export type ContainerState = PerpetualPageState; @@ -154,3 +187,136 @@ export type ContainerState = PerpetualPageState; export type CheckAndApproveResultWithError = CheckAndApproveResult & { error?: Error; }; + +export type PerpetualTradeAnalysis = { + amountChange: number; + amountTarget: number; + marginChange: number; + marginTarget: number; + partialUnrealizedPnL: number; + leverageTarget: number; + entryPrice: number; + limitPrice: number; + liquidationPrice: number; + orderCost: number; + tradingFee: number; + requiredAllowance: number; + loading: boolean; + validation?: Validation; +}; + +export enum PerpetualTxMethod { + trade = 'trade', + createLimitOrder = 'createLimitOrder', + cancelLimitOrder = 'cancelLimitOrder', + deposit = 'deposit', + withdraw = 'withdraw', + withdrawAll = 'withdrawAll', +} + +interface PerpetualTxBase { + pair: PerpetualPairType; + method: PerpetualTxMethod; + tx: Nullable; + origin?: PerpetualPageModals; + index?: number; + count?: number; + target?: { + leverage: number; + }; +} + +export interface PerpetualTxTrade extends PerpetualTxBase { + method: PerpetualTxMethod.trade; + /** amount as wei string */ + amount: string; + price: string; + leverage?: number; + slippage?: number; + tradingPosition?: TradingPosition; + isClosePosition?: boolean; + keepPositionLeverage?: boolean; + + approvalTx: Nullable; +} + +export interface PerpetualTxCreateLimitOrder extends PerpetualTxBase { + method: PerpetualTxMethod.createLimitOrder; + /** amount as wei string */ + amount: string; + /** limit as wei string */ + limit: string; + /** trigger as wei string */ + trigger: string; + expiry: number; + created: number; + leverage?: number; + tradingPosition?: TradingPosition; + + approvalTx: Nullable; +} + +export interface PerpetualTxCancelLimitOrder extends PerpetualTxBase { + method: PerpetualTxMethod.cancelLimitOrder; + /** LimitOrder digest/id */ + digest: string; +} + +export interface PerpetualTxDepositMargin extends PerpetualTxBase { + method: PerpetualTxMethod.deposit; + /** amount as wei string */ + amount: string; + + approvalTx: Nullable; +} + +export interface PerpetualTxWithdrawMargin extends PerpetualTxBase { + method: PerpetualTxMethod.withdraw; + /** amount as wei string */ + amount: string; +} + +export interface PerpetualTxWithdrawAllMargin extends PerpetualTxBase { + method: PerpetualTxMethod.withdrawAll; +} + +export type PerpetualTx = + | PerpetualTxTrade + | PerpetualTxCreateLimitOrder + | PerpetualTxCancelLimitOrder + | PerpetualTxDepositMargin + | PerpetualTxWithdrawMargin + | PerpetualTxWithdrawAllMargin; + +export const isPerpetualTx = (x: any): x is PerpetualTx => + x && typeof x === 'object' && PerpetualTxMethod[x.method] !== undefined; + +export const isTrade = ( + transaction: PerpetualTx, +): transaction is PerpetualTxTrade => + transaction.method === PerpetualTxMethod.trade; + +export const isDepositMargin = ( + transaction: PerpetualTx, +): transaction is PerpetualTxDepositMargin => + transaction.method === PerpetualTxMethod.deposit; + +export const isWithdrawMargin = ( + transaction: PerpetualTx, +): transaction is PerpetualTxWithdrawMargin => + transaction.method === PerpetualTxMethod.withdraw; + +export const isWithdrawAllMargin = ( + transaction: PerpetualTx, +): transaction is PerpetualTxWithdrawAllMargin => + transaction.method === PerpetualTxMethod.withdrawAll; + +export const isCreateLimitOrder = ( + transaction: PerpetualTx, +): transaction is PerpetualTxCreateLimitOrder => + transaction.method === PerpetualTxMethod.createLimitOrder; + +export const isCancelLimitOrder = ( + transaction: PerpetualTx, +): transaction is PerpetualTxCancelLimitOrder => + transaction.method === PerpetualTxMethod.cancelLimitOrder; diff --git a/src/app/pages/PerpetualPage/utils/contractUtils.tsx b/src/app/pages/PerpetualPage/utils/contractUtils.tsx index 6dd3c13f9..f24c35c19 100644 --- a/src/app/pages/PerpetualPage/utils/contractUtils.tsx +++ b/src/app/pages/PerpetualPage/utils/contractUtils.tsx @@ -14,7 +14,12 @@ import { import { getContract } from 'utils/blockchain/contract-helpers'; import marginTokenAbi from 'utils/blockchain/abi/MarginToken.json'; import { TradingPosition } from 'types/trading-position'; -import { getRequiredMarginCollateralWithGasFees } from './perpUtils'; +import { + MASK_CLOSE_ONLY, + MASK_MARKET_ORDER, + MASK_LIMIT_ORDER, + MASK_KEEP_POS_LEVERAGE, +} from './perpUtils'; import { perpUtils, TraderState, @@ -22,16 +27,40 @@ import { PerpParameters, LiqPoolState, } from '@sovryn/perpetual-swap'; -import { fromWei } from '../../../../utils/blockchain/math-helpers'; -import { CheckAndApproveResultWithError } from '../types'; +import { + fromWei, + numberFromWei, +} from '../../../../utils/blockchain/math-helpers'; +import { + CheckAndApproveResultWithError, + PERPETUAL_SLIPPAGE_DEFAULT, + PERPETUAL_CHAIN_ID, + PERPETUAL_CHAIN, + PerpetualTradeAnalysis, + PerpetualTradeType, +} from '../types'; import { BridgeNetworkDictionary } from '../../BridgeDepositPage/dictionaries/bridge-network-dictionary'; import { Trans } from 'react-i18next'; import { translations } from '../../../../locales/i18n'; import { numberToPercent } from '../../../../utils/display-text/format'; import perpetualManagerAbi from 'utils/blockchain/abi/PerpetualManager.json'; +import { + PerpetualTxMethod, + PerpetualTx, + PerpetualTxTrade, + PerpetualTxCreateLimitOrder, + PerpetualTxCancelLimitOrder, + PerpetualTxDepositMargin, + PerpetualTxWithdrawMargin, +} from '../types'; +import { ethGenesisAddress } from '../../../../utils/classifiers'; +import { SendTxResponseInterface } from '../../../hooks/useSendContractTx'; +import { PerpetualPair } from '../../../../utils/models/perpetual-pair'; +import { PerpetualPairDictionary } from '../../../../utils/dictionaries/perpetual-pair-dictionary'; const { - calculateSlippagePriceFromMidPrice, + calculateSlippagePrice, + createOrderDigest, getPrice, getMidPrice, isTraderInitialMarginSafe, @@ -179,15 +208,16 @@ export type Validation = { }; export const validatePositionChange = ( - amountChange: number, - marginChange: number, - targetLeverage: number, - slippage: number, + analysis: Pick< + PerpetualTradeAnalysis, + 'amountChange' | 'marginChange' | 'limitPrice' | 'orderCost' + >, availableBalance: number, traderState: TraderState, perpParameters: PerpParameters, ammState: AMMState, - useMetaTransactions: boolean, + tradeType: PerpetualTradeType | undefined = PerpetualTradeType.MARKET, + limitOrdersCount: number | undefined = 0, ) => { const result: Validation = { valid: true, @@ -196,23 +226,27 @@ export const validatePositionChange = ( errorMessages: [], }; + const isLimitOrder = [ + PerpetualTradeType.LIMIT, + PerpetualTradeType.STOP, + ].includes(tradeType); + if (ammState.indexS2PriceData === 0 || perpParameters.fLotSizeBC === 0) { return undefined; } - if (amountChange !== 0) { - const slippagePrice = calculateSlippagePriceFromMidPrice( + if (analysis.amountChange !== 0) { + const expectedPrice = getPrice( + analysis.amountChange, perpParameters, ammState, - slippage, - Math.sign(amountChange), ); - const expectedPrice = getPrice(amountChange, perpParameters, ammState); if ( - amountChange > 0 - ? expectedPrice > slippagePrice - : expectedPrice < slippagePrice + (analysis.amountChange > 0 + ? expectedPrice > analysis.limitPrice + : expectedPrice < analysis.limitPrice) && + !isLimitOrder ) { const midPrice = getMidPrice(perpParameters, ammState); const requiredSlippage = Math.abs(expectedPrice - midPrice) / midPrice; @@ -229,13 +263,40 @@ export const validatePositionChange = ( />, ); } + + if ( + isLimitOrder && + (analysis.amountChange > 0 + ? expectedPrice < analysis.limitPrice + : expectedPrice > analysis.limitPrice) + ) { + result.valid = false; + result.isWarning = true; + result.errors.push( + new Error('Expected price worse than the current market price!'), + ); + result.errorMessages?.push( + , + ); + } } + const isClosingTrade = + Math.abs(traderState.marginAccountPositionBC + analysis.amountChange) < + perpParameters.fLotSizeBC; + if ( + !isClosingTrade && !isTraderInitialMarginSafe( traderState, - marginChange, - amountChange, + analysis.marginChange, + analysis.amountChange, perpParameters, ammState, ) @@ -252,33 +313,40 @@ export const validatePositionChange = ( ); } - if (amountChange !== 0 || marginChange !== 0) { - const targetAmount = amountChange + traderState.marginAccountPositionBC; - - const requiredMargin = getRequiredMarginCollateralWithGasFees( - targetLeverage, - targetAmount, - perpParameters, - ammState, - traderState, - slippage, - useMetaTransactions, - ); - - if (requiredMargin > traderState.availableCashCC + availableBalance) { + if (analysis.amountChange !== 0 || analysis.marginChange !== 0) { + if (analysis.orderCost > availableBalance) { result.valid = false; result.isWarning = true; result.errors.push(new Error('Order cost exceeds total balance!')); result.errorMessages?.push( , ); } } + if (isLimitOrder && limitOrdersCount === 15) { + result.valid = false; + result.isWarning = false; + result.errors.push( + new Error('There is a maximal amount of 15 Limit/Stop orders.'), + ); + result.errorMessages?.push( + , + ); + } + return result; }; @@ -506,3 +574,175 @@ export const balanceCallData = ( parser: value => (value?.[0] ? String(value[0]) : '0'), }; }; + +export const getPerpetualTxContractName = ( + transaction?: PerpetualTx, +): ContractName => { + if (!transaction) { + return 'perpetualManager'; + } + + switch (transaction.method) { + case PerpetualTxMethod.trade: + case PerpetualTxMethod.deposit: + case PerpetualTxMethod.withdraw: + case PerpetualTxMethod.withdrawAll: + return 'perpetualManager'; + case PerpetualTxMethod.createLimitOrder: + case PerpetualTxMethod.cancelLimitOrder: + return PerpetualPairDictionary.get(transaction.pair).limitOrderBook; + } +}; + +const perpetualTradeArgs = ( + account: string, + transaction: PerpetualTxTrade, +): Parameters[0] => { + const { + isClosePosition = false, + /** amount as wei string */ + price, + amount, + leverage = 1, + slippage = PERPETUAL_SLIPPAGE_DEFAULT, + tradingPosition = TradingPosition.LONG, + pair: pairType, + } = transaction; + const pair = PerpetualPairDictionary.get(pairType); + const signedAmount = getSignedAmount(tradingPosition, amount); + const tradeDirection = Math.sign(signedAmount); + + const limitPrice = calculateSlippagePrice( + numberFromWei(price), + slippage, + tradeDirection, + ); + + const deadline = Math.round(Date.now() / 1000) + 86400; // 1 day + const timeNow = Math.round(Date.now() / 1000); + + let flags = isClosePosition ? MASK_CLOSE_ONLY : MASK_MARKET_ORDER; + if (transaction.keepPositionLeverage) { + flags = flags.or(MASK_KEEP_POS_LEVERAGE); + } + + const order = [ + pair.id, + account, + floatToABK64x64(signedAmount), + floatToABK64x64(limitPrice), + 0, + deadline, + ethGenesisAddress, + flags, + floatToABK64x64(leverage), + timeNow, + ]; + + return [order]; +}; + +const perpetualCreateLimitTradeArgs = async ( + account: string, + transaction: PerpetualTxCreateLimitOrder, +): Promise[0]> => { + const { + tradingPosition = TradingPosition.LONG, + amount, + limit, + trigger, + expiry, + created, + leverage = 1, + pair: pairType, + } = transaction; + const pair = PerpetualPairDictionary.get(pairType); + + const signedAmount = getSignedAmount(tradingPosition, amount); + + const createdSeconds = Math.floor(created / 1000); + const deadlineSeconds = createdSeconds + expiry * 24 * 60 * 60; + + const order = { + iPerpetualId: pair.id, + traderAddr: account, + fAmount: floatToABK64x64(signedAmount).toString(), + fLimitPrice: weiToABK64x64(limit).toString(), + fTriggerPrice: weiToABK64x64(trigger).toString(), + iDeadline: deadlineSeconds, + referrerAddr: ethGenesisAddress, + flags: MASK_LIMIT_ORDER, + fLeverage: floatToABK64x64(leverage).toString(), + createdTimestamp: createdSeconds, + }; + + const digest = await createOrderDigest( + order, + true, + getContract('perpetualManager').address, + PERPETUAL_CHAIN_ID, + ); + + const signature = await walletService.request({ + method: 'personal_sign', + params: [digest, account.toLowerCase()], + }); + + return [order, signature]; +}; + +const perpetualCancelLimitTradeArgs = async ( + account: string, + transaction: PerpetualTxCancelLimitOrder, +): Promise[0]> => { + const { digest } = transaction; + + const contract = getContract(getPerpetualTxContractName(transaction)); + const order = await bridgeNetwork.call( + PERPETUAL_CHAIN, + contract.address, + contract.abi, + 'orderOfDigest', + [digest], + ); + + const cancelDigest = await createOrderDigest( + order, + false, + getContract('perpetualManager').address, + PERPETUAL_CHAIN_ID, + ); + + const signature = await walletService.request({ + method: 'personal_sign', + params: [cancelDigest, account.toLowerCase()], + }); + + return [digest, signature]; +}; + +export const perpetualTransactionArgs = async ( + pair: PerpetualPair, + account: string, + transaction: PerpetualTx, +): Promise[0]> => { + switch (transaction.method) { + case PerpetualTxMethod.trade: + const tradeTx: PerpetualTxTrade = transaction; + return perpetualTradeArgs(account, tradeTx); + case PerpetualTxMethod.createLimitOrder: + const createLimitTx: PerpetualTxCreateLimitOrder = transaction; + return await perpetualCreateLimitTradeArgs(account, createLimitTx); + case PerpetualTxMethod.cancelLimitOrder: + const cancelLimitTx: PerpetualTxCancelLimitOrder = transaction; + return await perpetualCancelLimitTradeArgs(account, cancelLimitTx); + case PerpetualTxMethod.deposit: + const depositTx: PerpetualTxDepositMargin = transaction; + return [pair.id, weiToABK64x64(depositTx.amount)]; + case PerpetualTxMethod.withdraw: + const withdrawTx: PerpetualTxWithdrawMargin = transaction; + return [pair.id, weiToABK64x64(withdrawTx.amount)]; + case PerpetualTxMethod.withdrawAll: + return [pair.id]; + } +}; diff --git a/src/app/pages/PerpetualPage/utils/perpUtils.ts b/src/app/pages/PerpetualPage/utils/perpUtils.ts index a1f8397ed..67d5f9bb3 100644 --- a/src/app/pages/PerpetualPage/utils/perpUtils.ts +++ b/src/app/pages/PerpetualPage/utils/perpUtils.ts @@ -11,6 +11,7 @@ import { PerpParameters, perpUtils, } from '@sovryn/perpetual-swap'; +import { BigNumber } from 'ethers'; const { getTraderPnL, getMarkPrice, getRequiredMarginCollateral } = perpUtils; @@ -35,11 +36,13 @@ export function getTraderPnLInBC( * Considers the trading fees and gas fees. * @param {number} leverage - The leverage that the trader wants to achieve, given the position size * @param {number} targetPos - The trader's (signed) target position in base currency - * @param {number} base2collateral - If the base currency is different than the collateral. If base is BTC, collateral is USD, this would be 100000 (the USD amount for 1 BTC) * @param {PerpParameters} perpParams - Contains parameter of the perpetual * @param {AMMState} ammData - AMM state + * @param {TraderState} traderState - Trader state * @param {number} slippagePercent - optional. Specify slippage compared to mid-price that the trader is willing to accept * @param {boolean} useMetaTransactions - optional, default false. Adds gas fees to the total + * @param {boolean} accountForExistingMargin - optional, default false. If true, subtracts existing margin and clamp to 0 + * @param {boolean} accountForExistingPosition - optional, default false. If false, the margin for a trade is calculated * @returns {number} balance required to arrive at the perpetual contract to obtain requested leverage */ export function getRequiredMarginCollateralWithGasFees( @@ -50,6 +53,8 @@ export function getRequiredMarginCollateralWithGasFees( traderState: TraderState, slippagePercent: number = 0, useMetaTransactions: boolean = false, + accountForExistingMargin: boolean = false, + accountForExistingPosition: boolean = false, ) { let requiredCollateral = getRequiredMarginCollateral( leverage, @@ -58,7 +63,8 @@ export function getRequiredMarginCollateralWithGasFees( ammData, traderState, slippagePercent, - false, + accountForExistingMargin, + accountForExistingPosition, ); if (useMetaTransactions) { @@ -67,3 +73,11 @@ export function getRequiredMarginCollateralWithGasFees( return requiredCollateral; } + +// FIXME: move to @sovryn/perpetual-swap +export const MASK_CLOSE_ONLY = BigNumber.from(0x80000000); +export const MASK_MARKET_ORDER = BigNumber.from(0x40000000); +export const MASK_STOP_LOSS_ORDER = BigNumber.from(0x20000000); +export const MASK_TAKE_PROFIT_ORDER = BigNumber.from(0x10000000); +export const MASK_KEEP_POS_LEVERAGE = BigNumber.from(0x08000000); +export const MASK_LIMIT_ORDER = BigNumber.from(0x04000000); diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index d728d56a8..83822ac32 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -2110,12 +2110,10 @@ "unrealized": "Unrealized P&L", "realized": "Realized P&L", "actions": "Actions", - "editSize": "Size", "editLeverage": "Leverage", "editMargin": "Margin", "editClose": "Close", "tooltips": { - "editSize": "Change the position size to a value allowed by your current margin.", "editLeverage": "Change the leverage of your position. This action will either withdraw from your margin account or deposit to your margin account.", "editMargin": "Deposit margin or withdraw margin. This action will change the leverage of your position.", "editClose": "Close the entire open position or just partially.", @@ -2128,6 +2126,44 @@ "realized": "The realized P&L that was paid to your margin account. This includes fees and slippage. Funding payments are reported separately." } }, + "openOrders": "Open Orders", + "openOrdersTable": { + "dateTime": "Date & Time", + "symbol": "Symbol", + "orderType": "Type", + "orderState": "State", + "collateral": "Collateral", + "orderSize": "Size", + "limitPrice": "Limit Price", + "triggerPrice": "Trigger Price", + "transactionId": "Transaction ID", + "timeInForce": "Time in force", + "cancel": "Cancel", + "orderTypes": { + "limit": "Limit", + "stop": "Stop", + "buy": "Buy", + "sell": "Sell" + }, + "tableData": { + "market": "Market", + "limit": "Limit", + "stopLoss": "Stop Loss", + "liquidation": "Liquidation", + "buy": "BUY", + "sell": "SELL", + "day": "day", + "days": "days" + }, + "tooltips": { + "orderType": "Limit Buy Order, Limit Sell Order, Stop Buy Order or Stop Sell Order", + "collateral": "Currency of the margin collateral", + "orderSize": "Notional of the order followed by base currency", + "limitPrice": "Limit price in quote currency of the order", + "triggerPrice": "Either empty (\"-\"), or a price in quote currency", + "cancel": "Cancel the order" + } + }, "layout": { "button": "Customize layout", "showAmmDepth": "Show AMM Depth", @@ -2159,6 +2195,8 @@ "orderFailed": "Order failed", "increaseMargin": "Increase margin", "decreaseMargin": "Decrease margin", + "createLimitOrder": "Create limit order", + "cancelLimitOrder": "Cancel limit order", "editLeverage": "Edit leverage to {{leverage}}x", "approvalComplete": "Approval complete", "approvalFailed": "Approval failed" @@ -2226,28 +2264,34 @@ "slippage": "Slippage Settings" }, "text": { - "welcome1": "This is a preview of our perpetuals dashboard.\n The market data to the side is live.", - "welcome2": "In order to start trading perpetuals on Sovryn you need to:", - "welcome3": "<0>Connect a Hardware, web or mobile wallet <0>Be connected to the Polygon network <0>Fund your account by depositing BTC", - "welcome4": "New from Bitmex? <0>Read our getting started guide" + "welcome1": "To start trading:", + "welcome2": "<0>Fund your wallet", + "welcome3": "Navigate back to this page and connect your wallet via button below", + "welcome4": "New to perpetuals? <0>Read our getting started guide" }, "labels": { "pair": "Symbol:", "asset": "Asset to receive:", - "leverageSelector": "Position Leverage:", + "leverageSelector": "Trade Leverage:", "leverage": "Leverage:", - "orderValue": "Order Value:", + "orderSize": "Order Size:", "limitPrice": "Limit Price:", + "expiry": "Expiry:", + "days": "Days", "orderCost": "Order Cost:", - "maxTradeSize": "Max Trade Size:", + "maxTradeSize": "Max:", "minEntryPrice": "Min Entry Price:", "maxEntryPrice": "Max Entry Price:", "minLiquidationPrice": "Min Liquidation Price:", "liquidationPrice": "Liquidation Price:", "tradingFee": "Trading Fee:", + "relayerFee": "Relayer Fee:", "slippage": "Slippage Tolerance:", "balance": "Available Balance: <0/> <1/>", - "txFee": "Tx Fee: <0/> {{symbol}}" + "txFee": "Tx Fee: <0/> {{symbol}}", + "triggerPrice": "Trigger Price:", + "resultingPosition": "Resulting Position", + "keepPositionLeverage": "Keep Position Leverage" }, "buttons": { "buy": "Buy", @@ -2255,22 +2299,26 @@ "connect": "Connect Wallet", "market": "Market", "limit": "Limit", + "stop": "Stop", "buyMarket": "Buy Market", "buyLimit": "Buy Limit", + "buyStop": "Buy Stop", "sellMarket": "Sell Market", "sellLimit": "Sell Limit", + "sellStop": "Sell Stop", "minLeverage": "MIN", "slippageSettings": "Slippage Settings" }, "disabledState": { "emptyBalanceExplanation": "Fund your account to start trading", - "explanation1": "Only one position can be opened at a time", - "explanation2": "You can edit and close your position in the action section below" + "whitelistExplanation": "Sovryn Perpetuals trading is not available for unregistered users. Based on your current address, you are not part of the selected users that are granted access to the Sovryn Perpetuals trading platform. This will change in the future and you will be able to trade with us! Please check-in again soon here, on <0>sovryn.app, or follow us on <1>Twitter to stay informed." }, "tooltips": { - "orderValue": "The notional amount for your position.", + "orderSize": "The notional amount for your position.", "orderCost": "The maximal amount withdrawn from your wallet to pay for the trade. Consists of required margin and trading fee. This value depends on your slippage settings.", - "tradingFee": "The trading fee. If fees are paid in BTC, this includes gas costs." + "tradingFee": "The trading fee. If fees are paid in BTC, this includes gas costs.", + "relayerFee": "Limit/Stop orders are processed with the assistance of a referrer. The referrer is compensated with the relayer fee.", + "keepPositionLeverage": "You can keep the leverage of the current position by checking this box. If unchecked, the leverage decreases." } }, "reviewTrade": { @@ -2279,13 +2327,16 @@ "newOrder": "Review Order", "editSize": "Review Edit Size", "editLeverage": "Review Edit Leverage", - "editMargin": "Review Edit Margin" + "editMargin": "Review Edit Margin", + "cancelOrder": "Cancel Order" }, "close": "Close", "buy": "Buy", "sell": "Sell", "market": "Market", "limit": "Limit", + "stop": "Stop", + "cancel": "Cancel", "deposit": "Deposit", "withdraw": "Withdraw", "leverage": "Leverage", @@ -2302,7 +2353,10 @@ "liquidationPrice": "Liquidation Price:", "totalToReceive": "Total to Receive:", "maxEntryPrice": "Max Entry Price:", - "minEntryPrice": "Min Entry Price:" + "minEntryPrice": "Min Entry Price:", + "limitPrice": "Limit Price:", + "triggerPrice": "Trigger Price:", + "orderSize": "Order Size:" }, "confirm": "Confirm" }, @@ -2338,9 +2392,6 @@ "close": "Close" } }, - "editPositionSize": { - "title": "Edit Position Size" - }, "editMargin": { "title": "Edit Margin", "increase": "Increase", @@ -2382,17 +2433,18 @@ "orderHistoryTable": { "dateTime": "Date & Time", "symbol": "Symbol", - "orderType": "Order Type", - "orderState": "Order State", + "orderType": "Type", + "orderState": "State", "collateral": "Collateral", - "orderSize": "Order Size", + "orderSize": "Size", + "triggerPrice": "Trigger Price", "limitPrice": "Limit Price", - "execSize": "Exec Size", "execPrice": "Exec Price", - "orderId": "Order ID", + "orderId": "ID", "tableData": { "market": "Market", "limit": "Limit", + "stop": "Stop", "liquidation": "Liquidation", "buy": "BUY", "sell": "SELL" @@ -2416,7 +2468,10 @@ "warnings": { "priceExceedsSlippage": "Trade is likely to fail, because the expected price exceeds limit price! Update slippage to at least {{slippage}}, to avoid transaction failure.", "targetMarginUnsafe": "Transaction is likely to fail. The resulting position does not meet the required maintenance margin.", - "exceedsBalance": "Order cost exceeds total balance, your trade could fail in case of high slippage." + "exceedsBalance": "Order cost exceeds total balance, your trade could fail in case of high slippage.", + "exceedsBalanceLimitOrder": "Order cost exceeds total balance, your trade could fail.", + "limitPriceWorseThanCurrentPrice": "Limit order price is worse than the current market price.", + "maximalAmountOfLimitOrders": "There is a maximal amount of 15 Limit/Stop orders." } }, "claimOriginBanner": { diff --git a/src/store/global/transactions-store/types.ts b/src/store/global/transactions-store/types.ts index 78cb14f0b..499b76eb6 100644 --- a/src/store/global/transactions-store/types.ts +++ b/src/store/global/transactions-store/types.ts @@ -56,6 +56,8 @@ export enum TxType { PERPETUAL_DEPOSIT_COLLATERAL = 'perpetual_deposit_collateral', PERPETUAL_WITHDRAW_COLLATERAL = 'perpetual_withdraw_collateral', PERPETUAL_TRADE = 'perpetual_trade', + PERPETUAL_CREATE_LIMIT_ORDER = 'perpetual_create_limit_order', + PERPETUAL_CANCEL_LIMIT_ORDER = 'perpetual_cancel_limit_order', LIMIT_ORDER = 'limit_order', SETTLEMENT_WITDHRAW = 'settlement_withdraw', } diff --git a/src/utils/blockchain/abi/PerpetualLimitOrderBook.json b/src/utils/blockchain/abi/PerpetualLimitOrderBook.json index fdbbc8a4e..7c2a9d107 100644 --- a/src/utils/blockchain/abi/PerpetualLimitOrderBook.json +++ b/src/utils/blockchain/abi/PerpetualLimitOrderBook.json @@ -14,6 +14,12 @@ "name": "trader", "type": "address" }, + { + "indexed": false, + "internalType": "int128", + "name": "tradeAmount", + "type": "int128" + }, { "indexed": false, "internalType": "int128", @@ -26,6 +32,36 @@ "name": "triggerPrice", "type": "int128" }, + { + "indexed": false, + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "referrerAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "flags", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "int128", + "name": "leverage", + "type": "int128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "createdTimestamp", + "type": "uint256" + }, { "indexed": false, "internalType": "bytes32", @@ -160,11 +196,6 @@ "internalType": "bytes", "name": "signature", "type": "bytes" - }, - { - "internalType": "uint256", - "name": "_allowance", - "type": "uint256" } ], "name": "createLimitOrder", @@ -261,6 +292,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_digest", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_referrerAddr", + "type": "address" + } + ], + "name": "executeLimitOrderByDigest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -440,19 +489,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "marginToken", - "outputs": [ - { - "internalType": "contract IERC20Upgradeable", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/src/utils/blockchain/abi/PerpetualManager.json b/src/utils/blockchain/abi/PerpetualManager.json index 8a72f6a58..eb08d1294 100644 --- a/src/utils/blockchain/abi/PerpetualManager.json +++ b/src/utils/blockchain/abi/PerpetualManager.json @@ -1692,12 +1692,7 @@ }, { "internalType": "int128", - "name": "_fPrice", - "type": "int128" - }, - { - "internalType": "int128", - "name": "_fTradeAmountBC", + "name": "_fDepositRequired", "type": "int128" }, { @@ -1758,7 +1753,7 @@ "type": "tuple" } ], - "name": "depositMarginForNewLeveragedTrade", + "name": "depositMarginForOpeningTrade", "outputs": [ { "internalType": "bool", @@ -2043,37 +2038,37 @@ "inputs": [ { "internalType": "int128", - "name": "_fPosition", + "name": "_fPosition0", "type": "int128" }, { "internalType": "int128", - "name": "_fLeverage", + "name": "_fBalance0", "type": "int128" }, { "internalType": "int128", - "name": "_fPrice", + "name": "_fTradeAmount", "type": "int128" }, { "internalType": "int128", - "name": "_fS2", + "name": "_fTargetLeverage", "type": "int128" }, { "internalType": "int128", - "name": "_fS2Mark", + "name": "_fPrice", "type": "int128" }, { "internalType": "int128", - "name": "_fS3", + "name": "_fS2Mark", "type": "int128" }, { "internalType": "int128", - "name": "_fTotalFeeRate", + "name": "_fS3", "type": "int128" } ], @@ -3059,6 +3054,55 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "int128", + "name": "_fDeltaPos", + "type": "int128" + }, + { + "internalType": "int128", + "name": "_fTreasuryFeeRate", + "type": "int128" + }, + { + "internalType": "int128", + "name": "_fPnLPartRate", + "type": "int128" + }, + { + "internalType": "int128", + "name": "_fReferralRebate", + "type": "int128" + }, + { + "internalType": "address", + "name": "_referrerAddr", + "type": "address" + } + ], + "name": "getTradeFees", + "outputs": [ + { + "internalType": "int128", + "name": "", + "type": "int128" + }, + { + "internalType": "int128", + "name": "", + "type": "int128" + }, + { + "internalType": "int128", + "name": "", + "type": "int128" + } + ], + "stateMutability": "pure", + "type": "function" + }, { "inputs": [ { @@ -3403,11 +3447,6 @@ "internalType": "int128", "name": "_fShareAmount", "type": "int128" - }, - { - "internalType": "int128", - "name": "_fMinTokenAmount", - "type": "int128" } ], "name": "removeLiquidity", diff --git a/src/utils/blockchain/contracts.testnet.ts b/src/utils/blockchain/contracts.testnet.ts index c0ac1e687..5caa6492e 100644 --- a/src/utils/blockchain/contracts.testnet.ts +++ b/src/utils/blockchain/contracts.testnet.ts @@ -319,18 +319,23 @@ export const contracts = { abi: fastBtcMultisigAbi, }, perpetualManager: { - address: '0x16106ADFeEe3aC73dC82E17D97a6232a322C823E', + address: '0x706A1A05971C10d147CFd7B2f9726423B5FA06bE', abi: perpetualManagerAbi, chainId: ChainId.BSC_TESTNET, }, - perpetualLimitOrderBook: { - address: '0x7F6581BE4e0b7e83C74FB43a71995CCD7d93B2A6', + PERPETUALS_token: { + address: '0x8d546B14E6003f265F076900Bd66e015b45E9375', + abi: marginTokenAbi, + chainId: ChainId.BSC_TESTNET, + }, + perpetualLimitOrderBookBTCUSD: { + address: '0xb8212BAda584308B69392663D296B82b40060267', abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, - PERPETUALS_token: { - address: '0xaD884Eef9fE43b1FD735b40fe9e5e7D5eFb21E39', - abi: marginTokenAbi, + perpetualLimitOrderBookBNBUSD: { + address: '0x5F90B2296318A4Fc72CC2cA541C46CF041A96607', + abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, }; diff --git a/src/utils/blockchain/contracts.ts b/src/utils/blockchain/contracts.ts index eb74e8457..8c3e9ac98 100644 --- a/src/utils/blockchain/contracts.ts +++ b/src/utils/blockchain/contracts.ts @@ -335,14 +335,19 @@ export const contracts = { abi: perpetualManagerAbi, chainId: ChainId.BSC_MAINNET, }, - perpetualLimitOrderBook: { - address: '0xaC4eFd35d52Bba2F8032a45Edd9ceA78fB5ae282', - abi: perpetualLimitOrderBookAbi, - chainId: ChainId.BSC_TESTNET, - }, PERPETUALS_token: { address: '0x1431Aa8f066795d3aB94F8516B74FdCC5fD7897F', abi: marginTokenAbi, chainId: ChainId.BSC_MAINNET, }, + perpetualLimitOrderBookBTCUSD: { + address: '0xF683eED9590E2f90fe991E9e5A736f8BEDEa84Cd', + abi: perpetualLimitOrderBookAbi, + chainId: ChainId.BSC_MAINNET, + }, + perpetualLimitOrderBookBNBUSD: { + address: '0xA9a91c803a994332c1020D2DACFEBbfC53D65533', + abi: perpetualLimitOrderBookAbi, + chainId: ChainId.BSC_MAINNET, + }, }; diff --git a/src/utils/classifiers.ts b/src/utils/classifiers.ts index 4c3dda582..2463270f5 100644 --- a/src/utils/classifiers.ts +++ b/src/utils/classifiers.ts @@ -33,8 +33,8 @@ export const rpcNodes = { 'https://public-node.rsk.co/', ], 31: ['https://testnet.sovryn.app/rpc', 'https://public-node.testnet.rsk.co/'], - 56: 'wss://bsc.sovryn.app/mainnet', - 97: 'wss://bsctestnet.sovryn.app/websocket', + 56: 'wss://bsc.sovryn.app/mainnet/websocket', + 97: 'wss://bsc.sovryn.app/testnet/websocket', }; export const bitocracyUrl = @@ -105,9 +105,10 @@ export const gasLimit = { [TxType.DEPOSIT_COLLATERAL]: 850000, [TxType.WITHDRAW_COLLATERAL]: 1400000, [TxType.FAST_BTC_WITHDRAW]: 300000, - [TxType.PERPETUAL_TRADE]: 3000000, + [TxType.PERPETUAL_TRADE]: 3300000, [TxType.PERPETUAL_DEPOSIT_COLLATERAL]: 1700000, [TxType.PERPETUAL_WITHDRAW_COLLATERAL]: 2400000, + [TxType.PERPETUAL_CREATE_LIMIT_ORDER]: 3000000, [TxType.LIMIT_ORDER]: 3000000, [TxType.SETTLEMENT_WITDHRAW]: 70000, }; diff --git a/src/utils/dictionaries/perpetual-pair-dictionary.ts b/src/utils/dictionaries/perpetual-pair-dictionary.ts index 849c57f83..09d1924c3 100644 --- a/src/utils/dictionaries/perpetual-pair-dictionary.ts +++ b/src/utils/dictionaries/perpetual-pair-dictionary.ts @@ -35,6 +35,7 @@ export class PerpetualPairDictionary { 'USD', 'BTC', Asset.BTCS, + 'perpetualLimitOrderBookBTCUSD', { leverage: { min: 0.1, @@ -44,18 +45,20 @@ export class PerpetualPairDictionary { }, }, false, + false, ), ], [ PerpetualPairType.BNBUSD, new PerpetualPair( - '0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0', + '0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', PerpetualPairType.BNBUSD, 'BNB/USD', 'BNB/USD', 'USD', 'BNB', Asset.BTCS, + 'perpetualLimitOrderBookBNBUSD', { leverage: { min: 0.1, @@ -65,6 +68,7 @@ export class PerpetualPairDictionary { }, }, false, + true, ), ], ]); diff --git a/src/utils/metamaskHelpers.ts b/src/utils/metamaskHelpers.ts index fbf002c70..abb674af7 100644 --- a/src/utils/metamaskHelpers.ts +++ b/src/utils/metamaskHelpers.ts @@ -46,7 +46,10 @@ const networks = { symbol: 'BNB', decimals: 18, }, - rpcUrls: ['https://bsc-dataseed.binance.org/'], + rpcUrls: [ + 'https://bsc.sovryn.app/mainnet/', + 'https://bsc-dataseed.binance.org/', + ], blockExplorerUrls: ['https://bscscan.com'], }, ], @@ -60,7 +63,7 @@ const networks = { decimals: 18, }, rpcUrls: [ - 'https://bsctestnet.sovryn.app/', + 'https://bsc.sovryn.app/testnet/', 'https://data-seed-prebsc-2-s3.binance.org:8545/', 'https://data-seed-prebsc-2-s2.binance.org:8545/', 'https://data-seed-prebsc-2-s1.binance.org:8545/', diff --git a/src/utils/models/perpetual-pair.ts b/src/utils/models/perpetual-pair.ts index 324a5d7e4..5ace71ce0 100644 --- a/src/utils/models/perpetual-pair.ts +++ b/src/utils/models/perpetual-pair.ts @@ -5,6 +5,7 @@ import type { PerpetualPairConfig, } from '../dictionaries/perpetual-pair-dictionary'; import { TradingPosition } from '../../types/trading-position'; +import { ContractName } from '../types/contracts'; export class PerpetualPair { constructor( @@ -15,8 +16,10 @@ export class PerpetualPair { public readonly quoteAsset: string, public readonly baseAsset: string, public readonly collateralAsset: Asset, + public readonly limitOrderBook: ContractName, public readonly config: PerpetualPairConfig, public readonly deprecated: boolean = false, + public readonly isQuanto: boolean = false, ) {} public getContractForPosition(position: TradingPosition) { diff --git a/yarn.lock b/yarn.lock index 321429e09..01507e7f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4714,10 +4714,10 @@ dependencies: debug "^4.3.1" -"@sovryn/perpetual-swap@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sovryn/perpetual-swap/-/perpetual-swap-3.0.0.tgz#778a1993960fc346226b3d43f16cd1940a9fc15b" - integrity sha512-Opu5Q2Bp1FOOq4OPp3ne/AS3q1WKyIyL3+ngjXZaceVdfUddqbb80fqTk1jjxDmEndwhKHQv6GWnL6yOLfnhMw== +"@sovryn/perpetual-swap@3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@sovryn/perpetual-swap/-/perpetual-swap-3.0.5.tgz#113256c457f7ec7dc8458b160ad96d57e44843fe" + integrity sha512-xEIHdDu7bMjOQfNwwA7tzrvtwwczkxQpQTuip0BV9Ud662PZob0jGhE19fSUZOKEemzBT9bnbFAGeTTorNeIxg== dependencies: "@opengsn/provider" "^2.2.4" "@openzeppelin/contracts" "4.5.0" From 8c5489ecdf62fe793107228154ac2683a56781db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jun 2022 09:50:17 +0300 Subject: [PATCH 20/39] chore(deps): bump eventsource from 1.1.0 to 1.1.1 (#2235) Bumps [eventsource](https://github.com/EventSource/eventsource) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/EventSource/eventsource/releases) - [Changelog](https://github.com/EventSource/eventsource/blob/master/HISTORY.md) - [Commits](https://github.com/EventSource/eventsource/compare/v1.1.0...v1.1.1) --- updated-dependencies: - dependency-name: eventsource dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 01507e7f5..8e2b53ffa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12622,9 +12622,9 @@ events@^3.0.0, events@^3.1.0, events@^3.2.0, events@^3.3.0: integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== eventsource@^1.0.7: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf" - integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.1.tgz#4544a35a57d7120fba4fa4c86cb4023b2c09df2f" + integrity sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA== dependencies: original "^1.0.0" @@ -25442,7 +25442,15 @@ url-parse@1.4.4: querystringify "^2.0.0" requires-port "^1.0.0" -url-parse@^1.4.3, url-parse@^1.5.3: +url-parse@^1.4.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +url-parse@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== From fbfde6a848b08395f14e469a3aa2f2c744057acc Mon Sep 17 00:00:00 2001 From: tiltom Date: Fri, 3 Jun 2022 12:13:06 +0200 Subject: [PATCH 21/39] Deployment adjustments (#2238) --- .../pages/PerpetualPage/contexts/RecentTradesContext.tsx | 2 +- src/app/pages/PerpetualPage/types.ts | 2 +- src/utils/blockchain/contracts.testnet.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/pages/PerpetualPage/contexts/RecentTradesContext.tsx b/src/app/pages/PerpetualPage/contexts/RecentTradesContext.tsx index 3bd2295f3..475c3a1ce 100644 --- a/src/app/pages/PerpetualPage/contexts/RecentTradesContext.tsx +++ b/src/app/pages/PerpetualPage/contexts/RecentTradesContext.tsx @@ -144,7 +144,7 @@ export const RecentTradesContextProvider = props => { (trade: RecentTradesDataEntry) => { setDisconnected(false); setValue(state => { - const prevPrice = state.trades[0].price; + const prevPrice = state?.trades[0]?.price || trade.price; trade.priceChange = getPriceChange(prevPrice, trade.price); return { ...state, diff --git a/src/app/pages/PerpetualPage/types.ts b/src/app/pages/PerpetualPage/types.ts index 0b6597478..03e4b3b46 100644 --- a/src/app/pages/PerpetualPage/types.ts +++ b/src/app/pages/PerpetualPage/types.ts @@ -17,7 +17,7 @@ export const PERPETUAL_CHAIN_ID = export const PERPETUAL_PAYMASTER = isMainnet ? '' // TODO: add mainnet paymaster address - : '0x516181Fe2053B3b5CfD547f1220Fa9cdD38e7f9B'; + : '0x69E3cC9f027f61bDf5c965C735EBA6d35Ebcc826'; export const PERPETUAL_GAS_PRICE_DEFAULT = isMainnet ? undefined diff --git a/src/utils/blockchain/contracts.testnet.ts b/src/utils/blockchain/contracts.testnet.ts index 5caa6492e..24e585ccc 100644 --- a/src/utils/blockchain/contracts.testnet.ts +++ b/src/utils/blockchain/contracts.testnet.ts @@ -319,22 +319,22 @@ export const contracts = { abi: fastBtcMultisigAbi, }, perpetualManager: { - address: '0x706A1A05971C10d147CFd7B2f9726423B5FA06bE', + address: '0x396526f42D3b0a81a804d541b794977963eFAc68', abi: perpetualManagerAbi, chainId: ChainId.BSC_TESTNET, }, PERPETUALS_token: { - address: '0x8d546B14E6003f265F076900Bd66e015b45E9375', + address: '0xd0370a808203da14B703826eF77072ef5F09840D', abi: marginTokenAbi, chainId: ChainId.BSC_TESTNET, }, perpetualLimitOrderBookBTCUSD: { - address: '0xb8212BAda584308B69392663D296B82b40060267', + address: '0x1dC615EA11DA95A81249869728083B7e8B4cd307', abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, perpetualLimitOrderBookBNBUSD: { - address: '0x5F90B2296318A4Fc72CC2cA541C46CF041A96607', + address: '0xd49BF7858568829aF4aad2B5F5e794f9492B4790', abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, From a33d543eb551a5a33a1127b1e2bb8f201eef9b14 Mon Sep 17 00:00:00 2001 From: tiltom Date: Mon, 6 Jun 2022 11:32:29 +0200 Subject: [PATCH 22/39] Perpetuals - bug fixes vol. 3 (#2239) * Do not show limitPriceWorseThanCurrentPrice warning for stop orders * Change leverage selector back icon * Reset trade form if pair changes * Grey out expired orders * Fix LeverageSelector stepper * Fix 1 lot size position bug * Adjust expired open orders styling --- .../components/LeverageSelector/index.tsx | 18 +++--- .../components/TradeFormStep.tsx | 2 +- .../components/OpenOrderRow.tsx | 59 +++++++++++++------ .../components/TradeForm/index.tsx | 14 +++++ ...usePerpetual_calculateResultingPosition.ts | 30 +++++++--- .../PerpetualPage/utils/contractUtils.tsx | 2 +- 6 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/app/pages/PerpetualPage/components/LeverageSelector/index.tsx b/src/app/pages/PerpetualPage/components/LeverageSelector/index.tsx index 172e80c77..135ca1d25 100644 --- a/src/app/pages/PerpetualPage/components/LeverageSelector/index.tsx +++ b/src/app/pages/PerpetualPage/components/LeverageSelector/index.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react'; import { ReactComponent as EditIcon } from 'assets/images/edit.svg'; -import { ReactComponent as ArrowForwardIcon } from 'assets/images/arrow_forward.svg'; import { Slider, SliderType } from 'app/components/Form/Slider'; import { useTranslation } from 'react-i18next'; import { translations } from '../../../../../locales/i18n'; @@ -9,6 +8,8 @@ import { FormGroup } from '../../../../components/Form/FormGroup'; import classNames from 'classnames'; import { toNumberFormat } from '../../../../../utils/display-text/format'; import { roundToSmaller } from '../../../../../utils/blockchain/math-helpers'; +import { Icon } from 'app/components/Icon'; +import { faXmark } from '@fortawesome/free-solid-svg-icons'; const leverageStepDeviation = 0.05; @@ -81,7 +82,7 @@ export const LeverageSelector: React.FC = ({ ]); const onInputChange = useCallback( - event => onChange(Number(event.target.value)), + (amount: string) => onChange(Number(amount)), [onChange], ); @@ -110,12 +111,9 @@ export const LeverageSelector: React.FC = ({ >
{manual || sliderValue < 0 ? ( - <> - = ({ max={max} step={0.01} onBlur={onInputBlur} - onChange={onInputChange} + onChangeText={onInputChange} /> - +
) : ( <>
- - {t(translations.userAssets.convertDialog.cta)} - +
); diff --git a/src/app/components/UserAssets/UnWrapDialog.tsx b/src/app/components/UserAssets/components/UnWrapDialog.tsx similarity index 74% rename from src/app/components/UserAssets/UnWrapDialog.tsx rename to src/app/components/UserAssets/components/UnWrapDialog.tsx index aad94d0c7..3bd868b94 100644 --- a/src/app/components/UserAssets/UnWrapDialog.tsx +++ b/src/app/components/UserAssets/components/UnWrapDialog.tsx @@ -1,23 +1,23 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Dialog } from 'app/containers/Dialog'; -import { FormGroup } from '../Form/FormGroup'; +import { FormGroup } from '../../Form/FormGroup'; import { useTranslation } from 'react-i18next'; import { translations } from 'locales/i18n'; -import { AmountInput } from '../Form/AmountInput'; +import { AmountInput } from '../../Form/AmountInput'; import { Asset } from 'types'; import { useWeiAmount } from 'app/hooks/useWeiAmount'; import { noop } from 'app/constants'; import { useCanInteract } from 'app/hooks/useCanInteract'; import { bignumber } from 'mathjs'; -import { BuyButton, Img } from './styled'; -import image from 'assets/images/arrow-down.svg'; import { weiToFixed } from 'utils/blockchain/math-helpers'; import { TxFeeCalculator } from 'app/pages/MarginTradePage/components/TxFeeCalculator'; import { useAccount } from 'app/hooks/useAccount'; -import { useUnWrap } from '../../hooks/portfolio/useUnWrap'; -import { gasLimit } from '../../../utils/classifiers'; -import { TxType } from '../../../store/global/transactions-store/types'; -import { TransactionDialog } from '../TransactionDialog'; +import { useUnWrap } from 'app/hooks/portfolio/useUnWrap'; +import { gasLimit } from 'utils/classifiers'; +import { TxType } from 'store/global/transactions-store/types'; +import { TransactionDialog } from '../../TransactionDialog'; +import { Button, ButtonColor, ButtonSize, ButtonStyle } from '../../Button'; +import { Icon } from '../../Icon'; interface IConversionDialogProps { isOpen: boolean; @@ -76,7 +76,13 @@ export const UnWrapDialog: React.FC = ({ /> - Arrow +
+ +
= ({ />
- - {t(translations.userAssets.unwrapDialog.cta)} - +
diff --git a/src/app/components/UserAssets/components/UserAssetsTableRow.tsx b/src/app/components/UserAssets/components/UserAssetsTableRow.tsx new file mode 100644 index 000000000..76402547d --- /dev/null +++ b/src/app/components/UserAssets/components/UserAssetsTableRow.tsx @@ -0,0 +1,224 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { bignumber } from 'mathjs'; +import { Icon, Tooltip } from '@blueprintjs/core'; +import { translations } from 'locales/i18n'; +import { getTokenContractName } from 'utils/blockchain/contract-helpers'; +import { AssetDetails } from 'utils/models/asset-details'; +import { LoadableValue } from '../../LoadableValue'; +import { Asset } from 'types'; +import { weiToNumberFormat, weiToUSD } from 'utils/display-text/format'; +import { contractReader } from 'utils/sovryn/contract-reader'; +import { useAccount, useBlockSync } from 'app/hooks/useAccount'; +import { AssetRenderer } from '../../AssetRenderer/'; +import { Sovryn } from 'utils/sovryn'; +import { useMaintenance } from 'app/hooks/useMaintenance'; +import { Button, ButtonSize, ButtonStyle } from '../../Button'; +import { BridgeLink } from './BridgeLink'; +import { useDollarValue } from 'app/hooks/useDollarValue'; +import { CrossBridgeAsset } from 'app/pages/BridgeDepositPage/types/cross-bridge-asset'; + +import busdIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/busd.svg'; +import usdtIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/usdt.svg'; +import usdcIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/usdc.svg'; +import daiIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/dai.svg'; + +interface IUserAssetsTableRow { + item: AssetDetails; + onTransack: () => void; + onConvert: (asset: Asset) => void; + onUnWrap: () => void; +} + +const XUSD_ASSETS: { + asset: CrossBridgeAsset; + image: string; +}[] = [ + { asset: CrossBridgeAsset.DAI, image: daiIcon }, + { asset: CrossBridgeAsset.USDT, image: usdtIcon }, + { asset: CrossBridgeAsset.USDC, image: usdcIcon }, + { asset: CrossBridgeAsset.BUSD, image: busdIcon }, +]; + +export const UserAssetsTableRow: React.FC = ({ + item, + onConvert, + onTransack, + onUnWrap, +}) => { + const { t } = useTranslation(); + const account = useAccount(); + const [loading, setLoading] = useState(true); + const [tokens, setTokens] = useState('0'); + const blockSync = useBlockSync(); + + const asset = useMemo(() => { + return item.asset; + }, [item.asset]); + + const { checkMaintenance, States } = useMaintenance(); + const fastBtcLocked = checkMaintenance(States.FASTBTC); + + useEffect(() => { + const get = async () => { + setLoading(true); + let tokenA = '0'; + if (asset === Asset.RBTC) { + tokenA = await Sovryn.getWeb3().eth.getBalance(account); + } else { + tokenA = await contractReader + .call(getTokenContractName(asset), 'balanceOf', [account]) + .catch(e => { + console.error('failed to load balance of ', asset, e); + return '0'; + }); + } + + let tokenB = '0'; + if (asset === Asset.CSOV) { + tokenB = await contractReader.call('CSOV2_token', 'balanceOf', [ + account, + ]); + } + setTokens( + bignumber(tokenA) + .add(tokenB || '0') + .toFixed(0), + ); + setLoading(false); + }; + get().catch(); + }, [asset, account, blockSync]); + + const assetDollarValue = useDollarValue(asset, tokens); + + if (tokens === '0' && item.hideIfZero) return ; + + return ( + + +
+
+ +
+ {asset === Asset.XUSD && ( +
+ {XUSD_ASSETS.map(xusdAsset => ( + {xusdAsset.asset} + ))} + + + +
+ )} +
+ + + + + + + + +
+ {asset === Asset.RBTC && ( + <> +
+ + + ); +}; diff --git a/src/app/components/UserAssets/VestedAssets.tsx b/src/app/components/UserAssets/components/VestedAssets.tsx similarity index 79% rename from src/app/components/UserAssets/VestedAssets.tsx rename to src/app/components/UserAssets/components/VestedAssets.tsx index 79eecd84e..00b2eb487 100644 --- a/src/app/components/UserAssets/VestedAssets.tsx +++ b/src/app/components/UserAssets/components/VestedAssets.tsx @@ -1,25 +1,23 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { translations } from 'locales/i18n'; -import { useAccount, useIsConnected } from '../../hooks/useAccount'; -import { Skeleton } from '../PageSkeleton'; -import { useListOfUserVestings } from './Vesting/useListOfUserVestings'; -import { FullVesting } from './Vesting/types'; -import { VestedItem } from './Vesting/VestedItem'; -import { VestingWithdrawDialog } from './Vesting/VestingWithdrawDialog'; +import { useAccount, useIsConnected } from '../../../hooks/useAccount'; +import { Skeleton } from '../../PageSkeleton'; +import { useListOfUserVestings } from '../Vesting/useListOfUserVestings'; +import { FullVesting } from '../Vesting/types'; +import { VestedItem } from '../Vesting/VestedItem'; +import { VestingWithdrawDialog } from '../Vesting/VestingWithdrawDialog'; import type { Nullable } from 'types'; import { useContractPauseState } from 'app/hooks/useContractPauseState'; -import { AlertBadge } from '../AlertBadge/AlertBadge'; +import { AlertBadge } from '../../AlertBadge/AlertBadge'; import { discordInvite } from 'utils/classifiers'; -export function VestedAssets() { +export const VestedAssets: React.FC = () => { const { t } = useTranslation(); const { frozen } = useContractPauseState('staking'); const connected = useIsConnected(); const account = useAccount(); - const { loading, items } = useListOfUserVestings(); - const [open, setOpen] = useState(false); const [vesting, setVesting] = useState>(null); @@ -45,12 +43,9 @@ export function VestedAssets() { , + + x + , ]} /> @@ -66,9 +61,7 @@ export function VestedAssets() { {t(translations.userAssets.tableHeaders.dollarBalance)} - - {t(translations.userAssets.tableHeaders.action)} - + @@ -121,4 +114,4 @@ export function VestedAssets() { /> ); -} +}; diff --git a/src/app/components/UserAssets/index.module.scss b/src/app/components/UserAssets/index.module.scss deleted file mode 100644 index 63e545da1..000000000 --- a/src/app/components/UserAssets/index.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.actionLink { - @apply tw-text-primary tw-tracking-normal hover:tw-text-primary hover:tw-underline tw-p-0 tw-font-normal tw-font-body; -} diff --git a/src/app/components/UserAssets/index.tsx b/src/app/components/UserAssets/index.tsx index 2cc6263ac..a621b01b3 100644 --- a/src/app/components/UserAssets/index.tsx +++ b/src/app/components/UserAssets/index.tsx @@ -1,44 +1,18 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { bignumber } from 'mathjs'; -import { Icon, Tooltip } from '@blueprintjs/core'; - -import { translations } from '../../../locales/i18n'; -import { getTokenContractName } from '../../../utils/blockchain/contract-helpers'; -import { AssetsDictionary } from '../../../utils/dictionaries/assets-dictionary'; -import { AssetDetails } from '../../../utils/models/asset-details'; -import { LoadableValue } from '../LoadableValue'; -import { Asset } from '../../../types'; +import { translations } from 'locales/i18n'; +import { AssetsDictionary } from 'utils/dictionaries/assets-dictionary'; +import { Asset } from 'types'; import { Skeleton } from '../PageSkeleton'; -import { - weiToNumberFormat, - weiToUSD, -} from '../../../utils/display-text/format'; -import { contractReader } from '../../../utils/sovryn/contract-reader'; -import { - useAccount, - useBlockSync, - useIsConnected, -} from '../../hooks/useAccount'; -import { AssetRenderer } from '../AssetRenderer/'; -import { Sovryn } from '../../../utils/sovryn'; +import { useAccount, useIsConnected } from 'app/hooks/useAccount'; import { useMaintenance } from 'app/hooks/useMaintenance'; -import { Dialog } from '../../containers/Dialog'; +import { Dialog } from 'app/containers/Dialog'; import { Button, ButtonSize, ButtonStyle } from '../Button'; import { discordInvite } from 'utils/classifiers'; -import { ConversionDialog } from './ConversionDialog'; -import { BridgeLink } from './BridgeLink'; -import { UnWrapDialog } from './UnWrapDialog'; -import { useDollarValue } from '../../hooks/useDollarValue'; -import styles from './index.module.scss'; -import { CrossBridgeAsset } from 'app/pages/BridgeDepositPage/types/cross-bridge-asset'; - -import busdIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/busd.svg'; -import usdtIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/usdt.svg'; -import usdcIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/usdc.svg'; -import daiIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/dai.svg'; -import { Link } from 'react-router-dom'; +import { ConversionDialog } from './components/ConversionDialog'; +import { UnWrapDialog } from './components/UnWrapDialog'; import { TransakDialog } from '../TransakDialog/TransakDialog'; +import { UserAssetsTableRow } from './components/UserAssetsTableRow'; export const UserAssets: React.FC = () => { const { t } = useTranslation(); @@ -86,9 +60,7 @@ export const UserAssets: React.FC = () => { {t(translations.userAssets.tableHeaders.dollarBalance)} - - {t(translations.userAssets.tableHeaders.action)} - + @@ -113,7 +85,7 @@ export const UserAssets: React.FC = () => { {connected && account && assets.map(item => ( - setTransack(true)} @@ -136,9 +108,7 @@ export const UserAssets: React.FC = () => { /> { - setTransack(false); - }} + onClose={() => setTransack(false)} >

@@ -164,9 +134,7 @@ export const UserAssets: React.FC = () => { text={t(translations.modal.close)} size={ButtonSize.lg} style={ButtonStyle.inverted} - onClick={() => { - setTransack(false); - }} + onClick={() => setTransack(false)} />

@@ -174,177 +142,3 @@ export const UserAssets: React.FC = () => { ); }; - -interface IAssetRowProps { - item: AssetDetails; - onTransack: () => void; - onConvert: (asset: Asset) => void; - onUnWrap: () => void; -} - -const XUSD_ASSETS: { - asset: CrossBridgeAsset; - image: string; -}[] = [ - { asset: CrossBridgeAsset.DAI, image: daiIcon }, - { asset: CrossBridgeAsset.USDT, image: usdtIcon }, - { asset: CrossBridgeAsset.USDC, image: usdcIcon }, - { asset: CrossBridgeAsset.BUSD, image: busdIcon }, -]; - -const AssetRow: React.FC = ({ - item, - onTransack, - onConvert, - onUnWrap, -}) => { - const { t } = useTranslation(); - const account = useAccount(); - const [loading, setLoading] = useState(true); - const [tokens, setTokens] = useState('0'); - const blockSync = useBlockSync(); - - const { checkMaintenance, States } = useMaintenance(); - const fastBtcLocked = checkMaintenance(States.FASTBTC); - - useEffect(() => { - const get = async () => { - setLoading(true); - let tokenA: string = '0'; - if (item.asset === Asset.RBTC) { - tokenA = await Sovryn.getWeb3().eth.getBalance(account); - } else { - tokenA = await contractReader - .call(getTokenContractName(item.asset), 'balanceOf', [ - account, - ]) - .catch(e => { - console.error('failed to load balance of ', item.asset, e); - return '0'; - }); - } - - let tokenB: string = '0'; - if (item.asset === Asset.CSOV) { - tokenB = await contractReader.call('CSOV2_token', 'balanceOf', [ - account, - ]); - } - setTokens( - bignumber(tokenA) - .add(tokenB || '0') - .toFixed(0), - ); - setLoading(false); - }; - get().catch(); - }, [item.asset, account, blockSync]); - - const assetDollarValue = useDollarValue(item.asset, tokens); - - if (tokens === '0' && item.hideIfZero) - return ; - - return ( - - -
-
- -
- {item.asset === Asset.XUSD && ( -
- {XUSD_ASSETS.map(xusdAsset => ( - {xusdAsset.asset} - ))} - - - -
- )} -
- - - - - - - - -
- {item.asset === Asset.RBTC && ( - <> - - {fastBtcLocked ? ( - {t(translations.maintenance.fastBTCPortfolio)}} - > -
- {t(translations.common.deposit)} -
-
- ) : ( - - {t(translations.common.deposit)} - - )} - {fastBtcLocked ? ( - {t(translations.maintenance.fastBTCPortfolio)}} - > -
- {t(translations.common.withdraw)} -
-
- ) : ( - - {t(translations.common.withdraw)} - - )} - - )} - {[Asset.USDT, Asset.RDOC].includes(item.asset) && ( - - )} - {[Asset.SOV, Asset.ETH, Asset.XUSD, Asset.BNB].includes( - item.asset, - ) && } - {item.asset === Asset.WRBTC && ( - - )} -
- - - ); -}; diff --git a/src/app/components/UserAssets/styled.ts b/src/app/components/UserAssets/styled.ts deleted file mode 100644 index 47854c547..000000000 --- a/src/app/components/UserAssets/styled.ts +++ /dev/null @@ -1,34 +0,0 @@ -import styled, { css } from 'styled-components'; - -interface IBuyButtonProps { - disabled?: boolean; -} - -export const BuyButton = styled.button` - height: 3.125rem; - width: 100%; - color: #000000; - font-size: 1.25rem; - line-height: 1.25; - font-weight: 700; - background: var(--primary); - border-radius: 0.5rem; - transition: opacity 0.3s; - text-transform: uppercase; - padding: initial; - letter-spacing: 0; - &:hover { - opacity: 75%; - } - ${props => - props.disabled && - css` - cursor: not-allowed; - `} -`; - -export const Img = styled.img` - width: 2rem; - height: 2rem; - margin: 1.5rem auto 0.5rem; -`; diff --git a/src/app/containers/VestedHistory/components/VestedHistoryTable.tsx b/src/app/containers/VestedHistory/components/VestedHistoryTable.tsx new file mode 100644 index 000000000..63ee250d7 --- /dev/null +++ b/src/app/containers/VestedHistory/components/VestedHistoryTable.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { VestedHistoryTableRow } from './VestedHistoryTableRow'; + +interface IVestedHistoryTable { + items: any; +} + +export const VestedHistoryTable: React.FC = ({ + items, +}) => ( + <> + {items.map((item, index) => ( + + ))} + +); diff --git a/src/app/containers/VestedHistory/components/VestedHistoryTableRow.tsx b/src/app/containers/VestedHistory/components/VestedHistoryTableRow.tsx new file mode 100644 index 000000000..e6e2f94f2 --- /dev/null +++ b/src/app/containers/VestedHistory/components/VestedHistoryTableRow.tsx @@ -0,0 +1,98 @@ +import dayjs from 'dayjs'; +import { bignumber } from 'mathjs'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import logoSvg from 'assets/images/tokens/sov.svg'; +import { translations } from 'locales/i18n'; +import { TxStatus } from 'store/global/transactions-store/types'; +import { useCachedAssetPrice } from 'app/hooks/trading/useCachedAssetPrice'; +import { weiToUSD } from 'utils/display-text/format'; +import { AssetsDictionary } from 'utils/dictionaries/assets-dictionary'; +import { LoadableValue } from 'app/components/LoadableValue'; +import { numberFromWei } from 'utils/blockchain/math-helpers'; +import { Asset } from 'types/asset'; +import { LinkToExplorer } from 'app/components/LinkToExplorer'; +import { Icon } from 'app/components/Icon'; + +interface IVestedHistoryTableRow { + item: any; +} + +export const VestedHistoryTableRow: React.FC = ({ + item, +}) => { + const { t } = useTranslation(); + const SOV = AssetsDictionary.get(Asset.SOV); + const dollars = useCachedAssetPrice(Asset.SOV, Asset.USDT); + const dollarValue = useMemo(() => { + if (item.returnVal.amount === undefined) return ''; + return bignumber(item.returnVal.amount) + .mul(dollars.value) + .div(10 ** SOV.decimals) + .toFixed(0); + }, [dollars.value, item.returnVal.amount, SOV.decimals]); + + const getStatusIcon = (tx: TxStatus) => { + if (tx === TxStatus.FAILED) + return ; + if (tx === TxStatus.PENDING) + return ; + return ; + }; + + const getStatusText = (tx: TxStatus) => { + if (tx === TxStatus.FAILED) + return

{t(translations.common.failed)}

; + if (tx === TxStatus.PENDING) + return

{t(translations.common.pending)}

; + return

{t(translations.common.confirmed)}

; + }; + + return ( + + + {dayjs + .tz(item.eventDate, 'UTC') + .tz(dayjs.tz.guess()) + .format('L - LTS Z')} + + +
+
+ sov +
+
+ {item.type} +
+
+ + + {numberFromWei(item.returnValues.amount)} {t(translations.stake.sov)} +
≈{' '} + + + + + + +
+
+ {getStatusText(item.status)} + +
+
{getStatusIcon(item.status)}
+
+ + + ); +}; diff --git a/src/app/containers/VestedHistory/index.tsx b/src/app/containers/VestedHistory/index.tsx index c12bb1430..510f23925 100644 --- a/src/app/containers/VestedHistory/index.tsx +++ b/src/app/containers/VestedHistory/index.tsx @@ -1,33 +1,19 @@ import dayjs from 'dayjs'; -import { bignumber } from 'mathjs'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; - -import iconPending from 'assets/images/icon-pending.svg'; -import iconRejected from 'assets/images/icon-rejected.svg'; -import iconSuccess from 'assets/images/icon-success.svg'; -import logoSvg from 'assets/images/tokens/sov.svg'; import { translations } from 'locales/i18n'; -import { TxStatus } from 'store/global/transactions-store/types'; -import { useCachedAssetPrice } from '../../hooks/trading/useCachedAssetPrice'; -import { weiToUSD } from 'utils/display-text/format'; -import { AssetsDictionary } from 'utils/dictionaries/assets-dictionary'; -import { LoadableValue } from '../../components/LoadableValue'; -import { numberFromWei } from 'utils/blockchain/math-helpers'; import { ethGenesisAddress } from 'utils/classifiers'; import { eventReader } from 'utils/sovryn/event-reader'; - -import { Asset } from '../../../types/asset'; -import { LinkToExplorer } from '../../components/LinkToExplorer'; -import { Pagination } from '../../components/Pagination'; -import { SkeletonRow } from '../../components/Skeleton/SkeletonRow'; -import { useStaking_getStakes } from '../../hooks/staking/useStaking_getStakes'; -import { useVesting_getOriginVesting } from '../../hooks/staking/useVesting_getOriginVesting'; -import { useVesting_getRewards } from '../../hooks/staking/useVesting_getRewards'; -import { useVesting_getTeamVesting } from '../../hooks/staking/useVesting_getTeamVesting'; -import { useVesting_getVesting } from '../../hooks/staking/useVesting_getVesting'; -import { useVesting_getFourYearVesting } from '../../hooks/staking/useVesting_getFourYearVesting'; -import { useAccount } from '../../hooks/useAccount'; +import { Pagination } from 'app/components/Pagination'; +import { SkeletonRow } from 'app/components/Skeleton/SkeletonRow'; +import { useStaking_getStakes } from 'app/hooks/staking/useStaking_getStakes'; +import { useVesting_getOriginVesting } from 'app/hooks/staking/useVesting_getOriginVesting'; +import { useVesting_getRewards } from 'app/hooks/staking/useVesting_getRewards'; +import { useVesting_getTeamVesting } from 'app/hooks/staking/useVesting_getTeamVesting'; +import { useVesting_getVesting } from 'app/hooks/staking/useVesting_getVesting'; +import { useAccount } from 'app/hooks/useAccount'; +import { VestedHistoryTable } from './components/VestedHistoryTable'; +import { useVesting_getFourYearVesting } from 'app/hooks/staking/useVesting_getFourYearVesting'; export function VestedHistory() { const { t } = useTranslation(); @@ -74,7 +60,6 @@ export function VestedHistory() { useEffect(() => { async function getHistory() { setLoading(true); - try { if (rewards.value !== ethGenesisAddress) { await eventReader @@ -176,7 +161,7 @@ export function VestedHistory() { - + {loading && ( @@ -194,13 +179,13 @@ export function VestedHistory() { !loading && ( - History is empty. + {t(translations.stake.history.emptyHistory)} )} {currentHistory && !loading && ( <> - + )} @@ -222,101 +207,3 @@ export function VestedHistory() {
); } - -interface HisoryAsset { - item: any; -} - -const HisoryTableAsset: React.FC = ({ item }) => { - const { t } = useTranslation(); - const SOV = AssetsDictionary.get(Asset.SOV); - const dollars = useCachedAssetPrice(Asset.SOV, Asset.USDT); - const dollarValue = useMemo(() => { - if (item.returnVal.amount === undefined) return ''; - return bignumber(item.returnVal.amount) - .mul(dollars.value) - .div(10 ** SOV.decimals) - .toFixed(0); - }, [dollars.value, item.returnVal.amount, SOV.decimals]); - return ( - - - {dayjs - .tz(item.eventDate, 'UTC') - .tz(dayjs.tz.guess()) - .format('L - LTS Z')} - - -
-
- sov -
-
- {item.type} -
-
- - - {numberFromWei(item.returnValues.amount)} SOV -
≈{' '} - - - - - - -
-
- {!item.status && ( -

{t(translations.common.confirmed)}

- )} - {item.status === TxStatus.FAILED && ( -

{t(translations.common.failed)}

- )} - {item.status === TxStatus.PENDING && ( -

{t(translations.common.pending)}

- )} - -
-
- {!item.status && ( - Confirmed - )} - {item.status === TxStatus.FAILED && ( - Failed - )} - {item.status === TxStatus.PENDING && ( - Pending - )} -
-
- - - ); -}; - -interface History { - items: any; -} - -const HistoryTable: React.FC = ({ items }) => { - return ( - <> - {items.map((item, index) => { - return ( - - ); - })} - - ); -}; diff --git a/src/app/containers/WalletConnector/index.tsx b/src/app/containers/WalletConnector/index.tsx index 18457231b..903bf8e51 100644 --- a/src/app/containers/WalletConnector/index.tsx +++ b/src/app/containers/WalletConnector/index.tsx @@ -104,10 +104,12 @@ const WalletConnectorContainer: React.FC = ({ = ({ icon={faClone} text={t(translations.wallet.copy_address)} dataActionId="copy-wallet-address" + className="bp3-popover-dismiss" /> ( - 'OriginInvestorsClaim', - 'investorsAmountsList', - '0', - useAccount(), - ); - const [isOpen, setOpen] = useState(false); - return ( - <> - {Number(value) !== 0 && ( -
-
-
{t(translations.claimOriginBanner.text)}
-
- -
-
-
- )} - setOpen(false)} /> - - ); -} - -const Div = styled.div` - background-color: rgba(254, 192, 4, 0.25); - padding: 31px; - font-weight: 500; - border: 1px solid #e8e8e8; - border-radius: 0.5rem; - line-height: 1; - font-size: 1rem; - ${media.lg` - padding-left: 100px; - padding-right: 100px; - font-size: 1.5rem; - `} -`; - -const Button = styled.button.attrs(_ => ({ - className: 'tw-bg-primary', -}))` - margin-left: 25px; - border: 0; - border-radius: 0.75rem; - white-space: nowrap; - color: #000; - font-size: 1.25rem; - font-family: 900; - padding: 13px 24px; - line-height: 1; - transition: opacity 300ms; - &:hover { - opacity: 0.75; - } -`; diff --git a/src/app/containers/WalletPage/components/OriginClaimDialog.tsx b/src/app/containers/WalletPage/components/OriginClaimDialog.tsx deleted file mode 100644 index 881683e55..000000000 --- a/src/app/containers/WalletPage/components/OriginClaimDialog.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import React, { useCallback } from 'react'; -import { Classes, Overlay } from '@blueprintjs/core'; -import classNames from 'classnames'; -import { bignumber } from 'mathjs'; -import styles from './dialog.module.scss'; -import arrowDown from './arrow-down.svg'; -import { FieldGroup } from '../../../components/FieldGroup'; -import { DummyField } from '../../../components/DummyField'; -import { Button, ButtonSize, ButtonStyle } from '../../../components/Button'; -import { useAccount } from '../../../hooks/useAccount'; -import { useCacheCallWithValue } from '../../../hooks/useCacheCallWithValue'; -import { useSendContractTx } from '../../../hooks/useSendContractTx'; -import { - TxStatus, - TxType, -} from '../../../../store/global/transactions-store/types'; -import { SendTxProgress } from '../../../components/SendTxProgress'; -import { - toNumberFormat, - weiToNumberFormat, -} from '../../../../utils/display-text/format'; -import { LinkToExplorer } from '../../../components/LinkToExplorer'; - -const pricePerSov = 9736; - -interface Props { - isOpen: boolean; - onClose: () => void; -} - -export function OriginClaimDialog(props: Props) { - const account = useAccount(); - - const { value: sovAmount, loading } = useCacheCallWithValue( - 'OriginInvestorsClaim', - 'investorsAmountsList', - '0', - account, - ); - - const btcAmount = bignumber(sovAmount).div(1e18).mul(pricePerSov).toString(); - - const { send, ...tx } = useSendContractTx('OriginInvestorsClaim', 'claim'); - const handleSubmit = useCallback(() => { - if (!tx.loading) { - send([], { from: account }, { type: TxType.SOV_ORIGIN_CLAIM }); - } - }, [account, send, tx]); - - const handleClosing = useCallback(() => { - if (tx.status === TxStatus.CONFIRMED) { - tx.reset(); - } - }, [tx]); - - return ( - <> - props.onClose()} - onClosing={() => handleClosing} - className={Classes.OVERLAY_SCROLL_CONTAINER} - hasBackdrop - canOutsideClickClose - canEscapeKeyClose - > -
-
-
- {tx.status === TxStatus.CONFIRMED ? ( - <> -

Redemption Successful

- -
- - - -
- -

- You will now be able to see your vested SOV -
- in our{' '} - - staking - {' '} - page. -

- -

- Congratulations you are also now owner of SOV! -

- -
-
Tx Hash:
- -
- -
-
- - ) : ( - <> -
-

Redeem Origins SOV

-

This transaction requires rBTC for gas.

- - -
-
- {toNumberFormat(Number(btcAmount) / 1e8, 5)} -
-
- BTC -
-
-
-
-
- Arrow Down -
- - -
-
- {weiToNumberFormat(sovAmount, 2)} -
-
- SOV -
-
-
-
-
Tx Fee: 0.0001 (r)BTC
-
- - - -
-
- - )} -
-
-
-
- - ); -} diff --git a/src/app/containers/WalletPage/components/arrow-down.svg b/src/app/containers/WalletPage/components/arrow-down.svg deleted file mode 100644 index 977a8d175..000000000 --- a/src/app/containers/WalletPage/components/arrow-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/app/containers/WalletPage/components/dialog.module.scss b/src/app/containers/WalletPage/components/dialog.module.scss deleted file mode 100644 index e5e814668..000000000 --- a/src/app/containers/WalletPage/components/dialog.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -.container { - @apply tw-relative tw-w-full - tw-text-sov-white tw-tracking-normal - tw-bg-black tw-rounded-2xl; - - width: 468px; - padding: 30px; -} - -.close { - position: absolute; - top: 10px; - right: 20px; - border: 0; - background-color: transparent; - width: 24px; - height: 24px; -} - -.close:hover { - opacity: 0.9; -} - -.title { - margin-top: 15px; - margin-bottom: 45px; - font-size: 1.75rem; - line-height: 1.375; - font-weight: 500; - text-align: center; - text-transform: none; -} - -.wrapper { - padding: 0 35px; -} - -.arrowDown { - width: 30px; - margin: 25px auto; -} - -.txFee { - @apply tw-text-white tw-text-sm tw-font-normal; - margin: 20px 0 35px; -} - -.right { - right: 0; -} diff --git a/src/app/containers/WalletPage/index.tsx b/src/app/containers/WalletPage/index.tsx deleted file mode 100644 index 0678563a9..000000000 --- a/src/app/containers/WalletPage/index.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; -import { Helmet } from 'react-helmet-async'; -import { useTranslation } from 'react-i18next'; - -import { translations } from '../../../locales/i18n'; -import { SkeletonRow } from '../../components/Skeleton/SkeletonRow'; -import { SovGenerationNFTS } from '../../components/SovGenerationNFTS'; -import { UserAssets } from '../../components/UserAssets'; -import { VestedAssets } from '../../components/UserAssets/VestedAssets'; -import { useAccount, useIsConnected } from '../../hooks/useAccount'; -import { TopUpHistory } from './components/TopUpHistory'; -import { SwapHistory } from '../SwapHistory'; -import { VestedHistory } from '../VestedHistory'; -import { OriginClaimBanner } from './components/OriginClaimBanner'; - -import './_overlay.scss'; -import { Tabs } from 'app/components/Tabs'; - -export const WalletPage: React.FC = () => { - const { t } = useTranslation(); - const connected = useIsConnected(); - const account = useAccount(); - - const historyTabs = [ - { - id: 'topUpHistory', - label: t(translations.topUpHistory.meta.title), - content: , - }, - { - id: 'swapHistory', - label: t(translations.swapHistory.title), - content: , - }, - { - id: 'vestedHistory', - label: t(translations.vestedHistory.title), - content: , - }, - ]; - - const walletTabs = [ - { - id: 'userAssets', - label: t(translations.walletPage.tabs.userAssets), - content: , - }, - { - id: 'vestedAssets', - label: t(translations.walletPage.tabs.vestedAssets), - content: , - }, - { - id: 'userNFTS', - label: t(translations.walletPage.tabs.userNFTS), - content: , - }, - ]; - return ( - <> - - {t(translations.walletPage.meta.title)} - - - -
- -
- -
-
-

- {t(translations.userAssets.meta.title)} -

-
- {connected && account ? ( - - ) : ( -
-
- -
-
- )} -
- {connected && account && ( - - )} - - ); -}; diff --git a/src/app/index.tsx b/src/app/index.tsx index 2f1ccf887..fdcbc422a 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -28,7 +28,7 @@ import { WalletProvider } from './containers/WalletProvider'; import { NotFoundPage } from './components/NotFoundPage/Loadable'; import { EmailPage } from './containers/EmailPage'; -import { WalletPage } from './containers/WalletPage/Loadable'; +import { PortfolioPage } from './pages/PortfolioPage/Loadable'; import { SwapPage } from './containers/SwapPage/Loadable'; import { RewardPage } from './pages/RewardPage/Loadable'; @@ -100,7 +100,7 @@ export function App() { component={LiquidityMiningPage} /> - + import('./index'), - module => module.WalletPage, + module => module.PortfolioPage, { fallback: }, ); diff --git a/src/app/pages/PortfolioPage/components/OriginClaimBanner.tsx b/src/app/pages/PortfolioPage/components/OriginClaimBanner.tsx new file mode 100644 index 000000000..ba1e2cf69 --- /dev/null +++ b/src/app/pages/PortfolioPage/components/OriginClaimBanner.tsx @@ -0,0 +1,37 @@ +import React, { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { translations } from 'locales/i18n'; +import { useCacheCallWithValue } from 'app/hooks/useCacheCallWithValue'; +import { OriginClaimDialog } from './OriginClaimDialog'; +import { useAccount } from 'app/hooks/useAccount'; +import { Button } from 'app/components/Button'; + +export function OriginClaimBanner() { + const { t } = useTranslation(); + const { value } = useCacheCallWithValue( + 'OriginInvestorsClaim', + 'investorsAmountsList', + '0', + useAccount(), + ); + + const [isOpen, setOpen] = useState(false); + const onClose = useCallback(() => setOpen(!isOpen), [isOpen]); + + return ( + <> + {value !== '0' && ( +
+

+ {t(translations.claimOriginBanner.text)} +

+
+ )} + + + ); +} diff --git a/src/app/pages/PortfolioPage/components/OriginClaimDialog.tsx b/src/app/pages/PortfolioPage/components/OriginClaimDialog.tsx new file mode 100644 index 000000000..e52ee05e9 --- /dev/null +++ b/src/app/pages/PortfolioPage/components/OriginClaimDialog.tsx @@ -0,0 +1,210 @@ +import React, { useCallback } from 'react'; +import { Classes, Overlay } from '@blueprintjs/core'; +import { Trans, useTranslation } from 'react-i18next'; +import { translations } from 'locales/i18n'; +import { bignumber } from 'mathjs'; +import { FieldGroup } from 'app/components/FieldGroup'; +import { DummyField } from 'app/components/DummyField'; +import { Button, ButtonSize, ButtonStyle } from 'app/components/Button'; +import { useAccount } from 'app/hooks/useAccount'; +import { useCacheCallWithValue } from 'app/hooks/useCacheCallWithValue'; +import { useSendContractTx } from 'app/hooks/useSendContractTx'; +import { TxStatus, TxType } from 'store/global/transactions-store/types'; +import { toNumberFormat, weiToNumberFormat } from 'utils/display-text/format'; +import { LinkToExplorer } from 'app/components/LinkToExplorer'; +import { Icon } from 'app/components/Icon'; +import { TransactionDialog } from 'app/components/TransactionDialog'; +import { bitocracyUrl } from 'utils/classifiers'; + +const pricePerSov = 9736; +interface IOriginClaimDialogProps { + isOpen: boolean; + onClose: () => void; +} + +export const OriginClaimDialog: React.FC = ({ + isOpen, + onClose, +}) => { + const { t } = useTranslation(); + const account = useAccount(); + + const { value: sovAmount, loading } = useCacheCallWithValue( + 'OriginInvestorsClaim', + 'investorsAmountsList', + '0', + account, + ); + + const btcAmount = bignumber(sovAmount).div(1e18).mul(pricePerSov).toString(); + + const { send, ...tx } = useSendContractTx('OriginInvestorsClaim', 'claim'); + const handleSubmit = useCallback(() => { + if (!tx.loading) { + send([], { from: account }, { type: TxType.SOV_ORIGIN_CLAIM }); + } + }, [account, send, tx]); + + const handleClose = useCallback(() => { + if (tx.status === TxStatus.CONFIRMED) { + tx.reset(); + } + }, [tx]); + + return ( + <> + +
+
+
+ {tx.status === TxStatus.CONFIRMED ? ( + <> +

+ {t( + translations.portfolioPage.claimDialog + .redemptionSuccessful, + )} +

+ +

+ + x + , + ]} + /> +

+ +

+ {t(translations.portfolioPage.claimDialog.congratulations)} +

+ +
+
+ {t(translations.portfolioPage.claimDialog.txHash)} +
+ +
+ +
+
+ + ) : ( + <> +
+

+ {t( + translations.portfolioPage.claimDialog.redeemOriginsSOV, + )} +

+

+ {t(translations.portfolioPage.claimDialog.requiresRBTC)} +

+ + +
+
+ {toNumberFormat(Number(btcAmount) / 1e8, 5)} +
+
+ {t(translations.portfolioPage.claimDialog.btc)} +
+
+
+
+ + + +
+
+ {weiToNumberFormat(sovAmount, 2)} +
+
+ {t(translations.portfolioPage.claimDialog.sov)} +
+
+
+
+
+ {t(translations.common.fee, { amount: '0.0001' })} +
+
+ + + +
+
+ + )} +
+
+
+
+ + ); +}; diff --git a/src/app/containers/WalletPage/components/TopUpHistory.tsx b/src/app/pages/PortfolioPage/components/TopUpHistory.tsx similarity index 74% rename from src/app/containers/WalletPage/components/TopUpHistory.tsx rename to src/app/pages/PortfolioPage/components/TopUpHistory.tsx index 4a9a73dac..92acd4904 100644 --- a/src/app/containers/WalletPage/components/TopUpHistory.tsx +++ b/src/app/pages/PortfolioPage/components/TopUpHistory.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; - import { LinkToExplorer } from 'app/components/LinkToExplorer'; import { HistoryItem, @@ -15,14 +14,15 @@ import { DisplayDate } from 'app/components/ActiveUserLoanContainer/components/D import { toNumberFormat } from 'utils/display-text/format'; import { AssetSymbolRenderer } from 'app/components/AssetSymbolRenderer'; import { btcInSatoshis } from 'app/constants'; +import { HistoryItemType } from '../types'; + +//interval time to check history +const CHECK_TIME = 15e3; // 15 seconds export const TopUpHistory: React.FC = () => { const { t } = useTranslation(); - const account = useAccount(); - const { ready, getDepositHistory } = useDepositSocket(); - const [state, setState] = useState<{ items: HistoryItem[]; loading: boolean; @@ -59,7 +59,7 @@ export const TopUpHistory: React.FC = () => { const timer = setInterval(() => { getHistory(); - }, 15e3); + }, CHECK_TIME); return () => clearInterval(timer); } @@ -71,16 +71,14 @@ export const TopUpHistory: React.FC = () => { - - - - + - @@ -116,59 +114,68 @@ export const TopUpHistory: React.FC = () => { )} {sortedHistoryItems.map(item => ( - - diff --git a/src/app/pages/PortfolioPage/index.tsx b/src/app/pages/PortfolioPage/index.tsx new file mode 100644 index 000000000..1db1ea539 --- /dev/null +++ b/src/app/pages/PortfolioPage/index.tsx @@ -0,0 +1,116 @@ +import React, { useMemo } from 'react'; +import { Helmet } from 'react-helmet-async'; +import { useTranslation } from 'react-i18next'; +import { translations } from 'locales/i18n'; +import { SkeletonRow } from 'app/components/Skeleton/SkeletonRow'; +import { SovGenerationNFTS } from 'app/components/SovGenerationNFTS'; +import { UserAssets } from 'app/components/UserAssets'; +import { VestedAssets } from 'app/components/UserAssets/components/VestedAssets'; +import { useAccount, useIsConnected } from 'app/hooks/useAccount'; +import { TopUpHistory } from './components/TopUpHistory'; +import { VestedHistory } from 'app/containers/VestedHistory'; +import { OriginClaimBanner } from './components/OriginClaimBanner'; +import { Button, ButtonStyle } from 'app/components/Button'; +import { Tabs } from 'app/components/Tabs'; + +export const PortfolioPage: React.FC = () => { + const { t } = useTranslation(); + const connected = useIsConnected(); + const account = useAccount(); + + const historyTabs = useMemo( + () => [ + { + id: 'topUpHistory', + label: t(translations.topUpHistory.meta.title), + content: , + }, + { + id: 'vestedHistory', + label: t(translations.vestedHistory.title), + content: , + }, + ], + [t], + ); + + const portfolioTabs = useMemo( + () => [ + { + id: 'userAssets', + label: t(translations.portfolioPage.tabs.userAssets), + content: , + }, + { + id: 'vestedAssets', + label: t(translations.portfolioPage.tabs.vestedAssets), + content: , + }, + { + id: 'userNFTS', + label: t(translations.portfolioPage.tabs.userNFTS), + content: , + }, + ], + [t], + ); + + return ( + <> + + {t(translations.portfolioPage.meta.title)} + + + +
+
+ +
+ +
+ +
+ {connected && account ? ( + + ) : ( +
+
+ +
+
+ )} +
+ {connected && account && ( + + )} + + ); +}; diff --git a/src/app/pages/PortfolioPage/types.ts b/src/app/pages/PortfolioPage/types.ts new file mode 100644 index 000000000..c65eeae7b --- /dev/null +++ b/src/app/pages/PortfolioPage/types.ts @@ -0,0 +1,4 @@ +export enum HistoryItemType { + DEPOSIT = 'deposit', + TRANSFER = 'transfer', +} diff --git a/src/app/pages/RewardPage/index.tsx b/src/app/pages/RewardPage/index.tsx index bb1785fe8..e57a555f8 100644 --- a/src/app/pages/RewardPage/index.tsx +++ b/src/app/pages/RewardPage/index.tsx @@ -11,8 +11,6 @@ import { RewardTab } from './components/RewardTab'; import { Tab } from './components/Tab'; import { RewardTabType } from './types'; -import imgSov from 'assets/images/reward/sov.svg'; -import imgBtc from 'assets/images/reward/Bitcoin.svg'; import styles from './index.module.scss'; import { FeesEarnedTab } from './components/FeesEarnedTab'; import { Asset } from 'types'; @@ -20,6 +18,7 @@ import { useGetLiquidSovClaimAmount } from './hooks/useGetLiquidSovClaimAmount'; import { useGetRewardSovClaimAmount } from './hooks/useGetRewardSovClaimAmount'; import { useGetFeesEarnedClaimAmount } from './hooks/useGetFeesEarnedClaimAmount'; import { useAccount } from 'app/hooks/useAccount'; +import { Button, ButtonStyle } from 'app/components/Button'; export function RewardPage() { const { t } = useTranslation(); @@ -54,15 +53,24 @@ export function RewardPage() { /> -
- SOV - BTC +
+
-
+
-
+
staking page.", + "congratulations": " Congratulations you are also now owner of SOV!", + "checkSov": "Check SOV", + "txHash": "Tx Hash:", + "redeemOriginsSOV": "Redeem Origins SOV", + "requiresRBTC": "This transaction requires rBTC for gas.", + "originsBTCdeposit": "Origins BTC deposit:", + "claimedSov": "SOV claimed at {{count}} sats", + "sov": "SOV", + "btc": "BTC" } }, "salesPage": { @@ -1218,8 +1236,7 @@ "asset": "Asset", "totalBalance": "Total Balance", "dollarBalance": "Dollar Balance", - "lockedAmount": "Locked Amount", - "action": "Action" + "lockedAmount": "Locked Amount" }, "actions": { "deposit": "Deposit", @@ -1233,6 +1250,8 @@ "redeemRBTC": "Redeem RBTC", "withdraw": "Withdraw" }, + "sendMessage": "Send {{asset}} from your connected address in the RSK network", + "receiveMessage": "Receive {{asset}} to your connected address in the RSK network", "convertDialog": { "title": "Convert", "from": "From", @@ -1266,6 +1285,18 @@ "hash": "RSK Relay Hash", "cta": "Close" } + }, + "gallery": { + "title": "SOV Generation 01 NFT's", + "community": "Community", + "hero": "Hero", + "superhero": "Superhero", + "preOrder": "SOV Genesis Pre-Order", + "purchaseLimit": "Purchase Limit:", + "tier": "Tier", + "sovOG": "SOV-OG", + "btc": "BTC", + "emptyGallery": "Gallery is empty." } }, "swapHistory": { @@ -1284,7 +1315,7 @@ "recentSwapHistory": "Recent Swap History" }, "vestedHistory": { - "title": "Vested History", + "title": "Vesting History", "tableHeaders": { "time": "Date/Time", "vestingSchedule": "Type", @@ -1321,7 +1352,8 @@ }, "emptyState": "History is empty.", "walletHistory": "Wallet must be connected.", - "loading": "Loading..." + "loading": "Loading...", + "btc": "BTC" }, "validationErrors": { "insufficientBalance": "insufficient funds available", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 3fff99bba..93f64b507 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -381,7 +381,7 @@ "title": "Re-Carga" } }, - "walletPage": { + "portfolioPage": { "meta": { "title": "My Wallet", "description": "Information about connected wallet" diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 1dd6a0546..be6cdca41 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -832,7 +832,7 @@ "my_wallet": "Mon portefeuille", "referrals": "Parrainer un ami" }, - "walletPage": { + "portfolioPage": { "meta": { "description": "Informations sur le portefeuille connecté", "title": "Mon portefeuille" diff --git a/src/locales/pt_br/translation.json b/src/locales/pt_br/translation.json index e6f3e35a4..b1ad6a75e 100644 --- a/src/locales/pt_br/translation.json +++ b/src/locales/pt_br/translation.json @@ -1153,7 +1153,7 @@ "title": "Depositar" } }, - "walletPage": { + "portfolioPage": { "meta": { "title": "Portfólio", "description": "Informações sobre a carteira conectada" From 2432ea6f45c0517a35f08686de6b74c480b75702 Mon Sep 17 00:00:00 2001 From: Victor Creed <69458664+creed-victor@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:44:47 +0300 Subject: [PATCH 31/39] chore: change perpetual contract addresses (#2261) --- src/app/pages/PerpetualPage/types.ts | 2 +- src/locales/en/translation.json | 2 +- src/utils/blockchain/contracts.testnet.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/pages/PerpetualPage/types.ts b/src/app/pages/PerpetualPage/types.ts index f3007d91a..4f5882c9b 100644 --- a/src/app/pages/PerpetualPage/types.ts +++ b/src/app/pages/PerpetualPage/types.ts @@ -17,7 +17,7 @@ export const PERPETUAL_CHAIN_ID = export const PERPETUAL_PAYMASTER = isMainnet ? '' // TODO: add mainnet paymaster address - : '0xD76464394C9792781D1ed33bD73Fe1ff9D13c554'; + : '0x402e4370f6871Ff59Db75aE578e038E101454dc1'; export const PERPETUAL_GAS_PRICE_DEFAULT = isMainnet ? undefined diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index eb13c6733..9756f76f5 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -2352,7 +2352,7 @@ "orderCostLimit": "A conservative estimate of the amount withdrawn from your wallet to pay for the trade. Consists of estimated margin and trading fee at the time of execution.", "tradingFee": "The trading fee. If fees are paid in BTC, this includes gas costs.", "relayerFee": "Limit/Stop orders are processed with the assistance of a referrer. The referrer is compensated with the relayer fee.", - "keepPositionLeverage": "You can keep the leverage of the current position by checking this box. If unchecked, the leverage decreases.", + "keepPositionLeverage": "You can keep the leverage of the current position by checking this box.", "maxTradeSize": "Maximal order size currently allowed by the system.", "leverage": "If at the time of order execution, there is an existing position, the position leverage will remain unchanged if the order partially closes the existing position. If instead, the order increases the existing position size, the order will add margin according to your choice of trade leverage. The resulting position leverage depends on how much margin was added with the trade and the amount of margin in the existing position before the trade.", "limitPrice": "The execution price will be at or below the limit price for buy orders, at or above for sell orders.", diff --git a/src/utils/blockchain/contracts.testnet.ts b/src/utils/blockchain/contracts.testnet.ts index 06209f02e..ab13a041a 100644 --- a/src/utils/blockchain/contracts.testnet.ts +++ b/src/utils/blockchain/contracts.testnet.ts @@ -319,7 +319,7 @@ export const contracts = { abi: fastBtcMultisigAbi, }, perpetualManager: { - address: '0x339bFb76EcECd31266937b2481C7170315d6E3d2', + address: '0x28193dcc74202cCa433279010917977137394d2E', abi: perpetualManagerAbi, chainId: ChainId.BSC_TESTNET, }, @@ -329,12 +329,12 @@ export const contracts = { chainId: ChainId.BSC_TESTNET, }, perpetualLimitOrderBookBTCUSD: { - address: '0xaB15b6F5364B4AFa82E233C25534Cd61Ae18926D', + address: '0x29a0BC198E7ae04E91d2924da8093FAAF9d94950', abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, perpetualLimitOrderBookBNBUSD: { - address: '0x809B6C07666e5cD4A26D911E64B3AFc22e4B6893', + address: '0xe2b95C2bcfEbb2fF73e103d86a2Eb2b82Cb34Dd5', abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, From 3aa2569c7bcefe34173a5c6eb96cfef6a22c474a Mon Sep 17 00:00:00 2001 From: Victor Creed <69458664+creed-victor@users.noreply.github.com> Date: Wed, 15 Jun 2022 18:15:06 +0300 Subject: [PATCH 32/39] feat(xusd): swap xusd amm pool contracts (#2209) * feat(xusd): swap xusd amm pool contracts * feat(xusd): swap xusd loan token contract * feat(xusd): change xusd token in aggregator * fix(xusd): show previous xusd pairs as deprecated in testnet Co-authored-by: soulBit --- src/app/components/AssetValue/types.ts | 2 + src/app/components/PairNavbar/Pair.tsx | 1 - .../components/SwapSelector/index.tsx | 5 ++ .../dictionaries/assets/eth-to-rsk.testnet.ts | 3 +- .../dictionaries/assets/rsk-to-bsc.testnet.ts | 2 +- .../dictionaries/assets/rsk-to-eth.testnet.ts | 2 +- src/types/asset.ts | 1 + src/utils/blockchain/contracts.testnet.ts | 13 ++++- src/utils/dictionaries/amm/testnet.ts | 9 ++-- src/utils/dictionaries/assets-dictionary.ts | 17 ++++++ .../dictionaries/lending-pool-dictionary.ts | 15 ++++++ .../dictionaries/trading-pair-dictionary.ts | 52 +++++++++++++++++++ 12 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/app/components/AssetValue/types.ts b/src/app/components/AssetValue/types.ts index 8b73d6154..41149abe9 100644 --- a/src/app/components/AssetValue/types.ts +++ b/src/app/components/AssetValue/types.ts @@ -19,6 +19,8 @@ export const AssetDecimals: { [key in Asset]: number } = { RIF: 2, MYNT: 2, TRADING: 1, + /** @deprecated */ + XUSD_legacy: 2, }; export enum AssetValueMode { diff --git a/src/app/components/PairNavbar/Pair.tsx b/src/app/components/PairNavbar/Pair.tsx index eff0a6746..13b70dffe 100644 --- a/src/app/components/PairNavbar/Pair.tsx +++ b/src/app/components/PairNavbar/Pair.tsx @@ -22,7 +22,6 @@ export const Pair: React.FC = ({ tradingType, type }) => { type === TradingType.SPOT ? spotPairs[tradingType] : marginPairs[tradingType]; - const [sourceToken, targetToken] = pair; const sourceTokenLogo = useMemo( diff --git a/src/app/containers/SwapFormContainer/components/SwapSelector/index.tsx b/src/app/containers/SwapFormContainer/components/SwapSelector/index.tsx index 82b372e9b..af8490a03 100644 --- a/src/app/containers/SwapFormContainer/components/SwapSelector/index.tsx +++ b/src/app/containers/SwapFormContainer/components/SwapSelector/index.tsx @@ -139,6 +139,11 @@ export const SwapSelector: React.FC = ({ const assetDetails = AssetsDictionary.getByTokenContractAddress( pair.base_id, ); + + if (!assetDetails || !assetDetailsRBTC) { + return null; + } + //as we don't have RBTC as source, we make a separate reverted pair from USDT_RBTC let pairDivRBTC; let pairDiv; diff --git a/src/app/pages/BridgeDepositPage/dictionaries/assets/eth-to-rsk.testnet.ts b/src/app/pages/BridgeDepositPage/dictionaries/assets/eth-to-rsk.testnet.ts index ef8642d79..9ffc95a84 100644 --- a/src/app/pages/BridgeDepositPage/dictionaries/assets/eth-to-rsk.testnet.ts +++ b/src/app/pages/BridgeDepositPage/dictionaries/assets/eth-to-rsk.testnet.ts @@ -6,7 +6,8 @@ import daiIcon from './icons/dai.svg'; import { AssetModel } from '../../types/asset-model'; import { CrossBridgeAsset } from '../../types/cross-bridge-asset'; -export const xusdAggregatorRSK = '0xca8b437d9d586b938CE000e765476A0594856b51'; +// @dev older version of xusd aggregator 0xca8b437d9d586b938CE000e765476A0594856b51 +export const xusdAggregatorRSK = '0x1572D7E4a78A8AD14AE722E6fE5f5600a2c7A149'; export const ethsAggregatorRSK = '0x04D92DaA8f3Ef7bD222195e8D1DbE8D89A8CebD3'; const ethToRskTestnetAssets = [ diff --git a/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-bsc.testnet.ts b/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-bsc.testnet.ts index cfa9334fe..5d3f13df1 100644 --- a/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-bsc.testnet.ts +++ b/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-bsc.testnet.ts @@ -66,7 +66,7 @@ const rskToBscTestnet = [ xusdIcon, 18, 4, - '0x74858FE37d391f81F89472e1D8BC8Ef9CF67B3b1', + '0xa9262CC3fB54Ea55B1B0af00EfCa9416B8d59570', false, CrossBridgeAsset.XUSD, true, diff --git a/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-eth.testnet.ts b/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-eth.testnet.ts index 77d771cb9..fe7c44d58 100644 --- a/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-eth.testnet.ts +++ b/src/app/pages/BridgeDepositPage/dictionaries/assets/rsk-to-eth.testnet.ts @@ -45,7 +45,7 @@ const rskToEthTestnetAssets = [ xusdIcon, 18, 4, - '0x74858FE37d391f81F89472e1D8BC8Ef9CF67B3b1', + '0xa9262CC3fB54Ea55B1B0af00EfCa9416B8d59570', false, CrossBridgeAsset.XUSD, true, diff --git a/src/types/asset.ts b/src/types/asset.ts index e6432479b..27d281e93 100644 --- a/src/types/asset.ts +++ b/src/types/asset.ts @@ -9,6 +9,7 @@ export enum Asset { RDOC = 'RDOC', USDT = 'USDT', XUSD = 'XUSD', + XUSD_legacy = 'XUSD_legacy', BPRO = 'BPRO', SOV = 'SOV', MOC = 'MOC', diff --git a/src/utils/blockchain/contracts.testnet.ts b/src/utils/blockchain/contracts.testnet.ts index ab13a041a..24e53a530 100644 --- a/src/utils/blockchain/contracts.testnet.ts +++ b/src/utils/blockchain/contracts.testnet.ts @@ -120,11 +120,22 @@ export const contracts = { blockNumber: 1406290, }, XUSD_token: { - address: '0x74858FE37d391f81F89472e1D8BC8Ef9CF67B3b1', + address: '0xa9262cc3fb54ea55b1b0af00efca9416b8d59570', abi: erc20TokenAbi, blockNumber: 1408174, }, XUSD_lending: { + address: '0xE27428101550f8104A6d06D830e2E0a097e1d006', + abi: LoanTokenLogicStandard, + blockNumber: 1406290, + }, + /** @deprecated */ + XUSD_legacy_token: { + address: '0x74858FE37d391f81F89472e1D8BC8Ef9CF67B3b1', + abi: erc20TokenAbi, + blockNumber: 1408174, + }, + XUSD_legacy_lending: { address: '0x9bD0cE087b14ef67C3D37C891139AaE7d94a961A', abi: LoanTokenLogicStandard, blockNumber: 1406290, diff --git a/src/utils/dictionaries/amm/testnet.ts b/src/utils/dictionaries/amm/testnet.ts index 13266b4ab..15b7c4623 100644 --- a/src/utils/dictionaries/amm/testnet.ts +++ b/src/utils/dictionaries/amm/testnet.ts @@ -19,11 +19,14 @@ export const testnetAmm = [ Asset.RBTC, 1, AppMode.TESTNET, - '0xe5e750ead0e564e489b0776273e4a10f3f3d4028', - '0x6601Ccd32342d644282e82Cb05A3Dd88964D18c1', + '0xD877fd00ECF08eD78BF549fbc74bac3001aBBb07', + '0xb89D193c8a9Ae3fadF73B23519c215a0B7DD1B37', ) .setLootDropColor(LootDropColors.Yellow) - .setPreviousConverters(['0x9a1aE300b23F4C676186e6d417ac586889aAfF42']), + .setPreviousConverters([ + '0x9a1aE300b23F4C676186e6d417ac586889aAfF42', + '0xe5e750ead0e564e489b0776273e4a10f3f3d4028', + ]), new AmmLiquidityPool( Asset.FISH, Asset.RBTC, diff --git a/src/utils/dictionaries/assets-dictionary.ts b/src/utils/dictionaries/assets-dictionary.ts index 875d1cb46..c213f2144 100644 --- a/src/utils/dictionaries/assets-dictionary.ts +++ b/src/utils/dictionaries/assets-dictionary.ts @@ -15,6 +15,7 @@ import rifToken from 'assets/images/tokens/rif.svg'; import mintIcon from 'assets/images/tokens/mint.svg'; import { AssetDetails } from '../models/asset-details'; +import { isMainnet } from 'utils/classifiers'; export class AssetsDictionary { public static assets: Map = new Map( @@ -175,3 +176,19 @@ export class AssetsDictionary { return assets.map(asset => this.get(asset)); } } + +if (!isMainnet) { + AssetsDictionary.assets.set( + Asset.XUSD_legacy, + new AssetDetails( + Asset.XUSD_legacy, + 'XUSD*', + 'XUSD Legacy', + 18, + 3, + xusdIcon, + false, + true, + ), + ); +} diff --git a/src/utils/dictionaries/lending-pool-dictionary.ts b/src/utils/dictionaries/lending-pool-dictionary.ts index 5793ab2fd..3f1ff9a31 100644 --- a/src/utils/dictionaries/lending-pool-dictionary.ts +++ b/src/utils/dictionaries/lending-pool-dictionary.ts @@ -1,4 +1,6 @@ +import { P } from 'app/components/NotFoundPage/P'; import { Asset } from 'types/asset'; +import { isMainnet } from 'utils/classifiers'; import { LendingPool } from '../models/lending-pool'; export class LendingPoolDictionary { @@ -66,3 +68,16 @@ export class LendingPoolDictionary { return Array.from(this.pools.entries()); } } + +if (!isMainnet) { + LendingPoolDictionary.pools.set( + Asset.XUSD_legacy, + new LendingPool( + 'XUSD*', + Asset.XUSD_legacy, + [Asset.RBTC, Asset.DOC, Asset.BPRO, Asset.SOV], + true, + true, + ), + ); +} diff --git a/src/utils/dictionaries/trading-pair-dictionary.ts b/src/utils/dictionaries/trading-pair-dictionary.ts index 1a2b4e404..6f015c505 100644 --- a/src/utils/dictionaries/trading-pair-dictionary.ts +++ b/src/utils/dictionaries/trading-pair-dictionary.ts @@ -1,6 +1,7 @@ import { Asset } from 'types/asset'; import { TradingPair } from '../models/trading-pair'; import { RenderTradingPairName } from '../../app/components/Helpers'; +import { isMainnet } from 'utils/classifiers'; export enum TradingPairType { RBTC_XUSD = 'RBTC_XUSD', @@ -13,6 +14,10 @@ export enum TradingPairType { SOV_XUSD = 'SOV_XUSD', SOV_BPRO = 'SOV_BPRO', SOV_DOC = 'SOV_DOC', + // @dev testnet only pairs + RBTC_XUSD_legacy = 'RBTC_XUSD_legacy', + BPRO_XUSD_legacy = 'BPRO_XUSD_legacy', + SOV_XUSD_legacy = 'SOV_XUSD_legacy', } export const pairs = { @@ -26,6 +31,10 @@ export const pairs = { [TradingPairType.RBTC_USDT]: [Asset.RBTC, Asset.USDT], [TradingPairType.RBTC_DOC]: [Asset.RBTC, Asset.DOC], [TradingPairType.RBTC_XUSD]: [Asset.RBTC, Asset.XUSD], + // @dev testnet only pairs + [TradingPairType.RBTC_XUSD_legacy]: [Asset.RBTC, Asset.XUSD_legacy], + [TradingPairType.BPRO_XUSD_legacy]: [Asset.BPRO, Asset.XUSD_legacy], + [TradingPairType.SOV_XUSD_legacy]: [Asset.BPRO, Asset.XUSD_legacy], }; export class TradingPairDictionary { @@ -193,3 +202,46 @@ export class TradingPairDictionary { ) as TradingPair; } } + +// @dev add deprecated legacy xusd pairs on testnet just for history purposes and crash prevention +if (!isMainnet) { + TradingPairDictionary.pairs.set( + TradingPairType.RBTC_XUSD_legacy, + new TradingPair( + TradingPairType.RBTC_XUSD_legacy, + RenderTradingPairName({ asset1: Asset.RBTC, asset2: Asset.XUSD_legacy }), + 'RBTC/XUSD*', + Asset.XUSD_legacy, + Asset.RBTC, + [Asset.RBTC, Asset.XUSD_legacy], + true, + ), + ); + TradingPairDictionary.pairs.set( + TradingPairType.BPRO_XUSD_legacy, + new TradingPair( + TradingPairType.BPRO_XUSD_legacy, + RenderTradingPairName({ asset1: Asset.BPRO, asset2: Asset.XUSD_legacy }), + 'RBTC/XUSD*', + Asset.XUSD_legacy, + Asset.BPRO, + [Asset.BPRO, Asset.XUSD_legacy], + true, + ), + ); + TradingPairDictionary.pairs.set( + TradingPairType.SOV_XUSD_legacy, + new TradingPair( + TradingPairType.SOV_XUSD_legacy, + RenderTradingPairName({ asset1: Asset.SOV, asset2: Asset.XUSD_legacy }), + 'SOV/XUSD', + Asset.XUSD_legacy, + Asset.SOV, + [Asset.SOV, Asset.XUSD_legacy], + true, + true, + false, + 2, + ), + ); +} From 9cb18103cc3a8cda9d5aeb27471492f7b8bc1e3a Mon Sep 17 00:00:00 2001 From: soulBit Date: Wed, 15 Jun 2022 16:17:39 +0100 Subject: [PATCH 33/39] chore: remove unused import --- src/utils/dictionaries/lending-pool-dictionary.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/dictionaries/lending-pool-dictionary.ts b/src/utils/dictionaries/lending-pool-dictionary.ts index 3f1ff9a31..11bdc0390 100644 --- a/src/utils/dictionaries/lending-pool-dictionary.ts +++ b/src/utils/dictionaries/lending-pool-dictionary.ts @@ -1,4 +1,3 @@ -import { P } from 'app/components/NotFoundPage/P'; import { Asset } from 'types/asset'; import { isMainnet } from 'utils/classifiers'; import { LendingPool } from '../models/lending-pool'; From c3dc0fc9b412dda49c950a32f302c186a24ecad7 Mon Sep 17 00:00:00 2001 From: soulBit Date: Wed, 15 Jun 2022 16:39:20 +0100 Subject: [PATCH 34/39] feat: add perps comp popup (#2263) --- public/index.html | 75 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/public/index.html b/public/index.html index 4880bd306..6af96846f 100644 --- a/public/index.html +++ b/public/index.html @@ -94,21 +94,68 @@ })(); + <% } %> <% if (process.env.REACT_APP_INTERCOM_ID) { %> + + + + <% } %> - <% if (process.env.REACT_APP_INTERCOM_ID) { %> - - - - - <% } %> + + + From 6202cff1a10ff430ffe065c424b99681e2dc3ba2 Mon Sep 17 00:00:00 2001 From: Pietro Maximoff <74987028+pietro-maximoff@users.noreply.github.com> Date: Wed, 15 Jun 2022 18:45:28 +0300 Subject: [PATCH 35/39] [Promotions] - move promotions to the its own component (#2240) * [Promotions] - move promotions to the its own component and update all connected dependencies * [Promotions] - rename setLootDropColor => setPromotionColor, move the wiki link to classifiers * [Promotions] - update vertical paddings - add small refactoring - trigger buttons to the deposit modal for the relevant pool * [Promotions] - add tw-mt-3 to promotion container * feat: reduce Promo card heights Co-authored-by: soulBit --- .../LootDrop/LootDropSectionWrapper/index.tsx | 5 - .../FinanceV2Components/LootDrop/index.tsx | 65 ---------- .../FinanceV2Components/LootDrop/styled.ts | 36 ------ .../PromotionCard/index.module.scss | 5 +- .../components/PromotionCard/index.tsx | 18 ++- .../components/PromotionCard/types.ts | 1 + .../components/PromotionCard/utils.tsx | 0 .../PromotionsCarousel/CustomDot.tsx | 0 .../PromotionsCarousel/CustomLeftArrow.tsx | 0 .../PromotionsCarousel/CustomRightArrow.tsx | 0 .../PromotionsCarousel/index.module.scss | 0 .../components/PromotionsCarousel/index.tsx | 44 ++++--- .../components/Promotions/index.module.scss | 0 src/app/components/Promotions/index.tsx | 112 ++++++++++++++++++ .../components/StakingDateSelector/index.tsx | 2 +- .../containers/SwapFormContainer/index.tsx | 2 +- src/app/pages/BorrowPage/index.tsx | 2 +- .../components/Promotions/index.tsx | 83 ------------- src/app/pages/LandingPage/index.tsx | 8 +- .../CurrencyContainer/CurrencyRow.tsx | 51 +++++--- .../components/CurrencyContainer/index.tsx | 6 +- .../components/LendingDialog/index.tsx | 46 +++---- src/app/pages/LendingPage/index.tsx | 41 ++++--- src/app/pages/LendingPage/types.ts | 6 + .../components/MiningPool/index.tsx | 37 ++++-- .../components/MiningPool/types.ts | 6 + src/app/pages/LiquidityMining/index.tsx | 99 +++++++++------- src/app/pages/MarginTradePage/index.tsx | 4 +- .../PerpetualPage/PerpetualPageContainer.tsx | 2 +- .../components/TradeForm/index.tsx | 2 +- src/locales/en/translation.json | 27 +++++ src/utils/classifiers.ts | 5 + src/utils/dictionaries/amm/mainnet.ts | 12 +- src/utils/dictionaries/amm/testnet.ts | 10 +- src/utils/models/amm-liquidity-pool.ts | 10 +- tailwind.config.js | 3 + 36 files changed, 397 insertions(+), 353 deletions(-) delete mode 100644 src/app/components/FinanceV2Components/LootDrop/LootDropSectionWrapper/index.tsx delete mode 100644 src/app/components/FinanceV2Components/LootDrop/index.tsx delete mode 100644 src/app/components/FinanceV2Components/LootDrop/styled.ts rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionCard/index.module.scss (84%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionCard/index.tsx (87%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionCard/types.ts (96%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionCard/utils.tsx (100%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionsCarousel/CustomDot.tsx (100%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionsCarousel/CustomLeftArrow.tsx (100%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionsCarousel/CustomRightArrow.tsx (100%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionsCarousel/index.module.scss (100%) rename src/app/{pages/LandingPage => }/components/Promotions/components/PromotionsCarousel/index.tsx (52%) rename src/app/{pages/LandingPage => }/components/Promotions/index.module.scss (100%) create mode 100644 src/app/components/Promotions/index.tsx delete mode 100644 src/app/pages/LandingPage/components/Promotions/index.tsx diff --git a/src/app/components/FinanceV2Components/LootDrop/LootDropSectionWrapper/index.tsx b/src/app/components/FinanceV2Components/LootDrop/LootDropSectionWrapper/index.tsx deleted file mode 100644 index 049aa1b3b..000000000 --- a/src/app/components/FinanceV2Components/LootDrop/LootDropSectionWrapper/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const LootDropSectionWrapper: React.FC = ({ children }) => ( -
{children}
-); diff --git a/src/app/components/FinanceV2Components/LootDrop/index.tsx b/src/app/components/FinanceV2Components/LootDrop/index.tsx deleted file mode 100644 index 98d845a0e..000000000 --- a/src/app/components/FinanceV2Components/LootDrop/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { AssetSymbolRenderer } from 'app/components/AssetSymbolRenderer'; -import React from 'react'; -import { Asset } from 'types/asset'; -import { HighlightedBorder, LootDropColors, LootDropWrapper } from './styled'; - -interface ILootDropProps { - title: string | JSX.Element; - asset1: Asset; - asset2?: Asset; - startDate?: string; - endDate?: string; - message?: string; - linkUrl: string; - linkText: string; - linkDataActionId: string; - highlightColor: LootDropColors; -} - -export const LootDrop: React.FC = ({ - highlightColor, - title, - asset1, - asset2, - startDate, - endDate, - message, - linkUrl, - linkText, - linkDataActionId, -}) => ( - -
-
- {title} -
-
-
- - {asset2 && ( - <> - / - - - )} -
-
-
- {!!startDate && !!endDate && `${startDate} - ${endDate}`} - {!!message && message} -
- -
- -
-); diff --git a/src/app/components/FinanceV2Components/LootDrop/styled.ts b/src/app/components/FinanceV2Components/LootDrop/styled.ts deleted file mode 100644 index e90d4b757..000000000 --- a/src/app/components/FinanceV2Components/LootDrop/styled.ts +++ /dev/null @@ -1,36 +0,0 @@ -import styled from 'styled-components'; - -export enum LootDropColors { - Purple = '#6063CC', - Yellow = '#F5E884', - Green = '#6DAC6F', - Pink = '#D69B9B', - Blue = '#8EDBDB', - Orange = '#db6e4d', -} - -export const LootDropWrapper = styled.div` - background: radial-gradient( - circle, - rgb(233 234 233 / 12%) 3%, - rgba(34, 34, 34, 1) 83% - ); - mix-blend-mode: lighten; - width: 310px; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - margin: 0 0.625rem; - display: flex; - flex-direction: column; - justify-content: space-between; -`; - -interface IHighlightedBorderProps { - highlightColor: LootDropColors; -} - -export const HighlightedBorder = styled.div` - width: 100%; - height: 0.5625rem; - background-color: ${props => props.highlightColor}; -`; diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/index.module.scss b/src/app/components/Promotions/components/PromotionCard/index.module.scss similarity index 84% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionCard/index.module.scss rename to src/app/components/Promotions/components/PromotionCard/index.module.scss index fded7bbf1..aea8f371a 100644 --- a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/index.module.scss +++ b/src/app/components/Promotions/components/PromotionCard/index.module.scss @@ -1,15 +1,12 @@ .cardItem { - @apply tw-mx-auto tw-mt-10; - + @apply tw-mx-auto; width: 23.1875rem; - margin-bottom: 3.125rem; } .cardImageSection { @apply tw-w-full tw-relative tw-flex tw-flex-col tw-justify-between tw-text-white tw-bg-no-repeat; height: 11.9375rem; - margin-bottom: 1.375rem; padding: 1.125rem; } diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/index.tsx b/src/app/components/Promotions/components/PromotionCard/index.tsx similarity index 87% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionCard/index.tsx rename to src/app/components/Promotions/components/PromotionCard/index.tsx index 02ce0f377..75e13d73b 100644 --- a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/index.tsx +++ b/src/app/components/Promotions/components/PromotionCard/index.tsx @@ -1,6 +1,7 @@ import { SpotPairType } from 'app/pages/SpotTradingPage/types'; import React from 'react'; import { Link } from 'react-router-dom'; +import classNames from 'classnames'; import { Asset } from 'types'; import { TradingPairType } from 'utils/dictionaries/trading-pair-dictionary'; import { AppSection, PromotionColor } from './types'; @@ -26,8 +27,11 @@ interface IPromotionCardProps { learnMoreLink?: string; linkAsset?: Asset | string; linkTargetAsset?: Asset; + linkDataActionId?: string; linkMarginPairType?: TradingPairType; linkSpotTradingPairType?: SpotPairType; + className?: string; + imageClassName?: string; } export const PromotionCard: React.FC = ({ @@ -41,16 +45,18 @@ export const PromotionCard: React.FC = ({ learnMoreLink, linkAsset, linkTargetAsset, + linkDataActionId, linkMarginPairType, linkSpotTradingPairType, + className, + imageClassName, }) => { const { t } = useTranslation(); - const sectionTitle = getSectionTitle(appSection); const linkPathname = getLinkPathname(appSection); return ( -
+
= ({ target: linkTargetAsset, marginTradingPair: linkMarginPairType, spotTradingPair: linkSpotTradingPairType, + promotionSelectedAsset: logoAsset1, }, }} className="tw-no-underline" >
= ({
{sectionTitle}
-
+
{title}
{duration} @@ -105,7 +112,7 @@ export const PromotionCard: React.FC = ({
{title} @@ -119,6 +126,7 @@ export const PromotionCard: React.FC = ({ target="_blank" rel="noopener noreferrer" className="tw-text-secondary tw-underline" + data-action-id={linkDataActionId} > {t(translations.landingPage.promotions.learnMore)} diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/types.ts b/src/app/components/Promotions/components/PromotionCard/types.ts similarity index 96% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionCard/types.ts rename to src/app/components/Promotions/components/PromotionCard/types.ts index 4f9b27816..be0ef365c 100644 --- a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/types.ts +++ b/src/app/components/Promotions/components/PromotionCard/types.ts @@ -32,4 +32,5 @@ export interface IPromotionLinkState { marginTradingPair?: TradingPairType; spotTradingPair?: SpotPairType; perpetualPair?: PerpetualPairType; + promotionSelectedAsset?: Asset; } diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionCard/utils.tsx b/src/app/components/Promotions/components/PromotionCard/utils.tsx similarity index 100% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionCard/utils.tsx rename to src/app/components/Promotions/components/PromotionCard/utils.tsx diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomDot.tsx b/src/app/components/Promotions/components/PromotionsCarousel/CustomDot.tsx similarity index 100% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomDot.tsx rename to src/app/components/Promotions/components/PromotionsCarousel/CustomDot.tsx diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomLeftArrow.tsx b/src/app/components/Promotions/components/PromotionsCarousel/CustomLeftArrow.tsx similarity index 100% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomLeftArrow.tsx rename to src/app/components/Promotions/components/PromotionsCarousel/CustomLeftArrow.tsx diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomRightArrow.tsx b/src/app/components/Promotions/components/PromotionsCarousel/CustomRightArrow.tsx similarity index 100% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomRightArrow.tsx rename to src/app/components/Promotions/components/PromotionsCarousel/CustomRightArrow.tsx diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/index.module.scss b/src/app/components/Promotions/components/PromotionsCarousel/index.module.scss similarity index 100% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/index.module.scss rename to src/app/components/Promotions/components/PromotionsCarousel/index.module.scss diff --git a/src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/index.tsx b/src/app/components/Promotions/components/PromotionsCarousel/index.tsx similarity index 52% rename from src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/index.tsx rename to src/app/components/Promotions/components/PromotionsCarousel/index.tsx index 025247e1d..d31fd33cf 100644 --- a/src/app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/index.tsx +++ b/src/app/components/Promotions/components/PromotionsCarousel/index.tsx @@ -28,23 +28,33 @@ const responsive = { }, }; -export const PromotionsCarousel: React.FC = ({ children }) => { +type PromotionsCarouselProps = { + children?: React.ReactNode; + className?: string; +}; + +export const PromotionsCarousel: React.FC = ({ + children, + className, +}) => { return ( - } - customRightArrow={} - customDot={} - showDots - swipeable - > - {children} - +
+ } + customRightArrow={} + customDot={} + showDots + swipeable + > + {children} + +
); }; diff --git a/src/app/pages/LandingPage/components/Promotions/index.module.scss b/src/app/components/Promotions/index.module.scss similarity index 100% rename from src/app/pages/LandingPage/components/Promotions/index.module.scss rename to src/app/components/Promotions/index.module.scss diff --git a/src/app/components/Promotions/index.tsx b/src/app/components/Promotions/index.tsx new file mode 100644 index 000000000..fe0f62b3e --- /dev/null +++ b/src/app/components/Promotions/index.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import classNames from 'classnames'; +import { PromotionsCarousel } from './components/PromotionsCarousel'; +import { AppSection, PromotionColor } from './components/PromotionCard/types'; +import { PromotionCard } from './components/PromotionCard'; +import { Asset } from 'types'; +import { useTranslation } from 'react-i18next'; +import { translations } from 'locales/i18n'; +import { LiquidityPoolDictionary } from 'utils/dictionaries/liquidity-pool-dictionary'; +import styles from './index.module.scss'; +import { learnMoreLending, learnMoreYieldFarming } from 'utils/classifiers'; + +type PromotionsProps = { + className?: string; + cardClassName?: string; + cardImageClassName?: string; +}; + +export const Promotions: React.FC = ({ + className, + cardClassName, + cardImageClassName, +}) => { + const { t } = useTranslation(); + + return ( +
+
+ {t(translations.landingPage.promotions.title)} +
+ +
+ + + + + + + +
+
+ ); +}; diff --git a/src/app/components/StakingDateSelector/index.tsx b/src/app/components/StakingDateSelector/index.tsx index b60fc1e61..6afea99b2 100644 --- a/src/app/components/StakingDateSelector/index.tsx +++ b/src/app/components/StakingDateSelector/index.tsx @@ -6,7 +6,7 @@ import { ItemPredicate } from '@blueprintjs/select/lib/esm/common/predicate'; import { useTranslation } from 'react-i18next'; import { translations } from '../../../locales/i18n'; import Carousel from 'react-multi-carousel'; -import { CustomDot } from 'app/pages/LandingPage/components/Promotions/components/PromotionsCarousel/CustomDot'; +import { CustomDot } from '../Promotions/components/PromotionsCarousel/CustomDot'; import classNames from 'classnames'; import 'react-multi-carousel/lib/styles.css'; import { CustomButtonGroup } from './CustomButtonGroup'; diff --git a/src/app/containers/SwapFormContainer/index.tsx b/src/app/containers/SwapFormContainer/index.tsx index 5309b722b..46a166ed8 100644 --- a/src/app/containers/SwapFormContainer/index.tsx +++ b/src/app/containers/SwapFormContainer/index.tsx @@ -27,7 +27,7 @@ import { ErrorBadge } from 'app/components/Form/ErrorBadge'; import { useMaintenance } from 'app/hooks/useMaintenance'; import { backendUrl, currentChainId, discordInvite } from 'utils/classifiers'; import { useSwapsExternal_getSwapExpectedReturn } from 'app/hooks/swap-network/useSwapsExternal_getSwapExpectedReturn'; -import { IPromotionLinkState } from 'app/pages/LandingPage/components/Promotions/components/PromotionCard/types'; +import { IPromotionLinkState } from '../../components/Promotions/components/PromotionCard/types'; import styles from './index.module.scss'; import { useSwapNetwork_approveAndConvertByPath } from 'app/hooks/swap-network/useSwapNetwork_approveAndConvertByPath'; import { useSwapNetwork_conversionPath } from 'app/hooks/swap-network/useSwapNetwork_conversionPath'; diff --git a/src/app/pages/BorrowPage/index.tsx b/src/app/pages/BorrowPage/index.tsx index b336929bd..f105b23e1 100644 --- a/src/app/pages/BorrowPage/index.tsx +++ b/src/app/pages/BorrowPage/index.tsx @@ -14,7 +14,7 @@ import { selectLendBorrowSovryn } from './selectors'; import { RepayPositionHandler } from 'app/containers/RepayPositionHandler/Loadable'; import { BorrowActivity } from '../../components/BorrowActivity/Loadable'; import { useHistory, useLocation } from 'react-router-dom'; -import { IPromotionLinkState } from '../LandingPage/components/Promotions/components/PromotionCard/types'; +import { IPromotionLinkState } from '../../components/Promotions/components/PromotionCard/types'; import { AddCollateralHandler } from './components/AddCollateralModal/AddCollateralHandler'; const BorrowPage: React.FC = () => { diff --git a/src/app/pages/LandingPage/components/Promotions/index.tsx b/src/app/pages/LandingPage/components/Promotions/index.tsx deleted file mode 100644 index 324328441..000000000 --- a/src/app/pages/LandingPage/components/Promotions/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import { PromotionsCarousel } from './components/PromotionsCarousel'; -import { AppSection, PromotionColor } from './components/PromotionCard/types'; -import { PromotionCard } from './components/PromotionCard'; -import { Asset } from 'types'; -import { useTranslation } from 'react-i18next'; -import { translations } from 'locales/i18n'; -import { LiquidityPoolDictionary } from 'utils/dictionaries/liquidity-pool-dictionary'; -import styles from './index.module.scss'; - -export const Promotions: React.FC = () => { - const { t } = useTranslation(); - - return ( -
-
- {t(translations.landingPage.promotions.title)} -
- -
- - - - - - - - - - - -
-
- ); -}; diff --git a/src/app/pages/LandingPage/index.tsx b/src/app/pages/LandingPage/index.tsx index 36b866688..1f62de117 100644 --- a/src/app/pages/LandingPage/index.tsx +++ b/src/app/pages/LandingPage/index.tsx @@ -8,7 +8,7 @@ import { TradingVolume } from './components/TradingVolume'; // import { BabelSoldOutBanner } from './components/Banner/BabelSoldOutBanner'; import { GetStartedBanner } from './components/Banner/GetStartedBanner'; import { TotalValueLocked } from './components/TotalValueLocked'; -import { Promotions } from './components/Promotions'; +import { Promotions } from '../../components/Promotions'; import { AmmBalance } from './components/AmmBalance'; import { backendUrl, currentChainId } from 'utils/classifiers'; import { TvlData } from 'app/containers/StatsPage/types'; @@ -160,7 +160,11 @@ export const LandingPage: React.FC = ({
- +
{pairsData && pairsData.pairs && ( diff --git a/src/app/pages/LendingPage/components/CurrencyContainer/CurrencyRow.tsx b/src/app/pages/LendingPage/components/CurrencyContainer/CurrencyRow.tsx index 26816c85f..4804ae184 100644 --- a/src/app/pages/LendingPage/components/CurrencyContainer/CurrencyRow.tsx +++ b/src/app/pages/LendingPage/components/CurrencyContainer/CurrencyRow.tsx @@ -1,10 +1,9 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { LendingPool } from 'utils/models/lending-pool'; import { translations } from 'locales/i18n'; import styled from 'styled-components/macro'; -import { useIsConnected } from '../../../../hooks/useAccount'; - +import { useIsConnected } from 'app/hooks/useAccount'; import { PoolChart } from './PoolChart'; import { CardRow } from 'app/components/FinanceV2Components/CardRow'; import { UserLendingInfo } from './UserLendingInfo'; @@ -13,19 +12,22 @@ import LeftSection from './LeftSection'; import { ActionButton } from 'app/components/Form/ActionButton'; import { Asset } from 'types'; import { Tooltip } from '@blueprintjs/core'; -import { LootDropColors } from 'app/components/FinanceV2Components/LootDrop/styled'; +import { + IPromotionLinkState, + PromotionColor, +} from 'app/components/Promotions/components/PromotionCard/types'; +import { useHistory, useLocation } from 'react-router-dom'; +import { DialogType } from '../../types'; -type Props = { +interface ICurrencyRowProps { lendingPool: LendingPool; lendingAmount: string; depositLocked: boolean; withdrawLocked: boolean; linkAsset?: Asset; -}; +} -export type DialogType = 'none' | 'add' | 'remove'; - -const CurrencyRow: React.FC = ({ +const CurrencyRow: React.FC = ({ lendingPool, lendingAmount, depositLocked, @@ -34,8 +36,10 @@ const CurrencyRow: React.FC = ({ }) => { const connected = useIsConnected(); const { t } = useTranslation(); + const location = useLocation(); + const history = useHistory(); const [dialog, setDialog] = useState( - lendingPool.getAsset() === linkAsset ? 'add' : 'none', + lendingPool.getAsset() === linkAsset ? DialogType.ADD : DialogType.NONE, ); const [isEmptyBalance, setIsEmptyBalance] = useState(true); @@ -45,13 +49,26 @@ const CurrencyRow: React.FC = ({ const asset = lendingPool.getAsset(); + useEffect(() => { + if ( + location.state?.promotionSelectedAsset === asset && + !depositLocked && + connected + ) { + setDialog(DialogType.ADD); + history.push({ + state: undefined, + }); + } + }, [setDialog, history, location.state, asset, connected, depositLocked]); + const Actions = () => { return (
{!depositLocked ? ( setDialog('add')} + onClick={() => setDialog(DialogType.ADD)} className="tw-block tw-w-full tw-mb-3 tw-rounded-lg tw-bg-primary-10 hover:tw-opacity-75" textClassName="tw-text-base" disabled={depositLocked || !connected} @@ -68,7 +85,7 @@ const CurrencyRow: React.FC = ({ > setDialog('add')} + onClick={() => setDialog(DialogType.ADD)} className="tw-block tw-w-full tw-mb-3 tw-rounded-lg tw-bg-primary-10 hover:tw-opacity-75" textClassName="tw-text-base" disabled={depositLocked} @@ -78,7 +95,7 @@ const CurrencyRow: React.FC = ({ {!withdrawLocked ? ( setDialog('remove')} + onClick={() => setDialog(DialogType.REMOVE)} className="tw-block tw-w-full tw-rounded-lg" textClassName="tw-text-base" disabled={isEmptyBalance || withdrawLocked || !connected} @@ -95,7 +112,7 @@ const CurrencyRow: React.FC = ({ > setDialog('remove')} + onClick={() => setDialog(DialogType.REMOVE)} className="tw-block tw-w-full tw-rounded-lg" textClassName="tw-text-base" disabled={isEmptyBalance || withdrawLocked} @@ -123,14 +140,14 @@ const CurrencyRow: React.FC = ({ onNonEmptyBalance={onNonEmptyBalance} /> } - leftColor={asset === Asset.XUSD ? LootDropColors.Yellow : undefined} + leftColor={asset === Asset.XUSD ? PromotionColor.Yellow : undefined} chartReady={true} /> setDialog('none')} + showModal={dialog !== DialogType.NONE} + onCloseModal={() => setDialog(DialogType.NONE)} type={dialog} lendingAmount={lendingAmount} /> diff --git a/src/app/pages/LendingPage/components/CurrencyContainer/index.tsx b/src/app/pages/LendingPage/components/CurrencyContainer/index.tsx index faf3c55d1..ce409ebb9 100644 --- a/src/app/pages/LendingPage/components/CurrencyContainer/index.tsx +++ b/src/app/pages/LendingPage/components/CurrencyContainer/index.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useState } from 'react'; import CurrencyRow from './CurrencyRow'; -import { useWeiAmount } from '../../../../hooks/useWeiAmount'; -import { LendingPoolDictionary } from '../../../../../utils/dictionaries/lending-pool-dictionary'; +import { useWeiAmount } from 'app/hooks/useWeiAmount'; +import { LendingPoolDictionary } from 'utils/dictionaries/lending-pool-dictionary'; import { useMaintenance } from 'app/hooks/useMaintenance'; import { useLocation, useHistory } from 'react-router-dom'; -import { IPromotionLinkState } from 'app/pages/LandingPage/components/Promotions/components/PromotionCard/types'; +import { IPromotionLinkState } from 'app/components/Promotions/components/PromotionCard/types'; import { useIsConnected } from 'app/hooks/useAccount'; const currencyRows = LendingPoolDictionary.list(); diff --git a/src/app/pages/LendingPage/components/LendingDialog/index.tsx b/src/app/pages/LendingPage/components/LendingDialog/index.tsx index 3602df0bd..3630f186b 100644 --- a/src/app/pages/LendingPage/components/LendingDialog/index.tsx +++ b/src/app/pages/LendingPage/components/LendingDialog/index.tsx @@ -1,10 +1,10 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react'; import { useTranslation, Trans } from 'react-i18next'; -import { translations } from '../../../../../locales/i18n'; +import { translations } from 'locales/i18n'; import { DialogButton } from 'app/components/Form/DialogButton'; import { Asset } from 'types'; -import { DialogType } from '../CurrencyContainer/CurrencyRow'; +import { DialogType } from '../../types'; import { FormGroup } from 'app/components/Form/FormGroup'; import { AmountInput } from 'app/components/Form/AmountInput'; @@ -34,7 +34,7 @@ import { TxFeeCalculator } from 'app/pages/MarginTradePage/components/TxFeeCalcu import { LendingPoolDictionary } from 'utils/dictionaries/lending-pool-dictionary'; import { TransactionDialog } from 'app/components/TransactionDialog'; -interface Props { +interface ILendingDialogProps { currency: Asset; showModal: boolean; onCloseModal: () => void; @@ -44,15 +44,17 @@ interface Props { const gasLimit = 340000; -export function LendingDialog({ +export const LendingDialog: React.FC = ({ currency, type, lendingAmount, ...props -}: Props) { +}) => { const { t } = useTranslation(); const modalTranslation = - translations.lendingPage.modal[type === 'add' ? 'deposit' : 'withdraw']; + translations.lendingPage.modal[ + type === DialogType.ADD ? 'deposit' : 'withdraw' + ]; const [amount, setAmount] = useState(''); const weiAmount = useWeiAmount(amount); @@ -142,10 +144,10 @@ export function LendingDialog({ setAmount(''); }, [currency]); - const disabled = () => (type === 'add' ? !isValid : !isValidRedeem); + const disabled = () => (type === DialogType.ADD ? !isValid : !isValidRedeem); const errorMessage = useMemo(() => { - if (type === 'add') { + if (type === DialogType.ADD) { if (!isGreaterThanZero) return t(translations.validationErrors.minimumZero); if (!hasSufficientBalance) @@ -158,14 +160,14 @@ export function LendingDialog({ const { useLM } = LendingPoolDictionary.get(currency); const getMethodName = useCallback(() => { - if (type === 'add') { + if (type === DialogType.ADD) { return currency === Asset.RBTC ? 'mintWithBTC' : 'mint'; } return currency === Asset.RBTC ? 'burnToBTC' : 'burn'; }, [type, currency]); const txFeeArgs = useMemo(() => { - if (type === 'add') + if (type === DialogType.ADD) return currency === Asset.RBTC ? [tokenAddress, useLM] : [tokenAddress, weiAmount, useLM]; @@ -173,7 +175,7 @@ export function LendingDialog({ }, [currency, tokenAddress, type, useLM, weiAmount, withdrawAmount]); const handleSubmit = () => - type === 'add' ? handleLendSubmit() : handleUnlendSubmit(); + type === DialogType.ADD ? handleLendSubmit() : handleUnlendSubmit(); const handleChange = useCallback((newValue: string, isTotal?: boolean) => { setAmount(newValue); @@ -197,7 +199,7 @@ export function LendingDialog({ handleChange(value, isTotal)} asset={currency} maxAmount={ - type === 'add' + type === DialogType.ADD ? maxMinusFee(userBalance, currency, gasLimit) : depositedAssetBalance } - dataActionId={`lend-${type === 'add' ? 'deposit' : 'withdraw'}`} + dataActionId={`lend-${ + type === DialogType.ADD ? 'deposit' : 'withdraw' + }`} />
- {type === 'add' && ( + {type === DialogType.ADD && (
)} - {type === 'remove' && ( + {type === DialogType.REMOVE && (
- {type === 'add' && ( + {type === DialogType.ADD && ( <>
- {type === 'add' && } - {type === 'remove' && } + {type === DialogType.ADD && } + {type === DialogType.REMOVE && } ); -} +}; diff --git a/src/app/pages/LendingPage/index.tsx b/src/app/pages/LendingPage/index.tsx index 5d6300b9f..7bbc942a3 100644 --- a/src/app/pages/LendingPage/index.tsx +++ b/src/app/pages/LendingPage/index.tsx @@ -1,18 +1,19 @@ import React from 'react'; import { Helmet } from 'react-helmet-async'; import { useTranslation } from 'react-i18next'; - -import { LootDrop } from 'app/components/FinanceV2Components/LootDrop'; -import { LootDropSectionWrapper } from 'app/components/FinanceV2Components/LootDrop/LootDropSectionWrapper'; -import { LootDropColors } from 'app/components/FinanceV2Components/LootDrop/styled'; import { translations } from 'locales/i18n'; import { Asset } from 'types'; - import { SkeletonRow } from '../../components/Skeleton/SkeletonRow'; import { useAccount } from '../../hooks/useAccount'; import CurrencyContainer from './components/CurrencyContainer'; import { HistoryTable } from './components/HistoryTable'; import { getNextMonday } from '../../../utils/dateHelpers'; +import { PromotionCard } from 'app/components/Promotions/components/PromotionCard'; +import { + AppSection, + PromotionColor, +} from 'app/components/Promotions/components/PromotionCard/types'; +import { learnMoreLending } from 'utils/classifiers'; const date = getNextMonday(); @@ -29,20 +30,22 @@ const LendingPage: React.FC = () => { content={t(translations.lendingPage.meta.description)} /> -
- - - +
+
diff --git a/src/app/pages/LendingPage/types.ts b/src/app/pages/LendingPage/types.ts index fa74af207..f502e2c12 100644 --- a/src/app/pages/LendingPage/types.ts +++ b/src/app/pages/LendingPage/types.ts @@ -30,3 +30,9 @@ export enum LendingEventType { } export type ContainerState = LendingPageState; + +export enum DialogType { + NONE, + ADD, + REMOVE, +} diff --git a/src/app/pages/LiquidityMining/components/MiningPool/index.tsx b/src/app/pages/LiquidityMining/components/MiningPool/index.tsx index ac0e65da2..d768487de 100644 --- a/src/app/pages/LiquidityMining/components/MiningPool/index.tsx +++ b/src/app/pages/LiquidityMining/components/MiningPool/index.tsx @@ -1,7 +1,7 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { translations } from '../../../../../locales/i18n'; +import { translations } from 'locales/i18n'; import { ActionButton } from 'app/components/Form/ActionButton'; import { Spinner } from 'app/components/Spinner'; import { AddLiquidityDialog } from '../AddLiquidityDialog'; @@ -9,14 +9,16 @@ import { RemoveLiquidityDialog } from '../RemoveLiquidityDialog'; import { PoolAssetInfo } from './PoolAssetInfo'; import { PoolChart } from './PoolChart'; import { UserPoolInfo } from './UserPoolInfo'; -import { useCanInteract } from '../../../../hooks/useCanInteract'; +import { useCanInteract } from 'app/hooks/useCanInteract'; import { AddLiquidityDialogV1 } from '../AddLiquidityDialog/AddLiquidityDialogV1'; import { RemoveLiquidityDialogV1 } from '../RemoveLiquidityDialog/RemoveLiquidityDialogV1'; import { CardRow } from 'app/components/FinanceV2Components/CardRow'; import { useMaintenance } from 'app/hooks/useMaintenance'; -import type { AmmHistory } from './types'; +import { AmmHistory, DialogType } from './types'; import type { AmmLiquidityPool } from 'utils/models/amm-liquidity-pool'; import { LiquidityPoolDictionary } from 'utils/dictionaries/liquidity-pool-dictionary'; +import { useHistory, useLocation } from 'react-router-dom'; +import { IPromotionLinkState } from 'app/components/Promotions/components/PromotionCard/types'; interface IMiningPoolProps { pool: AmmLiquidityPool; @@ -25,12 +27,6 @@ interface IMiningPoolProps { linkAsset?: string; } -enum DialogType { - NONE, - ADD, - REMOVE, -} - export const MiningPool: React.FC = ({ pool, ammData, @@ -38,6 +34,8 @@ export const MiningPool: React.FC = ({ linkAsset, }) => { const { t } = useTranslation(); + const location = useLocation(); + const history = useHistory(); const [dialog, setDialog] = useState( linkAsset && pool.key === LiquidityPoolDictionary.getByKey(linkAsset)?.key ? DialogType.ADD @@ -61,6 +59,25 @@ export const MiningPool: React.FC = ({ () => setSuccessfulTransactions(prevValue => prevValue + 1), [setSuccessfulTransactions], ); + useEffect(() => { + if ( + location.state?.promotionSelectedAsset === pool.assetA && + canInteract && + !addliquidityLocked + ) { + setDialog(DialogType.ADD); + history.push({ + state: undefined, + }); + } + }, [ + setDialog, + history, + addliquidityLocked, + canInteract, + location.state, + pool.assetA, + ]); const Actions = () => { return ( diff --git a/src/app/pages/LiquidityMining/components/MiningPool/types.ts b/src/app/pages/LiquidityMining/components/MiningPool/types.ts index 574b83c6a..0029d3792 100644 --- a/src/app/pages/LiquidityMining/components/MiningPool/types.ts +++ b/src/app/pages/LiquidityMining/components/MiningPool/types.ts @@ -31,3 +31,9 @@ export type UserInfo = { export type Balance = { 0: string; }; + +export enum DialogType { + NONE, + ADD, + REMOVE, +} diff --git a/src/app/pages/LiquidityMining/index.tsx b/src/app/pages/LiquidityMining/index.tsx index 37ca94715..df96dce5d 100644 --- a/src/app/pages/LiquidityMining/index.tsx +++ b/src/app/pages/LiquidityMining/index.tsx @@ -2,19 +2,14 @@ import classNames from 'classnames'; import React, { useCallback, useEffect, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { Trans, useTranslation } from 'react-i18next'; - -import { LootDropColors } from 'app/components/FinanceV2Components/LootDrop/styled'; import { LocalSharedArrayBuffer } from 'app/components/LocalSharedArrayBuffer'; import { useFetch } from 'app/hooks/useFetch'; import { useMaintenance } from 'app/hooks/useMaintenance'; import { translations } from 'locales/i18n'; import { Asset } from 'types'; -import { discordInvite } from 'utils/classifiers'; +import { discordInvite, learnMoreYieldFarming } from 'utils/classifiers'; import { backendUrl, currentChainId } from 'utils/classifiers'; - import { LiquidityPoolDictionary } from '../../../utils/dictionaries/liquidity-pool-dictionary'; -import { LootDrop } from '../../components/FinanceV2Components/LootDrop'; -import { LootDropSectionWrapper } from '../../components/FinanceV2Components/LootDrop/LootDropSectionWrapper'; import { SkeletonRow } from '../../components/Skeleton/SkeletonRow'; import { useAccount } from '../../hooks/useAccount'; import { AmmPoolsBanner } from './components/AmmPoolsBanner'; @@ -22,7 +17,13 @@ import { HistoryTable } from './components/HistoryTable'; import { MiningPool } from './components/MiningPool'; import { getNextMonday } from '../../../utils/dateHelpers'; import { useHistory, useLocation } from 'react-router-dom'; -import { IPromotionLinkState } from '../LandingPage/components/Promotions/components/PromotionCard/types'; +import { + AppSection, + IPromotionLinkState, + PromotionColor, +} from '../../components/Promotions/components/PromotionCard/types'; +import { PromotionCard } from 'app/components/Promotions/components/PromotionCard'; +import { PromotionsCarousel } from 'app/components/Promotions/components/PromotionsCarousel'; const pools = LiquidityPoolDictionary.list(); @@ -62,57 +63,69 @@ export function LiquidityMining() { {t(translations.liquidityMining.meta.title)} -
- - + + - - - - + @@ -135,7 +148,7 @@ export function LiquidityMining() { )}
diff --git a/src/app/pages/MarginTradePage/index.tsx b/src/app/pages/MarginTradePage/index.tsx index 06c0f7708..6064a0236 100644 --- a/src/app/pages/MarginTradePage/index.tsx +++ b/src/app/pages/MarginTradePage/index.tsx @@ -8,13 +8,13 @@ import { RecentTrades, RecentTradeType } from 'app/components/RecentTrades'; import { reducer, sliceKey } from './slice'; import { selectMarginTradePage } from './selectors'; import { marginTradePageSaga } from './saga'; -import { TradingPairDictionary } from '../../../utils/dictionaries/trading-pair-dictionary'; +import { TradingPairDictionary } from 'utils/dictionaries/trading-pair-dictionary'; import { TradeForm } from './components/TradeForm'; import { Theme, TradingChart } from '../../components/TradingChart'; import { useIsConnected } from '../../hooks/useAccount'; import { ClosedPositionsTable } from './components/ClosedPositionsTable'; import { useHistory, useLocation } from 'react-router-dom'; -import { IPromotionLinkState } from '../LandingPage/components/Promotions/components/PromotionCard/types'; +import { IPromotionLinkState } from '../../components/Promotions/components/PromotionCard/types'; import { PairNavbar } from 'app/components/PairNavbar'; import { TradingType } from 'types/trading-pairs'; import { Tabs } from 'app/components/Tabs'; diff --git a/src/app/pages/PerpetualPage/PerpetualPageContainer.tsx b/src/app/pages/PerpetualPage/PerpetualPageContainer.tsx index 5c3af0157..4e15c9656 100644 --- a/src/app/pages/PerpetualPage/PerpetualPageContainer.tsx +++ b/src/app/pages/PerpetualPage/PerpetualPageContainer.tsx @@ -17,7 +17,7 @@ import { import { TradingChart } from './components/TradingChart'; import { OpenPositionsTable } from './components/OpenPositionsTable'; import { useHistory, useLocation } from 'react-router-dom'; -import { IPromotionLinkState } from '../LandingPage/components/Promotions/components/PromotionCard/types'; +import { IPromotionLinkState } from '../../components/Promotions/components/PromotionCard/types'; import { selectPerpetualPage } from './selectors'; import { DataCard } from './components/DataCard'; import { AmmDepthChart } from './components/AmmDepthChart'; diff --git a/src/app/pages/SpotTradingPage/components/TradeForm/index.tsx b/src/app/pages/SpotTradingPage/components/TradeForm/index.tsx index 38928f542..8cb2cb589 100644 --- a/src/app/pages/SpotTradingPage/components/TradeForm/index.tsx +++ b/src/app/pages/SpotTradingPage/components/TradeForm/index.tsx @@ -7,7 +7,7 @@ import { OrderType } from 'app/components/OrderTypeTitle/types'; import { pairs, TradingTypes } from '../../types'; import { Asset } from 'types/asset'; import { useHistory, useLocation } from 'react-router-dom'; -import { IPromotionLinkState } from 'app/pages/LandingPage/components/Promotions/components/PromotionCard/types'; +import { IPromotionLinkState } from 'app/components/Promotions/components/PromotionCard/types'; import { LimitForm } from './LimitForm'; import { MarketForm } from './MarketForm'; diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 9756f76f5..275819a70 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -2639,6 +2639,33 @@ "circulatingSupplyTooltip": "We have updated the circulation supply formula to be showing the amount of liquid SOV token in line with market standards. SOV that has been unlocked from the adoption and development funds is now counted as circulating, and SOV that has been staked (either through vesting or voluntary staking) is not counted as circulating.

There is no change to your funds with this change. Just a standardisation of the displayed Circulating Supply." } }, + "promotions": { + "card1": { + "title": "5K SOV rewards", + "duration": "Ongoing weekly rewards", + "text": "Provide a 1:1 ratio of MYNT and rBTC to the MYNT/BTC AMM liquidity pool and instantly start accruing your share of 5,000 SOV rewards." + }, + "card2": { + "title": "15K SOV rewards", + "duration": "Ongoing weekly rewards", + "text": "Provide any amount of XUSD to the XUSD lending pool and instantly start accruing your share of 15,000 SOV rewards." + }, + "card3": { + "title": "25K SOV rewards", + "duration": "Ongoing weekly rewards", + "text": "Provide a 1:1 ratio of XUSD and rBTC to the XUSD/rBTC AMM liquidity pool and instantly start accruing your share of 25,000 SOV rewards." + }, + "card4": { + "title": "30K SOV rewards", + "duration": "Ongoing weekly rewards", + "text": "Provide a 1:1 ratio of SOV and rBTC to the SOV/rBTC AMM liquidity pool and instantly start accruing your share of 30,000 SOV rewards." + }, + "card5": { + "title": "5K SOV rewards", + "duration": "Ongoing weekly rewards", + "text": "Provide a 1:1 ratio of ETH and rBTC to the ETH/rBTC AMM liquidity pool and instantly start accruing your share of 5,000 SOV rewards." + } + }, "liquidityMining": { "deposit": "Deposit", "withdraw": "Withdraw", diff --git a/src/utils/classifiers.ts b/src/utils/classifiers.ts index abb00fb7c..6611e48ff 100644 --- a/src/utils/classifiers.ts +++ b/src/utils/classifiers.ts @@ -155,3 +155,8 @@ export const limitOrderUrl = { // 31: 'https://orderbook.test.sovryn.app/limitOrder', 31: 'https://_ob.sovryn.app/testnet/api', }; + +export const learnMoreYieldFarming = + 'https://wiki.sovryn.app/en/sovryn-dapp/market-making#yield-farming'; +export const learnMoreLending = + 'https://wiki.sovryn.app/en/sovryn-dapp/market-making'; diff --git a/src/utils/dictionaries/amm/mainnet.ts b/src/utils/dictionaries/amm/mainnet.ts index 12fa0b95a..a58213dd4 100644 --- a/src/utils/dictionaries/amm/mainnet.ts +++ b/src/utils/dictionaries/amm/mainnet.ts @@ -1,4 +1,4 @@ -import { LootDropColors } from 'app/components/FinanceV2Components/LootDrop/styled'; +import { PromotionColor } from 'app/components/Promotions/components/PromotionCard/types'; import { AppMode } from 'types/app-mode'; import { Asset } from 'types/asset'; import { AmmLiquidityPool } from 'utils/models/amm-liquidity-pool'; @@ -12,7 +12,7 @@ export const mainnetAmm = [ '0xe76Ea314b32fCf641C6c57f14110c5Baa1e45ff4', '0x09C5faF7723b13434ABdF1a65aB1B667BC02A902', ) - .setLootDropColor(LootDropColors.Purple) + .setPromotionColor(PromotionColor.Purple) .setPreviousConverters([ '0x3fd679b01ddab34da8f72b7ec301aa75ea25f338', '0x88a67a0e79e311fe93c6e2101d55d6d2ae3a7e94', @@ -25,7 +25,7 @@ export const mainnetAmm = [ '0xa9c3d9681215ef7623dc28ea6b75bf87fdf285d9', '0x6f96096687952349DD5944E0EB1Be327DcdeB705', ) - .setLootDropColor(LootDropColors.Yellow) + .setPromotionColor(PromotionColor.Yellow) .setPreviousConverters([ '0x029448377a56c15928ec783baf6ca736ed99a57f', '0x34163bb263ac77e9d6315676a2b9624cfc5ff861', @@ -58,7 +58,7 @@ export const mainnetAmm = [ '0x1684b871ec5f93de142e79a670b541d75be07ead', '0x8f3d24ab3510294f1466aa105f78901b90d79d4d', ) - .setLootDropColor(LootDropColors.Blue) + .setPromotionColor(PromotionColor.Blue) .setPreviousConverters([ '0x3a36919f1d6729ea8bd2a04f72bd9d5396f7e549', '0x150bc1f9f1020255d44385865928aadc6b7ad9f3', @@ -71,7 +71,7 @@ export const mainnetAmm = [ '0xa57ec11497f45fe86eca50f4f1c9e75c8016a1af', '0xF41Ed702df2B84AcE02772C6a0D8AE46465aA5F4', ) - .setLootDropColor(LootDropColors.Green) + .setPromotionColor(PromotionColor.Green) .setPreviousConverters([ '0xcef26b429e272960d8fa2ea190b06df5dd8f68e2', '0xd8397c1944862b6a9674c85a5496c208dc9417bb', @@ -126,6 +126,6 @@ export const mainnetAmm = [ '0x3a18e61d9c9f1546dea013478dd653c793098f17', '0x36263AC99ecDcf1aB20513D580B7d8D32D3C439d', ) - .setLootDropColor(LootDropColors.Orange) + .setPromotionColor(PromotionColor.Orange) .setPreviousConverters(['0x25B8D024B39174824424f032423E03dd7dcCF044']), ]; diff --git a/src/utils/dictionaries/amm/testnet.ts b/src/utils/dictionaries/amm/testnet.ts index 15b7c4623..4a530861a 100644 --- a/src/utils/dictionaries/amm/testnet.ts +++ b/src/utils/dictionaries/amm/testnet.ts @@ -1,4 +1,4 @@ -import { LootDropColors } from 'app/components/FinanceV2Components/LootDrop/styled'; +import { PromotionColor } from 'app/components/Promotions/components/PromotionCard/types'; import { AppMode } from 'types/app-mode'; import { Asset } from 'types/asset'; import { AmmLiquidityPool } from 'utils/models/amm-liquidity-pool'; @@ -12,7 +12,7 @@ export const testnetAmm = [ '0xc2d05263318e2304fc7cdad40eea6a091b310080', '0xdF298421CB18740a7059b0Af532167fAA45e7A98', ) - .setLootDropColor(LootDropColors.Purple) + .setPromotionColor(PromotionColor.Purple) .setPreviousConverters(['0xaBAABc2191A23D6Bb2cfa973892062c131cb7647']), new AmmLiquidityPool( Asset.XUSD, @@ -22,7 +22,7 @@ export const testnetAmm = [ '0xD877fd00ECF08eD78BF549fbc74bac3001aBBb07', '0xb89D193c8a9Ae3fadF73B23519c215a0B7DD1B37', ) - .setLootDropColor(LootDropColors.Yellow) + .setPromotionColor(PromotionColor.Yellow) .setPreviousConverters([ '0x9a1aE300b23F4C676186e6d417ac586889aAfF42', '0xe5e750ead0e564e489b0776273e4a10f3f3d4028', @@ -60,7 +60,7 @@ export const testnetAmm = [ '0x9f570ffe6c421e2c7611aaea14770b807e9fb424', '0xBb5B900EDa0F1459F582aB2436EA825a927f5bA2', ) - .setLootDropColor(LootDropColors.Blue) + .setPromotionColor(PromotionColor.Blue) .setPreviousConverters(['0x4c493276E14791472633B55aaD82E49D28540bC6']), new AmmLiquidityPool( Asset.MOC, @@ -105,5 +105,5 @@ export const testnetAmm = [ AppMode.TESTNET, '0x84953dAF0E7a9fFb8B4fDf7F948185e1cF85852e', '0xB12FA09a50c56e9a0C826b98e76DA7645017AB4D', - ).setLootDropColor(LootDropColors.Orange), + ).setPromotionColor(PromotionColor.Orange), ]; diff --git a/src/utils/models/amm-liquidity-pool.ts b/src/utils/models/amm-liquidity-pool.ts index 1470e0b0b..76dcd65ea 100644 --- a/src/utils/models/amm-liquidity-pool.ts +++ b/src/utils/models/amm-liquidity-pool.ts @@ -1,4 +1,4 @@ -import type { LootDropColors } from 'app/components/FinanceV2Components/LootDrop/styled'; +import { PromotionColor } from 'app/components/Promotions/components/PromotionCard/types'; import type { AppMode, Asset } from 'types'; import type { AbiItem } from 'web3-utils'; import LiquidityPoolV1Converter from '../blockchain/abi/LiquidityPoolV1Converter.json'; @@ -7,7 +7,7 @@ import LiquidityPoolV2Converter from '../blockchain/abi/LiquidityPoolV2Converter export type ConverterVersion = 1 | 2; export class AmmLiquidityPool { - private _lootDropColor?: LootDropColors | string; + private _promotionColor?: PromotionColor | string; private _hasSovRewards: boolean = true; private _previousConverters: string[] = []; constructor( @@ -35,12 +35,12 @@ export class AmmLiquidityPool { this.poolTokenB = poolTokenB.toLowerCase(); } } - public setLootDropColor(color: LootDropColors | string) { - this._lootDropColor = color; + public setPromotionColor(color: PromotionColor | string) { + this._promotionColor = color; return this; } public get lootDropColor() { - return this._lootDropColor; + return this._promotionColor; } public getPoolTokenAddress(asset: Asset) { if (asset === this.assetA) { diff --git a/tailwind.config.js b/tailwind.config.js index cdaf5b3c5..32a57ccb2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -153,6 +153,9 @@ module.exports = { animation: { 'spin-fast': 'spin .5s linear infinite', }, + scale: { + '85': '.85', + }, }, }, variants: { From 4bef88cfe74af512000c99a2c8186afc36d0468e Mon Sep 17 00:00:00 2001 From: soulBit Date: Wed, 15 Jun 2022 16:47:00 +0100 Subject: [PATCH 36/39] feat: add ZUSD to dapp, QoL changes to Portfolio (#2262) --- src/app/components/AssetValue/types.ts | 1 + .../components/SovGenerationNFTS/index.tsx | 114 ++-- .../UserAssets/Vesting/VestedItem.tsx | 8 +- .../components/BridgeLink.module.scss | 8 + .../UserAssets/components/BridgeLink.tsx | 49 +- .../components/UserAssetsTableRow.tsx | 19 +- src/app/components/UserAssets/index.tsx | 34 +- .../usePriceFeeds_tradingPairRates.ts | 1 + src/app/index.tsx | 3 +- .../components/ReturnToPortfolio/index.tsx | 4 +- src/app/pages/BridgeDepositPage/index.tsx | 2 +- .../components/ConfirmStep/index.tsx | 2 +- .../components/SidebarSteps/index.tsx | 2 +- src/app/pages/BridgeWithdrawPage/index.tsx | 2 +- .../Deposit/SidebarStepsDeposit.tsx | 2 +- .../components/Deposit/StatusScreen.tsx | 2 +- .../FastBtcPage/components/FastBtcHeader.tsx | 2 +- .../Withdraw/SidebarStepsWithdraw.tsx | 2 +- .../components/Withdraw/StatusScreen.tsx | 2 +- src/app/pages/FastBtcPage/index.tsx | 2 +- .../pages/ImportantInformationStep/index.tsx | 2 +- src/app/pages/PortfolioPage/index.tsx | 92 +-- .../hooks/useGetFeesEarnedClaimAmount.ts | 6 + src/assets/images/tokens/zusd.svg | 14 + src/locales/en/translation.json | 17 +- src/locales/es/translation.json | 77 ++- src/locales/fr/translation.json | 6 +- src/locales/pt_br/translation.json | 54 +- src/types/asset.ts | 1 + src/utils/blockchain/abi/ZUSDToken.json | 536 ++++++++++++++++++ src/utils/blockchain/contracts.testnet.ts | 7 +- src/utils/blockchain/contracts.ts | 7 +- src/utils/dictionaries/assets-dictionary.ts | 5 + 33 files changed, 897 insertions(+), 188 deletions(-) create mode 100644 src/app/components/UserAssets/components/BridgeLink.module.scss create mode 100644 src/assets/images/tokens/zusd.svg create mode 100644 src/utils/blockchain/abi/ZUSDToken.json diff --git a/src/app/components/AssetValue/types.ts b/src/app/components/AssetValue/types.ts index 41149abe9..6bd462e0c 100644 --- a/src/app/components/AssetValue/types.ts +++ b/src/app/components/AssetValue/types.ts @@ -19,6 +19,7 @@ export const AssetDecimals: { [key in Asset]: number } = { RIF: 2, MYNT: 2, TRADING: 1, + ZUSD: 2, /** @deprecated */ XUSD_legacy: 2, }; diff --git a/src/app/components/SovGenerationNFTS/index.tsx b/src/app/components/SovGenerationNFTS/index.tsx index 1e6c633ba..deef6ae40 100644 --- a/src/app/components/SovGenerationNFTS/index.tsx +++ b/src/app/components/SovGenerationNFTS/index.tsx @@ -10,73 +10,72 @@ import { useLoadSovNfts } from './useLoadSovNfts'; import SovNftToken from './SovNftToken'; import { Picture } from '../Picture'; import { translations } from 'locales/i18n'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; +import { discordInvite } from '../../../utils/classifiers'; export const SovGenerationNFTS: React.FC = () => { const account = useAccount(); const { t } = useTranslation(); - const { value: balanceCommunity } = useCacheCallWithValue( - 'SovrynNFTCommunity', - 'balanceOf', - '0', - account, - ); - const { value: balanceHero } = useCacheCallWithValue( + const { + value: balanceCommunity, + loading: loadingCommunity, + } = useCacheCallWithValue('SovrynNFTCommunity', 'balanceOf', '0', account); + const { value: balanceHero, loading: loadingHero } = useCacheCallWithValue( 'SovrynNFTHero', 'balanceOf', '0', account, ); - const { value: balanceSuperhero } = useCacheCallWithValue( - 'SovrynNFTSuperhero', - 'balanceOf', - '0', - account, - ); - const { value: balanceBday } = useCacheCallWithValue( + const { + value: balanceSuperhero, + loading: loadingSuperhero, + } = useCacheCallWithValue('SovrynNFTSuperhero', 'balanceOf', '0', account); + const { value: balanceBday, loading: loadingBday } = useCacheCallWithValue( 'SovrynNFTBday', 'balanceOf', '0', account, ); - const isEmpty = useMemo(() => { - return ( - balanceCommunity === '0' && - balanceHero === '0' && - balanceSuperhero === '0' && - balanceBday === '0' - ); - }, [balanceHero, balanceSuperhero, balanceBday, balanceCommunity]); + const tiers = useMemo( + () => + [ + { + title: t(translations.userAssets.gallery.community), + max: '0.03', + balance: balanceCommunity, + image: sov_1, + }, + { + title: t(translations.userAssets.gallery.hero), + max: '0.1', + balance: balanceHero, + image: sov_2, + }, + { + title: t(translations.userAssets.gallery.superhero), + max: '2', + balance: balanceSuperhero, + image: sov_3, + }, + ].filter(entry => entry.balance !== '0'), + [t, balanceCommunity, balanceHero, balanceSuperhero], + ); - const tiers = [ - { - title: t(translations.userAssets.gallery.community), - max: '0.03', - balance: balanceCommunity, - image: sov_1, - }, - { - title: t(translations.userAssets.gallery.hero), - max: '0.1', - balance: balanceHero, - image: sov_2, - }, - { - title: t(translations.userAssets.gallery.superhero), - max: '2', - balance: balanceSuperhero, - image: sov_3, - }, - ]; + const { items, loading: loadingList } = useLoadSovNfts(); - const { items } = useLoadSovNfts(); + const loading = useMemo( + () => + loadingBday || + loadingCommunity || + loadingHero || + loadingSuperhero || + loadingList, + [loadingBday, loadingCommunity, loadingHero, loadingList, loadingSuperhero], + ); return ( -
-

- {t(translations.userAssets.gallery.title)} -

+
{items.map(item => ( @@ -93,12 +92,23 @@ export const SovGenerationNFTS: React.FC = () => {
)} - {isEmpty && ( -

- {t(translations.userAssets.gallery.emptyGallery)} + {loading && ( +

+ {t(translations.userAssets.gallery.loadingMessage)} +

+ )} + {!loading && !items?.length && !tiers?.length && balanceBday === '0' && ( +

+ + x + , + ]} + />

)} - {tiers.map((item, index) => { return ( item.balance !== '0' && ( diff --git a/src/app/components/UserAssets/Vesting/VestedItem.tsx b/src/app/components/UserAssets/Vesting/VestedItem.tsx index 1a1d9a065..ac244f1aa 100644 --- a/src/app/components/UserAssets/Vesting/VestedItem.tsx +++ b/src/app/components/UserAssets/Vesting/VestedItem.tsx @@ -2,10 +2,7 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Tooltip } from '@blueprintjs/core'; import { LoadableValue } from '../../LoadableValue'; -import { - weiToNumberFormat, - weiToUSD, -} from '../../../../utils/display-text/format'; +import { weiToNumberFormat } from '../../../../utils/display-text/format'; import { translations } from '../../../../locales/i18n'; import { ActionButton } from '../../Form/ActionButton'; import { AssetsDictionary } from '../../../../utils/dictionaries/assets-dictionary'; @@ -13,6 +10,7 @@ import { useMaintenance } from '../../../hooks/useMaintenance'; import { FullVesting } from './types'; import { useDollarValue } from '../../../hooks/useDollarValue'; import { useGetVesting } from './useGetVesting'; +import { AssetValue } from 'app/components/AssetValue'; type VestedItemProps = { vesting: FullVesting; @@ -57,7 +55,7 @@ export const VestedItem: React.FC = ({
diff --git a/src/app/components/UserAssets/components/BridgeLink.module.scss b/src/app/components/UserAssets/components/BridgeLink.module.scss new file mode 100644 index 000000000..37a564f1c --- /dev/null +++ b/src/app/components/UserAssets/components/BridgeLink.module.scss @@ -0,0 +1,8 @@ +.actionLink { + @apply tw-text-primary tw-tracking-normal hover:tw-text-primary hover:tw-underline tw-p-0 tw-font-normal tw-font-body; + + &.disabled, + &:disabled { + @apply tw-opacity-50 tw-pointer-events-none; + } +} diff --git a/src/app/components/UserAssets/components/BridgeLink.tsx b/src/app/components/UserAssets/components/BridgeLink.tsx index a531f08bb..83a0241c6 100644 --- a/src/app/components/UserAssets/components/BridgeLink.tsx +++ b/src/app/components/UserAssets/components/BridgeLink.tsx @@ -8,9 +8,12 @@ import { useAccount } from 'app/hooks/useAccount'; import { Asset } from 'types'; import { useMaintenance } from 'app/hooks/useMaintenance'; import { useIsBridgeLinkLocked } from '../hooks/useIsBridgeLinkLocked'; +import classNames from 'classnames'; +import styles from './BridgeLink.module.scss'; interface IBridgeLinkProps { asset: Asset; + disableWithdrawal?: boolean; } enum CROSSCHAIN_TYPE { @@ -18,7 +21,10 @@ enum CROSSCHAIN_TYPE { WITHDRAW = 'withdraw', } -export const BridgeLink: React.FC = ({ asset }) => { +export const BridgeLink: React.FC = ({ + asset, + disableWithdrawal, +}) => { const receiver = useAccount(); const { t } = useTranslation(); const { checkMaintenance, States } = useMaintenance(); @@ -65,27 +71,40 @@ export const BridgeLink: React.FC = ({ asset }) => { hoverOpenDelay={0} hoverCloseDelay={0} interactionKind="hover" + disabled={disableWithdrawal} content={ <> - {assetDepositLocked - ? getMaintenanceTooltipCopy(asset, CROSSCHAIN_TYPE.DEPOSIT) + {assetWithdrawLocked + ? getMaintenanceTooltipCopy(asset, CROSSCHAIN_TYPE.WITHDRAW) : t(translations.userAssets.sendMessage, { asset })} } > - {assetDepositLocked ? ( + {assetWithdrawLocked ? (
{t(translations.common.send)}
) : ( - - {t(translations.common.send)} - + {' '} + + + {t(translations.common.send)} + + + )} @@ -96,20 +115,20 @@ export const BridgeLink: React.FC = ({ asset }) => { interactionKind="hover" content={ <> - {assetWithdrawLocked - ? getMaintenanceTooltipCopy(asset, CROSSCHAIN_TYPE.WITHDRAW) + {assetDepositLocked + ? getMaintenanceTooltipCopy(asset, CROSSCHAIN_TYPE.DEPOSIT) : t(translations.userAssets.receiveMessage, { asset })} } > - {assetWithdrawLocked ? ( + {assetDepositLocked ? (
{t(translations.common.receive)}
) : ( diff --git a/src/app/components/UserAssets/components/UserAssetsTableRow.tsx b/src/app/components/UserAssets/components/UserAssetsTableRow.tsx index 76402547d..81970e786 100644 --- a/src/app/components/UserAssets/components/UserAssetsTableRow.tsx +++ b/src/app/components/UserAssets/components/UserAssetsTableRow.tsx @@ -7,7 +7,7 @@ import { getTokenContractName } from 'utils/blockchain/contract-helpers'; import { AssetDetails } from 'utils/models/asset-details'; import { LoadableValue } from '../../LoadableValue'; import { Asset } from 'types'; -import { weiToNumberFormat, weiToUSD } from 'utils/display-text/format'; +import { weiToNumberFormat } from 'utils/display-text/format'; import { contractReader } from 'utils/sovryn/contract-reader'; import { useAccount, useBlockSync } from 'app/hooks/useAccount'; import { AssetRenderer } from '../../AssetRenderer/'; @@ -17,6 +17,7 @@ import { Button, ButtonSize, ButtonStyle } from '../../Button'; import { BridgeLink } from './BridgeLink'; import { useDollarValue } from 'app/hooks/useDollarValue'; import { CrossBridgeAsset } from 'app/pages/BridgeDepositPage/types/cross-bridge-asset'; +import { AssetValue } from 'app/components/AssetValue'; import busdIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/busd.svg'; import usdtIcon from 'app/pages/BridgeDepositPage/dictionaries/assets/icons/usdt.svg'; @@ -91,8 +92,8 @@ export const UserAssetsTableRow: React.FC = ({ }, [asset, account, blockSync]); const assetDollarValue = useDollarValue(asset, tokens); - - if (tokens === '0' && item.hideIfZero) return ; + const hasAnyTokens = useMemo(() => tokens !== '0', [tokens]); + if (!hasAnyTokens && item.hideIfZero) return ; return (
@@ -129,7 +130,9 @@ export const UserAssetsTableRow: React.FC = ({ @@ -163,7 +166,7 @@ export const UserAssetsTableRow: React.FC = ({ ) : ( {!connected && ( - <> - - - - - - - + + + + + + )} {connected && account && diff --git a/src/app/hooks/price-feeds/usePriceFeeds_tradingPairRates.ts b/src/app/hooks/price-feeds/usePriceFeeds_tradingPairRates.ts index c18314665..68a234a7b 100644 --- a/src/app/hooks/price-feeds/usePriceFeeds_tradingPairRates.ts +++ b/src/app/hooks/price-feeds/usePriceFeeds_tradingPairRates.ts @@ -21,6 +21,7 @@ const assetsWithoutOracle: Asset[] = [ Asset.RIF, Asset.RDOC, Asset.MYNT, + Asset.ZUSD, ]; const excludeAssets: Asset[] = [Asset.CSOV, Asset.RDOC, Asset.MYNT]; diff --git a/src/app/index.tsx b/src/app/index.tsx index fdcbc422a..56f402d17 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -100,7 +100,8 @@ export function App() { component={LiquidityMiningPage} /> - + + { const handleNetworkSwitch = useCallback(() => { dispatch(actions.selectSourceNetwork(Chain.RSK)); - history.push('/wallet'); + history.push('/portfolio'); }, [dispatch, history]); const addNetworkCallback = useCallback( @@ -119,7 +119,7 @@ export const ReturnToPortfolio: React.FC = () => { history.push('/wallet')} + onClick={() => history.push('/portfolio')} /> )} diff --git a/src/app/pages/BridgeDepositPage/index.tsx b/src/app/pages/BridgeDepositPage/index.tsx index 9ef89ffd3..08a616467 100644 --- a/src/app/pages/BridgeDepositPage/index.tsx +++ b/src/app/pages/BridgeDepositPage/index.tsx @@ -59,7 +59,7 @@ export const BridgeDepositPage: React.FC = () => { useEffect(() => { if (!location.state?.receiver || !location.state?.asset) { - history.push('/wallet'); + history.push('/portfolio'); } else { dispatch(actions.selectReceiver(location.state?.receiver)); // todo: change our main ETH to actual ETHs (in backend too). diff --git a/src/app/pages/BridgeWithdrawPage/components/ConfirmStep/index.tsx b/src/app/pages/BridgeWithdrawPage/components/ConfirmStep/index.tsx index b43a00cd9..b76b1b1c4 100644 --- a/src/app/pages/BridgeWithdrawPage/components/ConfirmStep/index.tsx +++ b/src/app/pages/BridgeWithdrawPage/components/ConfirmStep/index.tsx @@ -59,7 +59,7 @@ export const ConfirmStep: React.FC = () => { [chain, sourceAsset, targetChain], ); const handleComplete = useCallback(() => { - history.push('/wallet'); + history.push('/portfolio'); }, [history]); const renderInitialSteps = () => { diff --git a/src/app/pages/BridgeWithdrawPage/components/SidebarSteps/index.tsx b/src/app/pages/BridgeWithdrawPage/components/SidebarSteps/index.tsx index a5acbbe3d..3cfd54a30 100644 --- a/src/app/pages/BridgeWithdrawPage/components/SidebarSteps/index.tsx +++ b/src/app/pages/BridgeWithdrawPage/components/SidebarSteps/index.tsx @@ -174,7 +174,7 @@ export const SidebarSteps: React.FC = () => { const handleBack = useCallback(() => { if (step === WithdrawStep.CHAIN_SELECTOR) { - return history.push('/wallet'); + return history.push('/portfolio'); } else { changeStep(stepOrder[step - 1]); } diff --git a/src/app/pages/BridgeWithdrawPage/index.tsx b/src/app/pages/BridgeWithdrawPage/index.tsx index e4bf8572e..42b3df6a3 100644 --- a/src/app/pages/BridgeWithdrawPage/index.tsx +++ b/src/app/pages/BridgeWithdrawPage/index.tsx @@ -52,7 +52,7 @@ export const BridgeWithdrawPage: React.FC = () => { useEffect(() => { if (!location.state?.receiver || !location.state?.asset) { - history.push('/wallet'); + history.push('/portfolio'); } else { dispatch(actions.initReceiver(location.state?.receiver)); // todo: change our main ETH to actual ETHs (in backend too). diff --git a/src/app/pages/FastBtcPage/components/Deposit/SidebarStepsDeposit.tsx b/src/app/pages/FastBtcPage/components/Deposit/SidebarStepsDeposit.tsx index dcf815840..28b408483 100644 --- a/src/app/pages/FastBtcPage/components/Deposit/SidebarStepsDeposit.tsx +++ b/src/app/pages/FastBtcPage/components/Deposit/SidebarStepsDeposit.tsx @@ -144,7 +144,7 @@ export const SidebarStepsDeposit: React.FC = ({ ); const backToUrl = useMemo( - () => (network === Chain.BSC ? '/perpetuals' : '/wallet'), + () => (network === Chain.BSC ? '/perpetuals' : '/portfolio'), [network], ); diff --git a/src/app/pages/FastBtcPage/components/Deposit/StatusScreen.tsx b/src/app/pages/FastBtcPage/components/Deposit/StatusScreen.tsx index 646cf1590..638dbf181 100644 --- a/src/app/pages/FastBtcPage/components/Deposit/StatusScreen.tsx +++ b/src/app/pages/FastBtcPage/components/Deposit/StatusScreen.tsx @@ -25,7 +25,7 @@ export const StatusScreen: React.FC = ({ const history = useHistory(); const backToUrl = useMemo( - () => (network === Chain.BSC ? '/perpetuals' : '/wallet'), + () => (network === Chain.BSC ? '/perpetuals' : '/portfolio'), [network], ); diff --git a/src/app/pages/FastBtcPage/components/FastBtcHeader.tsx b/src/app/pages/FastBtcPage/components/FastBtcHeader.tsx index 58d4348af..ffb2e5d59 100644 --- a/src/app/pages/FastBtcPage/components/FastBtcHeader.tsx +++ b/src/app/pages/FastBtcPage/components/FastBtcHeader.tsx @@ -28,7 +28,7 @@ export const FastBtcHeader: React.FC = ({ address }) => { return (
- {t(translations.common.back)} + {t(translations.common.back)}
{prettyTx(address || '', 4, 4)} diff --git a/src/app/pages/FastBtcPage/components/Withdraw/SidebarStepsWithdraw.tsx b/src/app/pages/FastBtcPage/components/Withdraw/SidebarStepsWithdraw.tsx index b00490aa8..d80e08c5b 100644 --- a/src/app/pages/FastBtcPage/components/Withdraw/SidebarStepsWithdraw.tsx +++ b/src/app/pages/FastBtcPage/components/Withdraw/SidebarStepsWithdraw.tsx @@ -165,7 +165,7 @@ export const SidebarStepsWithdraw: React.FC = ({ ); const backToUrl = useMemo( - () => (network === Chain.BSC ? '/perpetuals' : '/wallet'), + () => (network === Chain.BSC ? '/perpetuals' : '/portfolio'), [network], ); diff --git a/src/app/pages/FastBtcPage/components/Withdraw/StatusScreen.tsx b/src/app/pages/FastBtcPage/components/Withdraw/StatusScreen.tsx index a3640f08e..5d53258ea 100644 --- a/src/app/pages/FastBtcPage/components/Withdraw/StatusScreen.tsx +++ b/src/app/pages/FastBtcPage/components/Withdraw/StatusScreen.tsx @@ -33,7 +33,7 @@ export const StatusScreen: React.FC = ({ tx, network }) => { const history = useHistory(); const backToUrl = useMemo( - () => (network === Chain.BSC ? '/perpetuals' : '/wallet'), + () => (network === Chain.BSC ? '/perpetuals' : '/portfolio'), [network], ); diff --git a/src/app/pages/FastBtcPage/index.tsx b/src/app/pages/FastBtcPage/index.tsx index b3a4b8036..d615794fb 100644 --- a/src/app/pages/FastBtcPage/index.tsx +++ b/src/app/pages/FastBtcPage/index.tsx @@ -38,7 +38,7 @@ export const FastBtcPage: React.FC = () => { type, ) ) { - history.push('/wallet'); + history.push('/portfolio'); } }, [type, history]); diff --git a/src/app/pages/OriginsLaunchpad/pages/SalesDay/pages/ImportantInformationStep/index.tsx b/src/app/pages/OriginsLaunchpad/pages/SalesDay/pages/ImportantInformationStep/index.tsx index 6e1caeb9a..429a113df 100644 --- a/src/app/pages/OriginsLaunchpad/pages/SalesDay/pages/ImportantInformationStep/index.tsx +++ b/src/app/pages/OriginsLaunchpad/pages/SalesDay/pages/ImportantInformationStep/index.tsx @@ -90,7 +90,7 @@ export const ImportantInformationStep: React.FC i18nKey={baseTranslations.information[7]} values={{ token: saleName }} components={[ - + x , ]} diff --git a/src/app/pages/PortfolioPage/index.tsx b/src/app/pages/PortfolioPage/index.tsx index 1db1ea539..61ebee625 100644 --- a/src/app/pages/PortfolioPage/index.tsx +++ b/src/app/pages/PortfolioPage/index.tsx @@ -18,22 +18,6 @@ export const PortfolioPage: React.FC = () => { const connected = useIsConnected(); const account = useAccount(); - const historyTabs = useMemo( - () => [ - { - id: 'topUpHistory', - label: t(translations.topUpHistory.meta.title), - content: , - }, - { - id: 'vestedHistory', - label: t(translations.vestedHistory.title), - content: , - }, - ], - [t], - ); - const portfolioTabs = useMemo( () => [ { @@ -55,6 +39,22 @@ export const PortfolioPage: React.FC = () => { [t], ); + const historyTabs = useMemo( + () => [ + { + id: 'topUpHistory', + label: t(translations.topUpHistory.meta.title), + content: , + }, + { + id: 'vestedHistory', + label: t(translations.vestedHistory.title), + content: , + }, + ], + [t], + ); + return ( <> @@ -79,38 +79,42 @@ export const PortfolioPage: React.FC = () => { />
-
- -
- -
- {connected && account ? ( +
+
+ +
+
+ {connected && account ? ( + + ) : ( +
+
+ +
+
+ )} +
+ {connected && account && ( - ) : ( -
-
- -
-
)}
- {connected && account && ( - - )} ); }; diff --git a/src/app/pages/RewardPage/hooks/useGetFeesEarnedClaimAmount.ts b/src/app/pages/RewardPage/hooks/useGetFeesEarnedClaimAmount.ts index 1a5eff645..2386aa3f7 100644 --- a/src/app/pages/RewardPage/hooks/useGetFeesEarnedClaimAmount.ts +++ b/src/app/pages/RewardPage/hooks/useGetFeesEarnedClaimAmount.ts @@ -70,6 +70,12 @@ const defaultEarnedFees: IEarnedFee[] = [ value: '0', rbtcValue: 0, }, + { + asset: Asset.ZUSD, + contractAddress: getContract('ZUSD_token').address, + value: '0', + rbtcValue: 0, + }, ]; const useGetFeesEarned = () => { diff --git a/src/assets/images/tokens/zusd.svg b/src/assets/images/tokens/zusd.svg new file mode 100644 index 000000000..c07e115cb --- /dev/null +++ b/src/assets/images/tokens/zusd.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 275819a70..4bc6314b0 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -822,6 +822,7 @@ "currentVests": { "title": "Vesting contracts", "asset": "Asset", + "dollarBalance": "USD Value", "lockedAmount": "Locked Amount", "votingPower": "Voting Delegation", "stakingDate": "End Date", @@ -1199,8 +1200,8 @@ "portfolio": "Portfolio", "rewards": "Rewards", "meta": { - "title": "My Wallet", - "description": "Information about connected wallet" + "title": "Portfolio", + "description": "Manage supported assets in your connected wallet" }, "tabs": { "userAssets": "Assets", @@ -1229,13 +1230,14 @@ }, "userAssets": { "meta": { - "title": "Connected Wallet" + "title": "Portfolio", + "description": "Manage supported assets in your connected wallet" }, - "emptyVestTable": "You don't have any current vest", + "emptyVestTable": "You don't have any vesting contracts", "tableHeaders": { "asset": "Asset", - "totalBalance": "Total Balance", - "dollarBalance": "Dollar Balance", + "balance": "Balance", + "dollarBalance": "USD Value", "lockedAmount": "Locked Amount" }, "actions": { @@ -1296,7 +1298,8 @@ "tier": "Tier", "sovOG": "SOV-OG", "btc": "BTC", - "emptyGallery": "Gallery is empty." + "emptyGallery": "Your connected wallet does not currently have any supported NFTs. Join the <0>Sovryn community for opportunities to earn NFT rewards.", + "loadingMessage": "Loading your gallery." } }, "swapHistory": { diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 93f64b507..511e74877 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -382,9 +382,11 @@ } }, "portfolioPage": { + "portfolio": "Portfolio", + "rewards": "Rewards", "meta": { - "title": "My Wallet", - "description": "Information about connected wallet" + "title": "Portfolio", + "description": "Manage supported assets in your connected wallet" }, "tabs": { "userAssets": "Assets", @@ -399,19 +401,76 @@ }, "userAssets": { "meta": { - "title": "Connected Wallet" + "title": "Portfolio", + "description": "Manage supported assets in your connected wallet" }, + "emptyVestTable": "You don’t have any vesting contracts", "tableHeaders": { "asset": "Asset", - "totalBalance": "Total Balance", - "dollarBalance": "Dollar Balance", - "action": "Action" + "balance": "Balance", + "dollarBalance": "USD Value", + "lockedAmount": "Locked Amount" }, "actions": { - "fastBtc": "FastBTC", + "deposit": "Deposit", + "fastBtc": "Deposit", + "convert": "Convert to XUSD", + "unwrap": "Unwrap to RBTC", "swap": "Swap", - "trade": "Trade", - "claimSov": "Claim SOV" + "trade": "Margin Trade", + "buy": "Buy", + "claimSov": "Claim SOV", + "redeemRBTC": "Redeem RBTC", + "withdraw": "Withdraw" + }, + "sendMessage": "Send {{asset}} from your connected address in the RSK network", + "receiveMessage": "Receive {{asset}} to your connected address in the RSK network", + "convertDialog": { + "title": "Convert", + "from": "From", + "to": "To", + "cta": "Convert to XUSD", + "txDialog": { + "titleInProgress": "Transaction in Progress", + "titleCompleted": "Transaction Complete", + "titleFailed": "Transaction Failed", + "statusInProgress": "Minting can take a couple of minutes", + "dateTime": "Date/Time", + "amountSent": "Amount Sent", + "amountReceived": "Amount Received", + "hash": "RSK Relay Hash", + "cta": "Close" + } + }, + "unwrapDialog": { + "title": "Unwrap WRBTC", + "from": "From", + "to": "To", + "cta": "Unwrap to RBTC", + "txDialog": { + "titleInProgress": "Transaction in Progress", + "titleCompleted": "Transaction Complete", + "titleFailed": "Transaction Failed", + "statusInProgress": "Minting can take a couple of minutes", + "dateTime": "Date/Time", + "amountSent": "Amount Sent", + "amountReceived": "Amount Received", + "hash": "RSK Relay Hash", + "cta": "Close" + } + }, + "gallery": { + "title": "SOV Generation 01 NFT's", + "community": "Community", + "hero": "Hero", + "superhero": "Superhero", + "preOrder": "SOV Genesis Pre-Order", + "purchaseLimit": "Purchase Limit:", + "tier": "Tier", + "sovOG": "SOV-OG", + "btc": "BTC", + "emptyGallery": "Your connected wallet does not currently have any supported NFTs. Join the <0>Sovryn community for opportunities to earn NFT rewards.", + "loadingMessage": "Loading your gallery." } }, "swapHistory": { diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index be6cdca41..153006b83 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -833,9 +833,11 @@ "referrals": "Parrainer un ami" }, "portfolioPage": { + "portfolio": "Portfolio", + "rewards": "Rewards", "meta": { - "description": "Informations sur le portefeuille connecté", - "title": "Mon portefeuille" + "title": "Portfolio", + "description": "Manage supported assets in your connected wallet" }, "tabs": { "userAssets": "Actifs", diff --git a/src/locales/pt_br/translation.json b/src/locales/pt_br/translation.json index b1ad6a75e..32c621703 100644 --- a/src/locales/pt_br/translation.json +++ b/src/locales/pt_br/translation.json @@ -1154,14 +1154,16 @@ } }, "portfolioPage": { + "portfolio": "Portfólio", + "rewards": "Rewards", "meta": { "title": "Portfólio", - "description": "Informações sobre a carteira conectada" + "description": "Manage supported assets in your connected wallet" }, "tabs": { - "userAssets": "Meus ativos", - "vestedAssets": "SOV reservados", - "userNFTS": "NFTS" + "userAssets": "Assets", + "vestedAssets": "Vesting", + "userNFTS": "Gallery" } }, "salesPage": { @@ -1172,15 +1174,15 @@ }, "userAssets": { "meta": { - "title": "Carteira" + "title": "Portfolio", + "description": "Manage supported assets in your connected wallet" }, - "emptyVestTable": "Não existem SOV reservados para você", + "emptyVestTable": "You don’t have any vesting contracts", "tableHeaders": { - "asset": "Ativo", - "totalBalance": "Saldo total", - "dollarBalance": "Saldo em dólar", - "lockedAmount": "Quantidade bloqueada", - "action": "Ação" + "asset": "Asset", + "balance": "Balance", + "dollarBalance": "USD Value", + "lockedAmount": "Locked Amount" }, "actions": { "deposit": "Bridge", @@ -1210,6 +1212,36 @@ "hash": "ID transação", "cta": "Fechar" } + }, + "unwrapDialog": { + "title": "Unwrap WRBTC", + "from": "From", + "to": "To", + "cta": "Unwrap to RBTC", + "txDialog": { + "titleInProgress": "Transaction in Progress", + "titleCompleted": "Transaction Complete", + "titleFailed": "Transaction Failed", + "statusInProgress": "Minting can take a couple of minutes", + "dateTime": "Date/Time", + "amountSent": "Amount Sent", + "amountReceived": "Amount Received", + "hash": "RSK Relay Hash", + "cta": "Close" + } + }, + "gallery": { + "title": "SOV Generation 01 NFT's", + "community": "Community", + "hero": "Hero", + "superhero": "Superhero", + "preOrder": "SOV Genesis Pre-Order", + "purchaseLimit": "Purchase Limit:", + "tier": "Tier", + "sovOG": "SOV-OG", + "btc": "BTC", + "emptyGallery": "Your connected wallet does not currently have any supported NFTs. Join the <0>Sovryn community for opportunities to earn NFT rewards.", + "loadingMessage": "Loading your gallery." } }, "swapHistory": { diff --git a/src/types/asset.ts b/src/types/asset.ts index 27d281e93..9da3305bc 100644 --- a/src/types/asset.ts +++ b/src/types/asset.ts @@ -19,4 +19,5 @@ export enum Asset { TRADING = 'TRADING', RIF = 'RIF', MYNT = 'MYNT', + ZUSD = 'ZUSD', } diff --git a/src/utils/blockchain/abi/ZUSDToken.json b/src/utils/blockchain/abi/ZUSDToken.json new file mode 100644 index 000000000..fe60868f2 --- /dev/null +++ b/src/utils/blockchain/abi/ZUSDToken.json @@ -0,0 +1,536 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_newBorrowerOperationsAddress", + "type": "address" + } + ], + "name": "BorrowerOperationsAddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_newStabilityPoolAddress", + "type": "address" + } + ], + "name": "StabilityPoolAddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_troveManagerAddress", + "type": "address" + } + ], + "name": "TroveManagerAddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "ZUSDTokenBalanceUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "domainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_troveManagerAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_stabilityPoolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_borrowerOperationsAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "permitTypeHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_poolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "returnFromPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_poolAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "sendToPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/utils/blockchain/contracts.testnet.ts b/src/utils/blockchain/contracts.testnet.ts index 24e53a530..0a30e2c9b 100644 --- a/src/utils/blockchain/contracts.testnet.ts +++ b/src/utils/blockchain/contracts.testnet.ts @@ -33,7 +33,6 @@ import OrderBookAbi from './abi/OrderBook.json'; import OrderBookMarginAbi from './abi/OrderBookMargin.json'; import nftAbi from './abi/nftAbi.json'; import MYNTControllerAbi from './abi/MYNTController.json'; -import MYNTTokenAbi from './abi/MYNT_token.json'; import MYNTPresaleAbi from './abi/MYNTPresale.json'; import MYNTMarketMakerAbi from './abi/MYNTMarketMaker.json'; import perpetualManagerAbi from './abi/PerpetualManager.json'; @@ -310,7 +309,7 @@ export const contracts = { }, MYNT_token: { address: '0x139483e22575826183F5b56dd242f8f2C1AEf327', - abi: MYNTTokenAbi, + abi: erc20TokenAbi, blockNumber: 2267574, }, MYNTPresale: { @@ -349,4 +348,8 @@ export const contracts = { abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_TESTNET, }, + ZUSD_token: { + address: '0x6b41566353d6C7B8C2a7931d498F11489DacAc29', + abi: erc20TokenAbi, + }, }; diff --git a/src/utils/blockchain/contracts.ts b/src/utils/blockchain/contracts.ts index 8c3e9ac98..9ff1bc536 100644 --- a/src/utils/blockchain/contracts.ts +++ b/src/utils/blockchain/contracts.ts @@ -36,7 +36,6 @@ import OrderBookAbi from './abi/OrderBook.json'; import OrderBookMarginAbi from './abi/OrderBookMargin.json'; import nftAbi from './abi/nftAbi.json'; import MYNTControllerAbi from './abi/MYNTController.json'; -import MYNTTokenAbi from './abi/MYNT_token.json'; import MYNTPresaleAbi from './abi/MYNTPresale.json'; import MYNTMarketMakerAbi from './abi/MYNTMarketMaker.json'; import fastBtcBridgeAbi from './abi/fastBtcBridge.json'; @@ -310,7 +309,7 @@ export const contracts = { }, MYNT_token: { address: '0x2e6B1d146064613E8f521Eb3c6e65070af964EbB', - abi: MYNTTokenAbi, + abi: erc20TokenAbi, blockNumber: 3832084, }, MYNTPresale: { @@ -350,4 +349,8 @@ export const contracts = { abi: perpetualLimitOrderBookAbi, chainId: ChainId.BSC_MAINNET, }, + ZUSD_token: { + address: '0xdB107FA69E33f05180a4C2cE9c2E7CB481645C2d', + abi: erc20TokenAbi, + }, }; diff --git a/src/utils/dictionaries/assets-dictionary.ts b/src/utils/dictionaries/assets-dictionary.ts index c213f2144..b219b4f0b 100644 --- a/src/utils/dictionaries/assets-dictionary.ts +++ b/src/utils/dictionaries/assets-dictionary.ts @@ -13,6 +13,7 @@ import fishIcon from 'assets/images/tokens/babelfish.svg'; import rdoc from 'assets/images/tokens/rifd.svg'; import rifToken from 'assets/images/tokens/rif.svg'; import mintIcon from 'assets/images/tokens/mint.svg'; +import zusdIcon from 'assets/images/tokens/zusd.svg'; import { AssetDetails } from '../models/asset-details'; import { isMainnet } from 'utils/classifiers'; @@ -143,6 +144,10 @@ export class AssetsDictionary { Asset.MYNT, new AssetDetails(Asset.MYNT, 'MYNT', 'MYNT', 18, 3, mintIcon, true), ], + [ + Asset.ZUSD, + new AssetDetails(Asset.ZUSD, 'ZUSD', 'ZUSD', 18, 2, zusdIcon, true), + ], ], ); From a69f5f4e15dce4c6779834785439ef368c47b27f Mon Sep 17 00:00:00 2001 From: soulBit Date: Wed, 15 Jun 2022 17:19:48 +0100 Subject: [PATCH 37/39] fix: Portfolio tab responsive styling --- src/app/pages/PortfolioPage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/PortfolioPage/index.tsx b/src/app/pages/PortfolioPage/index.tsx index 61ebee625..b71fb6014 100644 --- a/src/app/pages/PortfolioPage/index.tsx +++ b/src/app/pages/PortfolioPage/index.tsx @@ -65,7 +65,7 @@ export const PortfolioPage: React.FC = () => { /> -
+
+ {t(translations.topUpHistory.tableHeaders.time)} + {t(translations.topUpHistory.tableHeaders.asset)} - {t(translations.topUpHistory.tableHeaders.amount)} - + {t(translations.topUpHistory.tableHeaders.amount)} {t(translations.topUpHistory.tableHeaders.depositTx)} @@ -88,7 +86,7 @@ export const TopUpHistory: React.FC = () => { {t(translations.topUpHistory.tableHeaders.txHash)} + {t(translations.topUpHistory.tableHeaders.transferTx)}
+ + {asset.asset}{' '} - {item.type === 'deposit' ? ( - 'BTC' + {item.type === HistoryItemType.DEPOSIT ? ( + t(translations.topUpHistory.btc) ) : ( )} -
- +
+ {toNumberFormat(item.valueBtc / btcInSatoshis, 6)} - {' '} - - {item.type === 'deposit' ? 'BTC' : 'rBTC'} - + {' '} + + {item.type === HistoryItemType.DEPOSIT ? ( + t(translations.topUpHistory.btc) + ) : ( + + )} +
- {item.type === 'deposit' && ( + {item.type === HistoryItemType.DEPOSIT ? ( + ) : ( + )} - {item.type === 'transfer' && ( + {item.type === HistoryItemType.TRANSFER ? ( + ) : ( + )}
} loading={dollarValue.loading || loading} />
+ } loading={assetDollarValue.loading} />
{t(translations.userAssets.tableHeaders.asset)} - {t(translations.userAssets.tableHeaders.totalBalance)} + {t(translations.userAssets.tableHeaders.balance)} - {t(translations.userAssets.tableHeaders.dollarBalance)} + {t(translations.stake.currentVests.dollarBalance)}
- - - - - - - -
+ + + + + + + +