Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
849bda8e8e | ||
|
|
57edb711d5 | ||
|
|
8235c64c93 | ||
|
|
750fb077f5 | ||
|
|
d781cb3199 | ||
|
|
f6bf635b0d | ||
|
|
56187f7a84 | ||
|
|
93c76f1c3f |
+9
-18
@@ -5,35 +5,29 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
import { store } from '@/state/_store';
|
import { store } from '@/state/_store';
|
||||||
|
|
||||||
import { GlobalStyle } from '@/styles/globalStyle';
|
|
||||||
|
|
||||||
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
||||||
|
|
||||||
import { AppThemeProvider } from '@/hooks/useAppTheme';
|
|
||||||
|
|
||||||
import { AppTheme, setAppTheme } from '@/state/configs';
|
|
||||||
import { setLocaleLoaded } from '@/state/localization';
|
import { setLocaleLoaded } from '@/state/localization';
|
||||||
|
|
||||||
import '@/index.css';
|
import '@/index.css';
|
||||||
import './ladle.css';
|
import './ladle.css';
|
||||||
|
|
||||||
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const [theme, setTheme] = useState(AppTheme.Classic);
|
const [theme, setTheme] = useState('Default theme');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
store.dispatch(setAppTheme(theme));
|
|
||||||
switch (theme) {
|
switch (theme) {
|
||||||
case AppTheme.Dark: {
|
case 'Dark theme': {
|
||||||
document?.documentElement?.classList.remove('theme-light');
|
document?.documentElement?.classList.remove('theme-light');
|
||||||
document?.documentElement?.classList.add('theme-dark');
|
document?.documentElement?.classList.add('theme-dark');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AppTheme.Light: {
|
case 'Light theme': {
|
||||||
document?.documentElement?.classList.remove('theme-dark');
|
document?.documentElement?.classList.remove('theme-dark');
|
||||||
document?.documentElement?.classList.add('theme-light');
|
document?.documentElement?.classList.add('theme-light');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AppTheme.Classic: {
|
default: {
|
||||||
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
|
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -54,15 +48,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
value: AppTheme.Classic,
|
value: 'Default theme',
|
||||||
label: 'Default theme',
|
label: 'Default theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: AppTheme.Dark,
|
value: 'Dark theme',
|
||||||
label: 'Dark theme',
|
label: 'Dark theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: AppTheme.Light,
|
value: 'Light theme',
|
||||||
label: 'Light theme',
|
label: 'Light theme',
|
||||||
},
|
},
|
||||||
].map(({ value, label }) => (
|
].map(({ value, label }) => (
|
||||||
@@ -75,11 +69,8 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
</SelectMenu>
|
</SelectMenu>
|
||||||
</StoryHeader>
|
</StoryHeader>
|
||||||
<hr />
|
<hr />
|
||||||
<AppThemeProvider>
|
<StoryContent>{children}</StoryContent>
|
||||||
<GlobalStyle />
|
</Provider>
|
||||||
<StoryContent>{children}</StoryContent>
|
|
||||||
</AppThemeProvider>
|
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })
|
||||||
+10
-7
@@ -33,17 +33,19 @@
|
|||||||
"packageManager": "pnpm@8.6.6",
|
"packageManager": "pnpm@8.6.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0xsquid/sdk": "^1.10.0",
|
"@0xsquid/sdk": "^1.10.0",
|
||||||
"@cosmjs/amino": "^0.32.1",
|
"@cosmjs/amino": "^0.31.0",
|
||||||
"@cosmjs/crypto": "^0.32.1",
|
"@cosmjs/crypto": "^0.31.0",
|
||||||
"@cosmjs/encoding": "^0.32.1",
|
"@cosmjs/encoding": "^0.31.0",
|
||||||
"@cosmjs/proto-signing": "^0.32.1",
|
"@cosmjs/proto-signing": "^0.31.0",
|
||||||
"@cosmjs/stargate": "^0.32.1",
|
"@cosmjs/stargate": "^0.31.0",
|
||||||
"@cosmjs/tendermint-rpc": "^0.32.1",
|
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||||
"@dydxprotocol/v4-abacus": "^1.2.5",
|
"@dydxprotocol/v4-abacus": "^1.2.5",
|
||||||
"@dydxprotocol/v4-client-js": "^1.0.14",
|
"@dydxprotocol/v4-client-js": "^1.0.11",
|
||||||
"@dydxprotocol/v4-localization": "^1.1.11",
|
"@dydxprotocol/v4-localization": "^1.1.11",
|
||||||
"@ethersproject/providers": "^5.7.2",
|
"@ethersproject/providers": "^5.7.2",
|
||||||
"@js-joda/core": "^5.5.3",
|
"@js-joda/core": "^5.5.3",
|
||||||
|
"@privy-io/react-auth": "^1.53.1",
|
||||||
|
"@privy-io/wagmi-connector": "^0.1.12",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
@@ -151,6 +153,7 @@
|
|||||||
"url-polyfill": "^1.1.12",
|
"url-polyfill": "^1.1.12",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
|
"vite-plugin-pwa": "^0.17.4",
|
||||||
"vite-plugin-svgr": "^3.2.0",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"vitest": "^0.32.2",
|
"vitest": "^0.32.2",
|
||||||
"w3name": "^1.0.8",
|
"w3name": "^1.0.8",
|
||||||
|
|||||||
Generated
+2753
-198
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
"ios": {
|
"ios": {
|
||||||
"scheme": "dydx-t-v4"
|
"scheme": "dydxv4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deployments": {
|
"deployments": {
|
||||||
@@ -215,11 +215,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"https://validator.v4dev4.dydx.exchange"
|
"http://validator.v4dev4.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||||
"faucet": "https://faucet.v4dev4.dydx.exchange"
|
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -474,7 +473,7 @@
|
|||||||
"apps": {
|
"apps": {
|
||||||
"ios": {
|
"ios": {
|
||||||
"minimalVersion": "1.0",
|
"minimalVersion": "1.0",
|
||||||
"build": 40000,
|
"build":40000,
|
||||||
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-41
@@ -2,6 +2,8 @@ import { lazy, Suspense } from 'react';
|
|||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
import { WagmiConfig } from 'wagmi';
|
import { WagmiConfig } from 'wagmi';
|
||||||
|
import { PrivyProvider } from '@privy-io/react-auth';
|
||||||
|
import { PrivyWagmiConnector } from '@privy-io/wagmi-connector';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import { GrazProvider } from 'graz';
|
import { GrazProvider } from 'graz';
|
||||||
|
|
||||||
@@ -16,7 +18,6 @@ import {
|
|||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
import { DydxProvider } from '@/hooks/useDydxClient';
|
import { DydxProvider } from '@/hooks/useDydxClient';
|
||||||
import { AccountsProvider } from '@/hooks/useAccounts';
|
import { AccountsProvider } from '@/hooks/useAccounts';
|
||||||
import { AppThemeProvider } from '@/hooks/useAppTheme';
|
|
||||||
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
|
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
|
||||||
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
||||||
import { NotificationsProvider } from '@/hooks/useNotifications';
|
import { NotificationsProvider } from '@/hooks/useNotifications';
|
||||||
@@ -33,10 +34,9 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
|
|||||||
import { DialogManager } from '@/layout/DialogManager';
|
import { DialogManager } from '@/layout/DialogManager';
|
||||||
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
||||||
|
|
||||||
import { config } from '@/lib/wagmi';
|
import { config, privyConfig } from '@/lib/wagmi';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { GlobalStyle } from '@/styles/globalStyle';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
||||||
|
|
||||||
@@ -68,52 +68,49 @@ const Content = () => {
|
|||||||
const { chainTokenLabel } = useTokenConfigs();
|
const { chainTokenLabel } = useTokenConfigs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
||||||
<GlobalStyle />
|
{isNotTablet && <HeaderDesktop />}
|
||||||
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
|
||||||
{isNotTablet && <HeaderDesktop />}
|
|
||||||
|
|
||||||
<Styled.Main>
|
<Styled.Main>
|
||||||
<Suspense fallback={<LoadingSpace id="main" />}>
|
<Suspense fallback={<LoadingSpace id="main" />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={AppRoute.Trade}>
|
<Route path={AppRoute.Trade}>
|
||||||
<Route path=":market" element={<TradePage />} />
|
<Route path=":market" element={<TradePage />} />
|
||||||
<Route path={AppRoute.Trade} element={<TradePage />} />
|
<Route path={AppRoute.Trade} element={<TradePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
||||||
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
||||||
{isTablet && (
|
{isTablet && (
|
||||||
<>
|
<>
|
||||||
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
||||||
<Route path={AppRoute.Profile} element={<ProfilePage />} />
|
<Route path={AppRoute.Profile} element={<ProfilePage />} />
|
||||||
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
|
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route element={<GuardedMobileRoute />}>
|
<Route element={<GuardedMobileRoute />}>
|
||||||
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
||||||
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Styled.Main>
|
</Styled.Main>
|
||||||
|
|
||||||
{isTablet ? <FooterMobile /> : <FooterDesktop />}
|
{isTablet ? <FooterMobile /> : <FooterDesktop />}
|
||||||
|
|
||||||
<Styled.NotificationsToastArea />
|
<Styled.NotificationsToastArea />
|
||||||
|
|
||||||
<Styled.DialogArea ref={setDialogArea}>
|
<Styled.DialogArea ref={setDialogArea}>
|
||||||
<DialogManager />
|
<DialogManager />
|
||||||
</Styled.DialogArea>
|
</Styled.DialogArea>
|
||||||
|
|
||||||
<GlobalCommandDialog />
|
<GlobalCommandDialog />
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,8 +122,13 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const providers = [
|
const providers = [
|
||||||
|
wrapProvider(PrivyProvider, {
|
||||||
|
appId: import.meta.env.VITE_PRIVY_APP_ID,
|
||||||
|
config: privyConfig,
|
||||||
|
}),
|
||||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||||
wrapProvider(GrazProvider),
|
wrapProvider(GrazProvider),
|
||||||
|
wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: config }),
|
||||||
wrapProvider(WagmiConfig, { config }),
|
wrapProvider(WagmiConfig, { config }),
|
||||||
wrapProvider(LocaleProvider),
|
wrapProvider(LocaleProvider),
|
||||||
wrapProvider(RestrictionProvider),
|
wrapProvider(RestrictionProvider),
|
||||||
@@ -136,7 +138,6 @@ const providers = [
|
|||||||
wrapProvider(LocalNotificationsProvider),
|
wrapProvider(LocalNotificationsProvider),
|
||||||
wrapProvider(NotificationsProvider),
|
wrapProvider(NotificationsProvider),
|
||||||
wrapProvider(DialogAreaProvider),
|
wrapProvider(DialogAreaProvider),
|
||||||
wrapProvider(AppThemeProvider),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { forwardRef } from 'react';
|
|
||||||
|
|
||||||
type CanvasProps = {
|
|
||||||
className?: string;
|
|
||||||
width: number | string;
|
|
||||||
height: number | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
|
|
||||||
({ className, width, height }, canvasRef) => (
|
|
||||||
<canvas ref={canvasRef} className={className} width={width} height={height} />
|
|
||||||
)
|
|
||||||
);
|
|
||||||
@@ -11,5 +11,6 @@ export const DetachedScrollableSection = styled.section`
|
|||||||
|
|
||||||
export const AttachedExpandingSection = styled.section`
|
export const AttachedExpandingSection = styled.section`
|
||||||
${layoutMixins.contentSectionAttached}
|
${layoutMixins.contentSectionAttached}
|
||||||
|
${layoutMixins.expandingColumnWithHeader}
|
||||||
gap: var(--border-width);
|
gap: var(--border-width);
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const DiffArrowStory: Story<DiffArrowProps> = (args) => (
|
|||||||
|
|
||||||
DiffArrowStory.argTypes = {
|
DiffArrowStory.argTypes = {
|
||||||
direction: {
|
direction: {
|
||||||
options: ['left', 'right', 'up', 'down'],
|
options: ['left', 'right'],
|
||||||
control: { type: 'select' },
|
control: { type: 'select' },
|
||||||
defaultValue: 'right',
|
defaultValue: 'right',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type ElementProps = {
|
|||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
direction?: 'right' | 'left' | 'up' | 'down';
|
direction?: 'right' | 'left';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiffArrowProps = ElementProps & StyleProps;
|
export type DiffArrowProps = ElementProps & StyleProps;
|
||||||
@@ -57,11 +57,5 @@ Styled.DiffArrowContainer = styled.span<DiffArrowProps>`
|
|||||||
left: css`
|
left: css`
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
`,
|
`,
|
||||||
up: css`
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
`,
|
|
||||||
down: css`
|
|
||||||
transform: rotate(90deg);
|
|
||||||
`
|
|
||||||
}[direction || 'right'])}
|
}[direction || 'right'])}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import {
|
|||||||
MenuIcon,
|
MenuIcon,
|
||||||
MigrateIcon,
|
MigrateIcon,
|
||||||
MintscanIcon,
|
MintscanIcon,
|
||||||
MoonIcon,
|
|
||||||
OrderbookIcon,
|
OrderbookIcon,
|
||||||
OrderCanceledIcon,
|
OrderCanceledIcon,
|
||||||
OrderFilledIcon,
|
OrderFilledIcon,
|
||||||
@@ -67,7 +66,6 @@ import {
|
|||||||
ShowIcon,
|
ShowIcon,
|
||||||
TogglesMenuIcon,
|
TogglesMenuIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
SunIcon,
|
|
||||||
TerminalIcon,
|
TerminalIcon,
|
||||||
TradeIcon,
|
TradeIcon,
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
@@ -121,7 +119,6 @@ export enum IconName {
|
|||||||
Menu = 'Menu',
|
Menu = 'Menu',
|
||||||
Migrate = 'Migrate',
|
Migrate = 'Migrate',
|
||||||
Mintscan = 'Mintscan',
|
Mintscan = 'Mintscan',
|
||||||
Moon = 'Moon',
|
|
||||||
Onboarding = 'Onboarding',
|
Onboarding = 'Onboarding',
|
||||||
Orderbook = 'OrderbookIcon',
|
Orderbook = 'OrderbookIcon',
|
||||||
OrderCanceled = 'OrderCanceled',
|
OrderCanceled = 'OrderCanceled',
|
||||||
@@ -144,7 +141,6 @@ export enum IconName {
|
|||||||
Share = 'Share',
|
Share = 'Share',
|
||||||
Show = 'Show',
|
Show = 'Show',
|
||||||
Star = 'Star',
|
Star = 'Star',
|
||||||
Sun = 'Sun',
|
|
||||||
Terminal = 'Terminal',
|
Terminal = 'Terminal',
|
||||||
TogglesMenu = 'TogglesMenu',
|
TogglesMenu = 'TogglesMenu',
|
||||||
Trade = 'Trade',
|
Trade = 'Trade',
|
||||||
@@ -199,7 +195,6 @@ const icons = {
|
|||||||
[IconName.Menu]: MenuIcon,
|
[IconName.Menu]: MenuIcon,
|
||||||
[IconName.Migrate]: MigrateIcon,
|
[IconName.Migrate]: MigrateIcon,
|
||||||
[IconName.Mintscan]: MintscanIcon,
|
[IconName.Mintscan]: MintscanIcon,
|
||||||
[IconName.Moon]: MoonIcon,
|
|
||||||
[IconName.Orderbook]: OrderbookIcon,
|
[IconName.Orderbook]: OrderbookIcon,
|
||||||
[IconName.OrderCanceled]: OrderCanceledIcon,
|
[IconName.OrderCanceled]: OrderCanceledIcon,
|
||||||
[IconName.OrderFilled]: OrderFilledIcon,
|
[IconName.OrderFilled]: OrderFilledIcon,
|
||||||
@@ -221,7 +216,6 @@ const icons = {
|
|||||||
[IconName.Share]: ShareIcon,
|
[IconName.Share]: ShareIcon,
|
||||||
[IconName.Show]: ShowIcon,
|
[IconName.Show]: ShowIcon,
|
||||||
[IconName.Star]: StarIcon,
|
[IconName.Star]: StarIcon,
|
||||||
[IconName.Sun]: SunIcon,
|
|
||||||
[IconName.Terminal]: TerminalIcon,
|
[IconName.Terminal]: TerminalIcon,
|
||||||
[IconName.TogglesMenu]: TogglesMenuIcon,
|
[IconName.TogglesMenu]: TogglesMenuIcon,
|
||||||
[IconName.Trade]: TradeIcon,
|
[IconName.Trade]: TradeIcon,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { RiskLevels } from '@/constants/abacus';
|
import { RiskLevels } from '@/constants/abacus';
|
||||||
|
import { UsageColorFromRiskLevel } from '@/styles/colors';
|
||||||
|
|
||||||
import { Ring } from '@/components/Ring';
|
import { Ring } from '@/components/Ring';
|
||||||
|
|
||||||
import { abacusHelper } from '@/lib/abacus';
|
import { abacusHelper } from '@/lib/abacus';
|
||||||
import { UsageColorFromRiskLevel } from '@/lib/styles';
|
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
value: number;
|
value: number;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
|||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
type PanelProps = {
|
type PanelProps = {
|
||||||
slotHeaderContent?: React.ReactNode;
|
slotHeaderContent?: string;
|
||||||
slotHeader?: React.ReactNode;
|
slotHeader?: React.ReactNode;
|
||||||
slotRight?: React.ReactNode;
|
slotRight?: React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
|
|||||||
--switch-thumb-backgroundColor: var(--color-layer-6);
|
--switch-thumb-backgroundColor: var(--color-layer-6);
|
||||||
|
|
||||||
--switch-active-backgroundColor: var(--color-accent);
|
--switch-active-backgroundColor: var(--color-accent);
|
||||||
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
|
--switch-active-thumb-backgroundColor: var(--color-white);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: var(--switch-width);
|
width: var(--switch-width);
|
||||||
|
|||||||
@@ -33,20 +33,17 @@ import {
|
|||||||
|
|
||||||
import { useAsyncList } from 'react-stately';
|
import { useAsyncList } from 'react-stately';
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints } from '@/hooks';
|
||||||
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
|
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
|
||||||
|
|
||||||
|
import { Checkbox } from '@/components/Checkbox';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { CaretIcon } from '@/icons';
|
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
import { Icon, IconName } from './Icon';
|
import { Icon, IconName } from './Icon';
|
||||||
import { Tag } from './Tag';
|
import { Tag } from './Tag';
|
||||||
import { Button } from './Button';
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
export { TableCell } from './Table/TableCell';
|
export { TableCell } from './Table/TableCell';
|
||||||
export { TableColumnHeader } from './Table/TableColumnHeader';
|
export { TableColumnHeader } from './Table/TableColumnHeader';
|
||||||
@@ -68,7 +65,7 @@ export type TableItem<TableRowData> = {
|
|||||||
onSelect?: (key: TableRowData) => void;
|
onSelect?: (key: TableRowData) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ColumnDef<TableRowData extends object> = {
|
export type ColumnDef<TableRowData extends object> = {
|
||||||
columnKey: string;
|
columnKey: string;
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
tag?: React.ReactNode;
|
tag?: React.ReactNode;
|
||||||
@@ -83,7 +80,7 @@ type ColumnDef<TableRowData extends object> = {
|
|||||||
width?: ColumnSize;
|
width?: ColumnSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
|
type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
|
||||||
label?: string;
|
label?: string;
|
||||||
columns: ColumnDef<TableRowData>[];
|
columns: ColumnDef<TableRowData>[];
|
||||||
data: TableRowData[];
|
data: TableRowData[];
|
||||||
@@ -95,7 +92,6 @@ export type ElementProps<TableRowData extends object | CustomRowConfig, TableRow
|
|||||||
selectionBehavior?: 'replace' | 'toggle';
|
selectionBehavior?: 'replace' | 'toggle';
|
||||||
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
|
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
|
||||||
slotEmpty?: React.ReactNode;
|
slotEmpty?: React.ReactNode;
|
||||||
initialNumRowsToShow?: number;
|
|
||||||
// collection: TableCollection<string>;
|
// collection: TableCollection<string>;
|
||||||
// children: React.ReactNode;
|
// children: React.ReactNode;
|
||||||
};
|
};
|
||||||
@@ -125,7 +121,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
selectionMode = 'single',
|
selectionMode = 'single',
|
||||||
selectionBehavior = 'toggle',
|
selectionBehavior = 'toggle',
|
||||||
slotEmpty,
|
slotEmpty,
|
||||||
initialNumRowsToShow = data.length,
|
|
||||||
// shouldRowRender,
|
// shouldRowRender,
|
||||||
|
|
||||||
// collection,
|
// collection,
|
||||||
@@ -141,7 +136,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
style,
|
style,
|
||||||
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
|
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
|
||||||
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
|
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
|
||||||
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
|
|
||||||
|
|
||||||
const currentBreakpoints = useBreakpoints();
|
const currentBreakpoints = useBreakpoints();
|
||||||
const shownColumns = columns.filter(
|
const shownColumns = columns.filter(
|
||||||
@@ -215,12 +209,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
onRowAction &&
|
onRowAction &&
|
||||||
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
|
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
|
||||||
}
|
}
|
||||||
numColumns={shownColumns.length}
|
|
||||||
onViewMoreClick={
|
|
||||||
numRowsToShow !== undefined && numRowsToShow < data.length
|
|
||||||
? () => setNumRowsToShow(data.length)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
// shouldRowRender={shouldRowRender}
|
// shouldRowRender={shouldRowRender}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
withGradientCardRows={withGradientCardRows}
|
withGradientCardRows={withGradientCardRows}
|
||||||
@@ -245,7 +233,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
|||||||
)}
|
)}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
|
||||||
<TableBody items={list.items.slice(0, numRowsToShow)}>
|
<TableBody items={list.items}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<Row key={getRowKey(item)}>
|
<Row key={getRowKey(item)}>
|
||||||
{(columnKey) => (
|
{(columnKey) => (
|
||||||
@@ -279,8 +267,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
onRowAction?: (key: TableRowKey) => void;
|
onRowAction?: (key: TableRowKey) => void;
|
||||||
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
|
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
|
||||||
children: CollectionChildren<TableRowData>;
|
children: CollectionChildren<TableRowData>;
|
||||||
numColumns: number;
|
|
||||||
onViewMoreClick?: () => void;
|
|
||||||
|
|
||||||
hideHeader?: boolean;
|
hideHeader?: boolean;
|
||||||
withGradientCardRows?: boolean;
|
withGradientCardRows?: boolean;
|
||||||
@@ -290,7 +276,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
withScrollSnapColumns?: boolean;
|
withScrollSnapColumns?: boolean;
|
||||||
withScrollSnapRows?: boolean;
|
withScrollSnapRows?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props;
|
const { selectionMode, selectionBehavior } = props;
|
||||||
|
|
||||||
const state = useTableState<TableRowData>({
|
const state = useTableState<TableRowData>({
|
||||||
...props,
|
...props,
|
||||||
@@ -351,7 +337,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
<TableBodyRowGroup
|
<TableBodyRowGroup
|
||||||
withGradientCardRows={props.withGradientCardRows}
|
withGradientCardRows={props.withGradientCardRows}
|
||||||
withInnerBorders={props.withInnerBorders}
|
withInnerBorders={props.withInnerBorders}
|
||||||
withOuterBorder={props.withOuterBorder}
|
|
||||||
>
|
>
|
||||||
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
|
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
|
||||||
{[...collection.body.childNodes].map((row) =>
|
{[...collection.body.childNodes].map((row) =>
|
||||||
@@ -397,9 +382,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{onViewMoreClick ? (
|
|
||||||
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
|
|
||||||
) : undefined}
|
|
||||||
</TableBodyRowGroup>
|
</TableBodyRowGroup>
|
||||||
</Styled.Table>
|
</Styled.Table>
|
||||||
);
|
);
|
||||||
@@ -433,7 +415,6 @@ const TableBodyRowGroup = ({
|
|||||||
children,
|
children,
|
||||||
withGradientCardRows,
|
withGradientCardRows,
|
||||||
withInnerBorders,
|
withInnerBorders,
|
||||||
withOuterBorder,
|
|
||||||
}: { children: React.ReactNode } & StyleProps) => {
|
}: { children: React.ReactNode } & StyleProps) => {
|
||||||
const { rowGroupProps } = useTableRowGroup();
|
const { rowGroupProps } = useTableRowGroup();
|
||||||
|
|
||||||
@@ -442,7 +423,6 @@ const TableBodyRowGroup = ({
|
|||||||
{...rowGroupProps}
|
{...rowGroupProps}
|
||||||
withGradientCardRows={withGradientCardRows}
|
withGradientCardRows={withGradientCardRows}
|
||||||
withInnerBorders={withInnerBorders}
|
withInnerBorders={withInnerBorders}
|
||||||
withOuterBorder={withOuterBorder}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Styled.Tbody>
|
</Styled.Tbody>
|
||||||
@@ -509,23 +489,6 @@ const TableColumnHeader = <TableRowData extends object>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
return (
|
|
||||||
<Styled.Tr key="viewmore">
|
|
||||||
<Styled.Td
|
|
||||||
colSpan={colSpan}
|
|
||||||
onMouseDown={(e: MouseEvent) => e.preventDefault()}
|
|
||||||
onPointerDown={(e: MouseEvent) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
|
|
||||||
</Styled.ViewMoreButton>
|
|
||||||
</Styled.Td>
|
|
||||||
</Styled.Tr>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TableRow = <TableRowData extends object>({
|
export const TableRow = <TableRowData extends object>({
|
||||||
item,
|
item,
|
||||||
children,
|
children,
|
||||||
@@ -697,7 +660,7 @@ Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
|
|||||||
|
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
padding: 4rem;
|
padding: 2rem;
|
||||||
gap: 0.75em;
|
gap: 0.75em;
|
||||||
|
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
@@ -905,18 +868,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
|
|||||||
--stickyArea2-paddingLeft: var(--border-width);
|
--stickyArea2-paddingLeft: var(--border-width);
|
||||||
--stickyArea2-paddingRight: var(--border-width);
|
--stickyArea2-paddingRight: var(--border-width);
|
||||||
|
|
||||||
tr:first-of-type {
|
|
||||||
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
|
|
||||||
${({ withOuterBorder }) =>
|
|
||||||
withOuterBorder &&
|
|
||||||
css`
|
|
||||||
tr:last-of-type:not(:only-of-type) {
|
|
||||||
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:first-of-type {
|
tr:first-of-type {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -971,15 +922,3 @@ Styled.Row = styled.div`
|
|||||||
${layoutMixins.inlineRow}
|
${layoutMixins.inlineRow}
|
||||||
padding: var(--tableCell-padding);
|
padding: var(--tableCell-padding);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.ViewMoreButton = styled(Button)`
|
|
||||||
--button-backgroundColor: var(--color-layer-2);
|
|
||||||
--button-textColor: var(--color-text-1);
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 0.675rem;
|
|
||||||
margin-left: 0.5ch;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
|||||||
|
|
||||||
import { type RiskLevels } from '@/constants/abacus';
|
import { type RiskLevels } from '@/constants/abacus';
|
||||||
|
|
||||||
import { abacusHelper } from '@/lib/abacus';
|
import { UsageColorFromRiskLevel } from '@/styles/colors';
|
||||||
import { UsageColorFromRiskLevel } from '@/lib/styles';
|
|
||||||
|
|
||||||
|
import { abacusHelper } from '@/lib/abacus';
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -110,7 +110,12 @@ Styled.Abbr = styled.abbr`
|
|||||||
|
|
||||||
Styled.Content = styled(Content)`
|
Styled.Content = styled(Content)`
|
||||||
--tooltip-backgroundColor: var(--color-layer-4);
|
--tooltip-backgroundColor: var(--color-layer-4);
|
||||||
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
|
--tooltip-backgroundColor: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness) + 4%),
|
||||||
|
0.66
|
||||||
|
);
|
||||||
|
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
--popover-backgroundColor: var(--tooltip-backgroundColor);
|
--popover-backgroundColor: var(--tooltip-backgroundColor);
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
|
|||||||
|
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
--popover-radius: 0.5rem;
|
--popover-radius: 0.5rem;
|
||||||
--popover-background-color: ${({ theme }) => theme.popoverBackground};
|
--popover-background-color: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness)),
|
||||||
|
0.9
|
||||||
|
);
|
||||||
--popover-backdrop-filter: saturate(120%) blur(12px);
|
--popover-backdrop-filter: saturate(120%) blur(12px);
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
+1
-16
@@ -282,22 +282,13 @@ export const TRADE_TYPES: Record<
|
|||||||
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||||
|
|
||||||
[AbacusOrderType.liquidated.name]: null,
|
[AbacusOrderType.liquidated.name]: null,
|
||||||
[AbacusOrderType.liquidated.rawValue]: null,
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
|
|
||||||
[AbacusOrderType.liquidation.name]: null,
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
[AbacusOrderType.liquidation.rawValue]: null,
|
[AbacusOrderType.liquidation.rawValue]: null,
|
||||||
|
|
||||||
[AbacusOrderType.trailingStop.name]: null,
|
[AbacusOrderType.trailingStop.name]: null,
|
||||||
[AbacusOrderType.trailingStop.rawValue]: null,
|
[AbacusOrderType.trailingStop.rawValue]: null,
|
||||||
|
|
||||||
[AbacusOrderType.offsetting.name]: null,
|
|
||||||
[AbacusOrderType.offsetting.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.deleveraged.name]: null,
|
|
||||||
[AbacusOrderType.deleveraged.rawValue]: null,
|
|
||||||
|
|
||||||
[AbacusOrderType.finalSettlement.name]: null,
|
|
||||||
[AbacusOrderType.finalSettlement.rawValue]: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom types involving Abacus
|
// Custom types involving Abacus
|
||||||
@@ -316,9 +307,3 @@ export type NetworkConfig = Partial<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
||||||
|
|
||||||
export type PerpetualMarketOrderbookLevel = OrderbookLine & {
|
|
||||||
side?: 'ask' | 'bid';
|
|
||||||
mine: number;
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* @description Orderbook display constants
|
|
||||||
*/
|
|
||||||
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
|
|
||||||
export const ORDERBOOK_ANIMATION_DURATION = 400;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Orderbook pixel constants
|
|
||||||
* @note ORDERBOOK_ROW_HEIGHT should be a divisor of ORDERBOOK_HEIGHT so that we do not have a partial row at the bottom
|
|
||||||
* @note To change the Orderbook width, --orderbook-trades-width and ORDERBOOK_WIDTH must be changed to the same value
|
|
||||||
*/
|
|
||||||
export const ORDERBOOK_HEIGHT = 800;
|
|
||||||
export const ORDERBOOK_WIDTH = 300;
|
|
||||||
export const ORDERBOOK_ROW_HEIGHT = 24;
|
|
||||||
export const ORDERBOOK_ROW_PADDING_RIGHT = 8;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
export enum ColorToken {
|
|
||||||
Black = '#000000',
|
|
||||||
White = '#FFFFFF',
|
|
||||||
|
|
||||||
LightGray0 = '#FAFAFA',
|
|
||||||
LightGray1 = '#F7F8FA',
|
|
||||||
LightGray2 = '#FAFAFD',
|
|
||||||
LightGray3 = '#F5F5F5',
|
|
||||||
LightGray4 = '#EDF0F2',
|
|
||||||
LightGray5 = '#EDEDED',
|
|
||||||
LightGray6 = '#E9ECEE',
|
|
||||||
LightGray7 = '#E6E6E6',
|
|
||||||
LightGray8 = '#DBDBDB',
|
|
||||||
LightGray9 = '#D5DADF',
|
|
||||||
LightGray10 = '#D1D9E0',
|
|
||||||
|
|
||||||
MediumGray0 = '#CACACE',
|
|
||||||
MediumGray1 = '#C2CBD3',
|
|
||||||
|
|
||||||
DarkGray0 = '#888891',
|
|
||||||
DarkGray1 = '#737373',
|
|
||||||
DarkGray2 = '#56565C',
|
|
||||||
DarkGray3 = '#4B4B4B',
|
|
||||||
DarkGray4 = '#494950',
|
|
||||||
DarkGray5 = '#3B3B3F',
|
|
||||||
DarkGray6 = '#303033',
|
|
||||||
DarkGray7 = '#313135',
|
|
||||||
DarkGray8 = '#292929',
|
|
||||||
DarkGray9 = '#23232F',
|
|
||||||
DarkGray10 = '#212124',
|
|
||||||
DarkGray11 = '#18181B',
|
|
||||||
DarkGray12 = '#181818',
|
|
||||||
DarkGray13 = '#0C0C0D',
|
|
||||||
|
|
||||||
GrayBlue0 = '#464659',
|
|
||||||
GrayBlue1 = '#38384D',
|
|
||||||
GrayBlue2 = '#303045',
|
|
||||||
GrayBlue3 = '#29293D',
|
|
||||||
GrayBlue4 = '#212131',
|
|
||||||
GrayBlue5 = '#181825',
|
|
||||||
GrayBlue6 = '#101018',
|
|
||||||
GrayBlue7 = '#08080C',
|
|
||||||
|
|
||||||
GrayPurple0 = '#DFDADF',
|
|
||||||
GrayPurple1 = '#C8C7D8',
|
|
||||||
GrayPurple2 = '#807E98',
|
|
||||||
|
|
||||||
Purple0 = '#7774FF',
|
|
||||||
Purple1 = '#6966FF',
|
|
||||||
|
|
||||||
Green0 = '#1AFFB9',
|
|
||||||
Green1 = '#3ED9A4',
|
|
||||||
Green2 = '#2CCC98',
|
|
||||||
Green3 = '#3EB68A',
|
|
||||||
|
|
||||||
Yellow0 = '#FFCC48',
|
|
||||||
Yellow1 = '#FFB647',
|
|
||||||
|
|
||||||
Red0 = '#FF5C5C',
|
|
||||||
Red1 = '#E76565',
|
|
||||||
Red2 = '#E45555',
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Maps opacity to corresponding hex value */
|
|
||||||
export enum OpacityToken {
|
|
||||||
Opacity16 = '29',
|
|
||||||
Opacity20 = '33',
|
|
||||||
Opacity66 = 'A8',
|
|
||||||
Opacity90 = 'E6',
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
export type ThemeColors = LayerColors &
|
|
||||||
BorderColors &
|
|
||||||
TextColors &
|
|
||||||
GradientColors &
|
|
||||||
AccentColors &
|
|
||||||
StatusColors &
|
|
||||||
DirectionalColors &
|
|
||||||
RiskColors &
|
|
||||||
IconColors &
|
|
||||||
ComponentColors;
|
|
||||||
|
|
||||||
type LayerColors = {
|
|
||||||
layer0: string;
|
|
||||||
layer1: string;
|
|
||||||
layer2: string;
|
|
||||||
layer3: string;
|
|
||||||
layer4: string;
|
|
||||||
layer5: string;
|
|
||||||
layer6: string;
|
|
||||||
layer7: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BorderColors = {
|
|
||||||
borderDefault: string;
|
|
||||||
borderDestructive: string;
|
|
||||||
borderButton: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TextColors = {
|
|
||||||
textPrimary: string;
|
|
||||||
textSecondary: string;
|
|
||||||
textTertiary: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GradientColors = {
|
|
||||||
gradientBase0: string;
|
|
||||||
gradientBase1: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AccentColors = {
|
|
||||||
accent: string;
|
|
||||||
accentFaded: string;
|
|
||||||
favorite: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StatusColors = {
|
|
||||||
success: string;
|
|
||||||
warning: string;
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DirectionalColors = {
|
|
||||||
positive: string;
|
|
||||||
negative: string;
|
|
||||||
positiveFaded: string;
|
|
||||||
negativeFaded: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RiskColors = {
|
|
||||||
riskLow: string;
|
|
||||||
riskMedium: string;
|
|
||||||
riskHigh: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IconColors = {
|
|
||||||
logoFill: string;
|
|
||||||
profileYellow: string;
|
|
||||||
profileRed: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ComponentColors = {
|
|
||||||
inputBackground: string;
|
|
||||||
popoverBackground: string;
|
|
||||||
switchThumbActiveBackground: string;
|
|
||||||
toggleBackground: string;
|
|
||||||
tooltipBackground: string;
|
|
||||||
};
|
|
||||||
@@ -31,6 +31,7 @@ import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
|
|||||||
export enum WalletConnectionType {
|
export enum WalletConnectionType {
|
||||||
CoinbaseWalletSdk = 'coinbaseWalletSdk',
|
CoinbaseWalletSdk = 'coinbaseWalletSdk',
|
||||||
CosmosSigner = 'CosmosSigner',
|
CosmosSigner = 'CosmosSigner',
|
||||||
|
Privy = 'Privy',
|
||||||
InjectedEip1193 = 'injectedEip1193',
|
InjectedEip1193 = 'injectedEip1193',
|
||||||
WalletConnect2 = 'walletConnect2',
|
WalletConnect2 = 'walletConnect2',
|
||||||
}
|
}
|
||||||
@@ -68,6 +69,9 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
|
|||||||
[WalletConnectionType.CosmosSigner]: {
|
[WalletConnectionType.CosmosSigner]: {
|
||||||
name: 'CosmosSigner',
|
name: 'CosmosSigner',
|
||||||
},
|
},
|
||||||
|
[WalletConnectionType.Privy]: {
|
||||||
|
name: 'Privy',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wallets
|
// Wallets
|
||||||
@@ -90,6 +94,7 @@ export enum WalletType {
|
|||||||
WalletConnect2 = 'WALLETCONNECT_2',
|
WalletConnect2 = 'WALLETCONNECT_2',
|
||||||
// TestWallet = 'TEST_WALLET',
|
// TestWallet = 'TEST_WALLET',
|
||||||
OtherWallet = 'OTHER_WALLET',
|
OtherWallet = 'OTHER_WALLET',
|
||||||
|
Privy = 'PRIVY',
|
||||||
}
|
}
|
||||||
|
|
||||||
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
||||||
@@ -243,6 +248,12 @@ export const wallets: Record<WalletType, WalletConfig> = {
|
|||||||
icon: KeplrIcon,
|
icon: KeplrIcon,
|
||||||
connectionTypes: [WalletConnectionType.CosmosSigner],
|
connectionTypes: [WalletConnectionType.CosmosSigner],
|
||||||
},
|
},
|
||||||
|
[WalletType.Privy]: {
|
||||||
|
type: WalletType.Privy,
|
||||||
|
stringKey: STRING_KEYS.KEPLR,
|
||||||
|
icon: GenericWalletIcon,
|
||||||
|
connectionTypes: [WalletConnectionType.Privy],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Injected EIP-1193 Providers
|
// Injected EIP-1193 Providers
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from './useCenterOrderbook';
|
|
||||||
export * from './useSpreadRowScrollListener';
|
|
||||||
export * from './useDrawOrderbook';
|
|
||||||
export * from './useOrderbookValues';
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useEffect, type RefObject } from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
marketId: string;
|
|
||||||
orderbookRef: RefObject<HTMLDivElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Center Orderbook on load and market change
|
|
||||||
* Assumed that the two sides are the same height
|
|
||||||
*/
|
|
||||||
export const useCenterOrderbook = ({ marketId, orderbookRef }: Props) => {
|
|
||||||
const orderbookEl = orderbookRef.current;
|
|
||||||
const { clientHeight, scrollHeight } = orderbookEl ?? {};
|
|
||||||
const shouldScroll = scrollHeight && clientHeight && scrollHeight > clientHeight;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (orderbookEl && shouldScroll) {
|
|
||||||
orderbookEl.scrollTo({ top: (scrollHeight - clientHeight) / 2 });
|
|
||||||
}
|
|
||||||
}, [shouldScroll, marketId]);
|
|
||||||
};
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
import { useEffect, useMemo, useRef } from 'react';
|
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
|
||||||
import { SMALL_USD_DECIMALS, TOKEN_DECIMALS } from '@/constants/numbers';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ORDERBOOK_ANIMATION_DURATION,
|
|
||||||
ORDERBOOK_HEIGHT,
|
|
||||||
ORDERBOOK_ROW_HEIGHT,
|
|
||||||
ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
ORDERBOOK_WIDTH,
|
|
||||||
} from '@/constants/orderbook';
|
|
||||||
|
|
||||||
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getHistogramXValues,
|
|
||||||
getRektFromIdx,
|
|
||||||
getXByColumn,
|
|
||||||
getYForElements,
|
|
||||||
} from '@/lib/orderbookHelpers';
|
|
||||||
import { useAppThemeContext } from '../useAppTheme';
|
|
||||||
|
|
||||||
type ElementProps = {
|
|
||||||
data: Array<PerpetualMarketOrderbookLevel | undefined>;
|
|
||||||
histogramRange: number;
|
|
||||||
side: PerpetualMarketOrderbookLevel['side'];
|
|
||||||
};
|
|
||||||
|
|
||||||
type StyleProps = {
|
|
||||||
histogramSide: 'left' | 'right';
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OrderbookRowAnimationType {
|
|
||||||
REMOVE,
|
|
||||||
NEW,
|
|
||||||
NONE,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDrawOrderbook = ({
|
|
||||||
data,
|
|
||||||
histogramRange,
|
|
||||||
histogramSide,
|
|
||||||
side,
|
|
||||||
}: ElementProps & StyleProps) => {
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const currentOrderbookMap = useSelector(getCurrentMarketOrderbookMap, shallowEqual);
|
|
||||||
const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
|
|
||||||
useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
|
||||||
const prevData = useRef<typeof data>(data);
|
|
||||||
const theme = useAppThemeContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scale canvas using device pixel ratio to unblur drawn text
|
|
||||||
* @url https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas/65124939#65124939
|
|
||||||
* @returns adjusted canvas width/height/rowHeight used in coordinates for drawing
|
|
||||||
**/
|
|
||||||
const { canvasWidth, canvasHeight, rowHeight } = useMemo(() => {
|
|
||||||
const ratio = window.devicePixelRatio || 1;
|
|
||||||
|
|
||||||
if (!canvas) {
|
|
||||||
return {
|
|
||||||
canvasWidth: ORDERBOOK_WIDTH / devicePixelRatio,
|
|
||||||
canvasHeight: ORDERBOOK_HEIGHT / devicePixelRatio,
|
|
||||||
rowHeight: ORDERBOOK_ROW_HEIGHT / devicePixelRatio,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
canvas.width = canvas.offsetWidth * ratio;
|
|
||||||
canvas.height = canvas.offsetHeight * ratio;
|
|
||||||
|
|
||||||
if (ctx) {
|
|
||||||
ctx.scale(ratio, ratio);
|
|
||||||
ctx.font = '12px Satoshi';
|
|
||||||
ctx.textAlign = 'right';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
ctx.imageSmoothingQuality = 'high';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
canvasWidth: canvas.width / ratio,
|
|
||||||
canvasHeight: canvas.height / ratio,
|
|
||||||
rowHeight: ORDERBOOK_ROW_HEIGHT,
|
|
||||||
};
|
|
||||||
}, [canvas]);
|
|
||||||
|
|
||||||
const drawBars = ({
|
|
||||||
barType,
|
|
||||||
ctx,
|
|
||||||
depthOrSizeValue,
|
|
||||||
gradientMultiplier,
|
|
||||||
histogramAccentColor,
|
|
||||||
histogramSide,
|
|
||||||
idx,
|
|
||||||
}: {
|
|
||||||
barType: 'depth' | 'size';
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
depthOrSizeValue: number;
|
|
||||||
gradientMultiplier: number;
|
|
||||||
histogramAccentColor: string;
|
|
||||||
histogramSide: 'left' | 'right';
|
|
||||||
idx: number;
|
|
||||||
}) => {
|
|
||||||
const { x1, x2, y1, y2 } = getRektFromIdx({
|
|
||||||
idx,
|
|
||||||
rowHeight,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
side,
|
|
||||||
});
|
|
||||||
|
|
||||||
// X values
|
|
||||||
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
|
|
||||||
const barWidth = depthOrSizeValue
|
|
||||||
? Math.min((depthOrSizeValue / histogramRange) * maxHistogramBarWidth, maxHistogramBarWidth)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
const { gradient, bar } = getHistogramXValues({
|
|
||||||
barWidth,
|
|
||||||
canvasWidth,
|
|
||||||
gradientMultiplier,
|
|
||||||
histogramSide,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Gradient
|
|
||||||
let linearGradient;
|
|
||||||
|
|
||||||
try {
|
|
||||||
linearGradient = ctx.createLinearGradient(gradient.x1, y1, gradient.x2, y2);
|
|
||||||
linearGradient.addColorStop(0, histogramAccentColor);
|
|
||||||
linearGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
|
||||||
ctx.fillStyle = linearGradient;
|
|
||||||
} catch (err) {
|
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
|
|
||||||
// Bar
|
|
||||||
const { bar: y } = getYForElements({ y: y1, rowHeight });
|
|
||||||
|
|
||||||
if (ctx.roundRect) {
|
|
||||||
ctx.roundRect(
|
|
||||||
bar.x1,
|
|
||||||
y,
|
|
||||||
bar.x2,
|
|
||||||
rowHeight - 2,
|
|
||||||
histogramSide === 'right' ? [2, 0, 0, 2] : [0, 2, 2, 0]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ctx.rect(bar.x1, y, bar.x2, rowHeight - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fill();
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawText = ({
|
|
||||||
animationType = OrderbookRowAnimationType.NONE,
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
mine,
|
|
||||||
price,
|
|
||||||
size,
|
|
||||||
}: {
|
|
||||||
animationType?: OrderbookRowAnimationType;
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
idx: number;
|
|
||||||
mine?: number;
|
|
||||||
price?: number;
|
|
||||||
size?: number;
|
|
||||||
}) => {
|
|
||||||
const { y1 } = getRektFromIdx({
|
|
||||||
idx,
|
|
||||||
rowHeight,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
side,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { text: y } = getYForElements({ y: y1, rowHeight });
|
|
||||||
|
|
||||||
let textColor: string;
|
|
||||||
let updatedTextColor: string | undefined;
|
|
||||||
|
|
||||||
switch (animationType) {
|
|
||||||
case OrderbookRowAnimationType.REMOVE: {
|
|
||||||
textColor = theme.textSecondary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case OrderbookRowAnimationType.NEW: {
|
|
||||||
updatedTextColor = side === 'bid' ? theme.positive : theme.negative;
|
|
||||||
textColor = theme.textPrimary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
textColor = theme.textPrimary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size text
|
|
||||||
if (size) {
|
|
||||||
ctx.fillStyle = updatedTextColor ?? textColor;
|
|
||||||
ctx.fillText(
|
|
||||||
MustBigNumber(size).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
|
|
||||||
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Price text
|
|
||||||
if (price) {
|
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.fillText(
|
|
||||||
MustBigNumber(price).toFixed(tickSizeDecimals ?? SMALL_USD_DECIMALS),
|
|
||||||
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mine text
|
|
||||||
if (mine) {
|
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.fillText(
|
|
||||||
MustBigNumber(mine).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
|
|
||||||
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawOrderbookRow = ({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender,
|
|
||||||
animationType = OrderbookRowAnimationType.NONE,
|
|
||||||
}: {
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
idx: number;
|
|
||||||
rowToRender?: PerpetualMarketOrderbookLevel;
|
|
||||||
animationType?: OrderbookRowAnimationType;
|
|
||||||
}) => {
|
|
||||||
if (!rowToRender) return;
|
|
||||||
const { depth, mine, price, size } = rowToRender;
|
|
||||||
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
|
|
||||||
|
|
||||||
// Depth Bar
|
|
||||||
if (depth) {
|
|
||||||
drawBars({
|
|
||||||
barType: 'depth',
|
|
||||||
ctx,
|
|
||||||
depthOrSizeValue: depth,
|
|
||||||
gradientMultiplier: 1.3,
|
|
||||||
histogramAccentColor,
|
|
||||||
histogramSide,
|
|
||||||
idx,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size Bar
|
|
||||||
drawBars({
|
|
||||||
barType: 'size',
|
|
||||||
ctx,
|
|
||||||
depthOrSizeValue: size,
|
|
||||||
gradientMultiplier: 5,
|
|
||||||
histogramAccentColor,
|
|
||||||
histogramSide,
|
|
||||||
idx,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Size, Price, Mine
|
|
||||||
drawText({
|
|
||||||
animationType,
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
mine,
|
|
||||||
price,
|
|
||||||
size,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update histograms and row contents on data change
|
|
||||||
useEffect(() => {
|
|
||||||
const ctx = canvas?.getContext('2d');
|
|
||||||
|
|
||||||
if (!canvas || !ctx) return;
|
|
||||||
|
|
||||||
// Clear canvas before redraw
|
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
||||||
|
|
||||||
// Animate row removal and update
|
|
||||||
const mapOfOrderbookPriceLevels =
|
|
||||||
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
|
|
||||||
const empty: number[] = [];
|
|
||||||
const removed: number[] = [];
|
|
||||||
const updated: number[] = [];
|
|
||||||
|
|
||||||
prevData.current.forEach((row, idx) => {
|
|
||||||
if (!row) {
|
|
||||||
empty.push(idx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
|
|
||||||
removed.push(idx);
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: row,
|
|
||||||
animationType: OrderbookRowAnimationType.REMOVE,
|
|
||||||
});
|
|
||||||
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: data[idx],
|
|
||||||
animationType: OrderbookRowAnimationType.NONE,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updated.push(idx);
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: row,
|
|
||||||
animationType: OrderbookRowAnimationType.NEW,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
[...empty, ...removed, ...updated].forEach((idx) => {
|
|
||||||
const { x1, y1, x2, y2 } = getRektFromIdx({
|
|
||||||
idx,
|
|
||||||
rowHeight,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
side,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
|
|
||||||
drawOrderbookRow({
|
|
||||||
ctx,
|
|
||||||
idx,
|
|
||||||
rowToRender: data[idx],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, ORDERBOOK_ANIMATION_DURATION);
|
|
||||||
|
|
||||||
prevData.current = data;
|
|
||||||
}, [
|
|
||||||
canvasHeight,
|
|
||||||
canvasWidth,
|
|
||||||
rowHeight,
|
|
||||||
data,
|
|
||||||
histogramRange,
|
|
||||||
stepSizeDecimals,
|
|
||||||
tickSizeDecimals,
|
|
||||||
histogramSide,
|
|
||||||
side,
|
|
||||||
theme,
|
|
||||||
currentOrderbookMap,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { canvasRef };
|
|
||||||
};
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
|
||||||
|
|
||||||
import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
|
||||||
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
|
||||||
|
|
||||||
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
|
||||||
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
|
|
||||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
|
||||||
|
|
||||||
const openOrdersBySideAndPrice =
|
|
||||||
useSelector(getSubaccountOpenOrdersBySideAndPrice, shallowEqual) || {};
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
|
|
||||||
orderbook?.asks?.toArray() ?? []
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(row, idx: number) =>
|
|
||||||
({
|
|
||||||
key: `ask-${idx}`,
|
|
||||||
side: 'ask',
|
|
||||||
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
|
|
||||||
...row,
|
|
||||||
} as PerpetualMarketOrderbookLevel)
|
|
||||||
)
|
|
||||||
.slice(0, maxRowsPerSide);
|
|
||||||
|
|
||||||
const bids: Array<PerpetualMarketOrderbookLevel | undefined> = (
|
|
||||||
orderbook?.bids?.toArray() ?? []
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(row, idx: number) =>
|
|
||||||
({
|
|
||||||
key: `bid-${idx}`,
|
|
||||||
side: 'bid',
|
|
||||||
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
|
|
||||||
...row,
|
|
||||||
} as PerpetualMarketOrderbookLevel)
|
|
||||||
)
|
|
||||||
.slice(0, maxRowsPerSide);
|
|
||||||
|
|
||||||
// Prevent the bid/ask sides from crossing by using the offsets.
|
|
||||||
// While the books are crossing...
|
|
||||||
while (asks[0] && bids[0] && bids[0]!.price >= asks[0].price) {
|
|
||||||
// Drop the order on the side with the lower offset.
|
|
||||||
// The offset of the other side is higher and so supercedes.
|
|
||||||
if (bids[0]!.offset === asks[0].offset) {
|
|
||||||
// If offsets are the same, give precedence to the larger size. In this case,
|
|
||||||
// one of the sizes *should* be zero, but we simply check for the larger size.
|
|
||||||
if (bids[0]!.size > asks[0].size) {
|
|
||||||
asks.shift();
|
|
||||||
} else {
|
|
||||||
bids.pop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Offsets are not equal. Give precedence to the larger offset.
|
|
||||||
if (bids[0]!.offset > asks[0].offset) {
|
|
||||||
asks.shift();
|
|
||||||
} else {
|
|
||||||
bids.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const spread =
|
|
||||||
asks[0]?.price && bids[0]?.price ? MustBigNumber(asks[0].price).minus(bids[0].price) : null;
|
|
||||||
|
|
||||||
const spreadPercent = orderbook?.spreadPercent;
|
|
||||||
|
|
||||||
const histogramRange = Math.max(
|
|
||||||
isNaN(Number(bids[bids.length - 1]?.depth)) ? 0 : Number(bids[bids.length - 1]?.depth),
|
|
||||||
isNaN(Number(asks[asks.length - 1]?.depth)) ? 0 : Number(asks[asks.length - 1]?.depth)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
asks,
|
|
||||||
bids,
|
|
||||||
spread,
|
|
||||||
spreadPercent,
|
|
||||||
histogramRange,
|
|
||||||
hasOrderbook: !!orderbook,
|
|
||||||
};
|
|
||||||
}, [orderbook, openOrdersBySideAndPrice]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useOrderbookValuesForDepthChart = () => {
|
|
||||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const bids = (orderbook?.bids?.toArray() ?? [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
|
||||||
|
|
||||||
const asks = (orderbook?.asks?.toArray() ?? [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
|
||||||
|
|
||||||
const lowestBid = bids[bids.length - 1];
|
|
||||||
const highestBid = bids[0];
|
|
||||||
const lowestAsk = asks[0];
|
|
||||||
const highestAsk = asks[asks.length - 1];
|
|
||||||
|
|
||||||
const midMarketPrice = orderbook?.midPrice;
|
|
||||||
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
|
||||||
const spreadPercent = orderbook?.spreadPercent;
|
|
||||||
|
|
||||||
return {
|
|
||||||
bids,
|
|
||||||
asks,
|
|
||||||
lowestBid,
|
|
||||||
highestBid,
|
|
||||||
lowestAsk,
|
|
||||||
highestAsk,
|
|
||||||
midMarketPrice,
|
|
||||||
spread,
|
|
||||||
spreadPercent,
|
|
||||||
orderbook,
|
|
||||||
};
|
|
||||||
}, [orderbook]);
|
|
||||||
};
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { type RefObject, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const useSpreadRowScrollListener = ({
|
|
||||||
orderbookRef,
|
|
||||||
spreadRowRef,
|
|
||||||
}: {
|
|
||||||
orderbookRef: RefObject<HTMLDivElement>;
|
|
||||||
spreadRowRef: RefObject<HTMLDivElement>;
|
|
||||||
}) => {
|
|
||||||
const [displaySide, setDisplaySide] = useState<string>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onScroll = () => {
|
|
||||||
if (spreadRowRef.current && orderbookRef.current) {
|
|
||||||
const { clientHeight } = orderbookRef.current;
|
|
||||||
const parent = orderbookRef.current.getBoundingClientRect();
|
|
||||||
const spread = spreadRowRef.current.getBoundingClientRect();
|
|
||||||
const spreadTop = spread.top - parent.top;
|
|
||||||
const spreadBottom = spread.bottom - parent.top;
|
|
||||||
|
|
||||||
if (spreadBottom > clientHeight) {
|
|
||||||
setDisplaySide('bottom');
|
|
||||||
} else if (spreadTop < 0) {
|
|
||||||
setDisplaySide('top');
|
|
||||||
} else {
|
|
||||||
setDisplaySide(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
orderbookRef.current?.addEventListener('scroll', onScroll, false);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
orderbookRef.current?.removeEventListener('scroll', onScroll, false);
|
|
||||||
};
|
|
||||||
}, [orderbookRef.current, spreadRowRef.current]);
|
|
||||||
|
|
||||||
return displaySide;
|
|
||||||
};
|
|
||||||
@@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
|
|||||||
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
import { AppTheme } from '@/state/configs';
|
||||||
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
import { getAppTheme } from '@/state/configsSelectors';
|
||||||
|
|
||||||
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
||||||
@@ -28,7 +29,7 @@ export const useTradingViewTheme = ({
|
|||||||
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
||||||
isWidgetReady?: boolean;
|
isWidgetReady?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const appTheme: AppTheme = useSelector(getAppTheme);
|
const appTheme = useSelector(getAppTheme);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tvWidget && isWidgetReady) {
|
if (tvWidget && isWidgetReady) {
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
|
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AES, enc } from 'crypto-js';
|
import { AES, enc } from 'crypto-js';
|
||||||
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
||||||
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
|
import {
|
||||||
|
DydxAddress,
|
||||||
|
EvmAddress,
|
||||||
|
PrivateInformation,
|
||||||
|
WalletConnectionType,
|
||||||
|
getSignTypedData,
|
||||||
|
} from '@/constants/wallets';
|
||||||
|
|
||||||
import { setOnboardingState, setOnboardingGuard } from '@/state/account';
|
import { setOnboardingState, setOnboardingGuard } from '@/state/account';
|
||||||
import { forceOpenDialog } from '@/state/dialogs';
|
import { forceOpenDialog } from '@/state/dialogs';
|
||||||
@@ -20,6 +26,11 @@ import { useLocalStorage } from './useLocalStorage';
|
|||||||
import { useRestrictions } from './useRestrictions';
|
import { useRestrictions } from './useRestrictions';
|
||||||
import { useWalletConnection } from './useWalletConnection';
|
import { useWalletConnection } from './useWalletConnection';
|
||||||
|
|
||||||
|
import { useSignTypedData } from 'wagmi';
|
||||||
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
|
import { usePrivy } from '@privy-io/react-auth';
|
||||||
|
|
||||||
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
|
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
|
||||||
|
|
||||||
AccountsContext.displayName = 'Accounts';
|
AccountsContext.displayName = 'Accounts';
|
||||||
@@ -153,6 +164,7 @@ const useAccountsContext = () => {
|
|||||||
|
|
||||||
// dYdX wallet / onboarding state
|
// dYdX wallet / onboarding state
|
||||||
const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>();
|
const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>();
|
||||||
|
const [localNobleWallet, setLocalNobleWallet] = useState<LocalWallet>();
|
||||||
const [hdKey, setHdKey] = useState<PrivateInformation>();
|
const [hdKey, setHdKey] = useState<PrivateInformation>();
|
||||||
|
|
||||||
const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]);
|
const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]);
|
||||||
@@ -162,6 +174,11 @@ const useAccountsContext = () => {
|
|||||||
[localDydxWallet]
|
[localDydxWallet]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nobleAddress = useMemo(
|
||||||
|
() => localNobleWallet?.address,
|
||||||
|
[localNobleWallet]
|
||||||
|
);
|
||||||
|
|
||||||
const setWalletFromEvmSignature = async (signature: string) => {
|
const setWalletFromEvmSignature = async (signature: string) => {
|
||||||
const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({
|
const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({
|
||||||
signature,
|
signature,
|
||||||
@@ -176,6 +193,20 @@ const useAccountsContext = () => {
|
|||||||
}
|
}
|
||||||
}, [evmAddress, dydxAddress]);
|
}, [evmAddress, dydxAddress]);
|
||||||
|
|
||||||
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
|
|
||||||
|
const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
|
||||||
|
|
||||||
|
const signTypedData = getSignTypedData(selectedNetwork);
|
||||||
|
const { signTypedDataAsync } = useSignTypedData({
|
||||||
|
...signTypedData,
|
||||||
|
domain: {
|
||||||
|
...signTypedData.domain,
|
||||||
|
chainId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { ready, authenticated } = usePrivy();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (connectedDydxAddress && signerGraz) {
|
if (connectedDydxAddress && signerGraz) {
|
||||||
@@ -192,7 +223,17 @@ const useAccountsContext = () => {
|
|||||||
|
|
||||||
const evmDerivedAccount = evmDerivedAddresses[evmAddress];
|
const evmDerivedAccount = evmDerivedAddresses[evmAddress];
|
||||||
|
|
||||||
if (evmDerivedAccount?.encryptedSignature) {
|
if (walletConnectionType === WalletConnectionType.Privy && authenticated && ready) {
|
||||||
|
try {
|
||||||
|
const signature = await signTypedDataAsync();
|
||||||
|
|
||||||
|
await setWalletFromEvmSignature(signature);
|
||||||
|
dispatch(setOnboardingState(OnboardingState.AccountConnected));
|
||||||
|
} catch (error) {
|
||||||
|
log('useAccounts/decryptSignature', error);
|
||||||
|
forgetEvmSignature();
|
||||||
|
}
|
||||||
|
} else if (evmDerivedAccount?.encryptedSignature) {
|
||||||
try {
|
try {
|
||||||
const signature = decryptSignature(evmDerivedAccount.encryptedSignature);
|
const signature = decryptSignature(evmDerivedAccount.encryptedSignature);
|
||||||
|
|
||||||
@@ -211,7 +252,15 @@ const useAccountsContext = () => {
|
|||||||
dispatch(setOnboardingState(OnboardingState.Disconnected));
|
dispatch(setOnboardingState(OnboardingState.Disconnected));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]);
|
}, [
|
||||||
|
evmAddress,
|
||||||
|
evmDerivedAddresses,
|
||||||
|
signerWagmi,
|
||||||
|
connectedDydxAddress,
|
||||||
|
signerGraz,
|
||||||
|
authenticated,
|
||||||
|
ready,
|
||||||
|
]);
|
||||||
|
|
||||||
// abacus
|
// abacus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -224,6 +273,7 @@ const useAccountsContext = () => {
|
|||||||
if (hdKey?.mnemonic) {
|
if (hdKey?.mnemonic) {
|
||||||
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
|
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
|
||||||
abacusStateManager.setNobleWallet(nobleWallet);
|
abacusStateManager.setNobleWallet(nobleWallet);
|
||||||
|
setLocalNobleWallet(nobleWallet);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setNobleWallet();
|
setNobleWallet();
|
||||||
@@ -322,6 +372,7 @@ const useAccountsContext = () => {
|
|||||||
localDydxWallet,
|
localDydxWallet,
|
||||||
dydxAccounts,
|
dydxAccounts,
|
||||||
dydxAddress,
|
dydxAddress,
|
||||||
|
nobleAddress,
|
||||||
|
|
||||||
// Onboarding state
|
// Onboarding state
|
||||||
saveHasAcknowledgedTerms,
|
saveHasAcknowledgedTerms,
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
|
||||||
|
|
||||||
import { Themes } from '@/styles/themes';
|
|
||||||
|
|
||||||
export const AppThemeProvider = ({ ...props }) => {
|
|
||||||
return <ThemeProvider theme={useAppThemeContext()} {...props} />
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAppThemeContext = () => {
|
|
||||||
const theme: AppTheme = useSelector(getAppTheme);
|
|
||||||
return Themes[theme];
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@ export const useDisplayedWallets = () => {
|
|||||||
// WalletType.BitKeep,
|
// WalletType.BitKeep,
|
||||||
// WalletType.Coin98,
|
// WalletType.Coin98,
|
||||||
|
|
||||||
|
WalletType.Privy,
|
||||||
WalletType.OtherWallet,
|
WalletType.OtherWallet,
|
||||||
].filter(isTruthy);
|
].filter(isTruthy);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useNetwork, useSwitchNetwork } from 'wagmi';
|
import { useNetwork, useSwitchNetwork } from 'wagmi';
|
||||||
|
import { useSwitchNetwork as useSwitchNetworkPrivy } from '@privy-io/wagmi-connector';
|
||||||
|
import { WalletConnectionType } from '@/constants/wallets';
|
||||||
|
|
||||||
|
import { useWalletConnection } from './useWalletConnection';
|
||||||
|
|
||||||
export const useMatchingEvmNetwork = ({
|
export const useMatchingEvmNetwork = ({
|
||||||
chainId,
|
chainId,
|
||||||
@@ -11,7 +15,10 @@ export const useMatchingEvmNetwork = ({
|
|||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { chain } = useNetwork();
|
const { chain } = useNetwork();
|
||||||
|
const { walletConnectionType } = useWalletConnection();
|
||||||
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
|
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
|
||||||
|
const { isLoading: isLoadingPrivy, switchNetworkAsync: switchNetworkAsyncPrivy } =
|
||||||
|
useSwitchNetworkPrivy({ onError });
|
||||||
|
|
||||||
// If chainId is not a number, we can assume it is a non EVM compatible chain
|
// If chainId is not a number, we can assume it is a non EVM compatible chain
|
||||||
const isMatchingNetwork = useMemo(
|
const isMatchingNetwork = useMemo(
|
||||||
@@ -21,7 +28,11 @@ export const useMatchingEvmNetwork = ({
|
|||||||
|
|
||||||
const matchNetwork = useCallback(async () => {
|
const matchNetwork = useCallback(async () => {
|
||||||
if (!isMatchingNetwork) {
|
if (!isMatchingNetwork) {
|
||||||
await switchNetworkAsync?.(Number(chainId));
|
if (walletConnectionType === WalletConnectionType.Privy) {
|
||||||
|
await switchNetworkAsyncPrivy?.(Number(chainId));
|
||||||
|
} else {
|
||||||
|
await switchNetworkAsync?.(Number(chainId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [chainId, chain]);
|
}, [chainId, chain]);
|
||||||
|
|
||||||
@@ -34,6 +45,6 @@ export const useMatchingEvmNetwork = ({
|
|||||||
return {
|
return {
|
||||||
isMatchingNetwork,
|
isMatchingNetwork,
|
||||||
matchNetwork,
|
matchNetwork,
|
||||||
isSwitchingNetwork: isLoading,
|
isSwitchingNetwork: isLoading || isLoadingPrivy,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
||||||
|
|
||||||
|
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
|
export const useOrderbookValuesForDepthChart = () => {
|
||||||
|
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const bids = (orderbook?.bids?.toArray() ?? [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
||||||
|
|
||||||
|
const asks = (orderbook?.asks?.toArray() ?? [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
||||||
|
|
||||||
|
const lowestBid = bids[bids.length - 1];
|
||||||
|
const highestBid = bids[0];
|
||||||
|
const lowestAsk = asks[0];
|
||||||
|
const highestAsk = asks[asks.length - 1];
|
||||||
|
|
||||||
|
const midMarketPrice = orderbook?.midPrice;
|
||||||
|
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
||||||
|
const spreadPercent = orderbook?.spreadPercent;
|
||||||
|
|
||||||
|
return {
|
||||||
|
bids,
|
||||||
|
asks,
|
||||||
|
lowestBid,
|
||||||
|
highestBid,
|
||||||
|
lowestAsk,
|
||||||
|
highestAsk,
|
||||||
|
midMarketPrice,
|
||||||
|
spread,
|
||||||
|
spreadPercent,
|
||||||
|
orderbook,
|
||||||
|
};
|
||||||
|
}, [orderbook]);
|
||||||
|
};
|
||||||
@@ -198,13 +198,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
);
|
);
|
||||||
const ibcMsg: EncodeObject = {
|
const ibcMsg: EncodeObject = {
|
||||||
typeUrl: transaction.msgTypeUrl,
|
typeUrl: transaction.msgTypeUrl,
|
||||||
value: {
|
value: transaction.msg,
|
||||||
...transaction.msg,
|
|
||||||
timeoutTimestamp: transaction.msg.timeoutTimestamp
|
|
||||||
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
|
||||||
? BigInt(Long.fromValue(transaction.msg.timeoutTimestamp).toString())
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return await compositeClient.send(
|
return await compositeClient.send(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState, useMemo } from 'react';
|
import { useCallback, useEffect, useState, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { usePrivy, useLogout } from '@privy-io/react-auth';
|
||||||
|
|
||||||
import { EvmDerivedAddresses } from '@/constants/account';
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
|
|
||||||
@@ -107,20 +107,21 @@ export const useWalletConnection = () => {
|
|||||||
|
|
||||||
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
|
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
|
||||||
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
||||||
const [evmDerivedAddresses] = useLocalStorage({
|
const { login, ready, authenticated } = usePrivy();
|
||||||
key: LocalStorageKey.EvmDerivedAddresses,
|
const { logout } = useLogout();
|
||||||
defaultValue: {} as EvmDerivedAddresses,
|
|
||||||
});
|
|
||||||
|
|
||||||
const connectWallet = useCallback(
|
const connectWallet = useCallback(
|
||||||
async ({ walletType, forceConnect }: { walletType?: WalletType; forceConnect?: boolean }) => {
|
async ({ walletType }: { walletType: WalletType }) => {
|
||||||
if (!walletType) return { walletType, walletConnectionType };
|
|
||||||
|
|
||||||
const walletConnection = getWalletConnection({ walletType });
|
const walletConnection = getWalletConnection({ walletType });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!walletConnection) {
|
if (!walletConnection) {
|
||||||
throw new Error('Onboarding: No wallet connection found.');
|
throw new Error('Onboarding: No wallet connection found.');
|
||||||
|
} else if (walletConnection.type === WalletConnectionType.Privy) {
|
||||||
|
|
||||||
|
if (!isConnectedWagmi && !authenticated && ready) {
|
||||||
|
login();
|
||||||
|
}
|
||||||
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
|
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
|
||||||
const cosmosWalletType = {
|
const cosmosWalletType = {
|
||||||
[WalletType.Keplr as string]: CosmosWalletType.KEPLR,
|
[WalletType.Keplr as string]: CosmosWalletType.KEPLR,
|
||||||
@@ -139,11 +140,7 @@ export const useWalletConnection = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const isAccountConnected = Boolean(
|
if (!isConnectedWagmi) {
|
||||||
evmAddress && evmDerivedAddresses[evmAddress]?.encryptedSignature
|
|
||||||
);
|
|
||||||
// if account connected (via remember me), do not show wagmi popup until forceConnect
|
|
||||||
if (!isConnectedWagmi && (forceConnect || !isAccountConnected)) {
|
|
||||||
await connectWagmi({
|
await connectWagmi({
|
||||||
connector: resolveWagmiConnector({
|
connector: resolveWagmiConnector({
|
||||||
walletType,
|
walletType,
|
||||||
@@ -167,7 +164,7 @@ export const useWalletConnection = () => {
|
|||||||
walletConnectionType: walletConnection.type,
|
walletConnectionType: walletConnection.type,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
|
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, login, ready, authenticated]
|
||||||
);
|
);
|
||||||
|
|
||||||
const disconnectWallet = useCallback(async () => {
|
const disconnectWallet = useCallback(async () => {
|
||||||
@@ -176,6 +173,7 @@ export const useWalletConnection = () => {
|
|||||||
|
|
||||||
if (isConnectedWagmi) await disconnectWagmi();
|
if (isConnectedWagmi) await disconnectWagmi();
|
||||||
if (isConnectedGraz) await disconnectGraz();
|
if (isConnectedGraz) await disconnectGraz();
|
||||||
|
if (authenticated) await logout();
|
||||||
}, [isConnectedGraz, isConnectedWagmi]);
|
}, [isConnectedGraz, isConnectedWagmi]);
|
||||||
|
|
||||||
// Wallet selection
|
// Wallet selection
|
||||||
@@ -239,12 +237,6 @@ export const useWalletConnection = () => {
|
|||||||
evmAddressWagmi,
|
evmAddressWagmi,
|
||||||
signerWagmi,
|
signerWagmi,
|
||||||
publicClientWagmi,
|
publicClientWagmi,
|
||||||
isConnectedWagmi,
|
|
||||||
connectWallet: () =>
|
|
||||||
connectWallet({
|
|
||||||
walletType: selectedWalletType,
|
|
||||||
forceConnect: true,
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Wallet connection (Cosmos)
|
// Wallet connection (Cosmos)
|
||||||
dydxAddress,
|
dydxAddress,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export { default as MarketsIcon } from './markets.svg';
|
|||||||
export { default as MenuIcon } from './menu.svg';
|
export { default as MenuIcon } from './menu.svg';
|
||||||
export { default as MigrateIcon } from './migrate.svg';
|
export { default as MigrateIcon } from './migrate.svg';
|
||||||
export { default as MintscanIcon } from './logos/mintscan.svg';
|
export { default as MintscanIcon } from './logos/mintscan.svg';
|
||||||
export { default as MoonIcon } from './moon.svg';
|
|
||||||
export { default as OrderbookIcon } from './orderbook.svg';
|
export { default as OrderbookIcon } from './orderbook.svg';
|
||||||
export { default as OverviewIcon } from './overview.svg';
|
export { default as OverviewIcon } from './overview.svg';
|
||||||
export { default as PencilIcon } from './pencil.svg';
|
export { default as PencilIcon } from './pencil.svg';
|
||||||
@@ -57,7 +56,6 @@ export { default as SendIcon } from './send.svg';
|
|||||||
export { default as ShareIcon } from './share.svg';
|
export { default as ShareIcon } from './share.svg';
|
||||||
export { default as ShowIcon } from './show.svg';
|
export { default as ShowIcon } from './show.svg';
|
||||||
export { default as StarIcon } from './star.svg';
|
export { default as StarIcon } from './star.svg';
|
||||||
export { default as SunIcon } from './sun.svg';
|
|
||||||
export { default as TerminalIcon } from './terminal.svg';
|
export { default as TerminalIcon } from './terminal.svg';
|
||||||
export { default as TogglesMenuIcon } from './toggles-menu.svg';
|
export { default as TogglesMenuIcon } from './toggles-menu.svg';
|
||||||
export { default as TradeIcon } from './trade.svg';
|
export { default as TradeIcon } from './trade.svg';
|
||||||
|
|||||||
+43
-50
@@ -1,52 +1,45 @@
|
|||||||
import { useAppThemeContext } from '@/hooks/useAppTheme';
|
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => (
|
||||||
|
<svg
|
||||||
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => {
|
width="135"
|
||||||
const theme = useAppThemeContext();
|
height="145"
|
||||||
const fill = theme.logoFill;
|
viewBox="0 0 135 145"
|
||||||
|
fill="none"
|
||||||
return (
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg
|
>
|
||||||
width="135"
|
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill="white" />
|
||||||
height="145"
|
<path
|
||||||
viewBox="0 0 135 145"
|
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
|
||||||
fill="none"
|
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
/>
|
||||||
>
|
<path
|
||||||
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill={fill} />
|
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
|
||||||
<path
|
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
|
||||||
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
|
/>
|
||||||
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
|
<defs>
|
||||||
/>
|
<linearGradient
|
||||||
<path
|
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
|
||||||
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
|
x1="27.1293"
|
||||||
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
|
y1="9.0625"
|
||||||
/>
|
x2="69.773"
|
||||||
<defs>
|
y2="60.4324"
|
||||||
<linearGradient
|
gradientUnits="userSpaceOnUse"
|
||||||
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
|
>
|
||||||
x1="27.1293"
|
<stop stopColor="white" />
|
||||||
y1="9.0625"
|
<stop offset="1" stopColor="white" stopOpacity="0.55" />
|
||||||
x2="69.773"
|
</linearGradient>
|
||||||
y2="60.4324"
|
<linearGradient
|
||||||
gradientUnits="userSpaceOnUse"
|
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
|
||||||
>
|
x1="111.1"
|
||||||
<stop stopColor={fill} />
|
y1="133.996"
|
||||||
<stop offset="1" stopColor={fill} stopOpacity="0.55" />
|
x2="58.6959"
|
||||||
</linearGradient>
|
y2="63.4999"
|
||||||
<linearGradient
|
gradientUnits="userSpaceOnUse"
|
||||||
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
|
>
|
||||||
x1="111.1"
|
<stop stopColor="#6966FF" />
|
||||||
y1="133.996"
|
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
|
||||||
x2="58.6959"
|
</linearGradient>
|
||||||
y2="63.4999"
|
</defs>
|
||||||
gradientUnits="userSpaceOnUse"
|
</svg>
|
||||||
>
|
);
|
||||||
<stop stopColor="#6966FF" />
|
|
||||||
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LogoShortIcon;
|
export default LogoShortIcon;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 380 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 393 B |
+2
-2
@@ -1,5 +1,6 @@
|
|||||||
@import 'styles/fonts.css';
|
@import 'styles/fonts.css';
|
||||||
@import 'styles/text.css';
|
@import 'styles/text.css';
|
||||||
|
@import 'styles/colors.css';
|
||||||
@import 'styles/animations.css';
|
@import 'styles/animations.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -8,7 +9,6 @@
|
|||||||
background-color: var(--color-layer-2);
|
background-color: var(--color-layer-2);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
--border: var(--default-border-width) solid var(--color-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
@@ -54,7 +54,7 @@ button:disabled {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
:focus-visible {
|
:focus-visible{
|
||||||
outline: var(--color-accent) 1px solid;
|
outline: var(--color-accent) 1px solid;
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
/* outline-offset: -2px; */
|
/* outline-offset: -2px; */
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
this.store = undefined;
|
this.store = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isNobleClientConnected(): boolean {
|
|
||||||
return this.nobleClient?.isConnected ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setStore(store: RootStore): void {
|
setStore(store: RootStore): void {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
}
|
}
|
||||||
@@ -173,10 +169,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
return parsedObj as T;
|
return parsedObj as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof x === 'bigint') {
|
|
||||||
return x.toString() as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unsupported data type: ${typeof x}`);
|
throw new Error(`Unsupported data type: ${typeof x}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,13 +359,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
try {
|
try {
|
||||||
const ibcMsg = {
|
const ibcMsg = {
|
||||||
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
|
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
|
||||||
value: {
|
value: params.msg,
|
||||||
...params.msg,
|
|
||||||
timeoutTimestamp: params.msg.timeoutTimestamp
|
|
||||||
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
|
||||||
? BigInt(Long.fromValue(params.msg.timeoutTimestamp).toString())
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
|
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
|
||||||
|
|
||||||
@@ -423,13 +409,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
);
|
);
|
||||||
const ibcMsg: EncodeObject = {
|
const ibcMsg: EncodeObject = {
|
||||||
typeUrl: parsedIbcPayload.msgTypeUrl,
|
typeUrl: parsedIbcPayload.msgTypeUrl,
|
||||||
value: {
|
value: parsedIbcPayload.msg,
|
||||||
...parsedIbcPayload.msg,
|
|
||||||
timeoutTimestamp: parsedIbcPayload.msg.timeoutTimestamp
|
|
||||||
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
|
||||||
? BigInt(Long.fromValue(parsedIbcPayload.msg.timeoutTimestamp).toString())
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tx = await this.compositeClient.send(
|
const tx = await this.compositeClient.send(
|
||||||
|
|||||||
+5
-10
@@ -171,15 +171,6 @@ class AbacusStateManager {
|
|||||||
this.setTransferValue({ value: null, field: TransferInputField.usdcSize });
|
this.setTransferValue({ value: null, field: TransferInputField.usdcSize });
|
||||||
};
|
};
|
||||||
|
|
||||||
resetInputState = () => {
|
|
||||||
this.clearTransferInputValues();
|
|
||||||
this.setTransferValue({
|
|
||||||
field: TransferInputField.type,
|
|
||||||
value: null,
|
|
||||||
});
|
|
||||||
this.clearTradeInputValues();
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------ Set Data ------ //
|
// ------ Set Data ------ //
|
||||||
setStore = (store: RootStore) => {
|
setStore = (store: RootStore) => {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
@@ -271,7 +262,11 @@ class AbacusStateManager {
|
|||||||
) => this.stateManager.cancelOrder(orderId, callback);
|
) => this.stateManager.cancelOrder(orderId, callback);
|
||||||
|
|
||||||
cctpWithdraw = (
|
cctpWithdraw = (
|
||||||
callback: (success: boolean, parsingError: Nullable<ParsingError>, data: string) => void
|
callback: (
|
||||||
|
success: boolean,
|
||||||
|
parsingError: Nullable<ParsingError>,
|
||||||
|
data: string,
|
||||||
|
) => void
|
||||||
): void => this.stateManager.commitCCTPWithdraw(callback);
|
): void => this.stateManager.commitCCTPWithdraw(callback);
|
||||||
|
|
||||||
// ------ Utils ------ //
|
// ------ Utils ------ //
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
// ------ Canvas helper methods ------ //
|
|
||||||
|
|
||||||
import type { MarketOrderbook, Nullable, PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns top left x,y and bottom x,y from array idx
|
|
||||||
*/
|
|
||||||
export const getRektFromIdx = ({
|
|
||||||
idx,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
rowHeight,
|
|
||||||
side = 'bid',
|
|
||||||
}: {
|
|
||||||
idx: number;
|
|
||||||
canvasWidth: number;
|
|
||||||
canvasHeight: number;
|
|
||||||
rowHeight: number;
|
|
||||||
side: PerpetualMarketOrderbookLevel['side'];
|
|
||||||
}) => {
|
|
||||||
/**
|
|
||||||
* Does not change
|
|
||||||
*/
|
|
||||||
const x1 = 0;
|
|
||||||
const x2 = Math.floor(canvasWidth);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes based on side
|
|
||||||
* Asks: The drawing should starts from the bottom of the orderbook
|
|
||||||
*/
|
|
||||||
if (side === 'ask') {
|
|
||||||
const y1 = Math.floor(canvasHeight - (idx + 1) * rowHeight);
|
|
||||||
const y2 = Math.floor(y1 + rowHeight);
|
|
||||||
return { x1, x2, y1, y2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const y1 = Math.floor(idx * rowHeight);
|
|
||||||
const y2 = Math.floor(y1 + rowHeight);
|
|
||||||
return { x1, x2, y1, y2 };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns y value for text and bar
|
|
||||||
*/
|
|
||||||
export const getYForElements = ({ y, rowHeight }: { y: number; rowHeight: number }) => ({
|
|
||||||
text: Math.floor(y + rowHeight / 2),
|
|
||||||
bar: Math.floor(y + 1),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description X coordinate for text
|
|
||||||
* @returns Get X value by column, colIdx starts at 0
|
|
||||||
*/
|
|
||||||
export const getXByColumn = ({ canvasWidth, colIdx }: { canvasWidth: number; colIdx: number }) => {
|
|
||||||
return Math.floor(((colIdx + 1) * canvasWidth) / 3);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to === 'right' to === 'left'
|
|
||||||
* |===================| |===================|
|
|
||||||
* bar: a b c d
|
|
||||||
* a = 0
|
|
||||||
* b = depthBarWidth
|
|
||||||
* c = canvasWidth - depthBarWidth
|
|
||||||
* d = canvasWidth
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const getHistogramXValues = ({
|
|
||||||
barWidth,
|
|
||||||
canvasWidth,
|
|
||||||
gradientMultiplier,
|
|
||||||
histogramSide,
|
|
||||||
}: {
|
|
||||||
barWidth: number;
|
|
||||||
canvasWidth: number;
|
|
||||||
gradientMultiplier: number;
|
|
||||||
histogramSide: 'left' | 'right';
|
|
||||||
}) => {
|
|
||||||
const gradient = {
|
|
||||||
x1: Math.floor(histogramSide === 'left' ? barWidth : canvasWidth - barWidth),
|
|
||||||
x2: Math.floor(
|
|
||||||
histogramSide === 'left'
|
|
||||||
? 0 - (canvasWidth * gradientMultiplier - canvasWidth)
|
|
||||||
: canvasWidth * gradientMultiplier
|
|
||||||
),
|
|
||||||
};
|
|
||||||
const bar = {
|
|
||||||
x1: Math.floor(histogramSide === 'left' ? 0 : canvasWidth - barWidth),
|
|
||||||
x2: Math.floor(histogramSide === 'left' ? Math.min(barWidth, canvasWidth) : canvasWidth),
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
bar,
|
|
||||||
gradient,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------ Orderbook helper methods ------ //
|
|
||||||
export const processOrderbookToCreateMap = ({
|
|
||||||
orderbookMap,
|
|
||||||
newOrderbook,
|
|
||||||
}: {
|
|
||||||
orderbookMap?: {
|
|
||||||
asks: Record<string, number>;
|
|
||||||
bids: Record<string, number>;
|
|
||||||
};
|
|
||||||
newOrderbook: Nullable<MarketOrderbook>;
|
|
||||||
}) => {
|
|
||||||
// Create Orderbooks Map indexed by Price
|
|
||||||
const asks = newOrderbook?.asks?.toArray() ?? [];
|
|
||||||
const bids = newOrderbook?.bids?.toArray() ?? [];
|
|
||||||
const prevAsks = orderbookMap?.asks ?? {};
|
|
||||||
const prevBids = orderbookMap?.bids ?? {};
|
|
||||||
const newAsks = Object.fromEntries(asks.map((ask) => [ask.price, ask.size]) ?? {});
|
|
||||||
const newBids = Object.fromEntries(bids.map((bid) => [bid.price, bid.size]) ?? {});
|
|
||||||
|
|
||||||
Object.keys(prevAsks).forEach((price) => {
|
|
||||||
if (!newAsks[price] || newAsks[price] === 0) {
|
|
||||||
newAsks[price] = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(prevBids).forEach((price) => {
|
|
||||||
if (!newBids[price] || newBids[price] === 0) {
|
|
||||||
newBids[price] = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
newAsks,
|
|
||||||
newBids,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { css } from 'styled-components';
|
|
||||||
|
|
||||||
import { type RiskLevels } from '@/constants/abacus';
|
|
||||||
|
|
||||||
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
|
|
||||||
({
|
|
||||||
low: css`
|
|
||||||
color: var(--color-risk-low);
|
|
||||||
`,
|
|
||||||
medium: css`
|
|
||||||
color: var(--color-risk-medium);
|
|
||||||
`,
|
|
||||||
high: css`
|
|
||||||
color: var(--color-risk-high);
|
|
||||||
`,
|
|
||||||
}[riskLevel.name]);
|
|
||||||
|
|
||||||
export const generateFadedColorVariant = (colorHex: string, opacityHex: string) => {
|
|
||||||
return `${colorHex}${opacityHex}`;
|
|
||||||
};
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import Color from 'color';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
|
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
|
||||||
|
import { Colors } from '@/styles/colors';
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
import { AppTheme } from '@/state/configs';
|
||||||
|
|
||||||
import { Themes } from '@/styles/themes';
|
|
||||||
|
|
||||||
export const mapCandle = ({
|
export const mapCandle = ({
|
||||||
startedAt,
|
startedAt,
|
||||||
open,
|
open,
|
||||||
@@ -47,42 +49,42 @@ export const getHistorySlice = ({
|
|||||||
return bars.filter(({ time }) => time >= fromMs);
|
return bars.filter(({ time }) => time >= fromMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWidgetOverrides = (appTheme: AppTheme) => {
|
export const getWidgetOverrides = (theme: AppTheme) => {
|
||||||
const theme = Themes[appTheme];
|
const colors = Colors[theme];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
overrides: {
|
overrides: {
|
||||||
'paneProperties.background': theme.layer2,
|
'paneProperties.background': Color(colors.layer2).hex(),
|
||||||
'paneProperties.horzGridProperties.color': theme.layer3,
|
'paneProperties.horzGridProperties.color': Color(colors.layer3).hex(),
|
||||||
'paneProperties.vertGridProperties.color': theme.layer3,
|
'paneProperties.vertGridProperties.color': Color(colors.layer3).hex(),
|
||||||
'paneProperties.crossHairProperties.style': 1,
|
'paneProperties.crossHairProperties.style': 1,
|
||||||
'paneProperties.legendProperties.showBarChange': false,
|
'paneProperties.legendProperties.showBarChange': false,
|
||||||
'paneProperties.backgroundType': 'solid',
|
'paneProperties.backgroundType': 'solid',
|
||||||
|
|
||||||
'mainSeriesProperties.style': 1,
|
'mainSeriesProperties.style': 1,
|
||||||
'mainSeriesProperties.candleStyle.upColor': theme.positive,
|
'mainSeriesProperties.candleStyle.upColor': Color(colors.positive).hex(),
|
||||||
'mainSeriesProperties.candleStyle.borderUpColor': theme.positive,
|
'mainSeriesProperties.candleStyle.borderUpColor': Color(colors.positive).hex(),
|
||||||
'mainSeriesProperties.candleStyle.wickUpColor': theme.positive,
|
'mainSeriesProperties.candleStyle.wickUpColor': Color(colors.positive).hex(),
|
||||||
'mainSeriesProperties.candleStyle.downColor': theme.negative,
|
'mainSeriesProperties.candleStyle.downColor': Color(colors.negative).hex(),
|
||||||
'mainSeriesProperties.candleStyle.borderDownColor': theme.negative,
|
'mainSeriesProperties.candleStyle.borderDownColor': Color(colors.negative).hex(),
|
||||||
'mainSeriesProperties.candleStyle.wickDownColor': theme.negative,
|
'mainSeriesProperties.candleStyle.wickDownColor': Color(colors.negative).hex(),
|
||||||
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
|
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
|
||||||
|
|
||||||
'scalesProperties.textColor': theme.textPrimary,
|
'scalesProperties.textColor': Color(colors.text1).hex(),
|
||||||
'scalesProperties.backgroundColor': theme.layer2,
|
'scalesProperties.backgroundColor': Color(colors.layer2).hex(),
|
||||||
'scalesProperties.lineColor': theme.layer3,
|
'scalesProperties.lineColor': Color(colors.layer3).hex(),
|
||||||
},
|
},
|
||||||
studies_overrides: {
|
studies_overrides: {
|
||||||
'volume.volume.color.0': theme.negative,
|
'volume.volume.color.0': Color(colors.negative).hex(),
|
||||||
'volume.volume.color.1': theme.positive,
|
'volume.volume.color.1': Color(colors.positive).hex(),
|
||||||
'volume.volume ma.visible': false,
|
'volume.volume ma.visible': false,
|
||||||
'relative strength index.plot.color': theme.accent,
|
'relative strength index.plot.color': Color(colors.accent).hex(),
|
||||||
'relative strength index.plot.linewidth': 1.5,
|
'relative strength index.plot.linewidth': 1.5,
|
||||||
'relative strength index.hlines background.color': '#134A9F',
|
'relative strength index.hlines background.color': '#134A9F',
|
||||||
},
|
},
|
||||||
loading_screen: {
|
loading_screen: {
|
||||||
backgroundColor: theme.layer2,
|
backgroundColor: Color(colors.layer2).hex(),
|
||||||
foregroundColor: theme.layer2,
|
foregroundColor: Color(colors.layer2).hex(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
|
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
|
||||||
import { goerli } from 'wagmi/chains';
|
import { goerli } from 'wagmi/chains';
|
||||||
|
import type { PrivyClientConfig } from '@privy-io/react-auth';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
arbitrum,
|
arbitrum,
|
||||||
@@ -85,6 +86,19 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
|
|||||||
kava,
|
kava,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const privyConfig: PrivyClientConfig = {
|
||||||
|
embeddedWallets: {
|
||||||
|
createOnLogin: 'users-without-wallets',
|
||||||
|
requireUserPasswordOnCreate: true,
|
||||||
|
noPromptOnSignature: true,
|
||||||
|
},
|
||||||
|
loginMethods: ['email', 'sms', 'twitter', 'google', 'apple'],
|
||||||
|
appearance: {
|
||||||
|
theme: '#28283c',
|
||||||
|
},
|
||||||
|
defaultChain: sepolia
|
||||||
|
};
|
||||||
|
|
||||||
const { chains, publicClient, webSocketPublicClient } = configureChains(
|
const { chains, publicClient, webSocketPublicClient } = configureChains(
|
||||||
WAGMI_SUPPORTED_CHAINS,
|
WAGMI_SUPPORTED_CHAINS,
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ export const getWalletConnection = ({
|
|||||||
type: WalletConnectionType.CosmosSigner,
|
type: WalletConnectionType.CosmosSigner,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WalletConnectionType.Privy: {
|
||||||
|
return {
|
||||||
|
type: WalletConnectionType.Privy,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-11
@@ -5,7 +5,7 @@ import { useEnsName } from 'wagmi';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
import { ButtonSize } from '@/constants/buttons';
|
||||||
import { TransferType } from '@/constants/abacus';
|
import { TransferInputField, TransferType } from '@/constants/abacus';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
@@ -26,13 +26,12 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
|
|||||||
import { getOnboardingState } from '@/state/accountSelectors';
|
import { getOnboardingState } from '@/state/accountSelectors';
|
||||||
import { openDialog } from '@/state/dialogs';
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
|
||||||
|
import abacusStateManager from '@/lib/abacus';
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
import { truncateAddress } from '@/lib/wallet';
|
import { truncateAddress } from '@/lib/wallet';
|
||||||
|
|
||||||
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
|
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
|
||||||
import { MigratePanel } from './rewards/MigratePanel';
|
import { MigratePanel } from './rewards/MigratePanel';
|
||||||
import { GovernancePanel } from './rewards/GovernancePanel';
|
|
||||||
import { StakingPanel } from './rewards/StakingPanel';
|
|
||||||
|
|
||||||
const ENS_CHAIN_ID = 1; // Ethereum
|
const ENS_CHAIN_ID = 1; // Ethereum
|
||||||
|
|
||||||
@@ -179,7 +178,7 @@ const Profile = () => {
|
|||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
||||||
/>
|
/>
|
||||||
</Styled.EqualGrid>
|
</Styled.EqualGrid>
|
||||||
|
|
||||||
<MigratePanel />
|
<MigratePanel />
|
||||||
<DYDXBalancePanel />
|
<DYDXBalancePanel />
|
||||||
|
|
||||||
@@ -231,9 +230,6 @@ const Profile = () => {
|
|||||||
withInnerBorders={false}
|
withInnerBorders={false}
|
||||||
/>
|
/>
|
||||||
</Styled.TablePanel>
|
</Styled.TablePanel>
|
||||||
|
|
||||||
<GovernancePanel />
|
|
||||||
<StakingPanel />
|
|
||||||
</Styled.MobileProfileLayout>
|
</Styled.MobileProfileLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -247,7 +243,6 @@ Styled.MobileProfileLayout = styled.div`
|
|||||||
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1.25rem 0.9rem;
|
padding: 1.25rem 0.9rem;
|
||||||
max-width: 100vw;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Header = styled.header`
|
Styled.Header = styled.header`
|
||||||
@@ -264,8 +259,8 @@ Styled.ProfileIcon = styled.div`
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
${({ theme }) => theme.profileYellow} 0%,
|
var(--theme-classic-color-yellow) 0%,
|
||||||
${({ theme }) => theme.profileRed} 100%
|
var(--theme-classic-color-red) 100%
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -342,7 +337,7 @@ Styled.Details = styled(Details)`
|
|||||||
|
|
||||||
Styled.RewardsPanel = styled(Panel)`
|
Styled.RewardsPanel = styled(Panel)`
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
> * {
|
> * {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
|
||||||
|
|
||||||
import { useStringGetter, useURLConfigs } from '@/hooks';
|
|
||||||
|
|
||||||
import { Panel } from '@/components/Panel';
|
|
||||||
import { IconName } from '@/components/Icon';
|
|
||||||
import { IconButton } from '@/components/IconButton';
|
|
||||||
import { Link } from '@/components/Link';
|
|
||||||
|
|
||||||
import { openDialog } from '@/state/dialogs';
|
|
||||||
|
|
||||||
export const GovernancePanel = () => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { governanceLearnMore } = useURLConfigs();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Panel
|
|
||||||
slotHeaderContent={
|
|
||||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
|
||||||
}
|
|
||||||
slotRight={
|
|
||||||
<Styled.Arrow>
|
|
||||||
<Styled.IconButton
|
|
||||||
action={ButtonAction.Base}
|
|
||||||
iconName={IconName.Arrow}
|
|
||||||
size={ButtonSize.Small}
|
|
||||||
/>
|
|
||||||
</Styled.Arrow>
|
|
||||||
}
|
|
||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
|
||||||
>
|
|
||||||
<Styled.Description>
|
|
||||||
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
|
|
||||||
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
|
||||||
</Link>
|
|
||||||
</Styled.Description>
|
|
||||||
</Panel>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.Description = styled.div`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
--link-color: var(--color-text-1);
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline;
|
|
||||||
::before {
|
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.IconButton = styled(IconButton)`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
--color-border: var(--color-layer-6);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Arrow = styled.div`
|
|
||||||
padding-right: 1.5rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Title = styled.h3`
|
|
||||||
font: var(--font-medium-book);
|
|
||||||
color: var(--color-text-2);
|
|
||||||
margin-bottom: -1rem;
|
|
||||||
`;
|
|
||||||
@@ -146,15 +146,14 @@ Styled.Title = styled.h3`
|
|||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
|
|
||||||
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
|
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
|
||||||
margin-bottom: -0.5rem;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.MigrateAction = styled.div`
|
Styled.MigrateAction = styled.div`
|
||||||
${layoutMixins.flexEqualColumns}
|
${layoutMixins.flexEqualColumns}
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin: 1rem;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
background-color: var(--color-layer-2);
|
background-color: var(--color-layer-2);
|
||||||
@@ -234,4 +233,4 @@ Styled.InlineRow = styled.div`
|
|||||||
${layoutMixins.inlineRow}
|
${layoutMixins.inlineRow}
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
--link-color: var(--color-text-1);
|
--link-color: var(--color-text-1);
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,29 +3,45 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||||
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { AppRoute } from '@/constants/routes';
|
import { AppRoute } from '@/constants/routes';
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { BackButton } from '@/components/BackButton';
|
import { BackButton } from '@/components/BackButton';
|
||||||
import { Panel } from '@/components/Panel';
|
import { Panel } from '@/components/Panel';
|
||||||
|
import { IconName } from '@/components/Icon';
|
||||||
|
import { IconButton } from '@/components/IconButton';
|
||||||
|
import { Link } from '@/components/Link';
|
||||||
|
|
||||||
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
|
||||||
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
||||||
import { MigratePanel } from './MigratePanel';
|
import { MigratePanel } from './MigratePanel';
|
||||||
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
||||||
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
||||||
import { GovernancePanel } from './GovernancePanel';
|
|
||||||
import { StakingPanel } from './StakingPanel';
|
|
||||||
|
|
||||||
const RewardsPage = () => {
|
const RewardsPage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
|
||||||
const { isTablet, isNotTablet } = useBreakpoints();
|
const { isTablet, isNotTablet } = useBreakpoints();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const panelArrow = (
|
||||||
|
<Styled.Arrow>
|
||||||
|
<Styled.IconButton
|
||||||
|
action={ButtonAction.Base}
|
||||||
|
iconName={IconName.Arrow}
|
||||||
|
size={ButtonSize.Small}
|
||||||
|
/>
|
||||||
|
</Styled.Arrow>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Page>
|
<Styled.Page>
|
||||||
{isTablet && (
|
{isTablet && (
|
||||||
@@ -45,12 +61,37 @@ const RewardsPage = () => {
|
|||||||
</Styled.PanelRowIncentivesAndBalance>
|
</Styled.PanelRowIncentivesAndBalance>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isNotTablet && (
|
<Styled.PanelRow>
|
||||||
<Styled.PanelRow>
|
<Styled.Panel
|
||||||
<GovernancePanel />
|
slotHeaderContent={
|
||||||
<StakingPanel />
|
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
||||||
</Styled.PanelRow>
|
}
|
||||||
)}
|
slotRight={panelArrow}
|
||||||
|
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||||
|
>
|
||||||
|
<Styled.Description>
|
||||||
|
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
|
||||||
|
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
|
||||||
|
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
||||||
|
</Link>
|
||||||
|
</Styled.Description>
|
||||||
|
</Styled.Panel>
|
||||||
|
|
||||||
|
<Styled.Panel
|
||||||
|
slotHeaderContent={
|
||||||
|
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
|
||||||
|
}
|
||||||
|
slotRight={panelArrow}
|
||||||
|
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||||
|
>
|
||||||
|
<Styled.Description>
|
||||||
|
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
|
||||||
|
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
|
||||||
|
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
||||||
|
</Link>
|
||||||
|
</Styled.Description>
|
||||||
|
</Styled.Panel>
|
||||||
|
</Styled.PanelRow>
|
||||||
|
|
||||||
<RewardsHelpPanel />
|
<RewardsHelpPanel />
|
||||||
</Styled.Page>
|
</Styled.Page>
|
||||||
@@ -99,6 +140,24 @@ Styled.Panel = styled(Panel)`
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.Title = styled.h3`
|
||||||
|
font: var(--font-medium-book);
|
||||||
|
color: var(--color-text-2);
|
||||||
|
margin-bottom: -1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Description = styled.div`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
--link-color: var(--color-text-1);
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline;
|
||||||
|
::before {
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
Styled.PanelRow = styled.div`
|
Styled.PanelRow = styled.div`
|
||||||
${layoutMixins.gridEqualColumns}
|
${layoutMixins.gridEqualColumns}
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
@@ -112,3 +171,12 @@ Styled.PanelRow = styled.div`
|
|||||||
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
|
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
|
||||||
grid-template-columns: 2fr 1fr;
|
grid-template-columns: 2fr 1fr;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.IconButton = styled(IconButton)`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
--color-border: var(--color-layer-6);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Arrow = styled.div`
|
||||||
|
padding: 1rem;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
|
||||||
|
|
||||||
import { useStringGetter, useURLConfigs } from '@/hooks';
|
|
||||||
|
|
||||||
import { Panel } from '@/components/Panel';
|
|
||||||
import { IconName } from '@/components/Icon';
|
|
||||||
import { IconButton } from '@/components/IconButton';
|
|
||||||
import { Link } from '@/components/Link';
|
|
||||||
|
|
||||||
import { openDialog } from '@/state/dialogs';
|
|
||||||
|
|
||||||
export const StakingPanel = () => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { stakingLearnMore } = useURLConfigs();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Panel
|
|
||||||
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
|
|
||||||
slotRight={
|
|
||||||
<Styled.Arrow>
|
|
||||||
<Styled.IconButton
|
|
||||||
action={ButtonAction.Base}
|
|
||||||
iconName={IconName.Arrow}
|
|
||||||
size={ButtonSize.Small}
|
|
||||||
/>
|
|
||||||
</Styled.Arrow>
|
|
||||||
}
|
|
||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
|
||||||
>
|
|
||||||
<Styled.Description>
|
|
||||||
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
|
|
||||||
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
|
||||||
</Link>
|
|
||||||
</Styled.Description>
|
|
||||||
</Panel>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.Description = styled.div`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
--link-color: var(--color-text-1);
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline;
|
|
||||||
::before {
|
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.IconButton = styled(IconButton)`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
--color-border: var(--color-layer-6);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Arrow = styled.div`
|
|
||||||
padding-right: 1.5rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Title = styled.h3`
|
|
||||||
font: var(--font-medium-book);
|
|
||||||
color: var(--color-text-2);
|
|
||||||
margin-bottom: -1rem;
|
|
||||||
`;
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { TradeLayouts } from '@/constants/layout';
|
import { TradeLayouts } from '@/constants/layout';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
@@ -7,9 +9,11 @@ import { useStringGetter } from '@/hooks';
|
|||||||
|
|
||||||
import { Tabs } from '@/components/Tabs';
|
import { Tabs } from '@/components/Tabs';
|
||||||
|
|
||||||
import { CanvasOrderbook } from '@/views/CanvasOrderbook';
|
import { Orderbook, orderbookMixins, OrderbookScrollBehavior } from '@/views/tables/Orderbook';
|
||||||
import { LiveTrades } from '@/views/tables/LiveTrades';
|
import { LiveTrades } from '@/views/tables/LiveTrades';
|
||||||
|
|
||||||
|
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
enum Tab {
|
enum Tab {
|
||||||
Orderbook = 'Orderbook',
|
Orderbook = 'Orderbook',
|
||||||
Trades = 'Trades',
|
Trades = 'Trades',
|
||||||
@@ -23,22 +27,29 @@ const HISTOGRAM_SIDES_BY_LAYOUT = {
|
|||||||
|
|
||||||
export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => {
|
export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
const [value, setValue] = useState(Tab.Orderbook);
|
const [value, setValue] = useState(Tab.Orderbook);
|
||||||
|
const [scrollBehavior, setScrollBehavior] = useState<OrderbookScrollBehavior>('snapToCenter');
|
||||||
|
|
||||||
|
const marketId = useSelector(getCurrentMarketId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setScrollBehavior('snapToCenter');
|
||||||
|
}, [marketId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Styled.Tabs
|
||||||
fullWidthTabs
|
fullWidthTabs
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(value: Tab) => {
|
onValueChange={(value: Tab) => {
|
||||||
|
setScrollBehavior('snapToCenter');
|
||||||
setValue(value);
|
setValue(value);
|
||||||
}}
|
}}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
asChild: true,
|
content: <Orderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
||||||
content: <CanvasOrderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
|
||||||
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }),
|
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }),
|
||||||
value: Tab.Orderbook,
|
value: Tab.Orderbook,
|
||||||
forceMount: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
||||||
@@ -46,7 +57,26 @@ export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) =>
|
|||||||
value: Tab.Trades,
|
value: Tab.Trades,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
onWheel={() => setScrollBehavior('free')}
|
||||||
withTransitions={false}
|
withTransitions={false}
|
||||||
|
isShowingOrderbook={value === Tab.Orderbook}
|
||||||
|
scrollBehavior={scrollBehavior}
|
||||||
|
// style={{
|
||||||
|
// scrollSnapType: {
|
||||||
|
// 'snapToCenter': 'y mandatory',
|
||||||
|
// 'free': 'none',
|
||||||
|
// 'snapToCenterUnlessHovered': 'none',
|
||||||
|
// }[scrollBehavior],
|
||||||
|
// }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.Tabs = styled(Tabs)<{
|
||||||
|
isShowingOrderbook: boolean;
|
||||||
|
scrollBehavior: OrderbookScrollBehavior;
|
||||||
|
}>`
|
||||||
|
${orderbookMixins.scrollArea}
|
||||||
|
`;
|
||||||
|
|||||||
+5
-26
@@ -14,7 +14,6 @@ import { LocalStorageKey } from '@/constants/localStorage';
|
|||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
|
|
||||||
import { getLocalStorage } from '@/lib/localStorage';
|
import { getLocalStorage } from '@/lib/localStorage';
|
||||||
import { processOrderbookToCreateMap } from '@/lib/orderbookHelpers';
|
|
||||||
|
|
||||||
interface CandleDataByMarket {
|
interface CandleDataByMarket {
|
||||||
data: Record<string, Candle[]>;
|
data: Record<string, Candle[]>;
|
||||||
@@ -27,13 +26,6 @@ export interface PerpetualsState {
|
|||||||
liveTrades?: Record<string, MarketTrade[]>;
|
liveTrades?: Record<string, MarketTrade[]>;
|
||||||
markets?: Record<string, PerpetualMarket>;
|
markets?: Record<string, PerpetualMarket>;
|
||||||
orderbooks?: Record<string, MarketOrderbook>;
|
orderbooks?: Record<string, MarketOrderbook>;
|
||||||
orderbooksMap?: Record<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
asks: Record<string, number>;
|
|
||||||
bids: Record<string, number>;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
historicalFundings: Record<string, MarketHistoricalFunding[]>;
|
historicalFundings: Record<string, MarketHistoricalFunding[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +35,6 @@ const initialState: PerpetualsState = {
|
|||||||
liveTrades: {},
|
liveTrades: {},
|
||||||
markets: undefined,
|
markets: undefined,
|
||||||
orderbooks: undefined,
|
orderbooks: undefined,
|
||||||
orderbooksMap: undefined,
|
|
||||||
historicalFundings: {},
|
historicalFundings: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,24 +96,12 @@ export const perpetualsSlice = createSlice({
|
|||||||
setOrderbook: (
|
setOrderbook: (
|
||||||
state: PerpetualsState,
|
state: PerpetualsState,
|
||||||
action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }>
|
action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }>
|
||||||
) => {
|
) => ({
|
||||||
state.orderbooks = merge({}, state.orderbooks, {
|
...state,
|
||||||
|
orderbooks: merge({}, state.orderbooks, {
|
||||||
[action.payload.marketId]: action.payload.orderbook,
|
[action.payload.marketId]: action.payload.orderbook,
|
||||||
});
|
}),
|
||||||
|
}),
|
||||||
const { newAsks, newBids } = processOrderbookToCreateMap({
|
|
||||||
orderbookMap: state.orderbooksMap?.[action.payload.marketId],
|
|
||||||
newOrderbook: action.payload.orderbook,
|
|
||||||
});
|
|
||||||
|
|
||||||
state.orderbooksMap = {
|
|
||||||
...(state.orderbooksMap ?? {}),
|
|
||||||
[action.payload.marketId]: {
|
|
||||||
asks: newAsks,
|
|
||||||
bids: newBids,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setTvChartResolution: (
|
setTvChartResolution: (
|
||||||
state: PerpetualsState,
|
state: PerpetualsState,
|
||||||
action: PayloadAction<{ marketId: string; resolution: string }>
|
action: PayloadAction<{ marketId: string; resolution: string }>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { mapCandle } from '@/lib/tradingView/utils';
|
|||||||
import type { RootState } from './_store';
|
import type { RootState } from './_store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns marketId of the market the user is currently viewing
|
* @returns marketId of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
|
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
|
||||||
@@ -20,6 +21,7 @@ export const getCurrentMarketAssetId = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Record of PerpetualMarket indexed by MarketId
|
* @returns Record of PerpetualMarket indexed by MarketId
|
||||||
*/
|
*/
|
||||||
export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets;
|
export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets;
|
||||||
@@ -32,11 +34,13 @@ export const getMarketData = (marketId: string) => (state: RootState) =>
|
|||||||
getPerpetualMarkets(state)?.[marketId];
|
getPerpetualMarkets(state)?.[marketId];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns marketIds of all markets
|
* @returns marketIds of all markets
|
||||||
*/
|
*/
|
||||||
export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []);
|
export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns PerpetualMarket data of the market the user is currently viewing
|
* @returns PerpetualMarket data of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketData = (state: RootState) => {
|
export const getCurrentMarketData = (state: RootState) => {
|
||||||
@@ -45,6 +49,7 @@ export const getCurrentMarketData = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns MarketConfig data of the market the user is currently viewing
|
* @returns MarketConfig data of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs;
|
export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs;
|
||||||
@@ -57,11 +62,13 @@ export const getMarketConfig = (marketId: string) => (state: RootState) =>
|
|||||||
getMarketData(marketId)(state)?.configs;
|
getMarketData(marketId)(state)?.configs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Record of list of MarketTrades indexed by marketId
|
* @returns Record of list of MarketTrades indexed by marketId
|
||||||
*/
|
*/
|
||||||
export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades;
|
export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns List of MarketTrades of the market the user is currently viewing
|
* @returns List of MarketTrades of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketLiveTrades = (state: RootState) => {
|
export const getCurrentMarketLiveTrades = (state: RootState) => {
|
||||||
@@ -71,11 +78,13 @@ export const getCurrentMarketLiveTrades = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId.
|
* @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId.
|
||||||
*/
|
*/
|
||||||
export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks;
|
export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Orderbook data for the market the user is currently viewing
|
* @returns Orderbook data for the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketOrderbook = (state: RootState) => {
|
export const getCurrentMarketOrderbook = (state: RootState) => {
|
||||||
@@ -85,20 +94,13 @@ export const getCurrentMarketOrderbook = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns Orderbook data as a Map of price and size for the current market
|
* @param state
|
||||||
*/
|
|
||||||
export const getCurrentMarketOrderbookMap = (state: RootState) => {
|
|
||||||
const orderbookMap = state.perpetuals.orderbooksMap;
|
|
||||||
const currentMarketId = getCurrentMarketId(state);
|
|
||||||
return orderbookMap?.[currentMarketId || ''];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns Record of historical funding indexed by marketId.
|
* @returns Record of historical funding indexed by marketId.
|
||||||
*/
|
*/
|
||||||
export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings;
|
export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Historical funding data for the market the user is currently viewing
|
* @returns Historical funding data for the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketHistoricalFundings = createSelector(
|
export const getCurrentMarketHistoricalFundings = createSelector(
|
||||||
@@ -108,6 +110,7 @@ export const getCurrentMarketHistoricalFundings = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Mid market price for the market the user is currently viewing
|
* @returns Mid market price for the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketMidMarketPrice = (state: RootState) => {
|
export const getCurrentMarketMidMarketPrice = (state: RootState) => {
|
||||||
@@ -147,6 +150,7 @@ export const getSelectedResolutionForMarket = (marketId: string) => (state: Root
|
|||||||
state.perpetuals.candles?.[marketId]?.selectedResolution;
|
state.perpetuals.candles?.[marketId]?.selectedResolution;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param state
|
||||||
* @returns Current market's next funding rate
|
* @returns Current market's next funding rate
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketNextFundingRate = createSelector(
|
export const getCurrentMarketNextFundingRate = createSelector(
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
:root {
|
||||||
|
/* Parameters */
|
||||||
|
|
||||||
|
/*
|
||||||
|
--layer-base-hue
|
||||||
|
--layer-base-saturation
|
||||||
|
--layer-base-lightness
|
||||||
|
|
||||||
|
--color-text-0
|
||||||
|
--color-text-1
|
||||||
|
--color-text-2
|
||||||
|
|
||||||
|
--color-accent
|
||||||
|
--color-positive
|
||||||
|
--color-negative
|
||||||
|
--color-success
|
||||||
|
--color-warning
|
||||||
|
--color-error
|
||||||
|
--color-favorite
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Computed: Layers */
|
||||||
|
|
||||||
|
--color-layer-0: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness) - 12%)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-1: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness) - 8%)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-2: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness) - 4%)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-3: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
var(--layer-base-lightness)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-4: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
var(--layer-base-saturation),
|
||||||
|
calc(var(--layer-base-lightness) + 4%)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-5: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
calc(var(--layer-base-saturation) - 2%),
|
||||||
|
calc(var(--layer-base-lightness) + 7%)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-6: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
calc(var(--layer-base-saturation) - 4%),
|
||||||
|
calc(var(--layer-base-lightness) + 10%)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-layer-7: hsla(var(--layer-base-hue), 0%, 100%, 0.07);
|
||||||
|
|
||||||
|
--color-gradient-base-0: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
calc(var(--layer-base-saturation) - 5%),
|
||||||
|
var(--layer-base-lightness)
|
||||||
|
);
|
||||||
|
|
||||||
|
--color-gradient-base-1: hsl(
|
||||||
|
var(--layer-base-hue),
|
||||||
|
calc(var(--layer-base-saturation) - 1%),
|
||||||
|
calc(var(--layer-base-lightness) + 7%)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Computed: Borders */
|
||||||
|
|
||||||
|
--color-border: var(--color-layer-5);
|
||||||
|
--color-border-white: hsla(0, 0%, 100%, 0.2);
|
||||||
|
--color-border-red: hsla(360, 73%, 61%, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme: Classic (default) */
|
||||||
|
:root {
|
||||||
|
/* Constants */
|
||||||
|
|
||||||
|
--theme-classic-hue-purple: 240;
|
||||||
|
--theme-classic-color-green: hsl(159, 67%, 39%);
|
||||||
|
--theme-classic-color-yellow: hsl(36, 100%, 64%);
|
||||||
|
--theme-classic-color-red: hsl(360, 73%, 61%);
|
||||||
|
|
||||||
|
/* Parameters */
|
||||||
|
|
||||||
|
--layer-base-hue: var(--theme-classic-hue-purple);
|
||||||
|
--layer-base-saturation: 20%;
|
||||||
|
--layer-base-lightness: 16%;
|
||||||
|
|
||||||
|
--color-text-0: hsl(245, 11%, 55%);
|
||||||
|
--color-text-1: hsl(244, 18%, 81%);
|
||||||
|
--color-text-2: hsl(240, 43%, 99%);
|
||||||
|
|
||||||
|
--color-accent: hsl(var(--theme-classic-hue-purple), 100%, 70%);
|
||||||
|
--color-positive: var(--theme-classic-color-green);
|
||||||
|
--color-negative: var(--theme-classic-color-red);
|
||||||
|
--color-success: var(--theme-classic-color-green);
|
||||||
|
--color-warning: var(--theme-classic-color-yellow);
|
||||||
|
--color-error: var(--theme-classic-color-red);
|
||||||
|
--color-favorite: var(--theme-classic-color-yellow);
|
||||||
|
|
||||||
|
--color-white: #fff;
|
||||||
|
--color-black: #000;
|
||||||
|
|
||||||
|
--color-gradient-positive: hsla(158, 49%, 48%, 0.16);
|
||||||
|
--color-gradient-negative: hsla(0, 100%, 66%, 0.16);
|
||||||
|
--color-accent-faded: hsla(var(--theme-classic-hue-purple), 100%, 70%, 0.16);
|
||||||
|
|
||||||
|
--color-risk-low: var(--theme-classic-color-green);
|
||||||
|
--color-risk-medium: var(--theme-classic-color-yellow);
|
||||||
|
--color-risk-high: var(--theme-classic-color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme: Light */
|
||||||
|
:root.theme-light {
|
||||||
|
/* Constants */
|
||||||
|
|
||||||
|
--theme-light-hue-purple: 234;
|
||||||
|
--theme-light-color-green: hsl(159, 67%, 39%);
|
||||||
|
--theme-light-color-yellow: hsl(36, 100%, 64%);
|
||||||
|
--theme-light-color-red: hsl(360, 73%, 61%);
|
||||||
|
|
||||||
|
/* Parameters */
|
||||||
|
|
||||||
|
--layer-base-hue: var(--theme-light-hue-purple);
|
||||||
|
--layer-base-saturation: 0%;
|
||||||
|
--layer-base-lightness: 86%;
|
||||||
|
|
||||||
|
--color-text-0: hsl(0, 0%, 55%);
|
||||||
|
--color-text-1: hsl(0, 0%, 40%);
|
||||||
|
--color-text-2: hsl(0, 0%, 5%);
|
||||||
|
|
||||||
|
--color-accent: hsl(var(--theme-light-hue-purple), 100%, 70%);
|
||||||
|
--color-positive: var(--theme-light-color-green);
|
||||||
|
--color-negative: var(--theme-light-color-red);
|
||||||
|
--color-success: var(--theme-light-color-green);
|
||||||
|
--color-warning: var(--theme-light-color-yellow);
|
||||||
|
--color-error: var(--theme-light-color-red);
|
||||||
|
--color-favorite: var(--theme-light-color-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme: Dark */
|
||||||
|
:root.theme-dark {
|
||||||
|
/* Constants */
|
||||||
|
|
||||||
|
--theme-dark-hue-purple: 240;
|
||||||
|
--theme-dark-color-green: hsl(159, 67%, 39%);
|
||||||
|
--theme-dark-color-yellow: hsl(36, 100%, 64%);
|
||||||
|
--theme-dark-color-red: hsl(360, 73%, 61%);
|
||||||
|
|
||||||
|
/* Parameters */
|
||||||
|
|
||||||
|
--layer-base-hue: 0;
|
||||||
|
--layer-base-saturation: 0%;
|
||||||
|
--layer-base-lightness: 16%;
|
||||||
|
|
||||||
|
--color-text-0: hsl(0, 0%, 47%);
|
||||||
|
--color-text-1: hsl(0, 0%, 67%);
|
||||||
|
--color-text-2: hsl(0, 0%, 100%);
|
||||||
|
|
||||||
|
--color-accent: hsl(var(--theme-dark-hue-purple), 100%, 70%);
|
||||||
|
--color-positive: var(--theme-dark-color-green);
|
||||||
|
--color-negative: var(--theme-dark-color-red);
|
||||||
|
--color-success: var(--theme-dark-color-green);
|
||||||
|
--color-warning: var(--theme-dark-color-yellow);
|
||||||
|
--color-error: var(--theme-dark-color-red);
|
||||||
|
--color-favorite: var(--theme-dark-color-yellow);
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { type RiskLevels } from '@/constants/abacus';
|
||||||
|
|
||||||
|
import { AppTheme } from '@/state/configs';
|
||||||
|
|
||||||
|
const BaseElements = {
|
||||||
|
[AppTheme.Classic]: {
|
||||||
|
hue: 240,
|
||||||
|
saturation: 20,
|
||||||
|
lightness: 16,
|
||||||
|
},
|
||||||
|
[AppTheme.Dark]: {
|
||||||
|
hue: 0,
|
||||||
|
saturation: 0,
|
||||||
|
lightness: 16,
|
||||||
|
},
|
||||||
|
[AppTheme.Light]: {
|
||||||
|
hue: 234,
|
||||||
|
saturation: 0,
|
||||||
|
lightness: 86,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const LayerColors = ({ theme }: { theme: AppTheme }) => {
|
||||||
|
const { hue, saturation, lightness } = BaseElements[theme];
|
||||||
|
|
||||||
|
return {
|
||||||
|
layer0: `hsl(${hue}, ${saturation}%, ${lightness - 12}%)`,
|
||||||
|
layer1: `hsl(${hue}, ${saturation}%, ${lightness - 8}%)`,
|
||||||
|
layer2: `hsl(${hue}, ${saturation}%, ${lightness - 4}%)`,
|
||||||
|
layer3: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
|
||||||
|
layer4: `hsl(${hue}, ${saturation}%, ${lightness + 4}%)`,
|
||||||
|
layer5: `hsl(${hue}, ${saturation - 2}%, ${lightness + 7}%)`,
|
||||||
|
layer6: `hsl(${hue}, ${saturation - 4}%, ${lightness + 10}%)`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccentColor = ({ theme }: { theme: AppTheme }) => {
|
||||||
|
const purpleHue = {
|
||||||
|
Classic: 240,
|
||||||
|
Dark: 240,
|
||||||
|
Light: 234,
|
||||||
|
}[theme];
|
||||||
|
|
||||||
|
return {
|
||||||
|
accent: `hsl(${purpleHue}, 100%, 70%)`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Colors = {
|
||||||
|
[AppTheme.Classic]: {
|
||||||
|
...LayerColors({ theme: AppTheme.Classic }),
|
||||||
|
text0: 'hsl(245, 11%, 55%)',
|
||||||
|
text1: 'hsl(244, 18%, 81%)',
|
||||||
|
text2: 'hsl(240, 43%, 99%)',
|
||||||
|
|
||||||
|
...AccentColor({ theme: AppTheme.Classic }),
|
||||||
|
green: 'hsl(159, 67%, 39%)',
|
||||||
|
yellow: 'hsl(36, 100%, 64%)',
|
||||||
|
red: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
positive: 'hsl(159, 67%, 39%)',
|
||||||
|
negative: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
success: 'hsl(159, 67%, 39%)',
|
||||||
|
warning: 'hsl(36, 100%, 64%)',
|
||||||
|
error: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
favorite: 'hsl(36, 100%, 64%)',
|
||||||
|
},
|
||||||
|
[AppTheme.Dark]: {
|
||||||
|
...LayerColors({ theme: AppTheme.Dark }),
|
||||||
|
text0: 'hsl(0, 0%, 47%)',
|
||||||
|
text1: 'hsl(0, 0%, 67%)',
|
||||||
|
text2: 'hsl(0, 0%, 100%)',
|
||||||
|
|
||||||
|
...AccentColor({ theme: AppTheme.Dark }),
|
||||||
|
green: 'hsl(159, 67%, 39%)',
|
||||||
|
yellow: 'hsl(36, 100%, 64%)',
|
||||||
|
red: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
positive: 'hsl(159, 67%, 39%)',
|
||||||
|
negative: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
success: 'hsl(159, 67%, 39%)',
|
||||||
|
warning: 'hsl(36, 100%, 64%)',
|
||||||
|
error: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
favorite: 'hsl(36, 100%, 64%)',
|
||||||
|
},
|
||||||
|
[AppTheme.Light]: {
|
||||||
|
...LayerColors({ theme: AppTheme.Light }),
|
||||||
|
text0: 'hsl(0, 0%, 55%)',
|
||||||
|
text1: 'hsl(0, 0%, 40%)',
|
||||||
|
text2: 'hsl(0, 0%, 5%)',
|
||||||
|
|
||||||
|
...AccentColor({ theme: AppTheme.Light }),
|
||||||
|
green: 'hsl(159, 67%, 39%)',
|
||||||
|
yellow: 'hsl(36, 100%, 64%)',
|
||||||
|
red: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
positive: 'hsl(159, 67%, 39%)',
|
||||||
|
negative: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
success: 'hsl(159, 67%, 39%)',
|
||||||
|
warning: 'hsl(36, 100%, 64%)',
|
||||||
|
error: 'hsl(360, 73%, 61%)',
|
||||||
|
|
||||||
|
favorite: 'hsl(36, 100%, 64%)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
|
||||||
|
({
|
||||||
|
low: css`
|
||||||
|
color: var(--color-risk-low);
|
||||||
|
`,
|
||||||
|
medium: css`
|
||||||
|
color: var(--color-risk-medium);
|
||||||
|
`,
|
||||||
|
high: css`
|
||||||
|
color: var(--color-risk-high);
|
||||||
|
`,
|
||||||
|
}[riskLevel.name]);
|
||||||
@@ -21,7 +21,7 @@ export const formMixins: Record<
|
|||||||
--input-radius: 0.5em;
|
--input-radius: 0.5em;
|
||||||
--input-height: var(--form-input-height);
|
--input-height: var(--form-input-height);
|
||||||
--input-width: 100%;
|
--input-width: 100%;
|
||||||
--input-backgroundColor: ${({ theme }) => theme.inputBackground};
|
--input-backgroundColor: var(--color-layer-4);
|
||||||
--input-borderColor: var(--color-layer-6);
|
--input-borderColor: var(--color-layer-6);
|
||||||
|
|
||||||
${layoutMixins.row}
|
${layoutMixins.row}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { createGlobalStyle } from 'styled-components';
|
|
||||||
|
|
||||||
export const GlobalStyle = createGlobalStyle`
|
|
||||||
:root {
|
|
||||||
--color-layer-0: ${({ theme }) => theme.layer0};
|
|
||||||
--color-layer-1: ${({ theme }) => theme.layer1};
|
|
||||||
--color-layer-2: ${({ theme }) => theme.layer2};
|
|
||||||
--color-layer-3: ${({ theme }) => theme.layer3};
|
|
||||||
--color-layer-4: ${({ theme }) => theme.layer4};
|
|
||||||
--color-layer-5: ${({ theme }) => theme.layer5};
|
|
||||||
--color-layer-6: ${({ theme }) => theme.layer6};
|
|
||||||
--color-layer-7: ${({ theme }) => theme.layer7};
|
|
||||||
|
|
||||||
--color-border: ${({ theme }) => theme.borderDefault};
|
|
||||||
--color-border-white: ${({ theme }) => theme.borderButton};
|
|
||||||
--color-border-red: ${({ theme }) => theme.borderDestructive};
|
|
||||||
|
|
||||||
--color-text-0: ${({ theme }) => theme.textTertiary};
|
|
||||||
--color-text-1: ${({ theme }) => theme.textSecondary};
|
|
||||||
--color-text-2: ${({ theme }) => theme.textPrimary};
|
|
||||||
|
|
||||||
--color-gradient-base-0: ${({ theme }) => theme.gradientBase0};
|
|
||||||
--color-gradient-base-1: ${({ theme }) => theme.gradientBase1};
|
|
||||||
|
|
||||||
--color-accent: ${({ theme }) => theme.accent};
|
|
||||||
--color-accent-faded: ${({ theme }) => theme.accentFaded};
|
|
||||||
--color-favorite: ${({ theme }) => theme.favorite};
|
|
||||||
|
|
||||||
--color-success: ${({ theme }) => theme.success};
|
|
||||||
--color-warning: ${({ theme }) => theme.warning};
|
|
||||||
--color-error: ${({ theme }) => theme.error};
|
|
||||||
|
|
||||||
--color-positive: ${({ theme }) => theme.positive};
|
|
||||||
--color-negative: ${({ theme }) => theme.negative};
|
|
||||||
--color-gradient-positive: ${({ theme }) => theme.positiveFaded};
|
|
||||||
--color-gradient-negative: ${({ theme }) => theme.negativeFaded};
|
|
||||||
|
|
||||||
--color-risk-low: ${({ theme }) => theme.riskLow};
|
|
||||||
--color-risk-medium: ${({ theme }) => theme.riskMedium};
|
|
||||||
--color-risk-high: ${({ theme }) => theme.riskHigh};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import { AppTheme } from '@/state/configs';
|
|
||||||
import type { ThemeColors } from '@/constants/styles/colors';
|
|
||||||
import { ColorToken, OpacityToken } from '@/constants/styles/base';
|
|
||||||
import { generateFadedColorVariant } from '@/lib/styles';
|
|
||||||
|
|
||||||
const ClassicTheme: ThemeColors = {
|
|
||||||
layer0: ColorToken.GrayBlue7,
|
|
||||||
layer1: ColorToken.GrayBlue6,
|
|
||||||
layer2: ColorToken.GrayBlue5,
|
|
||||||
layer3: ColorToken.GrayBlue4,
|
|
||||||
layer4: ColorToken.GrayBlue3,
|
|
||||||
layer5: ColorToken.GrayBlue2,
|
|
||||||
layer6: ColorToken.GrayBlue1,
|
|
||||||
layer7: ColorToken.GrayBlue0,
|
|
||||||
|
|
||||||
borderDefault: ColorToken.GrayBlue2,
|
|
||||||
borderDestructive: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity20),
|
|
||||||
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
|
|
||||||
|
|
||||||
textPrimary: ColorToken.LightGray2,
|
|
||||||
textSecondary: ColorToken.GrayPurple1,
|
|
||||||
textTertiary: ColorToken.GrayPurple2,
|
|
||||||
|
|
||||||
gradientBase0: ColorToken.DarkGray9,
|
|
||||||
gradientBase1: ColorToken.GrayBlue2,
|
|
||||||
|
|
||||||
accent: ColorToken.Purple1,
|
|
||||||
accentFaded: generateFadedColorVariant(ColorToken.Purple1, OpacityToken.Opacity16),
|
|
||||||
favorite: ColorToken.Yellow0,
|
|
||||||
|
|
||||||
success: ColorToken.Green1,
|
|
||||||
warning: ColorToken.Yellow0,
|
|
||||||
error: ColorToken.Red2,
|
|
||||||
|
|
||||||
positive: ColorToken.Green1,
|
|
||||||
negative: ColorToken.Red2,
|
|
||||||
positiveFaded: generateFadedColorVariant(ColorToken.Green1, OpacityToken.Opacity16),
|
|
||||||
negativeFaded: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity16),
|
|
||||||
|
|
||||||
riskLow: ColorToken.Green1,
|
|
||||||
riskMedium: ColorToken.Yellow0,
|
|
||||||
riskHigh: ColorToken.Red2,
|
|
||||||
|
|
||||||
logoFill: ColorToken.White,
|
|
||||||
profileYellow: ColorToken.Yellow1,
|
|
||||||
profileRed: ColorToken.Red2,
|
|
||||||
|
|
||||||
inputBackground: ColorToken.GrayBlue3,
|
|
||||||
popoverBackground: generateFadedColorVariant(ColorToken.GrayBlue4, OpacityToken.Opacity90),
|
|
||||||
switchThumbActiveBackground: ColorToken.White,
|
|
||||||
toggleBackground: ColorToken.GrayBlue3,
|
|
||||||
tooltipBackground: generateFadedColorVariant(ColorToken.GrayBlue3, OpacityToken.Opacity66),
|
|
||||||
};
|
|
||||||
|
|
||||||
const DarkTheme: ThemeColors = {
|
|
||||||
layer0: ColorToken.Black,
|
|
||||||
layer1: ColorToken.DarkGray11,
|
|
||||||
layer2: ColorToken.DarkGray13,
|
|
||||||
layer3: ColorToken.DarkGray10,
|
|
||||||
layer4: ColorToken.DarkGray6,
|
|
||||||
layer5: ColorToken.DarkGray5,
|
|
||||||
layer6: ColorToken.DarkGray4,
|
|
||||||
layer7: ColorToken.DarkGray2,
|
|
||||||
|
|
||||||
borderDefault: ColorToken.DarkGray4,
|
|
||||||
borderDestructive: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity20),
|
|
||||||
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
|
|
||||||
|
|
||||||
textPrimary: ColorToken.LightGray0,
|
|
||||||
textSecondary: ColorToken.MediumGray0,
|
|
||||||
textTertiary: ColorToken.DarkGray0,
|
|
||||||
|
|
||||||
gradientBase0: ColorToken.DarkGray8,
|
|
||||||
gradientBase1: ColorToken.DarkGray5,
|
|
||||||
|
|
||||||
accent: ColorToken.Purple0,
|
|
||||||
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
|
|
||||||
favorite: ColorToken.Yellow0,
|
|
||||||
|
|
||||||
success: ColorToken.Green0,
|
|
||||||
warning: ColorToken.Yellow0,
|
|
||||||
error: ColorToken.Red0,
|
|
||||||
|
|
||||||
positive: ColorToken.Green0,
|
|
||||||
negative: ColorToken.Red0,
|
|
||||||
positiveFaded: generateFadedColorVariant(ColorToken.Green0, OpacityToken.Opacity16),
|
|
||||||
negativeFaded: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity16),
|
|
||||||
|
|
||||||
riskLow: ColorToken.Green0,
|
|
||||||
riskMedium: ColorToken.Yellow0,
|
|
||||||
riskHigh: ColorToken.Red0,
|
|
||||||
|
|
||||||
logoFill: ColorToken.White,
|
|
||||||
profileYellow: ColorToken.Yellow1,
|
|
||||||
profileRed: ColorToken.Red2,
|
|
||||||
|
|
||||||
inputBackground: ColorToken.DarkGray6,
|
|
||||||
popoverBackground: generateFadedColorVariant(ColorToken.DarkGray8, OpacityToken.Opacity90),
|
|
||||||
switchThumbActiveBackground: ColorToken.White,
|
|
||||||
toggleBackground: ColorToken.DarkGray6,
|
|
||||||
tooltipBackground: generateFadedColorVariant(ColorToken.DarkGray6, OpacityToken.Opacity66),
|
|
||||||
};
|
|
||||||
|
|
||||||
const LightTheme: ThemeColors = {
|
|
||||||
layer0: ColorToken.White,
|
|
||||||
layer1: ColorToken.LightGray6,
|
|
||||||
layer2: ColorToken.White,
|
|
||||||
layer3: ColorToken.LightGray1,
|
|
||||||
layer4: ColorToken.White,
|
|
||||||
layer5: ColorToken.LightGray4,
|
|
||||||
layer6: ColorToken.LightGray9,
|
|
||||||
layer7: ColorToken.MediumGray1,
|
|
||||||
|
|
||||||
borderDefault: ColorToken.LightGray10,
|
|
||||||
borderDestructive: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity20),
|
|
||||||
borderButton: generateFadedColorVariant(ColorToken.Black, OpacityToken.Opacity20),
|
|
||||||
|
|
||||||
textPrimary: ColorToken.DarkGray12,
|
|
||||||
textSecondary: ColorToken.DarkGray3,
|
|
||||||
textTertiary: ColorToken.DarkGray1,
|
|
||||||
|
|
||||||
gradientBase0: ColorToken.LightGray8,
|
|
||||||
gradientBase1: ColorToken.LightGray5,
|
|
||||||
|
|
||||||
accent: ColorToken.Purple0,
|
|
||||||
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
|
|
||||||
favorite: ColorToken.Yellow0,
|
|
||||||
|
|
||||||
success: ColorToken.Green2,
|
|
||||||
warning: ColorToken.Yellow0,
|
|
||||||
error: ColorToken.Red1,
|
|
||||||
|
|
||||||
positive: ColorToken.Green2,
|
|
||||||
negative: ColorToken.Red1,
|
|
||||||
positiveFaded: generateFadedColorVariant(ColorToken.Green2, OpacityToken.Opacity16),
|
|
||||||
negativeFaded: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity16),
|
|
||||||
|
|
||||||
riskLow: ColorToken.Green2,
|
|
||||||
riskMedium: ColorToken.Yellow0,
|
|
||||||
riskHigh: ColorToken.Red1,
|
|
||||||
|
|
||||||
logoFill: ColorToken.Black,
|
|
||||||
profileYellow: ColorToken.Yellow1,
|
|
||||||
profileRed: ColorToken.Red2,
|
|
||||||
|
|
||||||
inputBackground: ColorToken.White,
|
|
||||||
popoverBackground: generateFadedColorVariant(ColorToken.LightGray8, OpacityToken.Opacity90),
|
|
||||||
switchThumbActiveBackground: ColorToken.White,
|
|
||||||
toggleBackground: ColorToken.LightGray4,
|
|
||||||
tooltipBackground: generateFadedColorVariant(ColorToken.LightGray7, OpacityToken.Opacity66),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Themes = {
|
|
||||||
[AppTheme.Classic]: ClassicTheme,
|
|
||||||
[AppTheme.Dark]: DarkTheme,
|
|
||||||
[AppTheme.Light]: LightTheme,
|
|
||||||
};
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { forwardRef } from 'react';
|
|
||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
|
||||||
|
|
||||||
import type { Nullable } from '@/constants/abacus';
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { TOKEN_DECIMALS } from '@/constants/numbers';
|
|
||||||
import { ORDERBOOK_ROW_HEIGHT } from '@/constants/orderbook';
|
|
||||||
import { useStringGetter } from '@/hooks';
|
|
||||||
|
|
||||||
import { Output, OutputType } from '@/components/Output';
|
|
||||||
import { WithTooltip } from '@/components/WithTooltip';
|
|
||||||
|
|
||||||
type StyleProps = {
|
|
||||||
side?: 'top' | 'bottom';
|
|
||||||
};
|
|
||||||
|
|
||||||
type ElementProps = {
|
|
||||||
spread?: Nullable<number>;
|
|
||||||
spreadPercent?: Nullable<number>;
|
|
||||||
tickSizeDecimals?: Nullable<number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OrderbookRow = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
height: ${ORDERBOOK_ROW_HEIGHT}px;
|
|
||||||
min-height: ${ORDERBOOK_ROW_HEIGHT}px;
|
|
||||||
position: relative;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
font: var(--font-mini-book);
|
|
||||||
|
|
||||||
> span {
|
|
||||||
flex: 1 1 0%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SpreadRow = forwardRef<HTMLDivElement, StyleProps & ElementProps>(
|
|
||||||
({ side, spread, spreadPercent, tickSizeDecimals = TOKEN_DECIMALS }, ref) => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled.SpreadRow ref={ref} side={side}>
|
|
||||||
<span>
|
|
||||||
<WithTooltip tooltip="spread">
|
|
||||||
{stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD })}
|
|
||||||
</WithTooltip>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<Output type={OutputType.Number} value={spread} fractionDigits={tickSizeDecimals} />
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<Output type={OutputType.Percent} value={spreadPercent} />
|
|
||||||
</span>
|
|
||||||
</Styled.SpreadRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.SpreadRow = styled(OrderbookRow)<{ side?: 'top' | 'bottom' }>`
|
|
||||||
height: 2rem;
|
|
||||||
border-top: var(--border);
|
|
||||||
border-bottom: var(--border);
|
|
||||||
span {
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
${({ side }) =>
|
|
||||||
side &&
|
|
||||||
{
|
|
||||||
top: css`
|
|
||||||
border-top: none;
|
|
||||||
`,
|
|
||||||
bottom: css`
|
|
||||||
border-bottom: none;
|
|
||||||
`,
|
|
||||||
}[side]}
|
|
||||||
`;
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
import { forwardRef, useCallback, useMemo, useRef } from 'react';
|
|
||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
|
||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { Nullable, type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { ORDERBOOK_HEIGHT, ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
|
|
||||||
import { useStringGetter } from '@/hooks';
|
|
||||||
|
|
||||||
import {
|
|
||||||
useCalculateOrderbookData,
|
|
||||||
useCenterOrderbook,
|
|
||||||
useDrawOrderbook,
|
|
||||||
useSpreadRowScrollListener,
|
|
||||||
} from '@/hooks/Orderbook';
|
|
||||||
|
|
||||||
import { Canvas } from '@/components/Canvas';
|
|
||||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
|
||||||
import { Tag } from '@/components/Tag';
|
|
||||||
|
|
||||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
|
||||||
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
|
|
||||||
import { setTradeFormInputs } from '@/state/inputs';
|
|
||||||
import { getCurrentInput } from '@/state/inputsSelectors';
|
|
||||||
|
|
||||||
import { OrderbookRow, SpreadRow } from './OrderbookRow';
|
|
||||||
|
|
||||||
type ElementProps = {
|
|
||||||
maxRowsPerSide?: number;
|
|
||||||
layout?: 'vertical' | 'horizontal';
|
|
||||||
};
|
|
||||||
|
|
||||||
type StyleProps = {
|
|
||||||
hideHeader?: boolean;
|
|
||||||
histogramSide?: 'left' | 'right';
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CanvasOrderbook = forwardRef(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
histogramSide = 'right',
|
|
||||||
maxRowsPerSide = ORDERBOOK_MAX_ROWS_PER_SIDE,
|
|
||||||
}: ElementProps & StyleProps,
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const { asks, bids, hasOrderbook, histogramRange, spread, spreadPercent } =
|
|
||||||
useCalculateOrderbookData({
|
|
||||||
maxRowsPerSide,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
const currentMarket = useSelector(getCurrentMarketId) ?? '';
|
|
||||||
const currentMarketConfig = useSelector(getCurrentMarketConfig, shallowEqual);
|
|
||||||
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
|
||||||
|
|
||||||
const { tickSizeDecimals = 2 } = currentMarketConfig ?? {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slice asks and bids to maxRowsPerSide using empty rows
|
|
||||||
*/
|
|
||||||
const { asksSlice, bidsSlice } = useMemo(() => {
|
|
||||||
let asksSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
|
|
||||||
const emptyAskRows =
|
|
||||||
asks.length < maxRowsPerSide
|
|
||||||
? new Array<undefined>(maxRowsPerSide - asks.length).fill(undefined)
|
|
||||||
: [];
|
|
||||||
asksSlice = [...emptyAskRows, ...asks.reverse()];
|
|
||||||
|
|
||||||
let bidsSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
|
|
||||||
const emptyBidRows =
|
|
||||||
bids.length < maxRowsPerSide
|
|
||||||
? new Array<undefined>(maxRowsPerSide - bids.length).fill(undefined)
|
|
||||||
: [];
|
|
||||||
bidsSlice = [...bids, ...emptyBidRows];
|
|
||||||
|
|
||||||
return {
|
|
||||||
asksSlice,
|
|
||||||
bidsSlice,
|
|
||||||
};
|
|
||||||
}, [asks, bids]);
|
|
||||||
|
|
||||||
const orderbookRef = useRef<HTMLDivElement>(null);
|
|
||||||
useCenterOrderbook({ orderbookRef, marketId: currentMarket });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display top or bottom spreadRow when center spreadRow is off screen
|
|
||||||
*/
|
|
||||||
const spreadRowRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const displaySide = useSpreadRowScrollListener({
|
|
||||||
orderbookRef,
|
|
||||||
spreadRowRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Row action
|
|
||||||
*/
|
|
||||||
const currentInput = useSelector(getCurrentInput);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const onRowAction = useCallback(
|
|
||||||
(price: Nullable<number>) => {
|
|
||||||
if (currentInput === 'trade' && price) {
|
|
||||||
dispatch(setTradeFormInputs({ limitPriceInput: price?.toString() }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentInput]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { canvasRef: asksCanvasRef } = useDrawOrderbook({
|
|
||||||
data: [...asksSlice].reverse(),
|
|
||||||
histogramRange,
|
|
||||||
histogramSide,
|
|
||||||
side: 'ask',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { canvasRef: bidsCanvasRef } = useDrawOrderbook({
|
|
||||||
data: bidsSlice,
|
|
||||||
histogramRange,
|
|
||||||
histogramSide,
|
|
||||||
side: 'bid',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled.OrderbookContainer ref={ref}>
|
|
||||||
<Styled.OrderbookContent $isLoading={!hasOrderbook}>
|
|
||||||
<Styled.Header>
|
|
||||||
<span>
|
|
||||||
{stringGetter({ key: STRING_KEYS.SIZE })} {id && <Tag>{id}</Tag>}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{stringGetter({ key: STRING_KEYS.PRICE })} <Tag>USD</Tag>
|
|
||||||
</span>
|
|
||||||
<span>{stringGetter({ key: STRING_KEYS.MINE })}</span>
|
|
||||||
</Styled.Header>
|
|
||||||
|
|
||||||
{displaySide === 'top' && (
|
|
||||||
<Styled.SpreadRow
|
|
||||||
side="top"
|
|
||||||
spread={spread}
|
|
||||||
spreadPercent={spreadPercent}
|
|
||||||
tickSizeDecimals={tickSizeDecimals}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Styled.OrderbookWrapper ref={orderbookRef}>
|
|
||||||
<Styled.OrderbookSideContainer $side="asks">
|
|
||||||
<Styled.HoverRows $bottom>
|
|
||||||
{asksSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
|
|
||||||
row ? (
|
|
||||||
<Styled.Row
|
|
||||||
key={idx}
|
|
||||||
title={`${row.price}`}
|
|
||||||
onClick={() => {
|
|
||||||
onRowAction(row.price);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Styled.Row key={idx} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Styled.HoverRows>
|
|
||||||
<Styled.OrderbookCanvas ref={asksCanvasRef} width="100%" height="100%" />
|
|
||||||
</Styled.OrderbookSideContainer>
|
|
||||||
|
|
||||||
<SpreadRow
|
|
||||||
ref={spreadRowRef}
|
|
||||||
spread={spread?.toNumber()}
|
|
||||||
spreadPercent={spreadPercent}
|
|
||||||
tickSizeDecimals={tickSizeDecimals}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Styled.OrderbookSideContainer $side="bids">
|
|
||||||
<Styled.HoverRows>
|
|
||||||
{bidsSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
|
|
||||||
row ? (
|
|
||||||
<Styled.Row
|
|
||||||
key={idx}
|
|
||||||
title={`${row.price}`}
|
|
||||||
onClick={
|
|
||||||
row?.price
|
|
||||||
? () => {
|
|
||||||
onRowAction(row.price);
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Styled.Row key={idx} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Styled.HoverRows>
|
|
||||||
<Styled.OrderbookCanvas ref={bidsCanvasRef} width="100%" height="100%" />
|
|
||||||
</Styled.OrderbookSideContainer>
|
|
||||||
</Styled.OrderbookWrapper>
|
|
||||||
{displaySide === 'bottom' && (
|
|
||||||
<Styled.SpreadRow
|
|
||||||
side="bottom"
|
|
||||||
spread={spread}
|
|
||||||
spreadPercent={spreadPercent}
|
|
||||||
tickSizeDecimals={tickSizeDecimals}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Styled.OrderbookContent>
|
|
||||||
{!hasOrderbook && <LoadingSpace id="canvas-orderbook" />}
|
|
||||||
</Styled.OrderbookContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.OrderbookContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1 1 0%;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.OrderbookContent = styled.div<{ $isLoading?: boolean }>`
|
|
||||||
max-height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
${({ $isLoading }) => $isLoading && 'flex: 1;'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Header = styled(OrderbookRow)`
|
|
||||||
height: 2rem;
|
|
||||||
border-bottom: var(--border);
|
|
||||||
color: var(--color-text-0);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.OrderbookWrapper = styled.div`
|
|
||||||
overflow-y: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1 1 0%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.OrderbookSideContainer = styled.div<{ $side: 'bids' | 'asks' }>`
|
|
||||||
min-height: ${ORDERBOOK_HEIGHT}px;
|
|
||||||
${({ $side }) => css`
|
|
||||||
--accent-color: ${$side === 'bids' ? 'var(--color-positive)' : 'var(--color-negative)'};
|
|
||||||
`}
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.OrderbookCanvas = styled(Canvas)`
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
font-feature-settings: var(--fontFeature-monoNumbers);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.HoverRows = styled.div<{ $bottom?: boolean }>`
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
${({ $bottom }) => $bottom && 'bottom: 0;'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Row = styled(OrderbookRow)<{ onClick?: () => void }>`
|
|
||||||
${({ onClick }) =>
|
|
||||||
onClick
|
|
||||||
? css`
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-layer-4);
|
|
||||||
filter: darkness(0.1);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
: css`
|
|
||||||
cursor: default;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.SpreadRow = styled(SpreadRow)`
|
|
||||||
position: absolute;
|
|
||||||
`;
|
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues';
|
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
||||||
|
|
||||||
import { TooltipContent } from '@/components/visx/TooltipContent';
|
import { TooltipContent } from '@/components/visx/TooltipContent';
|
||||||
import { Details } from '@/components/Details';
|
import { Details } from '@/components/Details';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
import { StringGetterFunction } from '@/constants/localization';
|
import { StringGetterFunction } from '@/constants/localization';
|
||||||
|
|
||||||
import { useBreakpoints } from '@/hooks';
|
import { useBreakpoints } from '@/hooks';
|
||||||
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues';
|
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
||||||
|
|
||||||
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
|
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
|
||||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||||
@@ -83,8 +83,18 @@ export const DepthChart = ({
|
|||||||
const { stepSizeDecimals, tickSizeDecimals } =
|
const { stepSizeDecimals, tickSizeDecimals } =
|
||||||
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
|
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
|
||||||
|
|
||||||
const { bids, asks, lowestBid, highestBid, lowestAsk, highestAsk, midMarketPrice, orderbook } =
|
const {
|
||||||
useOrderbookValuesForDepthChart();
|
bids,
|
||||||
|
asks,
|
||||||
|
lowestBid,
|
||||||
|
highestBid,
|
||||||
|
lowestAsk,
|
||||||
|
highestAsk,
|
||||||
|
midMarketPrice,
|
||||||
|
spread,
|
||||||
|
spreadPercent,
|
||||||
|
orderbook,
|
||||||
|
} = useOrderbookValuesForDepthChart();
|
||||||
|
|
||||||
// Chart state
|
// Chart state
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
import { AlertType } from '@/constants/alerts';
|
import { AlertType } from '@/constants/alerts';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
@@ -13,7 +11,7 @@ import { Button } from '@/components/Button';
|
|||||||
import { Icon } from '@/components/Icon';
|
import { Icon } from '@/components/Icon';
|
||||||
import { Link } from '@/components/Link';
|
import { Link } from '@/components/Link';
|
||||||
|
|
||||||
import { useAccounts, useStringGetter, useURLConfigs } from '@/hooks';
|
import { useAccounts, useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
||||||
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
|
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
@@ -27,6 +25,8 @@ export const ChooseWallet = () => {
|
|||||||
|
|
||||||
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
|
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
|
||||||
|
|
||||||
|
const { isMobile } = useBreakpoints();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedWalletType && selectedWalletError && (
|
{selectedWalletType && selectedWalletError && (
|
||||||
@@ -56,7 +56,11 @@ export const ChooseWallet = () => {
|
|||||||
slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />}
|
slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />}
|
||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
>
|
>
|
||||||
<div>{stringGetter({ key: wallets[walletType].stringKey })}</div>
|
<div>
|
||||||
|
{walletType !== WalletType.Privy && import.meta.env.VITE_PRIVY_APP_ID
|
||||||
|
? stringGetter({ key: wallets[walletType].stringKey })
|
||||||
|
: `Socials ${isMobile ? '' : '(Email, SMS, etc.)'}`}
|
||||||
|
</div>
|
||||||
</Styled.WalletButton>
|
</Styled.WalletButton>
|
||||||
))}
|
))}
|
||||||
</Styled.Wallets>
|
</Styled.Wallets>
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export const GenerateKeys = ({
|
|||||||
if (message) {
|
if (message) {
|
||||||
log('GenerateKeys/switchNetwork', error, { walletErrorType });
|
log('GenerateKeys/switchNetwork', error, { walletErrorType });
|
||||||
setError(message);
|
setError(message);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,11 +34,30 @@ export const ChainSelectMenu = ({ label, selectedChain, onSelectChain }: Element
|
|||||||
slotBefore: <Styled.Img src={chain.iconUrl} alt="" />,
|
slotBefore: <Styled.Img src={chain.iconUrl} alt="" />,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const selectedOption = chains.find((item) => item.type === selectedChain);
|
const selectedOption = selectedChain === 'coinbase' ? {
|
||||||
|
stringKey: 'Coinbase',
|
||||||
|
iconUrl: '/wallets/coinbase-wallet.png',
|
||||||
|
} : chains.find((item) => item.type === selectedChain);
|
||||||
|
|
||||||
|
const exchanges = [
|
||||||
|
{
|
||||||
|
value: 'coinbase',
|
||||||
|
label: 'Coinbase',
|
||||||
|
onSelect: () => {
|
||||||
|
onSelectChain('coinbase');
|
||||||
|
},
|
||||||
|
slotBefore: <Styled.Img src="/wallets/coinbase-wallet.png" alt="" />,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchSelectMenu
|
<SearchSelectMenu
|
||||||
items={[
|
items={[
|
||||||
|
{
|
||||||
|
group: 'exchanges',
|
||||||
|
groupLabel: 'Exchanges',
|
||||||
|
items: exchanges,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
group: 'chains',
|
group: 'chains',
|
||||||
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),
|
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import type { EvmAddress } from '@/constants/wallets';
|
|||||||
import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks';
|
import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks';
|
||||||
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
|
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
|
||||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||||
import { useWalletConnection } from '@/hooks/useWalletConnection';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { formMixins } from '@/styles/formMixins';
|
import { formMixins } from '@/styles/formMixins';
|
||||||
@@ -39,12 +38,14 @@ import abacusStateManager from '@/lib/abacus';
|
|||||||
import { MustBigNumber } from '@/lib/numbers';
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
import { getNobleChainId, NATIVE_TOKEN_ADDRESS } from '@/lib/squid';
|
import { getNobleChainId, NATIVE_TOKEN_ADDRESS } from '@/lib/squid';
|
||||||
import { log } from '@/lib/telemetry';
|
import { log } from '@/lib/telemetry';
|
||||||
import { parseWalletError } from '@/lib/wallet';
|
import { parseWalletError, truncateAddress } from '@/lib/wallet';
|
||||||
|
|
||||||
import { ChainSelectMenu } from './ChainSelectMenu';
|
import { ChainSelectMenu } from './ChainSelectMenu';
|
||||||
import { TokenSelectMenu } from './TokenSelectMenu';
|
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||||
|
|
||||||
import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
|
import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
|
||||||
|
import { QrCode } from '@/components/QrCode';
|
||||||
|
import { CopyButton } from '@/components/CopyButton';
|
||||||
|
|
||||||
type DepositFormProps = {
|
type DepositFormProps = {
|
||||||
onDeposit?: () => void;
|
onDeposit?: () => void;
|
||||||
@@ -56,8 +57,9 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { selectedNetwork } = useSelectedNetwork();
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
|
const [isCoinbase, setIsCoinbase] = useState(false);
|
||||||
|
|
||||||
const { evmAddress, signerWagmi, publicClientWagmi } = useAccounts();
|
const { evmAddress, signerWagmi, publicClientWagmi, nobleAddress } = useAccounts();
|
||||||
|
|
||||||
const { addTransferNotification } = useLocalNotifications();
|
const { addTransferNotification } = useLocalNotifications();
|
||||||
|
|
||||||
@@ -121,7 +123,11 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abacusStateManager.resetInputState();
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -130,7 +136,10 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
const onSelectChain = useCallback((chain: string) => {
|
const onSelectChain = useCallback((chain: string) => {
|
||||||
if (chain) {
|
if (chain === 'coinbase') {
|
||||||
|
setIsCoinbase(true);
|
||||||
|
} else if (chain) {
|
||||||
|
setIsCoinbase(false);
|
||||||
abacusStateManager.clearTransferInputValues();
|
abacusStateManager.clearTransferInputValues();
|
||||||
abacusStateManager.setTransferValue({
|
abacusStateManager.setTransferValue({
|
||||||
field: TransferInputField.chain,
|
field: TransferInputField.chain,
|
||||||
@@ -232,10 +241,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
throw new Error('Missing request payload');
|
throw new Error('Missing request payload');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
|
|
||||||
throw new Error('Noble RPC endpoint unaccessible');
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
await validateTokenApproval();
|
await validateTokenApproval();
|
||||||
@@ -365,11 +370,43 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
return <LoadingSpace id="DepositForm" />;
|
return <LoadingSpace id="DepositForm" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [requireUserActionInWallet, setRequireUserActionInWallet] = useState(false);
|
if (isCoinbase) {
|
||||||
|
return (
|
||||||
|
<Styled.Form onSubmit={onSubmit}>
|
||||||
|
<ChainSelectMenu
|
||||||
|
selectedChain={isCoinbase ? 'coinbase' : chainIdStr || undefined}
|
||||||
|
onSelectChain={onSelectChain}
|
||||||
|
/>
|
||||||
|
{nobleAddress && (
|
||||||
|
<>
|
||||||
|
<Styled.ReceiveQr
|
||||||
|
side="bottom"
|
||||||
|
detailItems={[
|
||||||
|
{
|
||||||
|
key: 'nobleAddress',
|
||||||
|
label: "Noble Address",
|
||||||
|
value: nobleAddress,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<QrCode size={432} value={nobleAddress} />
|
||||||
|
</Styled.ReceiveQr>
|
||||||
|
<p>
|
||||||
|
* This address only supports transfers of USDC to the Noble chain.
|
||||||
|
</p>
|
||||||
|
<CopyButton value={nobleAddress} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Styled.Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Form onSubmit={onSubmit}>
|
<Styled.Form onSubmit={onSubmit}>
|
||||||
<ChainSelectMenu selectedChain={chainIdStr || undefined} onSelectChain={onSelectChain} />
|
<ChainSelectMenu
|
||||||
|
selectedChain={isCoinbase ? 'coinbase' : chainIdStr || undefined}
|
||||||
|
onSelectChain={onSelectChain}
|
||||||
|
/>
|
||||||
<TokenSelectMenu selectedToken={sourceToken || undefined} onSelectToken={onSelectToken} />
|
<TokenSelectMenu selectedToken={sourceToken || undefined} onSelectToken={onSelectToken} />
|
||||||
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
|
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
|
||||||
<FormInput
|
<FormInput
|
||||||
@@ -385,11 +422,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
/>
|
/>
|
||||||
</Styled.WithDetailsReceipt>
|
</Styled.WithDetailsReceipt>
|
||||||
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
|
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
|
||||||
{requireUserActionInWallet && (
|
|
||||||
<AlertMessage type={AlertType.Warning}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.CHECK_WALLET_FOR_REQUEST })}
|
|
||||||
</AlertMessage>
|
|
||||||
)}
|
|
||||||
<Styled.Footer>
|
<Styled.Footer>
|
||||||
<DepositButtonAndReceipt
|
<DepositButtonAndReceipt
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
@@ -398,8 +431,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
setSlippage={onSetSlippage}
|
setSlippage={onSetSlippage}
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
sourceToken={sourceToken || undefined}
|
sourceToken={sourceToken || undefined}
|
||||||
setRequireUserActionInWallet={setRequireUserActionInWallet}
|
|
||||||
setError={setError}
|
|
||||||
/>
|
/>
|
||||||
</Styled.Footer>
|
</Styled.Footer>
|
||||||
</Styled.Form>
|
</Styled.Form>
|
||||||
@@ -436,3 +467,13 @@ Styled.TransactionInfo = styled.span`
|
|||||||
Styled.FormInputButton = styled(Button)`
|
Styled.FormInputButton = styled(Button)`
|
||||||
${formMixins.inputInnerButton}
|
${formMixins.inputInnerButton}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.Exchange = styled.div`
|
||||||
|
${layoutMixins.flexColumn}
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.ReceiveQr = styled(WithDetailsReceipt)`
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { type Dispatch, type SetStateAction, useState, type ReactNode, useEffect } from 'react';
|
import { type Dispatch, type ReactNode, type SetStateAction, useState, useMemo } from 'react';
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import type { RouteData } from '@0xsquid/sdk';
|
import type { RouteData } from '@0xsquid/sdk';
|
||||||
|
import { formatUnits } from 'viem';
|
||||||
|
|
||||||
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
|
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||||
|
|
||||||
@@ -11,7 +12,6 @@ import { NumberSign, TOKEN_DECIMALS } from '@/constants/numbers';
|
|||||||
|
|
||||||
import { useStringGetter, useTokenConfigs } from '@/hooks';
|
import { useStringGetter, useTokenConfigs } from '@/hooks';
|
||||||
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
|
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
|
||||||
import { useWalletConnection } from '@/hooks/useWalletConnection';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
@@ -39,8 +39,7 @@ type ElementProps = {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|
||||||
chainId?: string | number;
|
chainId?: string | number;
|
||||||
setError?: Dispatch<SetStateAction<Error | null>>;
|
setError?: Dispatch<SetStateAction<Error | undefined>>;
|
||||||
setRequireUserActionInWallet: (val: boolean) => void;
|
|
||||||
slippage: number;
|
slippage: number;
|
||||||
slotError?: ReactNode;
|
slotError?: ReactNode;
|
||||||
setSlippage: (slippage: number) => void;
|
setSlippage: (slippage: number) => void;
|
||||||
@@ -58,7 +57,6 @@ export const DepositButtonAndReceipt = ({
|
|||||||
isDisabled,
|
isDisabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
slotError,
|
slotError,
|
||||||
setRequireUserActionInWallet,
|
|
||||||
}: ElementProps) => {
|
}: ElementProps) => {
|
||||||
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
|
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
|
||||||
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
|
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
|
||||||
@@ -66,24 +64,6 @@ export const DepositButtonAndReceipt = ({
|
|||||||
|
|
||||||
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
||||||
|
|
||||||
const { connectWallet, isConnectedWagmi } = useWalletConnection();
|
|
||||||
|
|
||||||
const connectWagmi = async () => {
|
|
||||||
try {
|
|
||||||
setRequireUserActionInWallet(false);
|
|
||||||
await connectWallet();
|
|
||||||
setRequireUserActionInWallet(false);
|
|
||||||
} catch (e) {
|
|
||||||
setRequireUserActionInWallet(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isConnectedWagmi && canAccountTrade) {
|
|
||||||
connectWagmi();
|
|
||||||
}
|
|
||||||
}, [isConnectedWagmi, canAccountTrade]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
matchNetwork: switchNetwork,
|
matchNetwork: switchNetwork,
|
||||||
isSwitchingNetwork,
|
isSwitchingNetwork,
|
||||||
@@ -100,7 +80,7 @@ export const DepositButtonAndReceipt = ({
|
|||||||
useSelector(getSubaccountBuyingPower, shallowEqual) || {};
|
useSelector(getSubaccountBuyingPower, shallowEqual) || {};
|
||||||
|
|
||||||
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
|
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
const { usdcLabel } = useTokenConfigs();
|
const { usdcDecimals, usdcLabel } = useTokenConfigs();
|
||||||
|
|
||||||
const feeSubitems: DetailsItem[] = [];
|
const feeSubitems: DetailsItem[] = [];
|
||||||
|
|
||||||
@@ -136,9 +116,7 @@ export const DepositButtonAndReceipt = ({
|
|||||||
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
|
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
value: (
|
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />,
|
||||||
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />
|
|
||||||
),
|
|
||||||
subitems: [
|
subitems: [
|
||||||
{
|
{
|
||||||
key: 'minimum-deposit-amount',
|
key: 'minimum-deposit-amount',
|
||||||
@@ -148,11 +126,7 @@ export const DepositButtonAndReceipt = ({
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
value: (
|
value: (
|
||||||
<Output
|
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} />
|
||||||
type={OutputType.Fiat}
|
|
||||||
fractionDigits={TOKEN_DECIMALS}
|
|
||||||
value={summary?.toAmountMin}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
tooltip: 'minimum-deposit-amount',
|
tooltip: 'minimum-deposit-amount',
|
||||||
},
|
},
|
||||||
@@ -279,8 +253,6 @@ export const DepositButtonAndReceipt = ({
|
|||||||
>
|
>
|
||||||
{!canAccountTrade ? (
|
{!canAccountTrade ? (
|
||||||
<OnboardingTriggerButton size={ButtonSize.Base} />
|
<OnboardingTriggerButton size={ButtonSize.Base} />
|
||||||
) : !isConnectedWagmi ? (
|
|
||||||
<Button action={ButtonAction.Primary} onClick={connectWallet} state={{ isLoading: true }} />
|
|
||||||
) : !isMatchingNetwork ? (
|
) : !isMatchingNetwork ? (
|
||||||
<Button
|
<Button
|
||||||
action={ButtonAction.Primary}
|
action={ButtonAction.Primary}
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ export const WithdrawForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abacusStateManager.resetInputState();
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -144,10 +148,6 @@ export const WithdrawForm = () => {
|
|||||||
throw new Error('Invalid request payload');
|
throw new Error('Invalid request payload');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
|
|
||||||
throw new Error('Noble RPC endpoint unaccessible');
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export const TradeSideToggle = memo(() => {
|
|||||||
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
|
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
|
||||||
--toggle-radius: 0.5em;
|
--toggle-radius: 0.5em;
|
||||||
--toggle-color: var(--color-negative);
|
--toggle-color: var(--color-negative);
|
||||||
--toggle-background: ${({ theme }) => theme.toggleBackground};
|
|
||||||
|
|
||||||
${({ value }) =>
|
${({ value }) =>
|
||||||
value === OrderSide.BUY &&
|
value === OrderSide.BUY &&
|
||||||
@@ -53,7 +52,7 @@ const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
|
|||||||
`}
|
`}
|
||||||
|
|
||||||
border-radius: var(--toggle-radius);
|
border-radius: var(--toggle-radius);
|
||||||
background-color: var(--toggle-background);
|
background-color: var(--color-layer-4);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
|
|||||||
@@ -118,7 +118,11 @@ export const TransferForm = ({
|
|||||||
onChangeAsset(selectedAsset);
|
onChangeAsset(selectedAsset);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abacusStateManager.resetInputState();
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import { openDialog } from '@/state/dialogs';
|
|||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
import { getHydratedTradingData } from '@/lib/orders';
|
import { getHydratedTradingData } from '@/lib/orders';
|
||||||
|
import { tableMixins } from '@/styles/tableMixins';
|
||||||
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
const MOBILE_FILLS_PER_PAGE = 50;
|
const MOBILE_FILLS_PER_PAGE = 50;
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,6 @@ Styled.SizeOutput = styled(Output)<StyleProps>`
|
|||||||
Styled.LiveTradesTable = styled(OrderbookTradesTable)<StyleProps>`
|
Styled.LiveTradesTable = styled(OrderbookTradesTable)<StyleProps>`
|
||||||
tr {
|
tr {
|
||||||
--histogram-bucket-size: 1;
|
--histogram-bucket-size: 1;
|
||||||
background-color: var(--color-layer-2);
|
|
||||||
|
|
||||||
&[data-side=${OrderSide.BUY}] {
|
&[data-side=${OrderSide.BUY}] {
|
||||||
--accent-color: var(--color-positive);
|
--accent-color: var(--color-positive);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
||||||
|
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
import { type OrderbookLine } from '@/constants/abacus';
|
import { type OrderbookLine, TradeInputField } from '@/constants/abacus';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
|
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
@@ -30,6 +29,8 @@ import { OrderbookTradesOutput, OrderbookTradesTable } from './OrderbookTradesTa
|
|||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
const ORDERBOOK_MAX_ROWS_PER_SIDE = 35;
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
maxRowsPerSide?: number;
|
maxRowsPerSide?: number;
|
||||||
layout?: 'vertical' | 'horizontal';
|
layout?: 'vertical' | 'horizontal';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { defineConfig } from 'vite';
|
|||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import svgr from 'vite-plugin-svgr';
|
import svgr from 'vite-plugin-svgr';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode }) => ({
|
export default defineConfig(({ mode }) => ({
|
||||||
@@ -50,6 +51,12 @@ export default defineConfig(({ mode }) => ({
|
|||||||
svgr({
|
svgr({
|
||||||
exportAsDefault: true,
|
exportAsDefault: true,
|
||||||
}),
|
}),
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'autoUpdate',
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
publicDir: 'public',
|
publicDir: 'public',
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user