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
This commit is contained in:
parent
c7e8d0c6ae
commit
22b9f7b518
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mars-v2-frontend",
|
"name": "mars-v2-frontend",
|
||||||
"version": "2.1.1",
|
"version": "2.1.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn validate-env && next build",
|
"build": "yarn validate-env && next build",
|
||||||
|
@ -21,11 +21,13 @@ interface Props {
|
|||||||
computeLiquidationPrice: (denom: string, kind: LiquidationPriceKind) => number | null
|
computeLiquidationPrice: (denom: string, kind: LiquidationPriceKind) => number | null
|
||||||
denom: string
|
denom: string
|
||||||
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
|
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
|
||||||
|
account: Account
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LiqPrice(props: Props) {
|
export default function LiqPrice(props: Props) {
|
||||||
const { denom, type, amount, computeLiquidationPrice } = props
|
const { denom, type, amount, account, computeLiquidationPrice } = props
|
||||||
const [lastLiquidationPrice, setLastLiquidationPrice] = useState<number | null>(null)
|
const [lastLiquidationPrice, setLastLiquidationPrice] = useState<number | null>(null)
|
||||||
|
const hasDebt = account.debts.length > 0
|
||||||
|
|
||||||
const liqPrice = useMemo(() => {
|
const liqPrice = useMemo(() => {
|
||||||
if (type === 'vault' || amount === 0) return 0
|
if (type === 'vault' || amount === 0) return 0
|
||||||
@ -38,19 +40,18 @@ export default function LiqPrice(props: Props) {
|
|||||||
if (lastLiquidationPrice !== liqPrice && liqPrice !== null) setLastLiquidationPrice(liqPrice)
|
if (lastLiquidationPrice !== liqPrice && liqPrice !== null) setLastLiquidationPrice(liqPrice)
|
||||||
}, [liqPrice, lastLiquidationPrice])
|
}, [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))
|
if (!lastLiquidationPrice || (liquidationPrice === 0 && lastLiquidationPrice === 0))
|
||||||
return (
|
return (
|
||||||
<Text size='xs' className='flex items-center justify-end number'>
|
<Text size='xs' className='flex items-center justify-end number'>
|
||||||
N/A
|
N/A
|
||||||
<Tooltip
|
<Tooltip content={tooltipText} type='info' className='ml-1'>
|
||||||
content={
|
|
||||||
type === 'vault'
|
|
||||||
? 'Liquidation prices cannot be calculated for farm positions. But it a drop in price of the underlying assets can still cause a liquidation.'
|
|
||||||
: 'The position size is too small to liquidate the account, even if the price goes to $0.00.'
|
|
||||||
}
|
|
||||||
type='info'
|
|
||||||
className='ml-1'
|
|
||||||
>
|
|
||||||
<InfoCircle className='w-3.5 h-3.5 text-white/40 hover:text-inherit' />
|
<InfoCircle className='w-3.5 h-3.5 text-white/40 hover:text-inherit' />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -81,6 +81,7 @@ export default function useAccountBalancesColumns(
|
|||||||
computeLiquidationPrice={computeLiquidationPrice}
|
computeLiquidationPrice={computeLiquidationPrice}
|
||||||
type={row.original.type}
|
type={row.original.type}
|
||||||
amount={row.original.amount.toNumber()}
|
amount={row.original.amount.toNumber()}
|
||||||
|
account={updatedAccount ?? account}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -98,5 +99,5 @@ export default function useAccountBalancesColumns(
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [computeLiquidationPrice, markets, showLiquidationPrice])
|
}, [computeLiquidationPrice, markets, showLiquidationPrice, account, updatedAccount])
|
||||||
}
|
}
|
||||||
|
59
src/components/DirectionSelect.tsx
Normal file
59
src/components/DirectionSelect.tsx
Normal file
@ -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 (
|
||||||
|
<div className='flex rounded-sm bg-black/20'>
|
||||||
|
<Direction
|
||||||
|
onClick={() => props.onChangeDirection(directions[0])}
|
||||||
|
direction={directions[0]}
|
||||||
|
isActive={props.direction === directions[0]}
|
||||||
|
asset={props.asset}
|
||||||
|
/>
|
||||||
|
<Direction
|
||||||
|
onClick={() => props.onChangeDirection(directions[1])}
|
||||||
|
direction={directions[1]}
|
||||||
|
isActive={props.direction === directions[1]}
|
||||||
|
asset={props.asset}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
'px-4 py-3 rounded-sm flex-1',
|
||||||
|
props.isActive && 'border bg-white/10',
|
||||||
|
`border-${classString}`,
|
||||||
|
)}
|
||||||
|
onClick={props.onClick}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
className={classNames(
|
||||||
|
'text-center first-letter:uppercase',
|
||||||
|
props.isActive ? `text-${classString}` : 'text-white/20',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.asset ? `${props.direction} ${props.asset.symbol}` : props.direction}
|
||||||
|
</Text>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
@ -6,8 +6,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Divider(props: Props) {
|
export default function Divider(props: Props) {
|
||||||
if (props.orientation === 'vertical') {
|
return (
|
||||||
return <div className={classNames(props.className, 'h-full w-[1px] bg-white/10')}></div>
|
<div
|
||||||
}
|
className={classNames(
|
||||||
return <div className={classNames('h-[1px] w-full bg-white/10', props.className)}></div>
|
props.orientation === 'vertical' ? 'h-full w-[1px]' : 'h-[1px] w-full',
|
||||||
|
props.className,
|
||||||
|
'bg-white/10',
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,36 @@ import { isDesktop } from 'react-device-detect'
|
|||||||
import AccountMenu from 'components/Account/AccountMenu'
|
import AccountMenu from 'components/Account/AccountMenu'
|
||||||
import EscButton from 'components/Button/EscButton'
|
import EscButton from 'components/Button/EscButton'
|
||||||
import OracleResyncButton from 'components/Header/OracleResyncButton'
|
import OracleResyncButton from 'components/Header/OracleResyncButton'
|
||||||
|
import { Coins, CoinsSwap } from 'components/Icons'
|
||||||
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
||||||
import RewardsCenter from 'components/RewardsCenter'
|
import RewardsCenter from 'components/RewardsCenter'
|
||||||
import Settings from 'components/Settings'
|
import Settings from 'components/Settings'
|
||||||
import Wallet from 'components/Wallet'
|
import Wallet from 'components/Wallet'
|
||||||
import useAccountId from 'hooks/useAccountId'
|
import useAccountId from 'hooks/useAccountId'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { ENABLE_HLS, ENABLE_PERPS } from 'utils/constants'
|
|
||||||
import { WalletID } from 'types/enums/wallet'
|
import { WalletID } from 'types/enums/wallet'
|
||||||
|
import { ENABLE_HLS, ENABLE_PERPS } from 'utils/constants'
|
||||||
import { getGovernanceUrl } from 'utils/helpers'
|
import { getGovernanceUrl } from 'utils/helpers'
|
||||||
|
|
||||||
export const menuTree = (walletId: WalletID): MenuTreeEntry[] => [
|
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: <Coins className='w-6 h-6' />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 'trade-advanced',
|
||||||
|
label: 'Spot Advanced',
|
||||||
|
subtitle: 'Trade any assets',
|
||||||
|
icon: <CoinsSwap className='w-6 h-6' />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
...(ENABLE_PERPS ? [{ pages: ['perps'] as Page[], label: 'Perps' }] : []),
|
...(ENABLE_PERPS ? [{ pages: ['perps'] as Page[], label: 'Perps' }] : []),
|
||||||
{ pages: ['lend', 'farm'], label: 'Earn' },
|
{ pages: ['lend', 'farm'], label: 'Earn' },
|
||||||
{ pages: ['borrow'], label: 'Borrow' },
|
{ pages: ['borrow'], label: 'Borrow' },
|
||||||
|
6
src/components/Icons/Coins.svg
Normal file
6
src/components/Icons/Coins.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M15.9377 15.9377C19.3603 15.4795 22 12.548 22 9C22 5.13401 18.866 2 15 2C11.452 2 8.52049 4.63967 8.06227 8.06227M16 15C16 18.866 12.866 22 9 22C5.13401 22 2 18.866 2 15C2 11.134 5.13401 8 9 8C12.866 8 16 11.134 16 15Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 346 B |
8
src/components/Icons/CoinsSwap.svg
Normal file
8
src/components/Icons/CoinsSwap.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M6 6L8 4M8 4L6 2M8 4H6C3.79086 4 2 5.79086 2 8M18 18L16 20M16 20L18 22M16 20H18C20.2091 20 22 18.2091 22 16M10.189 6.5C10.8551 3.91216 13.2042 2 16 2C19.3137 2 22 4.68629 22 8C22 10.7957 20.0879 13.1449 17.5001 13.811M14 16C14 19.3137 11.3137 22 8 22C4.68629 22 2 19.3137 2 16C2 12.6863 4.68629 10 8 10C11.3137 10 14 12.6863 14 16Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 514 B |
@ -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 ChevronRight } from 'components/Icons/ChevronRight.svg'
|
||||||
export { default as ChevronUp } from 'components/Icons/ChevronUp.svg'
|
export { default as ChevronUp } from 'components/Icons/ChevronUp.svg'
|
||||||
export { default as Circle } from 'components/Icons/Circle.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 Compass } from 'components/Icons/Compass.svg'
|
||||||
export { default as Copy } from 'components/Icons/Copy.svg'
|
export { default as Copy } from 'components/Icons/Copy.svg'
|
||||||
export { default as Cross } from 'components/Icons/Cross.svg'
|
export { default as Cross } from 'components/Icons/Cross.svg'
|
||||||
|
@ -1,34 +1,29 @@
|
|||||||
import { useShuttle } from '@delphi-labs/shuttle-react'
|
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import { menuTree } from 'components/Header/DesktopHeader'
|
import { menuTree } from 'components/Header/DesktopHeader'
|
||||||
import { ChevronDown, Logo } from 'components/Icons'
|
import { ChevronDown, Logo } from 'components/Icons'
|
||||||
import { NavLink } from 'components/Navigation/NavLink'
|
import { NavLink } from 'components/Navigation/NavLink'
|
||||||
import useAccountId from 'hooks/useAccountId'
|
import { NavMenu } from 'components/Navigation/NavMenu'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { WalletID } from 'types/enums/wallet'
|
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() {
|
export default function DesktopNavigation() {
|
||||||
const [showMenu, setShowMenu] = useToggle()
|
const [showMenu, setShowMenu] = useToggle()
|
||||||
const { recentWallet } = useShuttle()
|
const { recentWallet } = useShuttle()
|
||||||
const walletId = (recentWallet?.providerId as WalletID) ?? WalletID.Keplr
|
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 focusComponent = useStore((s) => s.focusComponent)
|
||||||
|
|
||||||
const menu = useMemo(() => menuTree(walletId), [walletId])
|
const menu = useMemo(() => menuTree(walletId), [walletId])
|
||||||
|
|
||||||
function getIsActive(pages: string[]) {
|
|
||||||
const segments = location.pathname.split('/')
|
|
||||||
return pages.some((page) => segments.includes(page))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -37,31 +32,29 @@ export default function DesktopNavigation() {
|
|||||||
: 'flex flex-1 items-center relative z-50',
|
: 'flex flex-1 items-center relative z-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<NavLink href={getRoute('trade', searchParams, address, accountId)}>
|
<NavLink isHome item={menu[0]}>
|
||||||
<span className='block w-10 h-10'>
|
<span className='block w-10 h-10'>
|
||||||
<Logo className='text-white' />
|
<Logo className='text-white' />
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{!focusComponent && (
|
{!focusComponent && (
|
||||||
<div className='flex gap-8 px-6 @container/navigation relative flex-1'>
|
<div className='flex gap-8 px-6 h-6 @container/navigation relative flex-1'>
|
||||||
{menu.map((item, index) => (
|
{menu.map((item, index) =>
|
||||||
|
item.submenu ? (
|
||||||
|
<NavMenu key={index} item={item} />
|
||||||
|
) : (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={index}
|
key={index}
|
||||||
href={
|
item={item}
|
||||||
item.externalUrl
|
|
||||||
? item.externalUrl
|
|
||||||
: getRoute(item.pages[0], searchParams, address, accountId)
|
|
||||||
}
|
|
||||||
isActive={getIsActive(item.pages)}
|
|
||||||
className={`@nav-${index}/navigation:inline-block hidden whitespace-nowrap`}
|
className={`@nav-${index}/navigation:inline-block hidden whitespace-nowrap`}
|
||||||
target={item.externalUrl ? '_blank' : undefined}
|
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
),
|
||||||
|
)}
|
||||||
<div className={`@nav-${menu.length - 1}/navigation:hidden flex items-center relative`}>
|
<div className={`@nav-${menu.length - 1}/navigation:hidden flex items-center relative`}>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<ChevronDown />}
|
leftIcon={<ChevronDown className='w-3' />}
|
||||||
color='quaternary'
|
color='quaternary'
|
||||||
variant='transparent'
|
variant='transparent'
|
||||||
onClick={() => setShowMenu(!showMenu)}
|
onClick={() => setShowMenu(!showMenu)}
|
||||||
@ -84,15 +77,9 @@ export default function DesktopNavigation() {
|
|||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<NavLink
|
<NavLink
|
||||||
href={
|
item={item}
|
||||||
item.externalUrl
|
|
||||||
? item.externalUrl
|
|
||||||
: getRoute(item.pages[0], searchParams, address, accountId)
|
|
||||||
}
|
|
||||||
onClick={() => setShowMenu(false)}
|
onClick={() => setShowMenu(false)}
|
||||||
isActive={getIsActive(item.pages)}
|
|
||||||
className='w-full px-4 whitespace-nowrap'
|
className='w-full px-4 whitespace-nowrap'
|
||||||
target={item.externalUrl ? '_blank' : undefined}
|
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -1,27 +1,41 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { NavLink as Link } from 'react-router-dom'
|
import { NavLink as Link, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { getIsActive } from 'components/Navigation/DesktopNavigation'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { getRoute } from 'utils/route'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
href: string
|
|
||||||
children: string | ReactNode
|
children: string | ReactNode
|
||||||
isActive?: boolean
|
item: MenuTreeEntry
|
||||||
|
isHome?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
target?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavLink = (props: Props) => {
|
export const NavLink = (props: Props) => {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const { isHome, item, className, onClick } = props
|
||||||
|
const accountId = useAccountId()
|
||||||
|
|
||||||
|
const itemLink = item.externalUrl
|
||||||
|
? item.externalUrl
|
||||||
|
: getRoute(item.pages[0], searchParams, address, accountId)
|
||||||
|
const link = isHome ? getRoute('trade', searchParams, address, accountId) : itemLink
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={props.href}
|
to={link}
|
||||||
onClick={props.onClick ? props.onClick : undefined}
|
onClick={onClick ? onClick : undefined}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.className,
|
className,
|
||||||
'font-semibold hover:text-white active:text-white',
|
'font-semibold hover:text-white active:text-white',
|
||||||
props.isActive ? 'pointer-events-none text-white' : 'text-white/60',
|
getIsActive(item.pages) ? 'pointer-events-none text-white' : 'text-white/60',
|
||||||
)}
|
)}
|
||||||
target={props.target}
|
target={item.externalUrl ? '_blank' : undefined}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Link>
|
</Link>
|
||||||
|
80
src/components/Navigation/NavMenu.tsx
Normal file
80
src/components/Navigation/NavMenu.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import Button from 'components/Button'
|
||||||
|
import Divider from 'components/Divider'
|
||||||
|
import { ChevronDown } from 'components/Icons'
|
||||||
|
import { NavLink } from 'components/Navigation//NavLink'
|
||||||
|
import { getIsActive } from 'components/Navigation/DesktopNavigation'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import useToggle from 'hooks/useToggle'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
item: MenuTreeEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NavMenu = (props: Props) => {
|
||||||
|
const { item } = props
|
||||||
|
const [showMenu, setShowMenu] = useToggle()
|
||||||
|
|
||||||
|
if (!item.submenu) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative flex items-center'>
|
||||||
|
<Button
|
||||||
|
rightIcon={<ChevronDown className='w-3' />}
|
||||||
|
color='quaternary'
|
||||||
|
variant='transparent'
|
||||||
|
onClick={() => setShowMenu(!showMenu)}
|
||||||
|
text={item.label}
|
||||||
|
className={classNames(
|
||||||
|
'!text-base !p-0 !min-h-0',
|
||||||
|
(getIsActive(item.pages) || showMenu) && '!text-white',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{showMenu && (
|
||||||
|
<>
|
||||||
|
<div className='absolute left-0 top-[calc(100%+4px)] z-50'>
|
||||||
|
<ul
|
||||||
|
className={classNames(
|
||||||
|
'py-4 list-none flex flex-wrap gap-2 bg-white/10 backdrop-blur-lg',
|
||||||
|
'relative isolate max-w-full overflow-hidden rounded-sm',
|
||||||
|
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.submenu.map((submenuitem, index) => (
|
||||||
|
<li className='w-full m-0 group/submenuitem' key={index}>
|
||||||
|
{index !== 0 && <Divider className='mb-2' />}
|
||||||
|
<NavLink
|
||||||
|
item={{ pages: [submenuitem.page], label: submenuitem.label }}
|
||||||
|
onClick={() => setShowMenu(false)}
|
||||||
|
className='flex items-center w-full gap-4 px-4 whitespace-nowrap'
|
||||||
|
>
|
||||||
|
{submenuitem.icon && <div className='w-6'>{submenuitem.icon}</div>}
|
||||||
|
<Text className='flex flex-wrap'>
|
||||||
|
{submenuitem.label}
|
||||||
|
{submenuitem.subtitle && (
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'w-full text-sm group-hover/submenuitem:text-white',
|
||||||
|
getIsActive([submenuitem.page]) ? 'text-white' : 'text-white/40',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{submenuitem.subtitle}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='fixed -top-6 -left-[55px] z-40 w-screen h-screen hover:cursor-pointer'
|
||||||
|
onClick={() => setShowMenu(false)}
|
||||||
|
role='button'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -2,10 +2,10 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
|
import { DirectionSelect } from 'components/DirectionSelect'
|
||||||
import { ChevronDown } from 'components/Icons'
|
import { ChevronDown } from 'components/Icons'
|
||||||
import { LeverageButtons } from 'components/Perps/Module/LeverageButtons'
|
import { LeverageButtons } from 'components/Perps/Module/LeverageButtons'
|
||||||
import { Or } from 'components/Perps/Module/Or'
|
import { Or } from 'components/Perps/Module/Or'
|
||||||
import { SelectLongShort } from 'components/Perps/Module/SelectLongShort'
|
|
||||||
import RangeInput from 'components/RangeInput'
|
import RangeInput from 'components/RangeInput'
|
||||||
import { Spacer } from 'components/Spacer'
|
import { Spacer } from 'components/Spacer'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
@ -23,7 +23,7 @@ export function PerpsModule() {
|
|||||||
<Card
|
<Card
|
||||||
contentClassName='px-4 gap-5 flex flex-col'
|
contentClassName='px-4 gap-5 flex flex-col'
|
||||||
title={
|
title={
|
||||||
<div className='flex justify-between bg-white/10 py-4 pl-4 pr-2 items-center'>
|
<div className='flex items-center justify-between py-4 pl-4 pr-2 bg-white/10'>
|
||||||
<Text>
|
<Text>
|
||||||
ETH<span className='text-white/60'>/USD</span>
|
ETH<span className='text-white/60'>/USD</span>
|
||||||
</Text>
|
</Text>
|
||||||
@ -35,7 +35,7 @@ export function PerpsModule() {
|
|||||||
className='mb-4'
|
className='mb-4'
|
||||||
>
|
>
|
||||||
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
||||||
<SelectLongShort
|
<DirectionSelect
|
||||||
direction={selectedOrderDirection}
|
direction={selectedOrderDirection}
|
||||||
onChangeDirection={setSelectedOrderDirection}
|
onChangeDirection={setSelectedOrderDirection}
|
||||||
/>
|
/>
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
|
|
||||||
import Text from 'components/Text'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
direction: OrderDirection
|
|
||||||
onChangeDirection: (direction: OrderDirection) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SelectLongShort(props: Props) {
|
|
||||||
return (
|
|
||||||
<div className='flex bg-black/20 rounded-sm'>
|
|
||||||
<Direction
|
|
||||||
onClick={() => props.onChangeDirection('long')}
|
|
||||||
direction='long'
|
|
||||||
isActive={props.direction === 'long'}
|
|
||||||
/>
|
|
||||||
<Direction
|
|
||||||
onClick={() => props.onChangeDirection('short')}
|
|
||||||
direction='short'
|
|
||||||
isActive={props.direction === 'short'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DirectionProps {
|
|
||||||
direction: 'long' | 'short'
|
|
||||||
isActive: boolean
|
|
||||||
onClick: () => void
|
|
||||||
}
|
|
||||||
function Direction(props: DirectionProps) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={classNames(
|
|
||||||
'px-4 py-3 rounded-sm flex-1',
|
|
||||||
borderColors[props.direction],
|
|
||||||
props.isActive && 'border bg-white/10',
|
|
||||||
)}
|
|
||||||
onClick={props.onClick}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
className={classNames(
|
|
||||||
'text-center capitalize',
|
|
||||||
props.isActive ? directionColors[props.direction] : 'text-white/20',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{props.direction}
|
|
||||||
</Text>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const directionColors = {
|
|
||||||
long: 'text-success',
|
|
||||||
short: 'text-error',
|
|
||||||
}
|
|
||||||
|
|
||||||
const borderColors = {
|
|
||||||
long: 'border-success',
|
|
||||||
short: 'border-error',
|
|
||||||
}
|
|
@ -11,8 +11,7 @@ import PerpsPage from 'pages/PerpsPage'
|
|||||||
import PortfolioAccountPage from 'pages/PortfolioAccountPage'
|
import PortfolioAccountPage from 'pages/PortfolioAccountPage'
|
||||||
import PortfolioPage from 'pages/PortfolioPage'
|
import PortfolioPage from 'pages/PortfolioPage'
|
||||||
import TradePage from 'pages/TradePage'
|
import TradePage from 'pages/TradePage'
|
||||||
import { ENABLE_PERPS } from 'utils/constants'
|
import { ENABLE_HLS, ENABLE_PERPS } from 'utils/constants'
|
||||||
import { ENABLE_HLS } from 'utils/constants'
|
|
||||||
|
|
||||||
export default function Routes() {
|
export default function Routes() {
|
||||||
return (
|
return (
|
||||||
@ -25,6 +24,7 @@ export default function Routes() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route path='/trade' element={<TradePage />} />
|
<Route path='/trade' element={<TradePage />} />
|
||||||
|
<Route path='/trade-advanced' element={<TradePage />} />
|
||||||
{ENABLE_PERPS && <Route path='/perps' element={<PerpsPage />} />}
|
{ENABLE_PERPS && <Route path='/perps' element={<PerpsPage />} />}
|
||||||
<Route path='/farm' element={<FarmPage />} />
|
<Route path='/farm' element={<FarmPage />} />
|
||||||
<Route path='/lend' element={<LendPage />} />
|
<Route path='/lend' element={<LendPage />} />
|
||||||
@ -36,6 +36,7 @@ export default function Routes() {
|
|||||||
<Route path='/' element={<TradePage />} />
|
<Route path='/' element={<TradePage />} />
|
||||||
<Route path='/wallets/:address'>
|
<Route path='/wallets/:address'>
|
||||||
<Route path='trade' element={<TradePage />} />
|
<Route path='trade' element={<TradePage />} />
|
||||||
|
<Route path='trade-advanced' element={<TradePage />} />
|
||||||
{ENABLE_PERPS && <Route path='perps' element={<PerpsPage />} />}
|
{ENABLE_PERPS && <Route path='perps' element={<PerpsPage />} />}
|
||||||
<Route path='farm' element={<FarmPage />} />
|
<Route path='farm' element={<FarmPage />} />
|
||||||
<Route path='lend' element={<LendPage />} />
|
<Route path='lend' element={<LendPage />} />
|
||||||
|
@ -27,15 +27,12 @@ export default function SwitchAutoLend(props: Props) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAutoLendEnabled) {
|
|
||||||
setIsAutoLendEnabled(false)
|
setIsAutoLendEnabled(false)
|
||||||
disableAutoLend(accountId)
|
disableAutoLend(accountId)
|
||||||
}
|
|
||||||
}, [
|
}, [
|
||||||
accountId,
|
accountId,
|
||||||
disableAutoLend,
|
disableAutoLend,
|
||||||
enableAutoLend,
|
enableAutoLend,
|
||||||
isAutoLendEnabled,
|
|
||||||
isAutoLendEnabledForAccount,
|
isAutoLendEnabledForAccount,
|
||||||
setIsAutoLendEnabled,
|
setIsAutoLendEnabled,
|
||||||
])
|
])
|
||||||
|
@ -18,6 +18,7 @@ export default function AccountDetailsCard() {
|
|||||||
|
|
||||||
if (account)
|
if (account)
|
||||||
return (
|
return (
|
||||||
|
<div className='w-full'>
|
||||||
<AccountBalancesTable
|
<AccountBalancesTable
|
||||||
account={account}
|
account={account}
|
||||||
borrowingData={borrowAssetsData}
|
borrowingData={borrowAssetsData}
|
||||||
@ -25,5 +26,6 @@ export default function AccountDetailsCard() {
|
|||||||
tableBodyClassName='gradient-card-content'
|
tableBodyClassName='gradient-card-content'
|
||||||
showLiquidationPrice
|
showLiquidationPrice
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ export const TVChartContainer = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
contentClassName='px-0.5 pb-0.5 h-full'
|
contentClassName='px-0.5 pb-0.5 h-full'
|
||||||
className='min-h-[55dvh] h-full'
|
className='h-[70dvh] max-h-[980px] min-h-[560px]'
|
||||||
>
|
>
|
||||||
<div ref={chartContainerRef} className='h-full overflow-hidden rounded-b-base' />
|
<div ref={chartContainerRef} className='h-full overflow-hidden rounded-b-base' />
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -45,7 +45,7 @@ export default function TradeChart(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
contentClassName='px-0.5 pb-0.5 h-full'
|
contentClassName='px-0.5 pb-0.5 h-full'
|
||||||
className='min-h-[55dvh]'
|
className='h-[70dvh] max-h-[980px] min-h-[560px]'
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-center w-full h-full rounded-b-base bg-chart'>
|
<div className='flex items-center justify-center w-full h-full rounded-b-base bg-chart'>
|
||||||
<CircularProgress size={60} className='opacity-50' />
|
<CircularProgress size={60} className='opacity-50' />
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import AssetImage from 'components/Asset/AssetImage'
|
import AssetImage from 'components/Asset/AssetImage'
|
||||||
import AssetSymbol from 'components/Asset/AssetSymbol'
|
import AssetSymbol from 'components/Asset/AssetSymbol'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
@ -38,6 +40,13 @@ export default function AssetItem(props: Props) {
|
|||||||
setFavoriteAssetsDenoms(favoriteAssetsDenoms.filter((item: string) => item !== asset.denom))
|
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 (
|
return (
|
||||||
<li className='border-b border-white/10 hover:bg-black/10'>
|
<li className='border-b border-white/10 hover:bg-black/10'>
|
||||||
<button
|
<button
|
||||||
@ -80,12 +89,16 @@ export default function AssetItem(props: Props) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{props.depositCap && (
|
{props.depositCap && capLeft <= 15 && (
|
||||||
<div className='flex gap-1'>
|
<div className='flex gap-1'>
|
||||||
<span className='text-xs text-left text-white/60'>Cap Left: </span>
|
<span className='text-xs text-left text-white/60'>Cap Left: </span>
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
className='text-xs text-left text-white/60'
|
className='text-xs text-left text-info/60'
|
||||||
coin={BNCoin.fromDenomAndBigNumber(props.depositCap.denom, props.depositCap.max)}
|
coin={BNCoin.fromDenomAndBigNumber(
|
||||||
|
props.depositCap.denom,
|
||||||
|
props.depositCap.max.minus(props.depositCap.used),
|
||||||
|
)}
|
||||||
|
options={{ suffix: ` (${capLeft.toFixed(2)}%)` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -3,54 +3,67 @@ import { useMemo } from 'react'
|
|||||||
|
|
||||||
import { ChevronDown } from 'components/Icons'
|
import { ChevronDown } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem'
|
import { ASSETS } from 'constants/assets'
|
||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
import useMarketAssets from 'hooks/useMarketAssets'
|
import useMarketAssets from 'hooks/useMarketAssets'
|
||||||
|
import useMarketDeposits from 'hooks/useMarketDeposits'
|
||||||
|
import usePrices from 'hooks/usePrices'
|
||||||
import { getMergedBalancesForAsset } from 'utils/accounts'
|
import { getMergedBalancesForAsset } from 'utils/accounts'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getEnabledMarketAssets } from 'utils/assets'
|
import { getEnabledMarketAssets, sortAssetsOrPairs } from 'utils/assets'
|
||||||
|
import AssetSelectorItem from 'components/Trade/TradeModule/AssetSelector/AssetSelectorItem'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'buy' | 'sell'
|
type: 'buy' | 'sell'
|
||||||
assets: Asset[]
|
assets: Asset[]
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
toggleOpen: () => void
|
toggleOpen: () => void
|
||||||
onChangeAsset: (asset: Asset) => void
|
onChangeAsset: (asset: Asset | AssetPair) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseDenom = ASSETS[0].denom
|
||||||
|
|
||||||
export default function AssetList(props: Props) {
|
export default function AssetList(props: Props) {
|
||||||
|
const { assets, type, isOpen, toggleOpen, onChangeAsset } = props
|
||||||
const account = useCurrentAccount()
|
const account = useCurrentAccount()
|
||||||
const { data: marketAssets } = useMarketAssets()
|
const { data: marketAssets } = useMarketAssets()
|
||||||
|
const { data: marketDeposits } = useMarketDeposits()
|
||||||
|
const { data: prices } = usePrices()
|
||||||
const balances = useMemo(() => {
|
const balances = useMemo(() => {
|
||||||
if (!account) return []
|
if (!account) return []
|
||||||
return getMergedBalancesForAsset(account, getEnabledMarketAssets())
|
return getMergedBalancesForAsset(account, getEnabledMarketAssets())
|
||||||
}, [account])
|
}, [account])
|
||||||
|
|
||||||
|
const sortedAssets = useMemo(
|
||||||
|
() => sortAssetsOrPairs(assets, prices, marketDeposits, balances, baseDenom) as Asset[],
|
||||||
|
[balances, prices, assets, marketDeposits],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<button
|
<button
|
||||||
className='flex items-center justify-between w-full p-4 bg-black/20'
|
className='flex items-center justify-between w-full p-4 bg-black/20'
|
||||||
onClick={props.toggleOpen}
|
onClick={toggleOpen}
|
||||||
>
|
>
|
||||||
<Text>{props.type === 'buy' ? 'Buy asset' : 'Sell asset'}</Text>
|
<Text>{type === 'buy' ? 'Buy asset' : 'Sell asset'}</Text>
|
||||||
<ChevronDown className={classNames(props.isOpen && '-rotate-180', 'w-4')} />
|
<ChevronDown className={classNames(isOpen && '-rotate-180', 'w-4')} />
|
||||||
</button>
|
</button>
|
||||||
{props.isOpen &&
|
{isOpen &&
|
||||||
(props.assets.length === 0 ? (
|
(sortedAssets.length === 0 ? (
|
||||||
<Text size='xs' className='p-4'>
|
<Text size='xs' className='p-4'>
|
||||||
No available assets found
|
No available assets found
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<ul>
|
<ul>
|
||||||
{props.assets.map((asset) => (
|
{sortedAssets.map((asset) => (
|
||||||
<AssetItem
|
<AssetSelectorItem
|
||||||
balances={balances}
|
balances={balances}
|
||||||
key={`${props.type}-${asset.symbol}`}
|
key={`${type}-${asset.symbol}`}
|
||||||
asset={asset}
|
onSelect={props.onChangeAsset}
|
||||||
onSelectAsset={props.onChangeAsset}
|
|
||||||
depositCap={
|
depositCap={
|
||||||
props.type === 'buy' ? marketAssets?.find(byDenom(asset.denom))?.cap : undefined
|
type === 'buy' ? marketAssets?.find(byDenom(asset.denom))?.cap : undefined
|
||||||
}
|
}
|
||||||
|
asset={asset}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -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 EscButton from 'components/Button/EscButton'
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import Overlay from 'components/Overlay'
|
import Overlay from 'components/Overlay'
|
||||||
import SearchBar from 'components/SearchBar'
|
import SearchBar from 'components/SearchBar'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList'
|
import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList'
|
||||||
|
import PairsList from 'components/Trade/TradeModule/AssetSelector/PairsList'
|
||||||
import useFilteredAssets from 'hooks/useFilteredAssets'
|
import useFilteredAssets from 'hooks/useFilteredAssets'
|
||||||
|
import { getAllAssets } from 'utils/assets'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
state: OverlayState
|
state: OverlayState
|
||||||
buyAsset: Asset
|
buyAsset: Asset
|
||||||
sellAsset: Asset
|
sellAsset: Asset
|
||||||
onChangeBuyAsset: (asset: Asset) => void
|
onChangeBuyAsset?: (asset: Asset) => void
|
||||||
onChangeSellAsset: (asset: Asset) => void
|
onChangeSellAsset?: (asset: Asset) => void
|
||||||
|
onChangeTradingPair?: (tradingPair: TradingPair) => void
|
||||||
onChangeState: (state: OverlayState) => void
|
onChangeState: (state: OverlayState) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AssetOverlay(props: Props) {
|
interface StablesFilterProps {
|
||||||
const { assets, searchString, onChangeSearch } = useFilteredAssets()
|
stables: Asset[]
|
||||||
const handleClose = useCallback(() => props.onChangeState('closed'), [props])
|
selectedStables: Asset[]
|
||||||
|
onFilter: (stables: Asset[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
const handleToggle = useCallback(
|
function StablesFilter(props: StablesFilterProps) {
|
||||||
() => props.onChangeState(props.state === 'buy' ? 'sell' : 'buy'),
|
const { stables, selectedStables, onFilter } = props
|
||||||
[props],
|
const isAllSelected = selectedStables.length > 1
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<div className='flex items-center w-full py-2 justify-evenly'>
|
||||||
|
<Button
|
||||||
|
onClick={() => onFilter(stables)}
|
||||||
|
text='All'
|
||||||
|
color={isAllSelected ? 'secondary' : 'quaternary'}
|
||||||
|
variant='transparent'
|
||||||
|
className={isAllSelected ? '!text-white !bg-white/10 border-white' : ''}
|
||||||
|
/>
|
||||||
|
{stables.map((stable) => {
|
||||||
|
const isCurrent = !isAllSelected && selectedStables[0].denom === stable.denom
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={stable.symbol}
|
||||||
|
onClick={() => onFilter([stable])}
|
||||||
|
text={stable.symbol}
|
||||||
|
color={isCurrent ? 'secondary' : 'quaternary'}
|
||||||
|
variant='transparent'
|
||||||
|
className={isCurrent ? '!text-white !bg-white/10 border-white' : ''}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Asset[]>(stableAssets)
|
||||||
|
|
||||||
const buyAssets = useMemo(
|
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(
|
const sellAssets = useMemo(
|
||||||
@ -36,14 +79,26 @@ export default function AssetOverlay(props: Props) {
|
|||||||
[assets, props.buyAsset],
|
[assets, props.buyAsset],
|
||||||
)
|
)
|
||||||
|
|
||||||
function onChangeBuyAsset(asset: Asset) {
|
function onChangeBuyAsset(asset: AssetPair | Asset) {
|
||||||
props.onChangeBuyAsset(asset)
|
const selectedAsset = asset as Asset
|
||||||
|
if (!props.onChangeBuyAsset) return
|
||||||
|
props.onChangeBuyAsset(selectedAsset)
|
||||||
props.onChangeState('sell')
|
props.onChangeState('sell')
|
||||||
onChangeSearch('')
|
onChangeSearch('')
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeSellAsset(asset: Asset) {
|
function onChangeSellAsset(asset: AssetPair | Asset) {
|
||||||
props.onChangeSellAsset(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('')
|
onChangeSearch('')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +109,16 @@ export default function AssetOverlay(props: Props) {
|
|||||||
setShow={handleClose}
|
setShow={handleClose}
|
||||||
>
|
>
|
||||||
<div className='flex justify-between p-4 overflow-hidden'>
|
<div className='flex justify-between p-4 overflow-hidden'>
|
||||||
<Text>Select asset</Text>
|
<Text>{isPairSelector ? 'Select a market' : 'Select asset'}</Text>
|
||||||
<EscButton onClick={handleClose} enableKeyPress />
|
<EscButton onClick={handleClose} enableKeyPress />
|
||||||
</div>
|
</div>
|
||||||
|
{isPairSelector && (
|
||||||
|
<StablesFilter
|
||||||
|
stables={stableAssets}
|
||||||
|
selectedStables={selectedStables}
|
||||||
|
onFilter={setSelectedStables}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<div className='p-4'>
|
<div className='p-4'>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
@ -68,6 +130,16 @@ export default function AssetOverlay(props: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
{isPairSelector ? (
|
||||||
|
<PairsList
|
||||||
|
assets={buyAssets}
|
||||||
|
stables={selectedStables}
|
||||||
|
isOpen={props.state === 'pair'}
|
||||||
|
toggleOpen={handleToggle}
|
||||||
|
onChangeAssetPair={onChangeAssetPair}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<AssetList
|
<AssetList
|
||||||
type='buy'
|
type='buy'
|
||||||
assets={buyAssets}
|
assets={buyAssets}
|
||||||
@ -82,6 +154,8 @@ export default function AssetOverlay(props: Props) {
|
|||||||
toggleOpen={handleToggle}
|
toggleOpen={handleToggle}
|
||||||
onChangeAsset={onChangeSellAsset}
|
onChangeAsset={onChangeSellAsset}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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<string[]>(
|
||||||
|
LocalStorageKeys.FAVORITE_ASSETS,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleToggleFavorite(event: React.MouseEvent<HTMLDivElement, 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 (
|
||||||
|
<li className='border-b border-white/10 hover:bg-black/10'>
|
||||||
|
<button
|
||||||
|
onClick={() => onSelect(sellAsset ? { buy: asset, sell: sellAsset } : asset)}
|
||||||
|
className='flex items-center justify-between w-full gap-2 p-4'
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div onClick={handleToggleFavorite}>
|
||||||
|
{asset.isFavorite ? <StarFilled width={16} /> : <StarOutlined width={16} />}
|
||||||
|
</div>
|
||||||
|
<AssetImage asset={asset} size={24} />
|
||||||
|
<div className='flex-col'>
|
||||||
|
<div className='flex gap-1 flex-nowrap max-w-[185px]'>
|
||||||
|
{sellAsset ? (
|
||||||
|
<Text size='sm' className='h-5 leading-5 text-left text-white/60'>
|
||||||
|
<span className='text-white'>{asset.symbol}</span>/{sellAsset.symbol}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text size='sm' className='h-5 leading-5 text-left truncate '>
|
||||||
|
{asset.name}
|
||||||
|
</Text>
|
||||||
|
<AssetSymbol symbol={asset.symbol} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{balances.length > 0 && (
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<span className='text-xs text-left text-white/80'>Balance: </span>
|
||||||
|
{amount >= 1 ? (
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xs text-left text-white/80'
|
||||||
|
amount={amount}
|
||||||
|
options={{ abbreviated: true, maxDecimals: MAX_AMOUNT_DECIMALS }}
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xs text-left text-white/80'
|
||||||
|
smallerThanThreshold={formattedAmount !== 0 && formattedAmount < MIN_AMOUNT}
|
||||||
|
amount={lowAmount}
|
||||||
|
options={{
|
||||||
|
maxDecimals: MAX_AMOUNT_DECIMALS,
|
||||||
|
minDecimals: 0,
|
||||||
|
}}
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{depositCap && capLeft <= 15 && (
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<span className='text-xs text-left text-white/60'>Cap Left: </span>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xs text-left text-info/60'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(
|
||||||
|
depositCap.denom,
|
||||||
|
depositCap.max.minus(depositCap.used),
|
||||||
|
)}
|
||||||
|
options={{ suffix: ` (${capLeft.toFixed(2)}%)` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-sm'
|
||||||
|
coin={
|
||||||
|
new BNCoin({
|
||||||
|
denom: asset.denom,
|
||||||
|
amount: BN_ONE.shiftedBy(asset.decimals).toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
@ -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<Settings['tradingPairSimple']>(
|
||||||
|
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 (
|
||||||
|
<div className='flex items-center justify-between w-full p-3 bg-white/5'>
|
||||||
|
<Text size='sm' className='text-white/60'>
|
||||||
|
<span className='text-white'>{buyAsset.symbol}</span>/{sellAsset.symbol}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
onClick={() => useStore.setState({ assetOverlayState: 'pair' })}
|
||||||
|
text='all markets'
|
||||||
|
color='quaternary'
|
||||||
|
variant='transparent'
|
||||||
|
className='pr-0'
|
||||||
|
rightIcon={<ChevronDown className='w-3 h-3' />}
|
||||||
|
/>
|
||||||
|
<AssetOverlay
|
||||||
|
state={assetOverlayState}
|
||||||
|
onChangeState={handleChangeState}
|
||||||
|
buyAsset={buyAsset}
|
||||||
|
sellAsset={sellAsset}
|
||||||
|
onChangeTradingPair={onChangeTradingPair}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -14,32 +14,31 @@ interface Props {
|
|||||||
sellAsset: Asset
|
sellAsset: Asset
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AssetSelector(props: Props) {
|
export default function AssetSelectorSingle(props: Props) {
|
||||||
const [tradingPair, setTradingPair] = useLocalStorage<Settings['tradingPair']>(
|
const [tradingPairAdvanced, settradingPairAdvanced] = useLocalStorage<
|
||||||
LocalStorageKeys.TRADING_PAIR,
|
Settings['tradingPairAdvanced']
|
||||||
DEFAULT_SETTINGS.tradingPair,
|
>(LocalStorageKeys.TRADING_PAIR_ADVANCED, DEFAULT_SETTINGS.tradingPairAdvanced)
|
||||||
)
|
|
||||||
const { buyAsset, sellAsset } = props
|
const { buyAsset, sellAsset } = props
|
||||||
const assetOverlayState = useStore((s) => s.assetOverlayState)
|
const assetOverlayState = useStore((s) => s.assetOverlayState)
|
||||||
|
|
||||||
const handleSwapAssets = useCallback(() => {
|
const handleSwapAssets = useCallback(() => {
|
||||||
setTradingPair({ buy: sellAsset.denom, sell: buyAsset.denom })
|
settradingPairAdvanced({ buy: sellAsset.denom, sell: buyAsset.denom })
|
||||||
}, [setTradingPair, sellAsset, buyAsset])
|
}, [settradingPairAdvanced, sellAsset, buyAsset])
|
||||||
|
|
||||||
const handleChangeBuyAsset = useCallback(
|
const handleChangeBuyAsset = useCallback(
|
||||||
(asset: Asset) => {
|
(asset: Asset) => {
|
||||||
setTradingPair({ buy: asset.denom, sell: sellAsset.denom })
|
settradingPairAdvanced({ buy: asset.denom, sell: sellAsset.denom })
|
||||||
useStore.setState({ assetOverlayState: 'sell' })
|
useStore.setState({ assetOverlayState: 'sell' })
|
||||||
},
|
},
|
||||||
[setTradingPair, sellAsset],
|
[settradingPairAdvanced, sellAsset],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleChangeSellAsset = useCallback(
|
const handleChangeSellAsset = useCallback(
|
||||||
(asset: Asset) => {
|
(asset: Asset) => {
|
||||||
setTradingPair({ buy: buyAsset.denom, sell: asset.denom })
|
settradingPairAdvanced({ buy: buyAsset.denom, sell: asset.denom })
|
||||||
useStore.setState({ assetOverlayState: 'closed' })
|
useStore.setState({ assetOverlayState: 'closed' })
|
||||||
},
|
},
|
||||||
[setTradingPair, buyAsset],
|
[settradingPairAdvanced, buyAsset],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleChangeState = useCallback((state: OverlayState) => {
|
const handleChangeState = useCallback((state: OverlayState) => {
|
||||||
@ -47,7 +46,7 @@ export default function AssetSelector(props: Props) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid-rows-auto grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
|
<div className='grid-rows-auto grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3 w-full'>
|
||||||
<Text size='sm'>Buy</Text>
|
<Text size='sm'>Buy</Text>
|
||||||
<Text size='sm' className='col-start-3'>
|
<Text size='sm' className='col-start-3'>
|
||||||
Sell
|
Sell
|
73
src/components/Trade/TradeModule/AssetSelector/PairsList.tsx
Normal file
73
src/components/Trade/TradeModule/AssetSelector/PairsList.tsx
Normal file
@ -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 (
|
||||||
|
<section>
|
||||||
|
{props.isOpen &&
|
||||||
|
(props.assets.length === 0 ? (
|
||||||
|
<Text size='xs' className='p-4'>
|
||||||
|
No available assets found
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<ul>
|
||||||
|
{sortedPairs.map((assetPair) => (
|
||||||
|
<AssetSelectorItem
|
||||||
|
balances={balances}
|
||||||
|
key={`${assetPair.buy.symbol}-${assetPair.sell.symbol}`}
|
||||||
|
onSelect={props.onChangeAssetPair}
|
||||||
|
depositCap={marketAssets?.find(byDenom(assetPair.buy.denom))?.cap}
|
||||||
|
asset={assetPair.buy}
|
||||||
|
sellAsset={assetPair.sell}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
@ -12,7 +12,7 @@ interface Props {
|
|||||||
|
|
||||||
export default function MarginToggle(props: Props) {
|
export default function MarginToggle(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-row justify-between flex-1 px-4 py-2 bg-white/5'>
|
<div className='flex justify-between w-full px-4 py-2 bg-white/5'>
|
||||||
<Text size='sm'>Margin</Text>
|
<Text size='sm'>Margin</Text>
|
||||||
|
|
||||||
<ConditionalWrapper
|
<ConditionalWrapper
|
||||||
|
@ -34,6 +34,8 @@ interface Props {
|
|||||||
sellAmount: BigNumber
|
sellAmount: BigNumber
|
||||||
sellAsset: Asset
|
sellAsset: Asset
|
||||||
showProgressIndicator: boolean
|
showProgressIndicator: boolean
|
||||||
|
isAdvanced?: boolean
|
||||||
|
direction?: OrderDirection
|
||||||
}
|
}
|
||||||
|
|
||||||
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
|
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
|
||||||
@ -53,6 +55,8 @@ export default function TradeSummary(props: Props) {
|
|||||||
route,
|
route,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
buyAmount,
|
buyAmount,
|
||||||
|
isAdvanced,
|
||||||
|
direction,
|
||||||
} = props
|
} = props
|
||||||
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
||||||
|
|
||||||
@ -80,10 +84,10 @@ export default function TradeSummary(props: Props) {
|
|||||||
return routeSymbols.join(' -> ')
|
return routeSymbols.join(' -> ')
|
||||||
}, [route, sellAsset.symbol])
|
}, [route, sellAsset.symbol])
|
||||||
|
|
||||||
const buttonText = useMemo(
|
const buttonText = useMemo(() => {
|
||||||
() => (route.length ? `Buy ${buyAsset.symbol}` : 'No route found'),
|
if (!isAdvanced && direction === 'sell') return `Sell ${sellAsset.symbol}`
|
||||||
[buyAsset.symbol, route],
|
return route.length ? `Buy ${buyAsset.symbol}` : 'No route found'
|
||||||
)
|
}, [buyAsset.symbol, route, sellAsset.symbol, isAdvanced, direction])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -4,8 +4,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|||||||
import estimateExactIn from 'api/swap/estimateExactIn'
|
import estimateExactIn from 'api/swap/estimateExactIn'
|
||||||
import AvailableLiquidityMessage from 'components/AvailableLiquidityMessage'
|
import AvailableLiquidityMessage from 'components/AvailableLiquidityMessage'
|
||||||
import DepositCapMessage from 'components/DepositCapMessage'
|
import DepositCapMessage from 'components/DepositCapMessage'
|
||||||
|
import { DirectionSelect } from 'components/DirectionSelect'
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import RangeInput from 'components/RangeInput'
|
import RangeInput from 'components/RangeInput'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import AssetSelectorPair from 'components/Trade/TradeModule/AssetSelector/AssetSelectorPair'
|
||||||
|
import AssetSelectorSingle from 'components/Trade/TradeModule/AssetSelector/AssetSelectorSingle'
|
||||||
import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput'
|
import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput'
|
||||||
import AutoRepayToggle from 'components/Trade/TradeModule/SwapForm/AutoRepayToggle'
|
import AutoRepayToggle from 'components/Trade/TradeModule/SwapForm/AutoRepayToggle'
|
||||||
import MarginToggle from 'components/Trade/TradeModule/SwapForm/MarginToggle'
|
import MarginToggle from 'components/Trade/TradeModule/SwapForm/MarginToggle'
|
||||||
@ -28,34 +32,45 @@ import useStore from 'store'
|
|||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { defaultFee, ENABLE_AUTO_REPAY } from 'utils/constants'
|
import { defaultFee, ENABLE_AUTO_REPAY } from 'utils/constants'
|
||||||
|
import { formatValue } from 'utils/formatters'
|
||||||
import { getCapLeftWithBuffer } from 'utils/generic'
|
import { getCapLeftWithBuffer } from 'utils/generic'
|
||||||
import { asyncThrottle, BN } from 'utils/helpers'
|
import { asyncThrottle, BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
buyAsset: Asset
|
buyAsset: Asset
|
||||||
sellAsset: Asset
|
sellAsset: Asset
|
||||||
|
isAdvanced: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwapForm(props: Props) {
|
export default function SwapForm(props: Props) {
|
||||||
const { buyAsset, sellAsset } = props
|
const { buyAsset, sellAsset, isAdvanced } = props
|
||||||
const useMargin = useStore((s) => s.useMargin)
|
const useMargin = useStore((s) => s.useMargin)
|
||||||
const useAutoRepay = useStore((s) => s.useAutoRepay)
|
const useAutoRepay = useStore((s) => s.useAutoRepay)
|
||||||
const account = useCurrentAccount()
|
const account = useCurrentAccount()
|
||||||
const swap = useStore((s) => s.swap)
|
const swap = useStore((s) => s.swap)
|
||||||
const [slippage] = useLocalStorage(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
||||||
const { computeMaxSwapAmount } = useHealthComputer(account)
|
const { computeMaxSwapAmount } = useHealthComputer(account)
|
||||||
|
const [orderDirection, setOrderDirection] = useState<OrderDirection>('buy')
|
||||||
const { data: borrowAssets } = useMarketBorrowings()
|
const { data: borrowAssets } = useMarketBorrowings()
|
||||||
const { data: marketAssets } = useMarketAssets()
|
const { data: marketAssets } = useMarketAssets()
|
||||||
const { data: route, isLoading: isRouteLoading } = useSwapRoute(sellAsset.denom, buyAsset.denom)
|
const [inputAsset, outputAsset] = useMemo(() => {
|
||||||
const isBorrowEnabled = !!marketAssets.find(byDenom(sellAsset.denom))?.borrowEnabled
|
if (isAdvanced) return [sellAsset, buyAsset]
|
||||||
const isRepayable = !!account?.debts.find(byDenom(buyAsset.denom))
|
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 [isMarginChecked, setMarginChecked] = useToggle(isBorrowEnabled ? useMargin : false)
|
||||||
const [isAutoRepayChecked, setAutoRepayChecked] = useToggle(
|
const [isAutoRepayChecked, setAutoRepayChecked] = useToggle(
|
||||||
isRepayable && ENABLE_AUTO_REPAY ? useAutoRepay : false,
|
isRepayable && ENABLE_AUTO_REPAY ? useAutoRepay : false,
|
||||||
)
|
)
|
||||||
const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO)
|
const [outputAssetAmount, setOutputAssetAmount] = useState(BN_ZERO)
|
||||||
const [sellAssetAmount, setSellAssetAmount] = useState(BN_ZERO)
|
const [inputAssetAmount, setInputAssetAmount] = useState(BN_ZERO)
|
||||||
const [maxBuyableAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO)
|
const [maxOutputAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO)
|
||||||
const [selectedOrderType, setSelectedOrderType] = useState<AvailableOrderType>('Market')
|
const [selectedOrderType, setSelectedOrderType] = useState<AvailableOrderType>('Market')
|
||||||
const [isConfirming, setIsConfirming] = useToggle()
|
const [isConfirming, setIsConfirming] = useToggle()
|
||||||
const [estimatedFee, setEstimatedFee] = useState(defaultFee)
|
const [estimatedFee, setEstimatedFee] = useState(defaultFee)
|
||||||
@ -66,117 +81,107 @@ export default function SwapForm(props: Props) {
|
|||||||
const throttledEstimateExactIn = useMemo(() => asyncThrottle(estimateExactIn, 250), [])
|
const throttledEstimateExactIn = useMemo(() => asyncThrottle(estimateExactIn, 250), [])
|
||||||
const { computeLiquidationPrice } = useHealthComputer(updatedAccount)
|
const { computeLiquidationPrice } = useHealthComputer(updatedAccount)
|
||||||
|
|
||||||
const borrowAsset = useMemo(
|
|
||||||
() => borrowAssets.find(byDenom(sellAsset.denom)),
|
|
||||||
[borrowAssets, sellAsset.denom],
|
|
||||||
)
|
|
||||||
|
|
||||||
const depositCapReachedCoins: BNCoin[] = useMemo(() => {
|
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)
|
const depositCapLeft = getCapLeftWithBuffer(outputMarketAsset.cap)
|
||||||
if (buyAssetAmount.isGreaterThan(depositCapLeft)) {
|
if (outputAssetAmount.isGreaterThan(depositCapLeft)) {
|
||||||
return [BNCoin.fromDenomAndBigNumber(buyAsset.denom, depositCapLeft)]
|
return [BNCoin.fromDenomAndBigNumber(outputAsset.denom, depositCapLeft)]
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}, [marketAssets, buyAsset.denom, buyAssetAmount])
|
}, [marketAssets, outputAsset.denom, outputAssetAmount])
|
||||||
|
|
||||||
const onChangeSellAmount = useCallback(
|
const onChangeInputAmount = useCallback(
|
||||||
(amount: BigNumber) => {
|
(amount: BigNumber) => {
|
||||||
setSellAssetAmount(amount)
|
setInputAssetAmount(amount)
|
||||||
throttledEstimateExactIn(
|
const swapTo = { denom: inputAsset.denom, amount: amount.toString() }
|
||||||
{ denom: sellAsset.denom, amount: amount.toString() },
|
throttledEstimateExactIn(swapTo, outputAsset.denom).then(setOutputAssetAmount)
|
||||||
buyAsset.denom,
|
|
||||||
).then(setBuyAssetAmount)
|
|
||||||
},
|
},
|
||||||
[sellAsset.denom, throttledEstimateExactIn, buyAsset.denom],
|
[inputAsset.denom, throttledEstimateExactIn, outputAsset.denom],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onChangeBuyAmount = useCallback(
|
const onChangeOutputAmount = useCallback(
|
||||||
(amount: BigNumber) => {
|
(amount: BigNumber) => {
|
||||||
setBuyAssetAmount(amount)
|
setOutputAssetAmount(amount)
|
||||||
const swapFrom = {
|
const swapFrom = {
|
||||||
denom: buyAsset.denom,
|
denom: outputAsset.denom,
|
||||||
amount: amount.toString(),
|
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(
|
const handleRangeInputChange = useCallback(
|
||||||
(value: number) => {
|
(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 [maxInputAmount, imputMarginThreshold, marginRatio] = useMemo(() => {
|
||||||
const maxAmount = computeMaxSwapAmount(sellAsset.denom, buyAsset.denom, 'default')
|
const maxAmount = computeMaxSwapAmount(inputAsset.denom, outputAsset.denom, 'default')
|
||||||
const maxAmountOnMargin = computeMaxSwapAmount(
|
const maxAmountOnMargin = computeMaxSwapAmount(
|
||||||
sellAsset.denom,
|
inputAsset.denom,
|
||||||
buyAsset.denom,
|
outputAsset.denom,
|
||||||
'margin',
|
'margin',
|
||||||
).integerValue()
|
).integerValue()
|
||||||
const marginRatio = maxAmount.dividedBy(maxAmountOnMargin)
|
const marginRatio = maxAmount.dividedBy(maxAmountOnMargin)
|
||||||
|
|
||||||
estimateExactIn(
|
estimateExactIn(
|
||||||
{
|
{
|
||||||
denom: sellAsset.denom,
|
denom: inputAsset.denom,
|
||||||
amount: (isMarginChecked ? maxAmountOnMargin : maxAmount).toString(),
|
amount: (isMarginChecked ? maxAmountOnMargin : maxAmount).toString(),
|
||||||
},
|
},
|
||||||
buyAsset.denom,
|
outputAsset.denom,
|
||||||
).then(setMaxBuyableAmountEstimation)
|
).then(setMaxBuyableAmountEstimation)
|
||||||
|
|
||||||
if (isMarginChecked) return [maxAmountOnMargin, maxAmount, marginRatio]
|
if (isMarginChecked) return [maxAmountOnMargin, maxAmount, marginRatio]
|
||||||
|
|
||||||
if (sellAssetAmount.isGreaterThan(maxAmount)) onChangeSellAmount(maxAmount)
|
if (inputAssetAmount.isGreaterThan(maxAmount)) onChangeInputAmount(maxAmount)
|
||||||
|
|
||||||
return [maxAmount, maxAmount, marginRatio]
|
return [maxAmount, maxAmount, marginRatio]
|
||||||
}, [
|
}, [
|
||||||
computeMaxSwapAmount,
|
computeMaxSwapAmount,
|
||||||
sellAsset.denom,
|
inputAsset.denom,
|
||||||
buyAsset.denom,
|
outputAsset.denom,
|
||||||
isMarginChecked,
|
isMarginChecked,
|
||||||
onChangeSellAmount,
|
onChangeInputAmount,
|
||||||
sellAssetAmount,
|
inputAssetAmount,
|
||||||
])
|
])
|
||||||
|
|
||||||
const buySideMarginThreshold = useMemo(() => {
|
const outputSideMarginThreshold = useMemo(() => {
|
||||||
return maxBuyableAmountEstimation.multipliedBy(marginRatio)
|
return maxOutputAmountEstimation.multipliedBy(marginRatio)
|
||||||
}, [marginRatio, maxBuyableAmountEstimation])
|
}, [marginRatio, maxOutputAmountEstimation])
|
||||||
|
|
||||||
const swapTx = useMemo(() => {
|
const swapTx = useMemo(() => {
|
||||||
const borrowCoin = sellAssetAmount.isGreaterThan(sellSideMarginThreshold)
|
const borrowCoin = inputAssetAmount.isGreaterThan(imputMarginThreshold)
|
||||||
? BNCoin.fromDenomAndBigNumber(
|
? BNCoin.fromDenomAndBigNumber(inputAsset.denom, inputAssetAmount.minus(imputMarginThreshold))
|
||||||
sellAsset.denom,
|
|
||||||
sellAssetAmount.minus(sellSideMarginThreshold),
|
|
||||||
)
|
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return swap({
|
return swap({
|
||||||
accountId: account?.id || '',
|
accountId: account?.id || '',
|
||||||
coinIn: BNCoin.fromDenomAndBigNumber(sellAsset.denom, sellAssetAmount.integerValue()),
|
coinIn: BNCoin.fromDenomAndBigNumber(inputAsset.denom, inputAssetAmount.integerValue()),
|
||||||
reclaim: removedLends[0],
|
reclaim: removedLends[0],
|
||||||
borrow: borrowCoin,
|
borrow: borrowCoin,
|
||||||
denomOut: buyAsset.denom,
|
denomOut: outputAsset.denom,
|
||||||
slippage,
|
slippage,
|
||||||
isMax: sellAssetAmount.isEqualTo(maxSellAmount),
|
isMax: inputAssetAmount.isEqualTo(maxInputAmount),
|
||||||
repay: isAutoRepayChecked,
|
repay: isAutoRepayChecked,
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
removedLends,
|
removedLends,
|
||||||
account?.id,
|
account?.id,
|
||||||
buyAsset.denom,
|
outputAsset.denom,
|
||||||
sellSideMarginThreshold,
|
imputMarginThreshold,
|
||||||
sellAsset.denom,
|
inputAsset.denom,
|
||||||
sellAssetAmount,
|
inputAssetAmount,
|
||||||
slippage,
|
slippage,
|
||||||
swap,
|
swap,
|
||||||
maxSellAmount,
|
maxInputAmount,
|
||||||
isAutoRepayChecked,
|
isAutoRepayChecked,
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -210,61 +215,10 @@ export default function SwapForm(props: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const liquidationPrice = useMemo(
|
const liquidationPrice = useMemo(
|
||||||
() => computeLiquidationPrice(props.buyAsset.denom, 'asset'),
|
() => computeLiquidationPrice(outputAsset.denom, 'asset'),
|
||||||
[computeLiquidationPrice, props.buyAsset.denom],
|
[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(() => {
|
useEffect(() => {
|
||||||
swapTx.estimateFee().then(setEstimatedFee)
|
swapTx.estimateFee().then(setEstimatedFee)
|
||||||
}, [swapTx])
|
}, [swapTx])
|
||||||
@ -276,24 +230,90 @@ export default function SwapForm(props: Props) {
|
|||||||
const isSucceeded = await swapTx.execute()
|
const isSucceeded = await swapTx.execute()
|
||||||
|
|
||||||
if (isSucceeded) {
|
if (isSucceeded) {
|
||||||
setSellAssetAmount(BN_ZERO)
|
setInputAssetAmount(BN_ZERO)
|
||||||
setBuyAssetAmount(BN_ZERO)
|
setOutputAssetAmount(BN_ZERO)
|
||||||
}
|
}
|
||||||
setIsConfirming(false)
|
setIsConfirming(false)
|
||||||
}
|
}
|
||||||
}, [account?.id, swapTx, setIsConfirming])
|
}, [account?.id, swapTx, setIsConfirming])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sellAssetAmount.isEqualTo(maxSellAmount) || buyAssetAmount.isZero()) return
|
onChangeOutputAmount(BN_ZERO)
|
||||||
if (buyAssetAmount.isEqualTo(maxBuyableAmountEstimation)) setSellAssetAmount(maxSellAmount)
|
onChangeInputAmount(BN_ZERO)
|
||||||
}, [sellAssetAmount, maxSellAmount, buyAssetAmount, maxBuyableAmountEstimation])
|
}, [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(
|
const borrowAmount = useMemo(
|
||||||
() =>
|
() =>
|
||||||
sellAssetAmount.isGreaterThan(sellSideMarginThreshold)
|
inputAssetAmount.isGreaterThan(imputMarginThreshold)
|
||||||
? sellAssetAmount.minus(sellSideMarginThreshold)
|
? inputAssetAmount.minus(imputMarginThreshold)
|
||||||
: BN_ZERO,
|
: BN_ZERO,
|
||||||
[sellAssetAmount, sellSideMarginThreshold],
|
[inputAssetAmount, imputMarginThreshold],
|
||||||
)
|
)
|
||||||
|
|
||||||
const availableLiquidity = useMemo(
|
const availableLiquidity = useMemo(
|
||||||
@ -303,59 +323,85 @@ export default function SwapForm(props: Props) {
|
|||||||
|
|
||||||
const isSwapDisabled = useMemo(
|
const isSwapDisabled = useMemo(
|
||||||
() =>
|
() =>
|
||||||
sellAssetAmount.isZero() ||
|
inputAssetAmount.isZero() ||
|
||||||
depositCapReachedCoins.length > 0 ||
|
depositCapReachedCoins.length > 0 ||
|
||||||
borrowAmount.isGreaterThan(availableLiquidity) ||
|
borrowAmount.isGreaterThan(availableLiquidity) ||
|
||||||
route.length === 0,
|
route.length === 0,
|
||||||
[sellAssetAmount, depositCapReachedCoins, borrowAmount, availableLiquidity, route],
|
[inputAssetAmount, depositCapReachedCoins, borrowAmount, availableLiquidity, route],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className='flex flex-wrap w-full'>
|
||||||
|
{isAdvanced ? (
|
||||||
|
<AssetSelectorSingle buyAsset={outputAsset} sellAsset={inputAsset} />
|
||||||
|
) : (
|
||||||
|
<AssetSelectorPair buyAsset={buyAsset} sellAsset={sellAsset} />
|
||||||
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<MarginToggle
|
<MarginToggle
|
||||||
checked={isMarginChecked}
|
checked={isMarginChecked}
|
||||||
onChange={handleMarginToggleChange}
|
onChange={handleMarginToggleChange}
|
||||||
disabled={!borrowAsset?.isMarket}
|
disabled={!borrowAsset?.isMarket}
|
||||||
borrowAssetSymbol={sellAsset.symbol}
|
borrowAssetSymbol={inputAsset.symbol}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{isRepayable && ENABLE_AUTO_REPAY && (
|
{isRepayable && ENABLE_AUTO_REPAY && (
|
||||||
<AutoRepayToggle
|
<AutoRepayToggle
|
||||||
checked={isAutoRepayChecked}
|
checked={isAutoRepayChecked}
|
||||||
onChange={handleAutoRepayToggleChange}
|
onChange={handleAutoRepayToggleChange}
|
||||||
buyAssetSymbol={buyAsset.symbol}
|
buyAssetSymbol={outputAsset.symbol}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Divider />
|
|
||||||
<div className='px-3'>
|
<div className='px-3'>
|
||||||
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col gap-6 px-3 mt-6'>
|
<div className='flex flex-col w-full gap-6 px-3 mt-6'>
|
||||||
|
{isAdvanced ? (
|
||||||
<AssetAmountInput
|
<AssetAmountInput
|
||||||
label='Buy'
|
label='Buy'
|
||||||
max={maxBuyableAmountEstimation}
|
max={maxOutputAmountEstimation}
|
||||||
amount={buyAssetAmount}
|
amount={outputAssetAmount}
|
||||||
setAmount={onChangeBuyAmount}
|
setAmount={onChangeOutputAmount}
|
||||||
asset={buyAsset}
|
asset={outputAsset}
|
||||||
maxButtonLabel='Max Amount:'
|
maxButtonLabel='Max Amount:'
|
||||||
disabled={isConfirming}
|
disabled={isConfirming}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<DirectionSelect
|
||||||
|
direction={orderDirection}
|
||||||
|
onChangeDirection={setOrderDirection}
|
||||||
|
asset={buyAsset}
|
||||||
|
/>
|
||||||
|
<AssetAmountInput
|
||||||
|
max={maxInputAmount}
|
||||||
|
amount={inputAssetAmount}
|
||||||
|
setAmount={onChangeInputAmount}
|
||||||
|
asset={inputAsset}
|
||||||
|
maxButtonLabel='Balance:'
|
||||||
|
disabled={isConfirming}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isAdvanced && <Divider />}
|
||||||
<RangeInput
|
<RangeInput
|
||||||
disabled={isConfirming || maxBuyableAmountEstimation.isZero()}
|
disabled={isConfirming || maxOutputAmountEstimation.isZero()}
|
||||||
onChange={handleRangeInputChange}
|
onChange={handleRangeInputChange}
|
||||||
value={buyAssetAmount.shiftedBy(-buyAsset.decimals).toNumber()}
|
value={outputAssetAmount.shiftedBy(-outputAsset.decimals).toNumber()}
|
||||||
max={maxBuyableAmountEstimation.shiftedBy(-buyAsset.decimals).toNumber()}
|
max={maxOutputAmountEstimation.shiftedBy(-outputAsset.decimals).toNumber()}
|
||||||
marginThreshold={
|
marginThreshold={
|
||||||
isMarginChecked
|
isMarginChecked
|
||||||
? buySideMarginThreshold.shiftedBy(-buyAsset.decimals).toNumber()
|
? outputSideMarginThreshold.shiftedBy(-outputAsset.decimals).toNumber()
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<DepositCapMessage
|
||||||
<DepositCapMessage action='buy' coins={depositCapReachedCoins} className='p-4 bg-white/5' />
|
action='buy'
|
||||||
|
coins={depositCapReachedCoins}
|
||||||
|
className='p-4 bg-white/5'
|
||||||
|
/>
|
||||||
|
|
||||||
{borrowAsset && borrowAmount.isGreaterThanOrEqualTo(availableLiquidity) && (
|
{borrowAsset && borrowAmount.isGreaterThanOrEqualTo(availableLiquidity) && (
|
||||||
<AvailableLiquidityMessage
|
<AvailableLiquidityMessage
|
||||||
@ -363,19 +409,39 @@ export default function SwapForm(props: Props) {
|
|||||||
asset={borrowAsset}
|
asset={borrowAsset}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isAdvanced ? (
|
||||||
<AssetAmountInput
|
<AssetAmountInput
|
||||||
label='Sell'
|
label='Sell'
|
||||||
max={maxSellAmount}
|
max={maxInputAmount}
|
||||||
amount={sellAssetAmount}
|
amount={inputAssetAmount}
|
||||||
setAmount={onChangeSellAmount}
|
setAmount={onChangeInputAmount}
|
||||||
asset={sellAsset}
|
asset={inputAsset}
|
||||||
maxButtonLabel='Balance:'
|
maxButtonLabel='Balance:'
|
||||||
disabled={isConfirming}
|
disabled={isConfirming}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<div className='flex justify-between w-full'>
|
||||||
|
<Text size='sm'>You receive</Text>
|
||||||
|
<Text size='sm'>
|
||||||
|
{formatValue(outputAssetAmount.toNumber(), {
|
||||||
|
decimals: outputAsset.decimals,
|
||||||
|
abbreviated: false,
|
||||||
|
suffix: ` ${outputAsset.symbol}`,
|
||||||
|
minDecimals: 0,
|
||||||
|
maxDecimals: outputAsset.decimals,
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex w-full px-3 pt-6'>
|
||||||
<TradeSummary
|
<TradeSummary
|
||||||
buyAsset={buyAsset}
|
sellAsset={inputAsset}
|
||||||
sellAsset={sellAsset}
|
buyAsset={outputAsset}
|
||||||
borrowRate={borrowAsset?.borrowRate}
|
borrowRate={borrowAsset?.borrowRate}
|
||||||
buyAction={handleBuyClick}
|
buyAction={handleBuyClick}
|
||||||
buyButtonDisabled={isSwapDisabled}
|
buyButtonDisabled={isSwapDisabled}
|
||||||
@ -385,8 +451,10 @@ export default function SwapForm(props: Props) {
|
|||||||
estimatedFee={estimatedFee}
|
estimatedFee={estimatedFee}
|
||||||
route={route}
|
route={route}
|
||||||
liquidationPrice={liquidationPrice}
|
liquidationPrice={liquidationPrice}
|
||||||
sellAmount={sellAssetAmount}
|
sellAmount={inputAssetAmount}
|
||||||
buyAmount={buyAssetAmount}
|
buyAmount={outputAssetAmount}
|
||||||
|
isAdvanced={isAdvanced}
|
||||||
|
direction={orderDirection}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import AssetSelector from 'components/Trade/TradeModule/AssetSelector'
|
|
||||||
import SwapForm from 'components/Trade/TradeModule/SwapForm'
|
import SwapForm from 'components/Trade/TradeModule/SwapForm'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
buyAsset: Asset
|
buyAsset: Asset
|
||||||
sellAsset: Asset
|
sellAsset: Asset
|
||||||
|
isAdvanced: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TradeModule(props: Props) {
|
export default function TradeModule(props: Props) {
|
||||||
const { buyAsset, sellAsset } = props
|
const { buyAsset, sellAsset, isAdvanced } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='row-span-2'>
|
<div className='row-span-2'>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'relative isolate max-w-full overflow-hidden rounded-base pb-4 z-30',
|
'max-h-[calc(100dvh-98px)] h-[980px] min-h-[830px]',
|
||||||
|
'relative isolate max-w-full overflow-hidden rounded-base pb-4 z-30 flex flex-wrap flex-col justify-between',
|
||||||
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
|
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<AssetSelector buyAsset={buyAsset} sellAsset={sellAsset} />
|
<SwapForm buyAsset={buyAsset} sellAsset={sellAsset} isAdvanced={isAdvanced} />
|
||||||
<SwapForm buyAsset={buyAsset} sellAsset={sellAsset} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,8 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
accountSummaryTabs: [true, true],
|
accountSummaryTabs: [true, true],
|
||||||
reduceMotion: false,
|
reduceMotion: false,
|
||||||
lendAssets: true,
|
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,
|
displayCurrency: ORACLE_DENOM,
|
||||||
slippage: 0.02,
|
slippage: 0.02,
|
||||||
tutorial: true,
|
tutorial: true,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export enum LocalStorageKeys {
|
export enum LocalStorageKeys {
|
||||||
TRADING_PAIR = 'tradingPair',
|
TRADING_PAIR_SIMPLE = 'tradingPairSimple',
|
||||||
|
TRADING_PAIR_ADVANCED = 'tradingPairAdvanced',
|
||||||
ACCOUNT_SUMMARY_TABS = 'accountSummaryTabs',
|
ACCOUNT_SUMMARY_TABS = 'accountSummaryTabs',
|
||||||
DISPLAY_CURRENCY = 'displayCurrency',
|
DISPLAY_CURRENCY = 'displayCurrency',
|
||||||
REDUCE_MOTION = 'reduceMotion',
|
REDUCE_MOTION = 'reduceMotion',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import MigrationBanner from 'components/MigrationBanner'
|
import MigrationBanner from 'components/MigrationBanner'
|
||||||
import AccountDetailsCard from 'components/Trade/AccountDetailsCard'
|
import AccountDetailsCard from 'components/Trade/AccountDetailsCard'
|
||||||
@ -10,30 +11,45 @@ import useLocalStorage from 'hooks/useLocalStorage'
|
|||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getEnabledMarketAssets } from 'utils/assets'
|
import { getEnabledMarketAssets } from 'utils/assets'
|
||||||
|
import { getPage } from 'utils/route'
|
||||||
|
|
||||||
export default function TradePage() {
|
export default function TradePage() {
|
||||||
const [tradingPair] = useLocalStorage<Settings['tradingPair']>(
|
const { pathname } = useLocation()
|
||||||
LocalStorageKeys.TRADING_PAIR,
|
const page = getPage(pathname)
|
||||||
DEFAULT_SETTINGS.tradingPair,
|
const isAdvanced = useMemo(() => page === 'trade-advanced', [page])
|
||||||
|
|
||||||
|
const [tradingPairAdvanced] = useLocalStorage<Settings['tradingPairAdvanced']>(
|
||||||
|
LocalStorageKeys.TRADING_PAIR_ADVANCED,
|
||||||
|
DEFAULT_SETTINGS.tradingPairAdvanced,
|
||||||
)
|
)
|
||||||
|
const [tradingPairSimple] = useLocalStorage<Settings['tradingPairSimple']>(
|
||||||
|
LocalStorageKeys.TRADING_PAIR_SIMPLE,
|
||||||
|
DEFAULT_SETTINGS.tradingPairSimple,
|
||||||
|
)
|
||||||
|
|
||||||
const enabledMarketAssets = getEnabledMarketAssets()
|
const enabledMarketAssets = getEnabledMarketAssets()
|
||||||
const assetOverlayState = useStore((s) => s.assetOverlayState)
|
const assetOverlayState = useStore((s) => s.assetOverlayState)
|
||||||
|
|
||||||
const buyAsset = useMemo(
|
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(
|
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 (
|
return (
|
||||||
<div className='flex flex-col w-full h-full gap-4'>
|
<div className='flex flex-col w-full h-full gap-4'>
|
||||||
<MigrationBanner />
|
<MigrationBanner />
|
||||||
<div className='grid h-full w-full grid-cols-[346px_auto] gap-4'>
|
<div className='grid w-full grid-cols-[auto_346px] gap-4'>
|
||||||
<TradeModule buyAsset={buyAsset} sellAsset={sellAsset} />
|
|
||||||
<TradeChart buyAsset={buyAsset} sellAsset={sellAsset} />
|
<TradeChart buyAsset={buyAsset} sellAsset={sellAsset} />
|
||||||
|
<TradeModule buyAsset={buyAsset} sellAsset={sellAsset} isAdvanced={isAdvanced} />
|
||||||
<AccountDetailsCard />
|
<AccountDetailsCard />
|
||||||
</div>
|
</div>
|
||||||
{assetOverlayState !== 'closed' && (
|
{assetOverlayState !== 'closed' && (
|
||||||
|
@ -27,7 +27,12 @@ function PageContainer(props: Props) {
|
|||||||
|
|
||||||
if (!props.focusComponent)
|
if (!props.focusComponent)
|
||||||
return (
|
return (
|
||||||
<div className={classNames('mx-auto h-full w-full', !props.fullWidth && 'max-w-content')}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'mx-auto flex items-start w-full',
|
||||||
|
!props.fullWidth && 'max-w-content',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
5
src/types/interfaces/asset.d.ts
vendored
5
src/types/interfaces/asset.d.ts
vendored
@ -69,6 +69,11 @@ interface Asset {
|
|||||||
isStaking?: boolean
|
isStaking?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AssetPair {
|
||||||
|
buy: Asset
|
||||||
|
sell: Asset
|
||||||
|
}
|
||||||
|
|
||||||
interface PseudoAsset {
|
interface PseudoAsset {
|
||||||
decimals: number
|
decimals: number
|
||||||
symbol: string
|
symbol: string
|
||||||
|
@ -1 +1 @@
|
|||||||
type OverlayState = 'buy' | 'sell' | 'closed'
|
type OverlayState = 'buy' | 'sell' | 'pair' | 'closed'
|
||||||
|
@ -2,4 +2,12 @@ interface MenuTreeEntry {
|
|||||||
pages: Page[]
|
pages: Page[]
|
||||||
label: string
|
label: string
|
||||||
externalUrl?: string
|
externalUrl?: string
|
||||||
|
submenu?: MenuTreeSubmenuEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuTreeSubmenuEntry {
|
||||||
|
page: Page
|
||||||
|
label: string
|
||||||
|
subtitle?: string
|
||||||
|
icon?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
4
src/types/interfaces/components/Trade.d.ts
vendored
Normal file
4
src/types/interfaces/components/Trade.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
interface TradingPair {
|
||||||
|
buy: string
|
||||||
|
sell: string
|
||||||
|
}
|
2
src/types/interfaces/perps.d.ts
vendored
2
src/types/interfaces/perps.d.ts
vendored
@ -1 +1 @@
|
|||||||
type OrderDirection = 'long' | 'short'
|
type OrderDirection = 'long' | 'short' | 'buy' | 'sell'
|
||||||
|
1
src/types/interfaces/route.d.ts
vendored
1
src/types/interfaces/route.d.ts
vendored
@ -1,5 +1,6 @@
|
|||||||
type Page =
|
type Page =
|
||||||
| 'trade'
|
| 'trade'
|
||||||
|
| 'trade-advanced'
|
||||||
| 'perps'
|
| 'perps'
|
||||||
| 'borrow'
|
| 'borrow'
|
||||||
| 'farm'
|
| 'farm'
|
||||||
|
3
src/types/interfaces/store/settings.d.ts
vendored
3
src/types/interfaces/store/settings.d.ts
vendored
@ -2,7 +2,8 @@ interface Settings {
|
|||||||
accountSummaryTabs: boolean[]
|
accountSummaryTabs: boolean[]
|
||||||
displayCurrency: string
|
displayCurrency: string
|
||||||
reduceMotion: boolean
|
reduceMotion: boolean
|
||||||
tradingPair: { buy: string; sell: string }
|
tradingPairSimple: TradingPair
|
||||||
|
tradingPairAdvanced: TradingPair
|
||||||
lendAssets: boolean
|
lendAssets: boolean
|
||||||
slippage: number
|
slippage: number
|
||||||
tutorial: boolean
|
tutorial: boolean
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { ASSETS } from 'constants/assets'
|
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 {
|
export function getAssetByDenom(denom: string): Asset | undefined {
|
||||||
return ASSETS.find((asset) => asset.denom === denom)
|
return ASSETS.find((asset) => asset.denom === denom)
|
||||||
@ -47,3 +51,42 @@ export function getBorrowEnabledAssets() {
|
|||||||
export function getStakingAssets() {
|
export function getStakingAssets() {
|
||||||
return ASSETS.filter((asset) => asset.isStaking)
|
return ASSETS.filter((asset) => asset.isStaking)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAssetPair(assetPair: Asset | AssetPair): assetPair is AssetPair {
|
||||||
|
return (<AssetPair>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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,236 +1,265 @@
|
|||||||
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) {
|
function addHeapObject(obj) {
|
||||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
if (heap_next === heap.length) heap.push(heap.length + 1)
|
||||||
const idx = heap_next;
|
const idx = heap_next
|
||||||
heap_next = heap[idx];
|
heap_next = heap[idx]
|
||||||
|
|
||||||
heap[idx] = obj;
|
heap[idx] = obj
|
||||||
return idx;
|
return idx
|
||||||
}
|
}
|
||||||
|
|
||||||
function dropObject(idx) {
|
function dropObject(idx) {
|
||||||
if (idx < 132) return;
|
if (idx < 132) return
|
||||||
heap[idx] = heap_next;
|
heap[idx] = heap_next
|
||||||
heap_next = idx;
|
heap_next = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
function takeObject(idx) {
|
function takeObject(idx) {
|
||||||
const ret = getObject(idx);
|
const ret = getObject(idx)
|
||||||
dropObject(idx);
|
dropObject(idx)
|
||||||
return ret;
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
let WASM_VECTOR_LEN = 0;
|
let WASM_VECTOR_LEN = 0
|
||||||
|
|
||||||
let cachedUint8Memory0 = null;
|
let cachedUint8Memory0 = null
|
||||||
|
|
||||||
function getUint8Memory0() {
|
function getUint8Memory0() {
|
||||||
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
|
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
|
||||||
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer)
|
||||||
}
|
}
|
||||||
return cachedUint8Memory0;
|
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) {
|
? function (arg, view) {
|
||||||
return cachedTextEncoder.encodeInto(arg, view);
|
return cachedTextEncoder.encodeInto(arg, view)
|
||||||
}
|
}
|
||||||
: function (arg, view) {
|
: function (arg, view) {
|
||||||
const buf = cachedTextEncoder.encode(arg);
|
const buf = cachedTextEncoder.encode(arg)
|
||||||
view.set(buf);
|
view.set(buf)
|
||||||
return {
|
return {
|
||||||
read: arg.length,
|
read: arg.length,
|
||||||
written: buf.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = arg.length;
|
function passStringToWasm0(arg, malloc, realloc) {
|
||||||
let ptr = malloc(len, 1) >>> 0;
|
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
|
||||||
|
}
|
||||||
|
|
||||||
const mem = getUint8Memory0();
|
let len = arg.length
|
||||||
|
let ptr = malloc(len, 1) >>> 0
|
||||||
|
|
||||||
let offset = 0;
|
const mem = getUint8Memory0()
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
for (; offset < len; offset++) {
|
for (; offset < len; offset++) {
|
||||||
const code = arg.charCodeAt(offset);
|
const code = arg.charCodeAt(offset)
|
||||||
if (code > 0x7F) break;
|
if (code > 0x7f) break
|
||||||
mem[ptr + offset] = code;
|
mem[ptr + offset] = code
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset !== len) {
|
if (offset !== len) {
|
||||||
if (offset !== 0) {
|
if (offset !== 0) {
|
||||||
arg = arg.slice(offset);
|
arg = arg.slice(offset)
|
||||||
}
|
}
|
||||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0
|
||||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
const view = getUint8Memory0().subarray(ptr + offset, ptr + len)
|
||||||
const ret = encodeString(arg, view);
|
const ret = encodeString(arg, view)
|
||||||
|
|
||||||
offset += ret.written;
|
offset += ret.written
|
||||||
}
|
}
|
||||||
|
|
||||||
WASM_VECTOR_LEN = offset;
|
WASM_VECTOR_LEN = offset
|
||||||
return ptr;
|
return ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLikeNone(x) {
|
function isLikeNone(x) {
|
||||||
return x === undefined || x === null;
|
return x === undefined || x === null
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedInt32Memory0 = null;
|
let cachedInt32Memory0 = null
|
||||||
|
|
||||||
function getInt32Memory0() {
|
function getInt32Memory0() {
|
||||||
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
|
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
|
||||||
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer)
|
||||||
}
|
}
|
||||||
return cachedInt32Memory0;
|
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) {
|
function getStringFromWasm0(ptr, len) {
|
||||||
ptr = ptr >>> 0;
|
ptr = ptr >>> 0
|
||||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len))
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {HealthComputer} c
|
* @param {HealthComputer} c
|
||||||
* @returns {HealthValuesResponse}
|
* @returns {HealthValuesResponse}
|
||||||
*/
|
*/
|
||||||
export function compute_health_js(c) {
|
export function compute_health_js(c) {
|
||||||
const ret = wasm.compute_health_js(addHeapObject(c));
|
const ret = wasm.compute_health_js(addHeapObject(c))
|
||||||
return takeObject(ret);
|
return takeObject(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HealthComputer} c
|
* @param {HealthComputer} c
|
||||||
* @param {string} withdraw_denom
|
* @param {string} withdraw_denom
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function max_withdraw_estimate_js(c, withdraw_denom) {
|
export function max_withdraw_estimate_js(c, withdraw_denom) {
|
||||||
let deferred2_0;
|
let deferred2_0
|
||||||
let deferred2_1;
|
let deferred2_1
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16)
|
||||||
const ptr0 = passStringToWasm0(withdraw_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(withdraw_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN
|
||||||
wasm.max_withdraw_estimate_js(retptr, addHeapObject(c), ptr0, len0);
|
wasm.max_withdraw_estimate_js(retptr, addHeapObject(c), ptr0, len0)
|
||||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
var r0 = getInt32Memory0()[retptr / 4 + 0]
|
||||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
var r1 = getInt32Memory0()[retptr / 4 + 1]
|
||||||
deferred2_0 = r0;
|
deferred2_0 = r0
|
||||||
deferred2_1 = r1;
|
deferred2_1 = r1
|
||||||
return getStringFromWasm0(r0, r1);
|
return getStringFromWasm0(r0, r1)
|
||||||
} finally {
|
} finally {
|
||||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
wasm.__wbindgen_add_to_stack_pointer(16)
|
||||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HealthComputer} c
|
* @param {HealthComputer} c
|
||||||
* @param {string} borrow_denom
|
* @param {string} borrow_denom
|
||||||
* @param {BorrowTarget} target
|
* @param {BorrowTarget} target
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function max_borrow_estimate_js(c, borrow_denom, target) {
|
export function max_borrow_estimate_js(c, borrow_denom, target) {
|
||||||
let deferred2_0;
|
let deferred2_0
|
||||||
let deferred2_1;
|
let deferred2_1
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16)
|
||||||
const ptr0 = passStringToWasm0(borrow_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(borrow_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN
|
||||||
wasm.max_borrow_estimate_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(target));
|
wasm.max_borrow_estimate_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(target))
|
||||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
var r0 = getInt32Memory0()[retptr / 4 + 0]
|
||||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
var r1 = getInt32Memory0()[retptr / 4 + 1]
|
||||||
deferred2_0 = r0;
|
deferred2_0 = r0
|
||||||
deferred2_1 = r1;
|
deferred2_1 = r1
|
||||||
return getStringFromWasm0(r0, r1);
|
return getStringFromWasm0(r0, r1)
|
||||||
} finally {
|
} finally {
|
||||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
wasm.__wbindgen_add_to_stack_pointer(16)
|
||||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HealthComputer} c
|
* @param {HealthComputer} c
|
||||||
* @param {string} from_denom
|
* @param {string} from_denom
|
||||||
* @param {string} to_denom
|
* @param {string} to_denom
|
||||||
* @param {SwapKind} kind
|
* @param {SwapKind} kind
|
||||||
* @param {Slippage} slippage
|
* @param {Slippage} slippage
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function max_swap_estimate_js(c, from_denom, to_denom, kind, slippage) {
|
export function max_swap_estimate_js(c, from_denom, to_denom, kind, slippage) {
|
||||||
let deferred3_0;
|
let deferred3_0
|
||||||
let deferred3_1;
|
let deferred3_1
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16)
|
||||||
const ptr0 = passStringToWasm0(from_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(from_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN
|
||||||
const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
|
||||||
const len1 = WASM_VECTOR_LEN;
|
const len1 = WASM_VECTOR_LEN
|
||||||
wasm.max_swap_estimate_js(retptr, addHeapObject(c), ptr0, len0, ptr1, len1, addHeapObject(kind), addHeapObject(slippage));
|
wasm.max_swap_estimate_js(
|
||||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
retptr,
|
||||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
addHeapObject(c),
|
||||||
deferred3_0 = r0;
|
ptr0,
|
||||||
deferred3_1 = r1;
|
len0,
|
||||||
return getStringFromWasm0(r0, r1);
|
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 {
|
} finally {
|
||||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
wasm.__wbindgen_add_to_stack_pointer(16)
|
||||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HealthComputer} c
|
* @param {HealthComputer} c
|
||||||
* @param {string} denom
|
* @param {string} denom
|
||||||
* @param {LiquidationPriceKind} kind
|
* @param {LiquidationPriceKind} kind
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function liquidation_price_js(c, denom, kind) {
|
export function liquidation_price_js(c, denom, kind) {
|
||||||
let deferred2_0;
|
let deferred2_0
|
||||||
let deferred2_1;
|
let deferred2_1
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16)
|
||||||
const ptr0 = passStringToWasm0(denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN
|
||||||
wasm.liquidation_price_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(kind));
|
wasm.liquidation_price_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(kind))
|
||||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
var r0 = getInt32Memory0()[retptr / 4 + 0]
|
||||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
var r1 = getInt32Memory0()[retptr / 4 + 1]
|
||||||
deferred2_0 = r0;
|
deferred2_0 = r0
|
||||||
deferred2_1 = r1;
|
deferred2_1 = r1
|
||||||
return getStringFromWasm0(r0, r1);
|
return getStringFromWasm0(r0, r1)
|
||||||
} finally {
|
} finally {
|
||||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
wasm.__wbindgen_add_to_stack_pointer(16)
|
||||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleError(f, args) {
|
function handleError(f, args) {
|
||||||
try {
|
try {
|
||||||
return f.apply(this, args);
|
return f.apply(this, args)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
wasm.__wbindgen_exn_store(addHeapObject(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,118 +267,124 @@ async function __wbg_load(module, imports) {
|
|||||||
if (typeof Response === 'function' && module instanceof Response) {
|
if (typeof Response === 'function' && module instanceof Response) {
|
||||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||||
try {
|
try {
|
||||||
return await WebAssembly.instantiateStreaming(module, imports);
|
return await WebAssembly.instantiateStreaming(module, imports)
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
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);
|
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 {
|
} else {
|
||||||
throw e;
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = await module.arrayBuffer();
|
const bytes = await module.arrayBuffer()
|
||||||
return await WebAssembly.instantiate(bytes, imports);
|
return await WebAssembly.instantiate(bytes, imports)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const instance = await WebAssembly.instantiate(module, imports);
|
const instance = await WebAssembly.instantiate(module, imports)
|
||||||
|
|
||||||
if (instance instanceof WebAssembly.Instance) {
|
if (instance instanceof WebAssembly.Instance) {
|
||||||
return { instance, module };
|
return { instance, module }
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return instance;
|
return instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function __wbg_get_imports() {
|
function __wbg_get_imports() {
|
||||||
const imports = {};
|
const imports = {}
|
||||||
imports.wbg = {};
|
imports.wbg = {}
|
||||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
imports.wbg.__wbindgen_object_clone_ref = function (arg0) {
|
||||||
const ret = getObject(arg0);
|
const ret = getObject(arg0)
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret)
|
||||||
};
|
}
|
||||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
imports.wbg.__wbindgen_is_undefined = function (arg0) {
|
||||||
const ret = getObject(arg0) === undefined;
|
const ret = getObject(arg0) === undefined
|
||||||
return ret;
|
return ret
|
||||||
};
|
}
|
||||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
imports.wbg.__wbindgen_object_drop_ref = function (arg0) {
|
||||||
takeObject(arg0);
|
takeObject(arg0)
|
||||||
};
|
}
|
||||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
imports.wbg.__wbindgen_string_get = function (arg0, arg1) {
|
||||||
const obj = getObject(arg1);
|
const obj = getObject(arg1)
|
||||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
const ret = typeof obj === 'string' ? obj : undefined
|
||||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
var ptr1 = isLikeNone(ret)
|
||||||
var len1 = WASM_VECTOR_LEN;
|
? 0
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
: passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
var len1 = WASM_VECTOR_LEN
|
||||||
};
|
getInt32Memory0()[arg0 / 4 + 1] = len1
|
||||||
imports.wbg.__wbg_parse_670c19d4e984792e = function() { return handleError(function (arg0, arg1) {
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1
|
||||||
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
|
}
|
||||||
return addHeapObject(ret);
|
imports.wbg.__wbg_parse_670c19d4e984792e = function () {
|
||||||
}, arguments) };
|
return handleError(function (arg0, arg1) {
|
||||||
imports.wbg.__wbg_stringify_e25465938f3f611f = function() { return handleError(function (arg0) {
|
const ret = JSON.parse(getStringFromWasm0(arg0, arg1))
|
||||||
const ret = JSON.stringify(getObject(arg0));
|
return addHeapObject(ret)
|
||||||
return addHeapObject(ret);
|
}, arguments)
|
||||||
}, arguments) };
|
}
|
||||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
imports.wbg.__wbg_stringify_e25465938f3f611f = function () {
|
||||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
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) {
|
function __wbg_finalize_init(instance, module) {
|
||||||
wasm = instance.exports;
|
wasm = instance.exports
|
||||||
__wbg_init.__wbindgen_wasm_module = module;
|
__wbg_init.__wbindgen_wasm_module = module
|
||||||
cachedInt32Memory0 = null;
|
cachedInt32Memory0 = null
|
||||||
cachedUint8Memory0 = null;
|
cachedUint8Memory0 = null
|
||||||
|
|
||||||
|
return wasm
|
||||||
return wasm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSync(module) {
|
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)) {
|
if (!(module instanceof WebAssembly.Module)) {
|
||||||
module = new WebAssembly.Module(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) {
|
async function __wbg_init(input) {
|
||||||
if (wasm !== undefined) return wasm;
|
if (wasm !== undefined) return wasm
|
||||||
|
|
||||||
if (typeof input === 'undefined') {
|
if (typeof input === 'undefined') {
|
||||||
input = new URL('index_bg.wasm', import.meta.url);
|
input = new URL('index_bg.wasm', import.meta.url)
|
||||||
}
|
}
|
||||||
const imports = __wbg_get_imports();
|
const imports = __wbg_get_imports()
|
||||||
|
|
||||||
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
if (
|
||||||
input = fetch(input);
|
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 { initSync }
|
||||||
export default __wbg_init;
|
export default __wbg_init
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BN } from './helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
export const devideByPotentiallyZero = (numerator: number, denominator: number): number => {
|
export const devideByPotentiallyZero = (numerator: number, denominator: number): number => {
|
||||||
if (denominator === 0) return 0
|
if (denominator === 0) return 0
|
||||||
|
@ -29,6 +29,7 @@ export function getRoute(
|
|||||||
export function getPage(pathname: string): Page {
|
export function getPage(pathname: string): Page {
|
||||||
const pages: Page[] = [
|
const pages: Page[] = [
|
||||||
'trade',
|
'trade',
|
||||||
|
'trade-advanced',
|
||||||
'perps',
|
'perps',
|
||||||
'borrow',
|
'borrow',
|
||||||
'farm',
|
'farm',
|
||||||
|
@ -4,6 +4,8 @@ const plugin = require('tailwindcss/plugin')
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./src/pages/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}'],
|
content: ['./src/pages/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}'],
|
||||||
safelist: [
|
safelist: [
|
||||||
|
'border-error',
|
||||||
|
'border-success',
|
||||||
'h-2',
|
'h-2',
|
||||||
'text-3xs',
|
'text-3xs',
|
||||||
'text-3xs-caps',
|
'text-3xs-caps',
|
||||||
|
Loading…
Reference in New Issue
Block a user