From 22b9f7b518679368073cc6255f44c81ab2738073 Mon Sep 17 00:00:00 2001 From: Linkie Link Date: Tue, 12 Dec 2023 11:14:29 +0100 Subject: [PATCH] Simple spot trading (#684) * env: added sharp * fix: use dvh over vh * feat: prepared the trade view for perps and spot * fix: adjusted heights for Trade * feat: added Navigation submenu * feat: added first interface itteration * feat: added logic * feat: added pairsList * feat: finished Trade Spot Simple * fix: fixed Sell button * fix: adjusted capLeft logic and added sorting util * fix: order of values * fix: fixed the autoLend switch to be deselectable * env: bump version * fix: changes according to feedback * fix: fixed naming * tidy: refactor --- package.json | 2 +- .../AccountBalancesTable/Columns/LiqPrice.tsx | 21 +- .../Columns/useAccountBalancesColumns.tsx | 3 +- src/components/DirectionSelect.tsx | 59 ++ src/components/Divider.tsx | 13 +- src/components/Header/DesktopHeader.tsx | 22 +- src/components/Icons/Coins.svg | 6 + src/components/Icons/CoinsSwap.svg | 8 + src/components/Icons/index.ts | 2 + .../Navigation/DesktopNavigation.tsx | 59 +- src/components/Navigation/NavLink.tsx | 32 +- src/components/Navigation/NavMenu.tsx | 80 +++ src/components/Perps/Module/PerpsModule.tsx | 6 +- .../Perps/Module/SelectLongShort.tsx | 62 -- src/components/Routes.tsx | 5 +- src/components/Switch/SwitchAutoLend.tsx | 7 +- src/components/Trade/AccountDetailsCard.tsx | 16 +- .../Trade/TradeChart/TVChartContainer.tsx | 2 +- src/components/Trade/TradeChart/index.tsx | 2 +- .../TradeModule/AssetSelector/AssetItem.tsx | 19 +- .../TradeModule/AssetSelector/AssetList.tsx | 41 +- .../AssetSelector/AssetOverlay.tsx | 134 ++++- .../AssetSelector/AssetSelectorItem.tsx | 129 ++++ .../AssetSelector/AssetSelectorPair.tsx | 59 ++ .../{index.tsx => AssetSelectorSingle.tsx} | 23 +- .../TradeModule/AssetSelector/PairsList.tsx | 73 +++ .../TradeModule/SwapForm/MarginToggle.tsx | 2 +- .../TradeModule/SwapForm/TradeSummary.tsx | 12 +- .../Trade/TradeModule/SwapForm/index.tsx | 434 ++++++++------ src/components/Trade/TradeModule/index.tsx | 11 +- src/constants/defaultSettings.ts | 3 +- src/constants/localStorageKeys.ts | 3 +- src/pages/TradePage.tsx | 36 +- src/pages/_layout.tsx | 7 +- src/types/interfaces/asset.d.ts | 5 + .../interfaces/components/AssetOverlay.d.ts | 2 +- .../interfaces/components/Navigation.d.ts | 8 + src/types/interfaces/components/Trade.d.ts | 4 + src/types/interfaces/perps.d.ts | 2 +- src/types/interfaces/route.d.ts | 1 + src/types/interfaces/store/settings.d.ts | 3 +- src/utils/assets.ts | 43 ++ src/utils/health_computer/index.js | 551 ++++++++++-------- src/utils/math.ts | 2 +- src/utils/route.ts | 1 + tailwind.config.js | 2 + 46 files changed, 1345 insertions(+), 672 deletions(-) create mode 100644 src/components/DirectionSelect.tsx create mode 100644 src/components/Icons/Coins.svg create mode 100644 src/components/Icons/CoinsSwap.svg create mode 100644 src/components/Navigation/NavMenu.tsx delete mode 100644 src/components/Perps/Module/SelectLongShort.tsx create mode 100644 src/components/Trade/TradeModule/AssetSelector/AssetSelectorItem.tsx create mode 100644 src/components/Trade/TradeModule/AssetSelector/AssetSelectorPair.tsx rename src/components/Trade/TradeModule/AssetSelector/{index.tsx => AssetSelectorSingle.tsx} (74%) create mode 100644 src/components/Trade/TradeModule/AssetSelector/PairsList.tsx create mode 100644 src/types/interfaces/components/Trade.d.ts diff --git a/package.json b/package.json index b91f0eda..7ee7803d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mars-v2-frontend", - "version": "2.1.1", + "version": "2.1.2", "private": true, "scripts": { "build": "yarn validate-env && next build", diff --git a/src/components/Account/AccountBalancesTable/Columns/LiqPrice.tsx b/src/components/Account/AccountBalancesTable/Columns/LiqPrice.tsx index f672bdd0..f82bc914 100644 --- a/src/components/Account/AccountBalancesTable/Columns/LiqPrice.tsx +++ b/src/components/Account/AccountBalancesTable/Columns/LiqPrice.tsx @@ -21,11 +21,13 @@ interface Props { computeLiquidationPrice: (denom: string, kind: LiquidationPriceKind) => number | null denom: string type: 'deposits' | 'borrowing' | 'lending' | 'vault' + account: Account } export default function LiqPrice(props: Props) { - const { denom, type, amount, computeLiquidationPrice } = props + const { denom, type, amount, account, computeLiquidationPrice } = props const [lastLiquidationPrice, setLastLiquidationPrice] = useState(null) + const hasDebt = account.debts.length > 0 const liqPrice = useMemo(() => { if (type === 'vault' || amount === 0) return 0 @@ -38,19 +40,18 @@ export default function LiqPrice(props: Props) { if (lastLiquidationPrice !== liqPrice && liqPrice !== null) setLastLiquidationPrice(liqPrice) }, [liqPrice, lastLiquidationPrice]) + const tooltipText = useMemo(() => { + if (type === 'vault') + return 'Liquidation prices cannot be calculated for farm positions. But it a drop in price of the underlying assets can still cause a liquidation.' + if (!hasDebt) return 'Your position cannot be liquidated as you currently have no debt.' + return 'The position size is too small to liquidate the account, even if the price goes to $0.00.' + }, [type, hasDebt]) + if (!lastLiquidationPrice || (liquidationPrice === 0 && lastLiquidationPrice === 0)) return ( N/A - + diff --git a/src/components/Account/AccountBalancesTable/Columns/useAccountBalancesColumns.tsx b/src/components/Account/AccountBalancesTable/Columns/useAccountBalancesColumns.tsx index 7f55b612..e05cfcb8 100644 --- a/src/components/Account/AccountBalancesTable/Columns/useAccountBalancesColumns.tsx +++ b/src/components/Account/AccountBalancesTable/Columns/useAccountBalancesColumns.tsx @@ -81,6 +81,7 @@ export default function useAccountBalancesColumns( computeLiquidationPrice={computeLiquidationPrice} type={row.original.type} amount={row.original.amount.toNumber()} + account={updatedAccount ?? account} /> ), }, @@ -98,5 +99,5 @@ export default function useAccountBalancesColumns( ), }, ] - }, [computeLiquidationPrice, markets, showLiquidationPrice]) + }, [computeLiquidationPrice, markets, showLiquidationPrice, account, updatedAccount]) } diff --git a/src/components/DirectionSelect.tsx b/src/components/DirectionSelect.tsx new file mode 100644 index 00000000..e94da6ed --- /dev/null +++ b/src/components/DirectionSelect.tsx @@ -0,0 +1,59 @@ +import classNames from 'classnames' + +import Text from 'components/Text' + +interface Props { + direction: OrderDirection + onChangeDirection: (direction: OrderDirection) => void + asset?: Asset +} + +export function DirectionSelect(props: Props) { + const hasAsset = props.asset + const directions: OrderDirection[] = hasAsset ? ['buy', 'sell'] : ['long', 'short'] + return ( +
+ props.onChangeDirection(directions[0])} + direction={directions[0]} + isActive={props.direction === directions[0]} + asset={props.asset} + /> + props.onChangeDirection(directions[1])} + direction={directions[1]} + isActive={props.direction === directions[1]} + asset={props.asset} + /> +
+ ) +} + +interface DirectionProps { + direction: 'long' | 'short' | 'buy' | 'sell' + isActive: boolean + onClick: () => void + asset?: Asset +} +function Direction(props: DirectionProps) { + const classString = props.direction === 'long' || props.direction === 'buy' ? 'success' : 'error' + return ( + + ) +} diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx index efd9fcca..7ba94868 100644 --- a/src/components/Divider.tsx +++ b/src/components/Divider.tsx @@ -6,8 +6,13 @@ interface Props { } export default function Divider(props: Props) { - if (props.orientation === 'vertical') { - return
- } - return
+ return ( +
+ ) } diff --git a/src/components/Header/DesktopHeader.tsx b/src/components/Header/DesktopHeader.tsx index 555d40c0..83771ef6 100644 --- a/src/components/Header/DesktopHeader.tsx +++ b/src/components/Header/DesktopHeader.tsx @@ -4,18 +4,36 @@ import { isDesktop } from 'react-device-detect' import AccountMenu from 'components/Account/AccountMenu' import EscButton from 'components/Button/EscButton' import OracleResyncButton from 'components/Header/OracleResyncButton' +import { Coins, CoinsSwap } from 'components/Icons' import DesktopNavigation from 'components/Navigation/DesktopNavigation' import RewardsCenter from 'components/RewardsCenter' import Settings from 'components/Settings' import Wallet from 'components/Wallet' import useAccountId from 'hooks/useAccountId' import useStore from 'store' -import { ENABLE_HLS, ENABLE_PERPS } from 'utils/constants' import { WalletID } from 'types/enums/wallet' +import { ENABLE_HLS, ENABLE_PERPS } from 'utils/constants' import { getGovernanceUrl } from 'utils/helpers' export const menuTree = (walletId: WalletID): MenuTreeEntry[] => [ - { pages: ['trade'], label: 'Trade' }, + { + pages: ['trade', 'trade-advanced'], + label: 'Trade', + submenu: [ + { + page: 'trade', + label: 'Spot', + subtitle: 'Trade assets against stables', + icon: , + }, + { + page: 'trade-advanced', + label: 'Spot Advanced', + subtitle: 'Trade any assets', + icon: , + }, + ], + }, ...(ENABLE_PERPS ? [{ pages: ['perps'] as Page[], label: 'Perps' }] : []), { pages: ['lend', 'farm'], label: 'Earn' }, { pages: ['borrow'], label: 'Borrow' }, diff --git a/src/components/Icons/Coins.svg b/src/components/Icons/Coins.svg new file mode 100644 index 00000000..0c43ca94 --- /dev/null +++ b/src/components/Icons/Coins.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/components/Icons/CoinsSwap.svg b/src/components/Icons/CoinsSwap.svg new file mode 100644 index 00000000..088c0862 --- /dev/null +++ b/src/components/Icons/CoinsSwap.svg @@ -0,0 +1,8 @@ + + + diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts index f078645d..fc2773f7 100644 --- a/src/components/Icons/index.ts +++ b/src/components/Icons/index.ts @@ -15,6 +15,8 @@ export { default as ChevronLeft } from 'components/Icons/ChevronLeft.svg' export { default as ChevronRight } from 'components/Icons/ChevronRight.svg' export { default as ChevronUp } from 'components/Icons/ChevronUp.svg' export { default as Circle } from 'components/Icons/Circle.svg' +export { default as Coins } from 'components/Icons/Coins.svg' +export { default as CoinsSwap } from 'components/Icons/CoinsSwap.svg' export { default as Compass } from 'components/Icons/Compass.svg' export { default as Copy } from 'components/Icons/Copy.svg' export { default as Cross } from 'components/Icons/Cross.svg' diff --git a/src/components/Navigation/DesktopNavigation.tsx b/src/components/Navigation/DesktopNavigation.tsx index e0ad5bee..73cd1a8a 100644 --- a/src/components/Navigation/DesktopNavigation.tsx +++ b/src/components/Navigation/DesktopNavigation.tsx @@ -1,34 +1,29 @@ import { useShuttle } from '@delphi-labs/shuttle-react' import classNames from 'classnames' import { useMemo } from 'react' -import { useSearchParams } from 'react-router-dom' import Button from 'components/Button' import { menuTree } from 'components/Header/DesktopHeader' import { ChevronDown, Logo } from 'components/Icons' import { NavLink } from 'components/Navigation/NavLink' -import useAccountId from 'hooks/useAccountId' +import { NavMenu } from 'components/Navigation/NavMenu' import useToggle from 'hooks/useToggle' import useStore from 'store' import { WalletID } from 'types/enums/wallet' -import { getRoute } from 'utils/route' + +export function getIsActive(pages: string[]) { + const segments = location.pathname.split('/') + return pages.some((page) => segments.includes(page)) +} export default function DesktopNavigation() { const [showMenu, setShowMenu] = useToggle() const { recentWallet } = useShuttle() const walletId = (recentWallet?.providerId as WalletID) ?? WalletID.Keplr - const address = useStore((s) => s.address) - const accountId = useAccountId() - const [searchParams] = useSearchParams() const focusComponent = useStore((s) => s.focusComponent) const menu = useMemo(() => menuTree(walletId), [walletId]) - function getIsActive(pages: string[]) { - const segments = location.pathname.split('/') - return pages.some((page) => segments.includes(page)) - } - return (
- + {!focusComponent && ( -
- {menu.map((item, index) => ( - - ))} +
+ {menu.map((item, index) => + item.submenu ? ( + + ) : ( + + ), + )}
- ) -} - -const directionColors = { - long: 'text-success', - short: 'text-error', -} - -const borderColors = { - long: 'border-success', - short: 'border-error', -} diff --git a/src/components/Routes.tsx b/src/components/Routes.tsx index 5ad3abf9..4561dbde 100644 --- a/src/components/Routes.tsx +++ b/src/components/Routes.tsx @@ -11,8 +11,7 @@ import PerpsPage from 'pages/PerpsPage' import PortfolioAccountPage from 'pages/PortfolioAccountPage' import PortfolioPage from 'pages/PortfolioPage' import TradePage from 'pages/TradePage' -import { ENABLE_PERPS } from 'utils/constants' -import { ENABLE_HLS } from 'utils/constants' +import { ENABLE_HLS, ENABLE_PERPS } from 'utils/constants' export default function Routes() { return ( @@ -25,6 +24,7 @@ export default function Routes() { } > } /> + } /> {ENABLE_PERPS && } />} } /> } /> @@ -36,6 +36,7 @@ export default function Routes() { } /> } /> + } /> {ENABLE_PERPS && } />} } /> } /> diff --git a/src/components/Switch/SwitchAutoLend.tsx b/src/components/Switch/SwitchAutoLend.tsx index 42254d53..7d711701 100644 --- a/src/components/Switch/SwitchAutoLend.tsx +++ b/src/components/Switch/SwitchAutoLend.tsx @@ -27,15 +27,12 @@ export default function SwitchAutoLend(props: Props) { return } - if (isAutoLendEnabled) { - setIsAutoLendEnabled(false) - disableAutoLend(accountId) - } + setIsAutoLendEnabled(false) + disableAutoLend(accountId) }, [ accountId, disableAutoLend, enableAutoLend, - isAutoLendEnabled, isAutoLendEnabledForAccount, setIsAutoLendEnabled, ]) diff --git a/src/components/Trade/AccountDetailsCard.tsx b/src/components/Trade/AccountDetailsCard.tsx index 0a994467..3f153243 100644 --- a/src/components/Trade/AccountDetailsCard.tsx +++ b/src/components/Trade/AccountDetailsCard.tsx @@ -18,12 +18,14 @@ export default function AccountDetailsCard() { if (account) return ( - +
+ +
) } diff --git a/src/components/Trade/TradeChart/TVChartContainer.tsx b/src/components/Trade/TradeChart/TVChartContainer.tsx index 4197e7e6..e2ecea4c 100644 --- a/src/components/Trade/TradeChart/TVChartContainer.tsx +++ b/src/components/Trade/TradeChart/TVChartContainer.tsx @@ -156,7 +156,7 @@ export const TVChartContainer = (props: Props) => {
} contentClassName='px-0.5 pb-0.5 h-full' - className='min-h-[55dvh] h-full' + className='h-[70dvh] max-h-[980px] min-h-[560px]' >
diff --git a/src/components/Trade/TradeChart/index.tsx b/src/components/Trade/TradeChart/index.tsx index 163969b9..865133fb 100644 --- a/src/components/Trade/TradeChart/index.tsx +++ b/src/components/Trade/TradeChart/index.tsx @@ -45,7 +45,7 @@ export default function TradeChart(props: Props) {
} contentClassName='px-0.5 pb-0.5 h-full' - className='min-h-[55dvh]' + className='h-[70dvh] max-h-[980px] min-h-[560px]' >
diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx index 27512dce..b3aa58bc 100644 --- a/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx +++ b/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx @@ -1,3 +1,5 @@ +import { useMemo } from 'react' + import AssetImage from 'components/Asset/AssetImage' import AssetSymbol from 'components/Asset/AssetSymbol' import DisplayCurrency from 'components/DisplayCurrency' @@ -38,6 +40,13 @@ export default function AssetItem(props: Props) { setFavoriteAssetsDenoms(favoriteAssetsDenoms.filter((item: string) => item !== asset.denom)) } + const capLeft = useMemo(() => { + if (!props.depositCap) return 0 + const percent = props.depositCap.used.dividedBy(props.depositCap.max).multipliedBy(100) + const depositCapLeft = 100 - Math.min(percent.toNumber(), 100) + return depositCapLeft + }, [props.depositCap]) + return (
  • - {props.isOpen && - (props.assets.length === 0 ? ( + {isOpen && + (sortedAssets.length === 0 ? ( No available assets found ) : (
      - {props.assets.map((asset) => ( - ( + ))}
    diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx index 30dbaeed..9e1f1465 100644 --- a/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx +++ b/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx @@ -1,34 +1,77 @@ -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useState } from 'react' +import Button from 'components/Button' import EscButton from 'components/Button/EscButton' import Divider from 'components/Divider' import Overlay from 'components/Overlay' import SearchBar from 'components/SearchBar' import Text from 'components/Text' import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList' +import PairsList from 'components/Trade/TradeModule/AssetSelector/PairsList' import useFilteredAssets from 'hooks/useFilteredAssets' +import { getAllAssets } from 'utils/assets' interface Props { state: OverlayState buyAsset: Asset sellAsset: Asset - onChangeBuyAsset: (asset: Asset) => void - onChangeSellAsset: (asset: Asset) => void + onChangeBuyAsset?: (asset: Asset) => void + onChangeSellAsset?: (asset: Asset) => void + onChangeTradingPair?: (tradingPair: TradingPair) => void onChangeState: (state: OverlayState) => void } -export default function AssetOverlay(props: Props) { - const { assets, searchString, onChangeSearch } = useFilteredAssets() - const handleClose = useCallback(() => props.onChangeState('closed'), [props]) +interface StablesFilterProps { + stables: Asset[] + selectedStables: Asset[] + onFilter: (stables: Asset[]) => void +} - const handleToggle = useCallback( - () => props.onChangeState(props.state === 'buy' ? 'sell' : 'buy'), - [props], +function StablesFilter(props: StablesFilterProps) { + const { stables, selectedStables, onFilter } = props + const isAllSelected = selectedStables.length > 1 + return ( + <> + +
    +
    + ) +} + +export default function AssetOverlay(props: Props) { + const isPairSelector = !!props.onChangeTradingPair + const { assets, searchString, onChangeSearch } = useFilteredAssets() + const allAssets = getAllAssets() + const stableAssets = useMemo(() => allAssets.filter((asset) => asset.isStable), [allAssets]) + const handleClose = useCallback(() => props.onChangeState('closed'), [props]) + const handleToggle = useCallback(() => props.onChangeState(props.state), [props]) + const [selectedStables, setSelectedStables] = useState(stableAssets) const buyAssets = useMemo( - () => assets.filter((asset) => asset.denom !== props.sellAsset.denom), - [assets, props.sellAsset], + () => + isPairSelector ? assets : assets.filter((asset) => asset.denom !== props.sellAsset.denom), + [assets, props.sellAsset, isPairSelector], ) const sellAssets = useMemo( @@ -36,14 +79,26 @@ export default function AssetOverlay(props: Props) { [assets, props.buyAsset], ) - function onChangeBuyAsset(asset: Asset) { - props.onChangeBuyAsset(asset) + function onChangeBuyAsset(asset: AssetPair | Asset) { + const selectedAsset = asset as Asset + if (!props.onChangeBuyAsset) return + props.onChangeBuyAsset(selectedAsset) props.onChangeState('sell') onChangeSearch('') } - function onChangeSellAsset(asset: Asset) { - props.onChangeSellAsset(asset) + function onChangeSellAsset(asset: AssetPair | Asset) { + const selectedAsset = asset as Asset + if (!props.onChangeSellAsset) return + props.onChangeSellAsset(selectedAsset) + onChangeSearch('') + } + + function onChangeAssetPair(assetPair: AssetPair | Asset) { + const selectedPair = assetPair as AssetPair + if (!props.onChangeTradingPair) return + props.onChangeTradingPair({ buy: selectedPair.buy.denom, sell: selectedPair.sell.denom }) + props.onChangeState('closed') onChangeSearch('') } @@ -54,9 +109,16 @@ export default function AssetOverlay(props: Props) { setShow={handleClose} >
    - Select asset + {isPairSelector ? 'Select a market' : 'Select asset'}
    + {isPairSelector && ( + + )}
    - - + {isPairSelector ? ( + + ) : ( + <> + + + + )} ) } diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetSelectorItem.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetSelectorItem.tsx new file mode 100644 index 00000000..bf9a0b0a --- /dev/null +++ b/src/components/Trade/TradeModule/AssetSelector/AssetSelectorItem.tsx @@ -0,0 +1,129 @@ +import { useMemo } from 'react' + +import AssetImage from 'components/Asset/AssetImage' +import AssetSymbol from 'components/Asset/AssetSymbol' +import DisplayCurrency from 'components/DisplayCurrency' +import { FormattedNumber } from 'components/FormattedNumber' +import { StarFilled, StarOutlined } from 'components/Icons' +import Text from 'components/Text' +import { LocalStorageKeys } from 'constants/localStorageKeys' +import { BN_ONE, BN_ZERO, MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math' +import useLocalStorage from 'hooks/useLocalStorage' +import { BNCoin } from 'types/classes/BNCoin' +import { byDenom } from 'utils/array' +import { demagnify, formatAmountToPrecision } from 'utils/formatters' + +interface Props { + asset: Asset + sellAsset?: Asset + balances: BNCoin[] + onSelect: (selected: Asset | AssetPair) => void + depositCap?: DepositCap +} +export default function AssetSelectorItem(props: Props) { + const { asset, sellAsset, balances, onSelect, depositCap } = props + + const amount = demagnify(props.balances.find(byDenom(asset.denom))?.amount ?? BN_ZERO, asset) + + const [favoriteAssetsDenoms, setFavoriteAssetsDenoms] = useLocalStorage( + LocalStorageKeys.FAVORITE_ASSETS, + [], + ) + + function handleToggleFavorite(event: React.MouseEvent) { + event.stopPropagation() + + if (!favoriteAssetsDenoms.includes(asset.denom)) { + setFavoriteAssetsDenoms([...favoriteAssetsDenoms, asset.denom]) + return + } + setFavoriteAssetsDenoms(favoriteAssetsDenoms.filter((item: string) => item !== asset.denom)) + } + const formattedAmount = formatAmountToPrecision(amount, MAX_AMOUNT_DECIMALS) + const lowAmount = formattedAmount === 0 ? 0 : Math.max(formattedAmount, MIN_AMOUNT) + + const capLeft = useMemo(() => { + if (!props.depositCap) return 0 + const percent = props.depositCap.used.dividedBy(props.depositCap.max).multipliedBy(100) + const depositCapLeft = 100 - Math.min(percent.toNumber(), 100) + return depositCapLeft + }, [props.depositCap]) + + return ( +
  • + +
  • + ) +} diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetSelectorPair.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetSelectorPair.tsx new file mode 100644 index 00000000..77bcc6e2 --- /dev/null +++ b/src/components/Trade/TradeModule/AssetSelector/AssetSelectorPair.tsx @@ -0,0 +1,59 @@ +import { useCallback } from 'react' + +import Button from 'components/Button' +import { ChevronDown } from 'components/Icons' +import Text from 'components/Text' +import AssetOverlay from 'components/Trade/TradeModule/AssetSelector/AssetOverlay' +import { DEFAULT_SETTINGS } from 'constants/defaultSettings' +import { LocalStorageKeys } from 'constants/localStorageKeys' +import useLocalStorage from 'hooks/useLocalStorage' +import useStore from 'store' + +interface Props { + buyAsset: Asset + sellAsset: Asset +} + +export default function AssetSelectorPair(props: Props) { + const [tradingPairSimple, setTradingPairSimple] = useLocalStorage( + LocalStorageKeys.TRADING_PAIR_SIMPLE, + DEFAULT_SETTINGS.tradingPairSimple, + ) + const { buyAsset, sellAsset } = props + const assetOverlayState = useStore((s) => s.assetOverlayState) + + const onChangeTradingPair = useCallback( + (tradingPair: TradingPair) => { + console.log(tradingPair.buy, tradingPair.sell) + setTradingPairSimple(tradingPair) + }, + [setTradingPairSimple], + ) + + const handleChangeState = useCallback((state: OverlayState) => { + useStore.setState({ assetOverlayState: state }) + }, []) + + return ( +
    + + {buyAsset.symbol}/{sellAsset.symbol} + +
    + ) +} diff --git a/src/components/Trade/TradeModule/AssetSelector/index.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetSelectorSingle.tsx similarity index 74% rename from src/components/Trade/TradeModule/AssetSelector/index.tsx rename to src/components/Trade/TradeModule/AssetSelector/AssetSelectorSingle.tsx index ec2ebcc9..89b3aa1a 100644 --- a/src/components/Trade/TradeModule/AssetSelector/index.tsx +++ b/src/components/Trade/TradeModule/AssetSelector/AssetSelectorSingle.tsx @@ -14,32 +14,31 @@ interface Props { sellAsset: Asset } -export default function AssetSelector(props: Props) { - const [tradingPair, setTradingPair] = useLocalStorage( - LocalStorageKeys.TRADING_PAIR, - DEFAULT_SETTINGS.tradingPair, - ) +export default function AssetSelectorSingle(props: Props) { + const [tradingPairAdvanced, settradingPairAdvanced] = useLocalStorage< + Settings['tradingPairAdvanced'] + >(LocalStorageKeys.TRADING_PAIR_ADVANCED, DEFAULT_SETTINGS.tradingPairAdvanced) const { buyAsset, sellAsset } = props const assetOverlayState = useStore((s) => s.assetOverlayState) const handleSwapAssets = useCallback(() => { - setTradingPair({ buy: sellAsset.denom, sell: buyAsset.denom }) - }, [setTradingPair, sellAsset, buyAsset]) + settradingPairAdvanced({ buy: sellAsset.denom, sell: buyAsset.denom }) + }, [settradingPairAdvanced, sellAsset, buyAsset]) const handleChangeBuyAsset = useCallback( (asset: Asset) => { - setTradingPair({ buy: asset.denom, sell: sellAsset.denom }) + settradingPairAdvanced({ buy: asset.denom, sell: sellAsset.denom }) useStore.setState({ assetOverlayState: 'sell' }) }, - [setTradingPair, sellAsset], + [settradingPairAdvanced, sellAsset], ) const handleChangeSellAsset = useCallback( (asset: Asset) => { - setTradingPair({ buy: buyAsset.denom, sell: asset.denom }) + settradingPairAdvanced({ buy: buyAsset.denom, sell: asset.denom }) useStore.setState({ assetOverlayState: 'closed' }) }, - [setTradingPair, buyAsset], + [settradingPairAdvanced, buyAsset], ) const handleChangeState = useCallback((state: OverlayState) => { @@ -47,7 +46,7 @@ export default function AssetSelector(props: Props) { }, []) return ( -
    +
    Buy Sell diff --git a/src/components/Trade/TradeModule/AssetSelector/PairsList.tsx b/src/components/Trade/TradeModule/AssetSelector/PairsList.tsx new file mode 100644 index 00000000..301fb243 --- /dev/null +++ b/src/components/Trade/TradeModule/AssetSelector/PairsList.tsx @@ -0,0 +1,73 @@ +import { useMemo } from 'react' + +import Text from 'components/Text' +import AssetSelectorItem from 'components/Trade/TradeModule/AssetSelector/AssetSelectorItem' +import { ASSETS } from 'constants/assets' +import useCurrentAccount from 'hooks/useCurrentAccount' +import useMarketAssets from 'hooks/useMarketAssets' +import useMarketDeposits from 'hooks/useMarketDeposits' +import usePrices from 'hooks/usePrices' +import { getMergedBalancesForAsset } from 'utils/accounts' +import { byDenom } from 'utils/array' +import { getEnabledMarketAssets, sortAssetsOrPairs } from 'utils/assets' + +interface Props { + assets: Asset[] + stables: Asset[] + isOpen: boolean + toggleOpen: () => void + onChangeAssetPair: (assetPair: AssetPair | Asset) => void +} + +const baseDenom = ASSETS[0].denom + +export default function PairsList(props: Props) { + const account = useCurrentAccount() + const { data: marketAssets } = useMarketAssets() + const { data: marketDeposits } = useMarketDeposits() + const { data: prices } = usePrices() + const balances = useMemo(() => { + if (!account) return [] + return getMergedBalancesForAsset(account, getEnabledMarketAssets()) + }, [account]) + + const pairs = useMemo(() => { + const tradingPairs: AssetPair[] = [] + props.stables.forEach((stable) => { + props.assets.forEach((buyAsset) => { + if (buyAsset.denom === stable.denom) return + tradingPairs.push({ buy: buyAsset, sell: stable }) + }) + }) + return tradingPairs + }, [props.stables, props.assets]) + + const sortedPairs = useMemo( + () => sortAssetsOrPairs(pairs, prices, marketDeposits, balances, baseDenom) as AssetPair[], + [balances, prices, pairs, marketDeposits], + ) + + return ( +
    + {props.isOpen && + (props.assets.length === 0 ? ( + + No available assets found + + ) : ( +
      + {sortedPairs.map((assetPair) => ( + + ))} +
    + ))} +
    + ) +} diff --git a/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx b/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx index b1c17f19..4580ea3b 100644 --- a/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx +++ b/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx @@ -12,7 +12,7 @@ interface Props { export default function MarginToggle(props: Props) { return ( -
    +
    Margin (LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage) @@ -80,10 +84,10 @@ export default function TradeSummary(props: Props) { return routeSymbols.join(' -> ') }, [route, sellAsset.symbol]) - const buttonText = useMemo( - () => (route.length ? `Buy ${buyAsset.symbol}` : 'No route found'), - [buyAsset.symbol, route], - ) + const buttonText = useMemo(() => { + if (!isAdvanced && direction === 'sell') return `Sell ${sellAsset.symbol}` + return route.length ? `Buy ${buyAsset.symbol}` : 'No route found' + }, [buyAsset.symbol, route, sellAsset.symbol, isAdvanced, direction]) return (
    s.useMargin) const useAutoRepay = useStore((s) => s.useAutoRepay) const account = useCurrentAccount() const swap = useStore((s) => s.swap) const [slippage] = useLocalStorage(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage) const { computeMaxSwapAmount } = useHealthComputer(account) + const [orderDirection, setOrderDirection] = useState('buy') const { data: borrowAssets } = useMarketBorrowings() const { data: marketAssets } = useMarketAssets() - const { data: route, isLoading: isRouteLoading } = useSwapRoute(sellAsset.denom, buyAsset.denom) - const isBorrowEnabled = !!marketAssets.find(byDenom(sellAsset.denom))?.borrowEnabled - const isRepayable = !!account?.debts.find(byDenom(buyAsset.denom)) + const [inputAsset, outputAsset] = useMemo(() => { + if (isAdvanced) return [sellAsset, buyAsset] + if (orderDirection === 'buy') return [sellAsset, buyAsset] + return [buyAsset, sellAsset] + }, [buyAsset, sellAsset, orderDirection, isAdvanced]) + const { data: route, isLoading: isRouteLoading } = useSwapRoute( + inputAsset.denom, + outputAsset.denom, + ) + const isBorrowEnabled = !!marketAssets.find(byDenom(inputAsset.denom))?.borrowEnabled + const isRepayable = !!account?.debts.find(byDenom(outputAsset.denom)) const [isMarginChecked, setMarginChecked] = useToggle(isBorrowEnabled ? useMargin : false) const [isAutoRepayChecked, setAutoRepayChecked] = useToggle( isRepayable && ENABLE_AUTO_REPAY ? useAutoRepay : false, ) - const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO) - const [sellAssetAmount, setSellAssetAmount] = useState(BN_ZERO) - const [maxBuyableAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO) + const [outputAssetAmount, setOutputAssetAmount] = useState(BN_ZERO) + const [inputAssetAmount, setInputAssetAmount] = useState(BN_ZERO) + const [maxOutputAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO) const [selectedOrderType, setSelectedOrderType] = useState('Market') const [isConfirming, setIsConfirming] = useToggle() const [estimatedFee, setEstimatedFee] = useState(defaultFee) @@ -66,117 +81,107 @@ export default function SwapForm(props: Props) { const throttledEstimateExactIn = useMemo(() => asyncThrottle(estimateExactIn, 250), []) const { computeLiquidationPrice } = useHealthComputer(updatedAccount) - const borrowAsset = useMemo( - () => borrowAssets.find(byDenom(sellAsset.denom)), - [borrowAssets, sellAsset.denom], - ) - const depositCapReachedCoins: BNCoin[] = useMemo(() => { - const buyMarketAsset = marketAssets.find(byDenom(buyAsset.denom)) + const outputMarketAsset = marketAssets.find(byDenom(outputAsset.denom)) - if (!buyMarketAsset) return [] + if (!outputMarketAsset) return [] - const depositCapLeft = getCapLeftWithBuffer(buyMarketAsset.cap) - if (buyAssetAmount.isGreaterThan(depositCapLeft)) { - return [BNCoin.fromDenomAndBigNumber(buyAsset.denom, depositCapLeft)] + const depositCapLeft = getCapLeftWithBuffer(outputMarketAsset.cap) + if (outputAssetAmount.isGreaterThan(depositCapLeft)) { + return [BNCoin.fromDenomAndBigNumber(outputAsset.denom, depositCapLeft)] } return [] - }, [marketAssets, buyAsset.denom, buyAssetAmount]) + }, [marketAssets, outputAsset.denom, outputAssetAmount]) - const onChangeSellAmount = useCallback( + const onChangeInputAmount = useCallback( (amount: BigNumber) => { - setSellAssetAmount(amount) - throttledEstimateExactIn( - { denom: sellAsset.denom, amount: amount.toString() }, - buyAsset.denom, - ).then(setBuyAssetAmount) + setInputAssetAmount(amount) + const swapTo = { denom: inputAsset.denom, amount: amount.toString() } + throttledEstimateExactIn(swapTo, outputAsset.denom).then(setOutputAssetAmount) }, - [sellAsset.denom, throttledEstimateExactIn, buyAsset.denom], + [inputAsset.denom, throttledEstimateExactIn, outputAsset.denom], ) - const onChangeBuyAmount = useCallback( + const onChangeOutputAmount = useCallback( (amount: BigNumber) => { - setBuyAssetAmount(amount) + setOutputAssetAmount(amount) const swapFrom = { - denom: buyAsset.denom, + denom: outputAsset.denom, amount: amount.toString(), } - throttledEstimateExactIn(swapFrom, sellAsset.denom).then(setSellAssetAmount) + throttledEstimateExactIn(swapFrom, inputAsset.denom).then(setInputAssetAmount) }, - [buyAsset.denom, throttledEstimateExactIn, sellAsset.denom], + [outputAsset.denom, throttledEstimateExactIn, inputAsset.denom], ) const handleRangeInputChange = useCallback( (value: number) => { - onChangeBuyAmount(BN(value).shiftedBy(buyAsset.decimals).integerValue()) + onChangeOutputAmount(BN(value).shiftedBy(outputAsset.decimals).integerValue()) }, - [onChangeBuyAmount, buyAsset.decimals], + [onChangeOutputAmount, outputAsset.decimals], ) - const [maxSellAmount, sellSideMarginThreshold, marginRatio] = useMemo(() => { - const maxAmount = computeMaxSwapAmount(sellAsset.denom, buyAsset.denom, 'default') + const [maxInputAmount, imputMarginThreshold, marginRatio] = useMemo(() => { + const maxAmount = computeMaxSwapAmount(inputAsset.denom, outputAsset.denom, 'default') const maxAmountOnMargin = computeMaxSwapAmount( - sellAsset.denom, - buyAsset.denom, + inputAsset.denom, + outputAsset.denom, 'margin', ).integerValue() const marginRatio = maxAmount.dividedBy(maxAmountOnMargin) estimateExactIn( { - denom: sellAsset.denom, + denom: inputAsset.denom, amount: (isMarginChecked ? maxAmountOnMargin : maxAmount).toString(), }, - buyAsset.denom, + outputAsset.denom, ).then(setMaxBuyableAmountEstimation) if (isMarginChecked) return [maxAmountOnMargin, maxAmount, marginRatio] - if (sellAssetAmount.isGreaterThan(maxAmount)) onChangeSellAmount(maxAmount) + if (inputAssetAmount.isGreaterThan(maxAmount)) onChangeInputAmount(maxAmount) return [maxAmount, maxAmount, marginRatio] }, [ computeMaxSwapAmount, - sellAsset.denom, - buyAsset.denom, + inputAsset.denom, + outputAsset.denom, isMarginChecked, - onChangeSellAmount, - sellAssetAmount, + onChangeInputAmount, + inputAssetAmount, ]) - const buySideMarginThreshold = useMemo(() => { - return maxBuyableAmountEstimation.multipliedBy(marginRatio) - }, [marginRatio, maxBuyableAmountEstimation]) + const outputSideMarginThreshold = useMemo(() => { + return maxOutputAmountEstimation.multipliedBy(marginRatio) + }, [marginRatio, maxOutputAmountEstimation]) const swapTx = useMemo(() => { - const borrowCoin = sellAssetAmount.isGreaterThan(sellSideMarginThreshold) - ? BNCoin.fromDenomAndBigNumber( - sellAsset.denom, - sellAssetAmount.minus(sellSideMarginThreshold), - ) + const borrowCoin = inputAssetAmount.isGreaterThan(imputMarginThreshold) + ? BNCoin.fromDenomAndBigNumber(inputAsset.denom, inputAssetAmount.minus(imputMarginThreshold)) : undefined return swap({ accountId: account?.id || '', - coinIn: BNCoin.fromDenomAndBigNumber(sellAsset.denom, sellAssetAmount.integerValue()), + coinIn: BNCoin.fromDenomAndBigNumber(inputAsset.denom, inputAssetAmount.integerValue()), reclaim: removedLends[0], borrow: borrowCoin, - denomOut: buyAsset.denom, + denomOut: outputAsset.denom, slippage, - isMax: sellAssetAmount.isEqualTo(maxSellAmount), + isMax: inputAssetAmount.isEqualTo(maxInputAmount), repay: isAutoRepayChecked, }) }, [ removedLends, account?.id, - buyAsset.denom, - sellSideMarginThreshold, - sellAsset.denom, - sellAssetAmount, + outputAsset.denom, + imputMarginThreshold, + inputAsset.denom, + inputAssetAmount, slippage, swap, - maxSellAmount, + maxInputAmount, isAutoRepayChecked, ]) @@ -210,61 +215,10 @@ export default function SwapForm(props: Props) { ) const liquidationPrice = useMemo( - () => computeLiquidationPrice(props.buyAsset.denom, 'asset'), - [computeLiquidationPrice, props.buyAsset.denom], + () => computeLiquidationPrice(outputAsset.denom, 'asset'), + [computeLiquidationPrice, outputAsset.denom], ) - useEffect(() => { - setBuyAssetAmount(BN_ZERO) - setSellAssetAmount(BN_ZERO) - setMarginChecked(isBorrowEnabled ? useMargin : false) - setAutoRepayChecked(isRepayable ? useAutoRepay : false) - simulateTrade( - BNCoin.fromDenomAndBigNumber(buyAsset.denom, BN_ZERO), - BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO), - BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO), - isAutoLendEnabled && !isAutoRepayChecked ? 'lend' : 'deposit', - isAutoRepayChecked, - ) - }, [ - isBorrowEnabled, - isRepayable, - useMargin, - useAutoRepay, - buyAsset.denom, - sellAsset.denom, - isAutoLendEnabled, - isAutoRepayChecked, - simulateTrade, - setMarginChecked, - setAutoRepayChecked, - ]) - - useEffect(() => { - const removeDepositAmount = sellAssetAmount.isGreaterThanOrEqualTo(sellSideMarginThreshold) - ? sellSideMarginThreshold - : sellAssetAmount - const addDebtAmount = sellAssetAmount.isGreaterThan(sellSideMarginThreshold) - ? sellAssetAmount.minus(sellSideMarginThreshold) - : BN_ZERO - - if (removeDepositAmount.isZero() && addDebtAmount.isZero() && buyAssetAmount.isZero() && modal) - return - const removeCoin = BNCoin.fromDenomAndBigNumber(sellAsset.denom, removeDepositAmount) - const debtCoin = BNCoin.fromDenomAndBigNumber(sellAsset.denom, addDebtAmount) - const addCoin = BNCoin.fromDenomAndBigNumber(buyAsset.denom, buyAssetAmount) - - debouncedUpdateAccount(removeCoin, addCoin, debtCoin) - }, [ - sellAssetAmount, - buyAssetAmount, - sellSideMarginThreshold, - buyAsset.denom, - sellAsset.denom, - debouncedUpdateAccount, - modal, - ]) - useEffect(() => { swapTx.estimateFee().then(setEstimatedFee) }, [swapTx]) @@ -276,24 +230,90 @@ export default function SwapForm(props: Props) { const isSucceeded = await swapTx.execute() if (isSucceeded) { - setSellAssetAmount(BN_ZERO) - setBuyAssetAmount(BN_ZERO) + setInputAssetAmount(BN_ZERO) + setOutputAssetAmount(BN_ZERO) } setIsConfirming(false) } }, [account?.id, swapTx, setIsConfirming]) useEffect(() => { - if (sellAssetAmount.isEqualTo(maxSellAmount) || buyAssetAmount.isZero()) return - if (buyAssetAmount.isEqualTo(maxBuyableAmountEstimation)) setSellAssetAmount(maxSellAmount) - }, [sellAssetAmount, maxSellAmount, buyAssetAmount, maxBuyableAmountEstimation]) + onChangeOutputAmount(BN_ZERO) + onChangeInputAmount(BN_ZERO) + }, [orderDirection, onChangeOutputAmount, onChangeInputAmount]) + + useEffect(() => { + setOutputAssetAmount(BN_ZERO) + setInputAssetAmount(BN_ZERO) + setMarginChecked(isBorrowEnabled ? useMargin : false) + setAutoRepayChecked(isRepayable ? useAutoRepay : false) + simulateTrade( + BNCoin.fromDenomAndBigNumber(inputAsset.denom, BN_ZERO), + BNCoin.fromDenomAndBigNumber(outputAsset.denom, BN_ZERO), + BNCoin.fromDenomAndBigNumber(inputAsset.denom, BN_ZERO), + isAutoLendEnabled && !isAutoRepayChecked ? 'lend' : 'deposit', + isAutoRepayChecked, + ) + }, [ + isBorrowEnabled, + isRepayable, + useMargin, + useAutoRepay, + outputAsset.denom, + inputAsset.denom, + isAutoLendEnabled, + isAutoRepayChecked, + simulateTrade, + setMarginChecked, + setAutoRepayChecked, + ]) + + useEffect(() => { + const removeDepositAmount = inputAssetAmount.isGreaterThanOrEqualTo(imputMarginThreshold) + ? imputMarginThreshold + : inputAssetAmount + const addDebtAmount = inputAssetAmount.isGreaterThan(imputMarginThreshold) + ? inputAssetAmount.minus(imputMarginThreshold) + : BN_ZERO + + if ( + removeDepositAmount.isZero() && + addDebtAmount.isZero() && + outputAssetAmount.isZero() && + modal + ) + return + const removeCoin = BNCoin.fromDenomAndBigNumber(inputAsset.denom, removeDepositAmount) + const addCoin = BNCoin.fromDenomAndBigNumber(outputAsset.denom, outputAssetAmount) + const debtCoin = BNCoin.fromDenomAndBigNumber(inputAsset.denom, addDebtAmount) + + debouncedUpdateAccount(removeCoin, addCoin, debtCoin) + }, [ + inputAssetAmount, + outputAssetAmount, + imputMarginThreshold, + outputAsset.denom, + inputAsset.denom, + debouncedUpdateAccount, + modal, + ]) + + const borrowAsset = useMemo( + () => borrowAssets.find(byDenom(inputAsset.denom)), + [borrowAssets, inputAsset.denom], + ) + + useEffect(() => { + if (inputAssetAmount.isEqualTo(maxInputAmount) || outputAssetAmount.isZero()) return + if (outputAssetAmount.isEqualTo(maxOutputAmountEstimation)) setInputAssetAmount(maxInputAmount) + }, [inputAssetAmount, maxInputAmount, outputAssetAmount, maxOutputAmountEstimation]) const borrowAmount = useMemo( () => - sellAssetAmount.isGreaterThan(sellSideMarginThreshold) - ? sellAssetAmount.minus(sellSideMarginThreshold) + inputAssetAmount.isGreaterThan(imputMarginThreshold) + ? inputAssetAmount.minus(imputMarginThreshold) : BN_ZERO, - [sellAssetAmount, sellSideMarginThreshold], + [inputAssetAmount, imputMarginThreshold], ) const availableLiquidity = useMemo( @@ -303,79 +323,125 @@ export default function SwapForm(props: Props) { const isSwapDisabled = useMemo( () => - sellAssetAmount.isZero() || + inputAssetAmount.isZero() || depositCapReachedCoins.length > 0 || borrowAmount.isGreaterThan(availableLiquidity) || route.length === 0, - [sellAssetAmount, depositCapReachedCoins, borrowAmount, availableLiquidity, route], + [inputAssetAmount, depositCapReachedCoins, borrowAmount, availableLiquidity, route], ) return ( <> - - - - - {isRepayable && ENABLE_AUTO_REPAY && ( - + {isAdvanced ? ( + + ) : ( + + )} + + - )} - -
    - -
    -
    - - - - - - - {borrowAsset && borrowAmount.isGreaterThanOrEqualTo(availableLiquidity) && ( - + {isRepayable && ENABLE_AUTO_REPAY && ( + )} - +
    + +
    +
    + {isAdvanced ? ( + + ) : ( + <> + + + + )} + {!isAdvanced && } + + + + {borrowAsset && borrowAmount.isGreaterThanOrEqualTo(availableLiquidity) && ( + + )} + {isAdvanced ? ( + + ) : ( + <> + +
    + You receive + + {formatValue(outputAssetAmount.toNumber(), { + decimals: outputAsset.decimals, + abbreviated: false, + suffix: ` ${outputAsset.symbol}`, + minDecimals: 0, + maxDecimals: outputAsset.decimals, + })} + +
    + + )} +
    +
    +
    diff --git a/src/components/Trade/TradeModule/index.tsx b/src/components/Trade/TradeModule/index.tsx index 7f74b3b6..5e8b916f 100644 --- a/src/components/Trade/TradeModule/index.tsx +++ b/src/components/Trade/TradeModule/index.tsx @@ -1,26 +1,25 @@ import classNames from 'classnames' -import AssetSelector from 'components/Trade/TradeModule/AssetSelector' import SwapForm from 'components/Trade/TradeModule/SwapForm' interface Props { buyAsset: Asset sellAsset: Asset + isAdvanced: boolean } export default function TradeModule(props: Props) { - const { buyAsset, sellAsset } = props - + const { buyAsset, sellAsset, isAdvanced } = props return (
    - - +
    ) diff --git a/src/constants/defaultSettings.ts b/src/constants/defaultSettings.ts index 43756554..95b4e95e 100644 --- a/src/constants/defaultSettings.ts +++ b/src/constants/defaultSettings.ts @@ -7,7 +7,8 @@ export const DEFAULT_SETTINGS: Settings = { accountSummaryTabs: [true, true], reduceMotion: false, lendAssets: true, - tradingPair: { buy: enabledMarketAssets[0].denom, sell: enabledMarketAssets[1].denom }, + tradingPairSimple: { buy: enabledMarketAssets[0].denom, sell: enabledMarketAssets[1].denom }, + tradingPairAdvanced: { buy: enabledMarketAssets[0].denom, sell: enabledMarketAssets[1].denom }, displayCurrency: ORACLE_DENOM, slippage: 0.02, tutorial: true, diff --git a/src/constants/localStorageKeys.ts b/src/constants/localStorageKeys.ts index bda120b1..12f15681 100644 --- a/src/constants/localStorageKeys.ts +++ b/src/constants/localStorageKeys.ts @@ -1,5 +1,6 @@ export enum LocalStorageKeys { - TRADING_PAIR = 'tradingPair', + TRADING_PAIR_SIMPLE = 'tradingPairSimple', + TRADING_PAIR_ADVANCED = 'tradingPairAdvanced', ACCOUNT_SUMMARY_TABS = 'accountSummaryTabs', DISPLAY_CURRENCY = 'displayCurrency', REDUCE_MOTION = 'reduceMotion', diff --git a/src/pages/TradePage.tsx b/src/pages/TradePage.tsx index 9f878560..9be1e1e1 100644 --- a/src/pages/TradePage.tsx +++ b/src/pages/TradePage.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react' +import { useLocation } from 'react-router-dom' import MigrationBanner from 'components/MigrationBanner' import AccountDetailsCard from 'components/Trade/AccountDetailsCard' @@ -10,30 +11,45 @@ import useLocalStorage from 'hooks/useLocalStorage' import useStore from 'store' import { byDenom } from 'utils/array' import { getEnabledMarketAssets } from 'utils/assets' +import { getPage } from 'utils/route' export default function TradePage() { - const [tradingPair] = useLocalStorage( - LocalStorageKeys.TRADING_PAIR, - DEFAULT_SETTINGS.tradingPair, + const { pathname } = useLocation() + const page = getPage(pathname) + const isAdvanced = useMemo(() => page === 'trade-advanced', [page]) + + const [tradingPairAdvanced] = useLocalStorage( + LocalStorageKeys.TRADING_PAIR_ADVANCED, + DEFAULT_SETTINGS.tradingPairAdvanced, ) + const [tradingPairSimple] = useLocalStorage( + LocalStorageKeys.TRADING_PAIR_SIMPLE, + DEFAULT_SETTINGS.tradingPairSimple, + ) + const enabledMarketAssets = getEnabledMarketAssets() const assetOverlayState = useStore((s) => s.assetOverlayState) const buyAsset = useMemo( - () => enabledMarketAssets.find(byDenom(tradingPair.buy)) ?? enabledMarketAssets[0], - [tradingPair, enabledMarketAssets], + () => + enabledMarketAssets.find( + byDenom(isAdvanced ? tradingPairAdvanced.buy : tradingPairSimple.buy), + ) ?? enabledMarketAssets[0], + [tradingPairAdvanced, tradingPairSimple, enabledMarketAssets, isAdvanced], ) const sellAsset = useMemo( - () => enabledMarketAssets.find(byDenom(tradingPair.sell)) ?? enabledMarketAssets[1], - [tradingPair, enabledMarketAssets], + () => + enabledMarketAssets.find( + byDenom(isAdvanced ? tradingPairAdvanced.sell : tradingPairSimple.sell), + ) ?? enabledMarketAssets[1], + [tradingPairAdvanced, tradingPairSimple, enabledMarketAssets, isAdvanced], ) - return (
    -
    - +
    +
    {assetOverlayState !== 'closed' && ( diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index 11635077..3ce2afbd 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -27,7 +27,12 @@ function PageContainer(props: Props) { if (!props.focusComponent) return ( -
    +
    {props.children}
    ) diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index e86091e6..580e3b8b 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -69,6 +69,11 @@ interface Asset { isStaking?: boolean } +interface AssetPair { + buy: Asset + sell: Asset +} + interface PseudoAsset { decimals: number symbol: string diff --git a/src/types/interfaces/components/AssetOverlay.d.ts b/src/types/interfaces/components/AssetOverlay.d.ts index 63877e9d..f73d8ae0 100644 --- a/src/types/interfaces/components/AssetOverlay.d.ts +++ b/src/types/interfaces/components/AssetOverlay.d.ts @@ -1 +1 @@ -type OverlayState = 'buy' | 'sell' | 'closed' +type OverlayState = 'buy' | 'sell' | 'pair' | 'closed' diff --git a/src/types/interfaces/components/Navigation.d.ts b/src/types/interfaces/components/Navigation.d.ts index 565d6bbe..b5113f90 100644 --- a/src/types/interfaces/components/Navigation.d.ts +++ b/src/types/interfaces/components/Navigation.d.ts @@ -2,4 +2,12 @@ interface MenuTreeEntry { pages: Page[] label: string externalUrl?: string + submenu?: MenuTreeSubmenuEntry[] +} + +interface MenuTreeSubmenuEntry { + page: Page + label: string + subtitle?: string + icon?: React.ReactNode } diff --git a/src/types/interfaces/components/Trade.d.ts b/src/types/interfaces/components/Trade.d.ts new file mode 100644 index 00000000..5bcfffe5 --- /dev/null +++ b/src/types/interfaces/components/Trade.d.ts @@ -0,0 +1,4 @@ +interface TradingPair { + buy: string + sell: string +} diff --git a/src/types/interfaces/perps.d.ts b/src/types/interfaces/perps.d.ts index 16d6cd20..6eb541e0 100644 --- a/src/types/interfaces/perps.d.ts +++ b/src/types/interfaces/perps.d.ts @@ -1 +1 @@ -type OrderDirection = 'long' | 'short' +type OrderDirection = 'long' | 'short' | 'buy' | 'sell' diff --git a/src/types/interfaces/route.d.ts b/src/types/interfaces/route.d.ts index 7b025aab..216b8c56 100644 --- a/src/types/interfaces/route.d.ts +++ b/src/types/interfaces/route.d.ts @@ -1,5 +1,6 @@ type Page = | 'trade' + | 'trade-advanced' | 'perps' | 'borrow' | 'farm' diff --git a/src/types/interfaces/store/settings.d.ts b/src/types/interfaces/store/settings.d.ts index bb51527a..4ea1bb9f 100644 --- a/src/types/interfaces/store/settings.d.ts +++ b/src/types/interfaces/store/settings.d.ts @@ -2,7 +2,8 @@ interface Settings { accountSummaryTabs: boolean[] displayCurrency: string reduceMotion: boolean - tradingPair: { buy: string; sell: string } + tradingPairSimple: TradingPair + tradingPairAdvanced: TradingPair lendAssets: boolean slippage: number tutorial: boolean diff --git a/src/utils/assets.ts b/src/utils/assets.ts index be0618ae..e02c025d 100644 --- a/src/utils/assets.ts +++ b/src/utils/assets.ts @@ -1,4 +1,8 @@ import { ASSETS } from 'constants/assets' +import { BN_ZERO } from 'constants/math' +import { BNCoin } from 'types/classes/BNCoin' +import { byDenom } from 'utils/array' +import { demagnify } from 'utils/formatters' export function getAssetByDenom(denom: string): Asset | undefined { return ASSETS.find((asset) => asset.denom === denom) @@ -47,3 +51,42 @@ export function getBorrowEnabledAssets() { export function getStakingAssets() { return ASSETS.filter((asset) => asset.isStaking) } + +function isAssetPair(assetPair: Asset | AssetPair): assetPair is AssetPair { + return (assetPair).buy !== undefined +} + +export function sortAssetsOrPairs( + assets: Asset[] | AssetPair[], + prices: BNCoin[], + marketDeposits: BNCoin[], + balances: BNCoin[], + baseDenom: string, +): Asset[] | AssetPair[] { + if (prices.length === 0 || marketDeposits.length === 0) return assets + + return assets.sort((a, b) => { + const assetA = isAssetPair(a) ? a.buy : a + const assetB = isAssetPair(b) ? b.buy : b + + const aDenom = assetA.denom + const bDenom = assetB.denom + const aBalance = balances?.find(byDenom(aDenom))?.amount ?? BN_ZERO + const aPrice = prices?.find(byDenom(aDenom))?.amount ?? BN_ZERO + const bBalance = balances?.find(byDenom(bDenom))?.amount ?? BN_ZERO + const bPrice = prices?.find(byDenom(bDenom))?.amount ?? BN_ZERO + + const aValue = demagnify(aBalance, assetA) * aPrice.toNumber() + const bValue = demagnify(bBalance, assetB) * bPrice.toNumber() + if (aValue > 0 || bValue > 0) return bValue - aValue + if (aDenom === baseDenom) return -1 + if (bDenom === baseDenom) return 1 + + const aMarketDeposit = marketDeposits?.find(byDenom(aDenom))?.amount ?? BN_ZERO + const bMarketDeposit = marketDeposits?.find(byDenom(bDenom))?.amount ?? BN_ZERO + const aMarketValue = demagnify(aMarketDeposit, assetA) * aPrice.toNumber() + const bMarketValue = demagnify(bMarketDeposit, assetB) * bPrice.toNumber() + + return bMarketValue - aMarketValue + }) +} diff --git a/src/utils/health_computer/index.js b/src/utils/health_computer/index.js index 0be3b92d..0f3385b4 100644 --- a/src/utils/health_computer/index.js +++ b/src/utils/health_computer/index.js @@ -1,355 +1,390 @@ -let wasm; +let wasm -const heap = new Array(128).fill(undefined); +const heap = new Array(128).fill(undefined) -heap.push(undefined, null, true, false); +heap.push(undefined, null, true, false) -function getObject(idx) { return heap[idx]; } +function getObject(idx) { + return heap[idx] +} -let heap_next = heap.length; +let heap_next = heap.length function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] - heap[idx] = obj; - return idx; + heap[idx] = obj + return idx } function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; + if (idx < 132) return + heap[idx] = heap_next + heap_next = idx } function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; + const ret = getObject(idx) + dropObject(idx) + return ret } -let WASM_VECTOR_LEN = 0; +let WASM_VECTOR_LEN = 0 -let cachedUint8Memory0 = null; +let cachedUint8Memory0 = null function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer) + } + return cachedUint8Memory0 } -const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); +const cachedTextEncoder = + typeof TextEncoder !== 'undefined' + ? new TextEncoder('utf-8') + : { + encode: () => { + throw Error('TextEncoder not available') + }, + } -const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' +const encodeString = + typeof cachedTextEncoder.encodeInto === 'function' ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); -} + return cachedTextEncoder.encodeInto(arg, view) + } : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length - }; -}); + const buf = cachedTextEncoder.encode(arg) + view.set(buf) + return { + read: arg.length, + written: buf.length, + } + } function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg) + const ptr = malloc(buf.length, 1) >>> 0 + getUint8Memory0() + .subarray(ptr, ptr + buf.length) + .set(buf) + WASM_VECTOR_LEN = buf.length + return ptr + } - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; + let len = arg.length + let ptr = malloc(len, 1) >>> 0 + + const mem = getUint8Memory0() + + let offset = 0 + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset) + if (code > 0x7f) break + mem[ptr + offset] = code + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset) } + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0 + const view = getUint8Memory0().subarray(ptr + offset, ptr + len) + const ret = encodeString(arg, view) - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; + offset += ret.written + } - const mem = getUint8Memory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; - const view = getUint8Memory0().subarray(ptr + offset, ptr + len); - const ret = encodeString(arg, view); - - offset += ret.written; - } - - WASM_VECTOR_LEN = offset; - return ptr; + WASM_VECTOR_LEN = offset + return ptr } function isLikeNone(x) { - return x === undefined || x === null; + return x === undefined || x === null } -let cachedInt32Memory0 = null; +let cachedInt32Memory0 = null function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachedInt32Memory0; + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer) + } + return cachedInt32Memory0 } -const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); +const cachedTextDecoder = + typeof TextDecoder !== 'undefined' + ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + : { + decode: () => { + throw Error('TextDecoder not available') + }, + } -if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; +if (typeof TextDecoder !== 'undefined') { + cachedTextDecoder.decode() +} function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); + ptr = ptr >>> 0 + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) } /** -* @param {HealthComputer} c -* @returns {HealthValuesResponse} -*/ + * @param {HealthComputer} c + * @returns {HealthValuesResponse} + */ export function compute_health_js(c) { - const ret = wasm.compute_health_js(addHeapObject(c)); - return takeObject(ret); + const ret = wasm.compute_health_js(addHeapObject(c)) + return takeObject(ret) } /** -* @param {HealthComputer} c -* @param {string} withdraw_denom -* @returns {string} -*/ + * @param {HealthComputer} c + * @param {string} withdraw_denom + * @returns {string} + */ export function max_withdraw_estimate_js(c, withdraw_denom) { - let deferred2_0; - let deferred2_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(withdraw_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.max_withdraw_estimate_js(retptr, addHeapObject(c), ptr0, len0); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - deferred2_0 = r0; - deferred2_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(withdraw_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.max_withdraw_estimate_js(retptr, addHeapObject(c), ptr0, len0) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } /** -* @param {HealthComputer} c -* @param {string} borrow_denom -* @param {BorrowTarget} target -* @returns {string} -*/ + * @param {HealthComputer} c + * @param {string} borrow_denom + * @param {BorrowTarget} target + * @returns {string} + */ export function max_borrow_estimate_js(c, borrow_denom, target) { - let deferred2_0; - let deferred2_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(borrow_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.max_borrow_estimate_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(target)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - deferred2_0 = r0; - deferred2_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(borrow_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.max_borrow_estimate_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(target)) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } /** -* @param {HealthComputer} c -* @param {string} from_denom -* @param {string} to_denom -* @param {SwapKind} kind -* @param {Slippage} slippage -* @returns {string} -*/ + * @param {HealthComputer} c + * @param {string} from_denom + * @param {string} to_denom + * @param {SwapKind} kind + * @param {Slippage} slippage + * @returns {string} + */ export function max_swap_estimate_js(c, from_denom, to_denom, kind, slippage) { - let deferred3_0; - let deferred3_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(from_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - wasm.max_swap_estimate_js(retptr, addHeapObject(c), ptr0, len0, ptr1, len1, addHeapObject(kind), addHeapObject(slippage)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - deferred3_0 = r0; - deferred3_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); - } + let deferred3_0 + let deferred3_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(from_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + wasm.max_swap_estimate_js( + retptr, + addHeapObject(c), + ptr0, + len0, + ptr1, + len1, + addHeapObject(kind), + addHeapObject(slippage), + ) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred3_0 = r0 + deferred3_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1) + } } /** -* @param {HealthComputer} c -* @param {string} denom -* @param {LiquidationPriceKind} kind -* @returns {string} -*/ + * @param {HealthComputer} c + * @param {string} denom + * @param {LiquidationPriceKind} kind + * @returns {string} + */ export function liquidation_price_js(c, denom, kind) { - let deferred2_0; - let deferred2_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.liquidation_price_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(kind)); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - deferred2_0 = r0; - deferred2_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.liquidation_price_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(kind)) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } + try { + return f.apply(this, args) + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)) + } } async function __wbg_load(module, imports) { - if (typeof Response === 'function' && module instanceof Response) { - if (typeof WebAssembly.instantiateStreaming === 'function') { - try { - return await WebAssembly.instantiateStreaming(module, imports); - - } catch (e) { - if (module.headers.get('Content-Type') != 'application/wasm') { - console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); - - } else { - throw e; - } - } - } - - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - - } else { - const instance = await WebAssembly.instantiate(module, imports); - - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; - + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports) + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn( + '`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n', + e, + ) } else { - return instance; + throw e } + } } + + const bytes = await module.arrayBuffer() + return await WebAssembly.instantiate(bytes, imports) + } else { + const instance = await WebAssembly.instantiate(module, imports) + + if (instance instanceof WebAssembly.Instance) { + return { instance, module } + } else { + return instance + } + } } function __wbg_get_imports() { - const imports = {}; - imports.wbg = {}; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_is_undefined = function(arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }; - imports.wbg.__wbindgen_object_drop_ref = function(arg0) { - takeObject(arg0); - }; - imports.wbg.__wbindgen_string_get = function(arg0, arg1) { - const obj = getObject(arg1); - const ret = typeof(obj) === 'string' ? obj : undefined; - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbg_parse_670c19d4e984792e = function() { return handleError(function (arg0, arg1) { - const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_stringify_e25465938f3f611f = function() { return handleError(function (arg0) { - const ret = JSON.stringify(getObject(arg0)); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbindgen_throw = function(arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }; + const imports = {} + imports.wbg = {} + imports.wbg.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0) + return addHeapObject(ret) + } + imports.wbg.__wbindgen_is_undefined = function (arg0) { + const ret = getObject(arg0) === undefined + return ret + } + imports.wbg.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0) + } + imports.wbg.__wbindgen_string_get = function (arg0, arg1) { + const obj = getObject(arg1) + const ret = typeof obj === 'string' ? obj : undefined + var ptr1 = isLikeNone(ret) + ? 0 + : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + var len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 + } + imports.wbg.__wbg_parse_670c19d4e984792e = function () { + return handleError(function (arg0, arg1) { + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)) + return addHeapObject(ret) + }, arguments) + } + imports.wbg.__wbg_stringify_e25465938f3f611f = function () { + return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)) + return addHeapObject(ret) + }, arguments) + } + imports.wbg.__wbindgen_throw = function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)) + } - return imports; + return imports } -function __wbg_init_memory(imports, maybe_memory) { - -} +function __wbg_init_memory(imports, maybe_memory) {} function __wbg_finalize_init(instance, module) { - wasm = instance.exports; - __wbg_init.__wbindgen_wasm_module = module; - cachedInt32Memory0 = null; - cachedUint8Memory0 = null; + wasm = instance.exports + __wbg_init.__wbindgen_wasm_module = module + cachedInt32Memory0 = null + cachedUint8Memory0 = null - - return wasm; + return wasm } function initSync(module) { - if (wasm !== undefined) return wasm; + if (wasm !== undefined) return wasm - const imports = __wbg_get_imports(); + const imports = __wbg_get_imports() - __wbg_init_memory(imports); + __wbg_init_memory(imports) - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module) + } - const instance = new WebAssembly.Instance(module, imports); + const instance = new WebAssembly.Instance(module, imports) - return __wbg_finalize_init(instance, module); + return __wbg_finalize_init(instance, module) } async function __wbg_init(input) { - if (wasm !== undefined) return wasm; + if (wasm !== undefined) return wasm - if (typeof input === 'undefined') { - input = new URL('index_bg.wasm', import.meta.url); - } - const imports = __wbg_get_imports(); + if (typeof input === 'undefined') { + input = new URL('index_bg.wasm', import.meta.url) + } + const imports = __wbg_get_imports() - if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { - input = fetch(input); - } + if ( + typeof input === 'string' || + (typeof Request === 'function' && input instanceof Request) || + (typeof URL === 'function' && input instanceof URL) + ) { + input = fetch(input) + } - __wbg_init_memory(imports); + __wbg_init_memory(imports) - const { instance, module } = await __wbg_load(await input, imports); + const { instance, module } = await __wbg_load(await input, imports) - return __wbg_finalize_init(instance, module); + return __wbg_finalize_init(instance, module) } export { initSync } -export default __wbg_init; +export default __wbg_init diff --git a/src/utils/math.ts b/src/utils/math.ts index 53cc40b1..ed7be0de 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -1,4 +1,4 @@ -import { BN } from './helpers' +import { BN } from 'utils/helpers' export const devideByPotentiallyZero = (numerator: number, denominator: number): number => { if (denominator === 0) return 0 diff --git a/src/utils/route.ts b/src/utils/route.ts index 7251d9b5..1ca2b6a8 100644 --- a/src/utils/route.ts +++ b/src/utils/route.ts @@ -29,6 +29,7 @@ export function getRoute( export function getPage(pathname: string): Page { const pages: Page[] = [ 'trade', + 'trade-advanced', 'perps', 'borrow', 'farm', diff --git a/tailwind.config.js b/tailwind.config.js index afe6345c..58e0561f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,6 +4,8 @@ const plugin = require('tailwindcss/plugin') module.exports = { content: ['./src/pages/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}'], safelist: [ + 'border-error', + 'border-success', 'h-2', 'text-3xs', 'text-3xs-caps',