Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a2cb2a296 | ||
|
|
ad6aa1af49 | ||
|
|
55b2c3fb4a | ||
|
|
83bcde00eb | ||
|
|
e8024b9337 | ||
|
|
8280e512bc | ||
|
|
b2234cb969 | ||
|
|
4bcb1f64ac |
+2
-2
@@ -39,9 +39,9 @@
|
||||
"@cosmjs/proto-signing": "^0.31.0",
|
||||
"@cosmjs/stargate": "^0.31.0",
|
||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||
"@dydxprotocol/v4-abacus": "^1.1.33",
|
||||
"@dydxprotocol/v4-abacus": "^1.2.3",
|
||||
"@dydxprotocol/v4-client-js": "^1.0.11",
|
||||
"@dydxprotocol/v4-localization": "^1.1.6",
|
||||
"@dydxprotocol/v4-localization": "^1.1.11",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
|
||||
Generated
+8
-8
@@ -27,14 +27,14 @@ dependencies:
|
||||
specifier: ^0.31.0
|
||||
version: 0.31.0
|
||||
'@dydxprotocol/v4-abacus':
|
||||
specifier: ^1.1.33
|
||||
version: 1.1.33
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.3
|
||||
'@dydxprotocol/v4-client-js':
|
||||
specifier: ^1.0.11
|
||||
version: 1.0.11
|
||||
'@dydxprotocol/v4-localization':
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.6
|
||||
specifier: ^1.1.11
|
||||
version: 1.1.11
|
||||
'@ethersproject/providers':
|
||||
specifier: ^5.7.2
|
||||
version: 5.7.2
|
||||
@@ -991,8 +991,8 @@ packages:
|
||||
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
||||
dev: true
|
||||
|
||||
/@dydxprotocol/v4-abacus@1.1.33:
|
||||
resolution: {integrity: sha512-VlIOaOPR4ImSWGmKK3+M4VHdMDdTk7qI77Iopppq11qU7f8wdwk4OeedTSuAMxezKez2nCP3J/x3Da+FtrKRqg==}
|
||||
/@dydxprotocol/v4-abacus@1.2.3:
|
||||
resolution: {integrity: sha512-OHGhN4uQo/ikRjeGzf+wGblFvb0uRA916QvQXG4LixsJy7H7HAt7AeZr9LqTM/GqYJicMmZCUS3WIMqgZ20hKA==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-client-js@1.0.11:
|
||||
@@ -1023,8 +1023,8 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-localization@1.1.6:
|
||||
resolution: {integrity: sha512-Bon6NSRU4/FqneAbnP2G28EAPr0hp4LhvAayX61o0O1PGkxnLzAHkXeFppdM0Zn0fOcp1S1MJ+gvz138ZDephQ==}
|
||||
/@dydxprotocol/v4-localization@1.1.11:
|
||||
resolution: {integrity: sha512-ZHnyrWD1bEpvY1tctkcmSV6A5+hQSYBFwyjbiyOqepqDqrv24J5a3hlZU94lDyYf+7Sdk3alOvy9Z7Lpk59nvw==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-proto@0.4.1:
|
||||
|
||||
+14
-14
@@ -39,7 +39,7 @@
|
||||
"environments": {
|
||||
"dydxprotocol-dev": {
|
||||
"name": "v4 Dev",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"dydxprotocol-dev-2": {
|
||||
"name": "v4 Dev 2",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -186,7 +186,7 @@
|
||||
},
|
||||
"dydxprotocol-dev-4": {
|
||||
"name": "v4 Dev 4",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
"dydxprotocol-dev-5": {
|
||||
"name": "v4 Dev 5",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -332,7 +332,7 @@
|
||||
},
|
||||
"dydxprotocol-staging": {
|
||||
"name": "v4 Staging",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -407,7 +407,7 @@
|
||||
},
|
||||
"dydxprotocol-staging-forced-update": {
|
||||
"name": "v4 Staging Forced Update",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -480,7 +480,7 @@
|
||||
},
|
||||
"dydxprotocol-staging-west": {
|
||||
"name": "v4 Staging West",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -555,7 +555,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet": {
|
||||
"name": "v4 Public Testnet",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -634,7 +634,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-dydx": {
|
||||
"name": "v4 Public Testnet/dYdX",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -710,7 +710,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
"name": "v4 Public Testnet/nodefleet",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -786,7 +786,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-kingnodes": {
|
||||
"name": "v4 Public Testnet/KingNodes",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -862,7 +862,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-liquify": {
|
||||
"name": "v4 Public Testnet/Liquify",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -938,7 +938,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-polkachu": {
|
||||
"name": "v4 Public Testnet/Polkahcu",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
@@ -1005,7 +1005,7 @@
|
||||
},
|
||||
"dydxprotocol-testnet-bware": {
|
||||
"name": "v4 Public Testnet/BWare",
|
||||
"ethereumChainId": "5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Story } from '@ladle/react';
|
||||
|
||||
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const Accordion: Story<AccordionProps> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<AccordionComponent {...args} />
|
||||
</StoryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
Accordion.args = {
|
||||
items: [
|
||||
{
|
||||
header: 'Question 1?',
|
||||
content: 'Answer 1.',
|
||||
},
|
||||
{
|
||||
header: 'Question 2?',
|
||||
content: 'Answer 2.',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -3,6 +3,8 @@ import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
|
||||
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
import { PlusIcon } from '@/icons';
|
||||
|
||||
export type AccordionItem = {
|
||||
@@ -10,7 +12,7 @@ export type AccordionItem = {
|
||||
content: React.ReactNode;
|
||||
};
|
||||
|
||||
type AccordionProps = {
|
||||
export type AccordionProps = {
|
||||
items: AccordionItem[];
|
||||
className?: string;
|
||||
};
|
||||
@@ -36,6 +38,13 @@ export const Accordion = ({ items, className }: AccordionProps) => (
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Root = styled(Root)`
|
||||
--accordion-paddingY: 1rem;
|
||||
--accordion-paddingX: 1rem;
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
--accordion-paddingX: 1.5rem;
|
||||
}
|
||||
|
||||
> *:not(:last-child) {
|
||||
border-bottom: var(--border-width) solid var(--border-color);
|
||||
}
|
||||
@@ -67,7 +76,8 @@ Styled.Icon = styled.div`
|
||||
Styled.Trigger = styled(Trigger)`
|
||||
${layoutMixins.spacedRow}
|
||||
width: 100%;
|
||||
padding: 1rem 0.75rem;
|
||||
padding: var(--accordion-paddingY) var(--accordion-paddingX);
|
||||
gap: 0.5rem;
|
||||
|
||||
color: var(--color-text-1);
|
||||
text-align: start;
|
||||
@@ -91,7 +101,7 @@ Styled.Trigger = styled(Trigger)`
|
||||
|
||||
Styled.Content = styled(Content)`
|
||||
overflow: hidden;
|
||||
margin: 0 0.75rem 1rem;
|
||||
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
|
||||
|
||||
color: var(--color-text-0);
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
CoinsIcon,
|
||||
CommentIcon,
|
||||
CopyIcon,
|
||||
CubeIcon,
|
||||
DepositIcon,
|
||||
DepthChartIcon,
|
||||
DiscordIcon,
|
||||
@@ -99,7 +98,6 @@ export enum IconName {
|
||||
Coins = 'Coins',
|
||||
Comment = 'Comment',
|
||||
Copy = 'Copy',
|
||||
Cube = 'Cube',
|
||||
Deposit = 'Deposit',
|
||||
DepthChart = 'DepthChart',
|
||||
Discord = 'Discord',
|
||||
@@ -175,7 +173,6 @@ const icons = {
|
||||
[IconName.Coins]: CoinsIcon,
|
||||
[IconName.Comment]: CommentIcon,
|
||||
[IconName.Copy]: CopyIcon,
|
||||
[IconName.Cube]: CubeIcon,
|
||||
[IconName.Deposit]: DepositIcon,
|
||||
[IconName.DepthChart]: DepthChartIcon,
|
||||
[IconName.Discord]: DiscordIcon,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
type PanelProps = {
|
||||
slotHeaderContent?: string;
|
||||
@@ -67,6 +68,12 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
|
||||
--panel-content-paddingY: var(--panel-paddingY);
|
||||
--panel-content-paddingX: var(--panel-paddingX);
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
--panel-paddingX: 1.5rem;
|
||||
--panel-paddingY: 1.25rem;
|
||||
--panel-content-paddingY: 1rem;
|
||||
}
|
||||
|
||||
${layoutMixins.row}
|
||||
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { PropsWithChildren, useContext, useState } from 'react';
|
||||
|
||||
import { Point } from '@visx/point';
|
||||
import { localPoint } from '@visx/event';
|
||||
import { XYChart, DataContext } from '@visx/xychart';
|
||||
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
|
||||
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
|
||||
|
||||
export const XYChartWithPointerEvents = ({
|
||||
onPointerMove, onPointerUp, onPointerPressedChange, ...props
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
onPointerPressedChange,
|
||||
...props
|
||||
}: {
|
||||
onPointerMove?: (point: Point) => void;
|
||||
onPointerUp?: (point: Point) => void;
|
||||
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
|
||||
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
|
||||
onPointerPressedChange?: (isPointerPressed: boolean) => void;
|
||||
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
|
||||
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
|
||||
const { xScale, yScale } = useContext(DataContext);
|
||||
|
||||
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
|
||||
|
||||
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
|
||||
|
||||
const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
|
||||
const pointerChartPosition =
|
||||
xScale &&
|
||||
yScale &&
|
||||
pointerContainerPosition &&
|
||||
new Point({
|
||||
// @ts-expect-error invert supposedly doesn't exist on AxisScale
|
||||
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
|
||||
// @ts-expect-error invert supposedly doesn't exist on AxisScale
|
||||
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
|
||||
});
|
||||
|
||||
@@ -29,15 +36,13 @@ export const XYChartWithPointerEvents = ({
|
||||
{...props}
|
||||
onPointerMove={({ event }) => {
|
||||
setLastPointerMoveEvent(event as React.PointerEvent);
|
||||
if (pointerChartPosition)
|
||||
onPointerMove?.(pointerChartPosition);
|
||||
if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
|
||||
}}
|
||||
onPointerOut={() => setLastPointerMoveEvent(undefined)}
|
||||
onPointerDown={() => onPointerPressedChange?.(true)}
|
||||
onPointerUp={() => {
|
||||
onPointerPressedChange?.(false);
|
||||
if (pointerChartPosition)
|
||||
onPointerUp?.(pointerChartPosition);
|
||||
if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -142,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
|
||||
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
|
||||
|
||||
// ------ Transfer Items ------ //
|
||||
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
|
||||
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
|
||||
const transferInputFields = [...TransferInputField.values()] as const;
|
||||
export type TransferInputFields = (typeof transferInputFields)[number];
|
||||
|
||||
@@ -151,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
|
||||
export type TransferTypes = (typeof transferTypes)[number];
|
||||
|
||||
// ------ Trade Items ------ //
|
||||
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
|
||||
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
|
||||
const tradeInputFields = [...TradeInputField.values()] as const;
|
||||
export type TradeInputFields = (typeof tradeInputFields)[number];
|
||||
|
||||
@@ -162,7 +162,7 @@ export type TradeState<T> = {
|
||||
};
|
||||
|
||||
export const ClosePositionInputField =
|
||||
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
|
||||
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
|
||||
|
||||
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
|
||||
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
|
||||
|
||||
@@ -96,6 +96,7 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
elapsedTime?: number;
|
||||
blockHeight?: number;
|
||||
indexerBlockHeight?: number;
|
||||
trailingBlocks?: number;
|
||||
}
|
||||
: // Navigation
|
||||
T extends AnalyticsEvent.NavigatePage
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import { FundingDirection } from './markets';
|
||||
|
||||
// ------ Depth Chart ------ //
|
||||
export enum DepthChartSeries {
|
||||
Asks = 'Asks',
|
||||
Bids = 'Bids',
|
||||
MidMarket = 'MidMarket',
|
||||
}
|
||||
|
||||
export type DepthChartDatum = {
|
||||
size: number;
|
||||
price: number;
|
||||
depth: number;
|
||||
seriesKey: DepthChartSeries;
|
||||
};
|
||||
|
||||
export type DepthChartPoint = {
|
||||
side: OrderSide;
|
||||
price: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export const SERIES_KEY_FOR_ORDER_SIDE = {
|
||||
[OrderSide.BUY]: DepthChartSeries.Bids,
|
||||
[OrderSide.SELL]: DepthChartSeries.Asks,
|
||||
};
|
||||
|
||||
// ------ Funding Chart ------ //
|
||||
export enum FundingRateResolution {
|
||||
OneHour = 'OneHour',
|
||||
EightHour = 'EightHour',
|
||||
Annualized = 'Annualized',
|
||||
}
|
||||
|
||||
export type FundingChartDatum = {
|
||||
time: number;
|
||||
fundingRate: number;
|
||||
direction: FundingDirection;
|
||||
};
|
||||
@@ -25,4 +25,5 @@ export const DEFAULT_MARKETID = 'ETH-USD';
|
||||
export enum FundingDirection {
|
||||
ToShort = 'ToShort',
|
||||
ToLong = 'ToLong',
|
||||
None = 'None',
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
|
||||
}, []);
|
||||
|
||||
// AnalyticsEvent.NetworkStatus
|
||||
const { height, indexerHeight, status } = useApiState();
|
||||
const { height, indexerHeight, status, trailingBlocks} = useApiState();
|
||||
|
||||
useEffect(() => {
|
||||
if (status) {
|
||||
@@ -114,6 +114,7 @@ export const useAnalytics = () => {
|
||||
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
||||
blockHeight: height ?? undefined,
|
||||
indexerBlockHeight: indexerHeight ?? undefined,
|
||||
trailingBlocks: trailingBlocks ?? undefined
|
||||
});
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
|
||||
export const useApiState = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const apiState = useSelector(getApiState, shallowEqual);
|
||||
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
|
||||
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
|
||||
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
|
||||
const indexerHeight = getIndexerHeight(apiState);
|
||||
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
@@ -1,3 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m.321829 1.09389c.200562-.192959.469529-.298401.747791-.29315.27827.00525.54307.120763.73621.32115l5.194 5.5132 5.19397-5.5132c.0944-.10408.2088-.188152.3363-.247234.1275-.059081.2655-.091968.406-.096713.1404-.004746.2804.018748.4116.069088s.2509.126504.3522.223979c.1012.09747.1818.21427.2371.34347.0552.12921.084.26819.0845.40871.0006.14052-.0271.27972-.0813.40936s-.1339.24707-.2344.34534l-5.94997 6.3c-.09795.10163-.21538.18245-.34527.23766-.1299.05521-.26959.08367-.41073.08367s-.28083-.02846-.41073-.08367c-.12989-.05521-.24732-.13603-.34527-.23766l-5.950001-6.3c-.192962-.20056-.29840394-.46953-.29315364-.74779.00525031-.27827.12076264-.54307.32115364-.73621z" fill="currentColor" fill-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 840 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.95513 1.00169C7.89506 1.00645 7.83628 1.02054 7.78124 1.04337L1.31971 3.71042C1.2243 3.74983 1.14329 3.81414 1.08653 3.89553C1.02977 3.97691 0.999705 4.07186 1 4.16883V11.8366C1.00078 11.9327 1.03135 12.0265 1.08805 12.1069C1.14474 12.1873 1.22517 12.2508 1.31971 12.2898L7.78124 14.9568C7.85011 14.9853 7.92463 15 8 15C8.07536 15 8.14989 14.9853 8.21876 14.9568L14.6803 12.2898C14.7748 12.2508 14.8552 12.1873 14.9119 12.1069C14.9686 12.0265 14.9992 11.9327 15 11.8366V4.16883C15.0003 4.07186 14.9702 3.97691 14.9134 3.89553C14.8567 3.81414 14.7756 3.74983 14.6803 3.71042L8.21876 1.04337C8.13605 1.00911 8.04538 0.994778 7.95513 1.00169ZM8 2.04351L13.1378 4.16883L8 6.28894L2.86219 4.16883L8 2.04351ZM2.07693 4.93457L7.46153 7.15886V13.7328L2.07693 11.5084V4.93457ZM13.923 4.93457V11.5084L8.53846 13.7328V7.15886L13.923 4.93457Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 969 B |
@@ -19,7 +19,6 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
|
||||
export { default as CoinsIcon } from './coins.svg';
|
||||
export { default as CommentIcon } from './comment.svg';
|
||||
export { default as CopyIcon } from './copy.svg';
|
||||
export { default as CubeIcon } from './cube.svg';
|
||||
export { default as DepositIcon } from './deposit.svg';
|
||||
export { default as DepthChartIcon } from './depth-chart.svg';
|
||||
export { default as DiscordIcon } from './discord.svg';
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
import { createConfig, configureChains, mainnet, Chain, Connector, usePublicClient } from 'wagmi';
|
||||
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
|
||||
import { goerli } from 'wagmi/chains';
|
||||
|
||||
import {
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
celoAlfajores,
|
||||
scroll,
|
||||
kava,
|
||||
sepolia,
|
||||
} from 'viem/chains';
|
||||
|
||||
import { alchemyProvider } from 'wagmi/providers/alchemy';
|
||||
@@ -55,7 +56,7 @@ import { isTruthy } from './isTruthy';
|
||||
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
|
||||
mainnet,
|
||||
goerli,
|
||||
|
||||
sepolia,
|
||||
arbitrum,
|
||||
arbitrumGoerli,
|
||||
avalanche,
|
||||
|
||||
+29
-13
@@ -30,6 +30,9 @@ import abacusStateManager from '@/lib/abacus';
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
import { truncateAddress } from '@/lib/wallet';
|
||||
|
||||
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
|
||||
import { MigratePanel } from './rewards/MigratePanel';
|
||||
|
||||
const ENS_CHAIN_ID = 1; // Ethereum
|
||||
|
||||
const Profile = () => {
|
||||
@@ -175,8 +178,27 @@ const Profile = () => {
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
||||
/>
|
||||
</Styled.EqualGrid>
|
||||
|
||||
<MigratePanel />
|
||||
<DYDXBalancePanel />
|
||||
|
||||
<Styled.EqualGrid>
|
||||
<Styled.RewardsPanel
|
||||
slotHeaderContent="Trading Rewards"
|
||||
href={`/${chainTokenLabel}`}
|
||||
hasSeparator
|
||||
>
|
||||
<Styled.Details
|
||||
items={[
|
||||
{
|
||||
key: 'week-rewards',
|
||||
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
|
||||
value: '-',
|
||||
},
|
||||
]}
|
||||
layout="grid"
|
||||
/>
|
||||
</Styled.RewardsPanel>
|
||||
<Panel
|
||||
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
|
||||
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
|
||||
@@ -191,19 +213,6 @@ const Profile = () => {
|
||||
layout="grid"
|
||||
/>
|
||||
</Panel>
|
||||
<Styled.RewardsPanel
|
||||
slotHeaderContent={stringGetter({ key: STRING_KEYS.REWARDS })}
|
||||
href={`/${chainTokenLabel}`}
|
||||
hasSeparator
|
||||
>
|
||||
<Styled.Details
|
||||
items={[
|
||||
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.YOU_WILL_EARN }), value: '-' },
|
||||
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.EPOCH_ENDS_IN }), value: '-' },
|
||||
]}
|
||||
layout="grid"
|
||||
/>
|
||||
</Styled.RewardsPanel>
|
||||
</Styled.EqualGrid>
|
||||
|
||||
<Styled.TablePanel
|
||||
@@ -327,6 +336,13 @@ Styled.Details = styled(Details)`
|
||||
`;
|
||||
|
||||
Styled.RewardsPanel = styled(Panel)`
|
||||
align-self: flex-start;
|
||||
|
||||
&,
|
||||
> * {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
dl {
|
||||
--details-grid-numColumns: 1;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export default () => {
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Positions,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
|
||||
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
|
||||
href: PortfolioRoute.Positions,
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ElementType } from 'react';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import breakpoints from '@/styles/breakpoints';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
|
||||
|
||||
@@ -33,7 +32,7 @@ export const DYDXBalancePanel = () => {
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
return (
|
||||
<Styled.Panel
|
||||
<Panel
|
||||
slotHeader={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
@@ -109,21 +108,12 @@ export const DYDXBalancePanel = () => {
|
||||
]}
|
||||
/>
|
||||
</Styled.Content>
|
||||
</Styled.Panel>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--panel-paddingY: 1.5rem;
|
||||
--panel-content-paddingY: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Header = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
gap: 1rem;
|
||||
|
||||
@@ -70,10 +70,12 @@ const EstimatedRewards = () => {
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
enabled: !!dydxAddress,
|
||||
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}`,
|
||||
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`,
|
||||
queryFn: async () => {
|
||||
if (!dydxAddress) return undefined;
|
||||
const resp = await fetch(`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`);
|
||||
const resp = await fetch(
|
||||
`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`
|
||||
);
|
||||
return (await resp.json())?.incentivePoints;
|
||||
},
|
||||
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
|
||||
@@ -151,15 +153,8 @@ const LaunchIncentivesContent = () => {
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
--panel-paddingY: 1rem;
|
||||
--panel-paddingX: 1.5rem;
|
||||
|
||||
background-color: var(--color-layer-4);
|
||||
width: 100%;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--panel-paddingY: 1.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.ForV4 = styled.span`
|
||||
@@ -223,7 +218,7 @@ Styled.Column = styled.div`
|
||||
Styled.EstimatedRewardsCard = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
padding: 1rem 1.25rem;
|
||||
min-width: 21.25rem;
|
||||
min-width: 19rem;
|
||||
height: calc(100% - calc(1.5rem * 2));
|
||||
margin: 1.5rem;
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||
|
||||
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Details } from '@/components/Details';
|
||||
@@ -28,7 +26,7 @@ import { MustBigNumber } from '@/lib/numbers';
|
||||
const TOKEN_MIGRATION_LEARN_MORE_LINK =
|
||||
'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx';
|
||||
|
||||
export const MigratePanel = () => {
|
||||
export const MigratePanel = ({ className }: { className?: string }) => {
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
@@ -45,6 +43,7 @@ export const MigratePanel = () => {
|
||||
|
||||
return isNotTablet ? (
|
||||
<Styled.MigratePanel
|
||||
className={className}
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
|
||||
slotRight={
|
||||
<Styled.MigrateAction>
|
||||
@@ -74,6 +73,7 @@ export const MigratePanel = () => {
|
||||
</Styled.MigratePanel>
|
||||
) : (
|
||||
<Styled.MigratePanel
|
||||
className={className}
|
||||
slotHeader={
|
||||
<Styled.MobileMigrateHeader>
|
||||
<h3>
|
||||
@@ -134,17 +134,11 @@ export const MigratePanel = () => {
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.MigratePanel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
width: 100%;
|
||||
|
||||
background-image: url('/dots-background.svg');
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--panel-paddingY: 1.5rem;
|
||||
--panel-content-paddingY: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
@@ -157,7 +151,7 @@ Styled.Title = styled.h3`
|
||||
Styled.MigrateAction = styled.div`
|
||||
${layoutMixins.flexEqualColumns}
|
||||
align-items: center;
|
||||
margin-right: 1rem;
|
||||
margin: 1rem;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
@@ -197,7 +191,7 @@ Styled.MobileMigrateHeader = styled.div`
|
||||
font: var(--font-small-book);
|
||||
color: var(--color-text-0);
|
||||
|
||||
padding: 1rem 1.5rem 0;
|
||||
padding: 1rem 1rem 0;
|
||||
|
||||
h3 {
|
||||
${layoutMixins.inlineRow}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
@@ -51,6 +52,7 @@ export const RewardsHelpPanel = () => {
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.HelpCard = styled(Panel)`
|
||||
--panel-content-paddingX: 0;
|
||||
--panel-content-paddingY: 0;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
@@ -64,11 +66,15 @@ Styled.Header = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
gap: 1ch;
|
||||
|
||||
padding: 1.25rem 1.5rem;
|
||||
padding: 1rem 1rem;
|
||||
border-bottom: var(--border-width) solid var(--border-color);
|
||||
|
||||
font: var(--font-small-book);
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
padding: 1.5rem 1.25rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
|
||||
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { BackButton } from '@/components/BackButton';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { IconName } from '@/components/Icon';
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
@@ -20,12 +23,14 @@ import { openDialog } from '@/state/dialogs';
|
||||
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
||||
import { MigratePanel } from './MigratePanel';
|
||||
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
||||
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
||||
|
||||
const RewardsPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const panelArrow = (
|
||||
<Styled.Arrow>
|
||||
@@ -39,13 +44,16 @@ const RewardsPage = () => {
|
||||
|
||||
return (
|
||||
<Styled.Page>
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
|
||||
{isTablet && (
|
||||
<Styled.MobileHeader>
|
||||
<BackButton onClick={() => navigate(AppRoute.Profile)} />
|
||||
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
|
||||
</Styled.MobileHeader>
|
||||
)}
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <MigratePanel />}
|
||||
|
||||
{isTablet ? (
|
||||
<>
|
||||
<LaunchIncentivesPanel />
|
||||
<DYDXBalancePanel />
|
||||
</>
|
||||
<LaunchIncentivesPanel />
|
||||
) : (
|
||||
<Styled.PanelRowIncentivesAndBalance>
|
||||
<LaunchIncentivesPanel />
|
||||
@@ -55,7 +63,9 @@ const RewardsPage = () => {
|
||||
|
||||
<Styled.PanelRow>
|
||||
<Styled.Panel
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
|
||||
slotHeaderContent={
|
||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
||||
}
|
||||
slotRight={panelArrow}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||
>
|
||||
@@ -68,7 +78,9 @@ const RewardsPage = () => {
|
||||
</Styled.Panel>
|
||||
|
||||
<Styled.Panel
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
|
||||
slotHeaderContent={
|
||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
|
||||
}
|
||||
slotRight={panelArrow}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||
>
|
||||
@@ -80,6 +92,8 @@ const RewardsPage = () => {
|
||||
</Styled.Description>
|
||||
</Styled.Panel>
|
||||
</Styled.PanelRow>
|
||||
|
||||
<RewardsHelpPanel />
|
||||
</Styled.Page>
|
||||
);
|
||||
};
|
||||
@@ -95,33 +109,41 @@ Styled.Page = styled.div`
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
--content-max-width: 70rem;
|
||||
--content-max-width: 80rem;
|
||||
max-width: min(calc(100vw - 4rem), var(--content-max-width));
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
padding: 1.25rem;
|
||||
--stickyArea-topHeight: var(--page-header-height-mobile);
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
> * {
|
||||
max-width: calc(100vw - 2.5rem);
|
||||
max-width: calc(100vw - 2rem);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
Styled.MobileHeader = styled.header`
|
||||
${layoutMixins.contentSectionDetachedScrollable}
|
||||
${layoutMixins.stickyHeader}
|
||||
z-index: 2;
|
||||
padding: 1.25rem 0;
|
||||
margin-bottom: -1.5rem;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--panel-paddingY: 1.5rem;
|
||||
--panel-content-paddingY: 1rem;
|
||||
}
|
||||
font: var(--font-large-medium);
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-layer-2);
|
||||
`;
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
height: fit-content;
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
padding: 1rem 1.5rem 0;
|
||||
margin-bottom: -1rem;
|
||||
`;
|
||||
|
||||
Styled.Description = styled.div`
|
||||
|
||||
@@ -26,7 +26,12 @@ export const calculateFundingRateHistory = createSelector(
|
||||
return data.map(({ effectiveAtMilliseconds, rate }) => ({
|
||||
time: effectiveAtMilliseconds,
|
||||
fundingRate: rate,
|
||||
direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort,
|
||||
direction:
|
||||
rate === 0
|
||||
? FundingDirection.None
|
||||
: rate < 0
|
||||
? FundingDirection.ToLong
|
||||
: FundingDirection.ToShort,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -119,12 +119,6 @@ export const MarketDetails: React.FC = () => {
|
||||
tooltip: 'initial-margin-fraction',
|
||||
value: <Output useGrouping value={initialMarginFraction} type={OutputType.SmallPercent} />,
|
||||
},
|
||||
{
|
||||
key: 'base-position-notional',
|
||||
label: stringGetter({ key: STRING_KEYS.BASE_POSITION_NOTIONAL }),
|
||||
tooltip: 'base-position-notional',
|
||||
value: <Output useGrouping value={basePositionNotional} type={OutputType.Number} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import { useMemo } from 'react';
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
|
||||
import type { Nullable } from '@/constants/abacus';
|
||||
import {
|
||||
DepthChartDatum,
|
||||
DepthChartPoint,
|
||||
DepthChartSeries,
|
||||
SERIES_KEY_FOR_ORDER_SIDE,
|
||||
} from '@/constants/charts';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
||||
|
||||
import { TooltipContent } from '@/components/visx/TooltipContent';
|
||||
import { Details } from '@/components/Details';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
|
||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
type DepthChartTooltipProps = {
|
||||
chartPointAtPointer: DepthChartPoint;
|
||||
isEditingOrder?: boolean;
|
||||
stepSizeDecimals: Nullable<number>;
|
||||
tickSizeDecimals: Nullable<number>;
|
||||
} & Pick<RenderTooltipParams<DepthChartDatum>, 'colorScale' | 'tooltipData'>;
|
||||
|
||||
export const DepthChartTooltipContent = ({
|
||||
chartPointAtPointer,
|
||||
colorScale,
|
||||
isEditingOrder,
|
||||
stepSizeDecimals,
|
||||
tickSizeDecimals,
|
||||
tooltipData,
|
||||
}: DepthChartTooltipProps) => {
|
||||
const { nearestDatum } = tooltipData || {};
|
||||
const stringGetter = useStringGetter();
|
||||
const { spread, spreadPercent, midMarketPrice } = useOrderbookValuesForDepthChart();
|
||||
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
||||
|
||||
const priceImpact = useMemo(() => {
|
||||
if (nearestDatum) {
|
||||
const depthChartSeries = nearestDatum.key as DepthChartSeries;
|
||||
|
||||
return {
|
||||
[DepthChartSeries.Bids]: MustBigNumber(nearestDatum?.datum.price)
|
||||
.minus(chartPointAtPointer.price)
|
||||
.div(nearestDatum?.datum.price),
|
||||
[DepthChartSeries.Asks]: MustBigNumber(chartPointAtPointer.price)
|
||||
.minus(nearestDatum?.datum.price)
|
||||
.div(chartPointAtPointer.price),
|
||||
[DepthChartSeries.MidMarket]: undefined,
|
||||
}[depthChartSeries];
|
||||
}
|
||||
return undefined;
|
||||
}, [nearestDatum, chartPointAtPointer.price]);
|
||||
|
||||
if (!isEditingOrder && !nearestDatum?.datum) return null;
|
||||
|
||||
return (
|
||||
<TooltipContent
|
||||
accentColor={
|
||||
nearestDatum?.key &&
|
||||
colorScale?.(
|
||||
isEditingOrder ? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side] : nearestDatum.key
|
||||
)
|
||||
}
|
||||
>
|
||||
<h4>
|
||||
{isEditingOrder
|
||||
? stringGetter({ key: STRING_KEYS.RELEASE_TO_EDIT })
|
||||
: nearestDatum &&
|
||||
{
|
||||
[DepthChartSeries.Bids]: stringGetter({ key: STRING_KEYS.BIDS }),
|
||||
[DepthChartSeries.Asks]: stringGetter({ key: STRING_KEYS.ASKS }),
|
||||
[DepthChartSeries.MidMarket]: stringGetter({ key: STRING_KEYS.MID_MARKET }),
|
||||
}[nearestDatum.key]}
|
||||
</h4>
|
||||
|
||||
<Details
|
||||
layout="column"
|
||||
items={
|
||||
isEditingOrder
|
||||
? [
|
||||
{
|
||||
key: 'side',
|
||||
label: stringGetter({ key: STRING_KEYS.SIDE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Text}
|
||||
value={
|
||||
{
|
||||
[OrderSide.BUY]: stringGetter({
|
||||
key: STRING_KEYS.BUY,
|
||||
}),
|
||||
[OrderSide.SELL]: stringGetter({
|
||||
key: STRING_KEYS.SELL,
|
||||
}),
|
||||
}[chartPointAtPointer.side]
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'limitPrice',
|
||||
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={chartPointAtPointer.price}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'size',
|
||||
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Asset}
|
||||
value={chartPointAtPointer.size}
|
||||
fractionDigits={stepSizeDecimals}
|
||||
tag={id}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: nearestDatum?.key === DepthChartSeries.MidMarket
|
||||
? [
|
||||
{
|
||||
key: 'midMarketPrice',
|
||||
label: stringGetter({ key: STRING_KEYS.PRICE }),
|
||||
value: (
|
||||
<Output type={OutputType.Fiat} value={midMarketPrice} useGrouping={false} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'spread',
|
||||
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
|
||||
value: (
|
||||
<>
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={spread}
|
||||
fractionDigits={tickSizeDecimals}
|
||||
useGrouping={false}
|
||||
/>
|
||||
<Output
|
||||
type={OutputType.SmallPercent}
|
||||
value={spreadPercent}
|
||||
withParentheses
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: 'price',
|
||||
label: stringGetter({ key: STRING_KEYS.PRICE }),
|
||||
value: (
|
||||
<>
|
||||
{nearestDatum &&
|
||||
{
|
||||
[DepthChartSeries.Bids]: '≥',
|
||||
[DepthChartSeries.Asks]: '≤',
|
||||
}[nearestDatum.key]}
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={nearestDatum?.datum.price}
|
||||
useGrouping={false}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'depth',
|
||||
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Asset}
|
||||
value={nearestDatum?.datum.depth}
|
||||
fractionDigits={stepSizeDecimals}
|
||||
tag={id}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'cost',
|
||||
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
|
||||
value: (
|
||||
<Output
|
||||
useGrouping
|
||||
type={OutputType.Fiat}
|
||||
value={
|
||||
nearestDatum
|
||||
? nearestDatum?.datum.price * nearestDatum?.datum.depth
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'priceImpact',
|
||||
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
|
||||
value: <Output useGrouping type={OutputType.Percent} value={priceImpact} />,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</TooltipContent>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,20 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled, { AnyStyledComponent, css, keyframes } from 'styled-components';
|
||||
import { useSelector, shallowEqual } from 'react-redux';
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
|
||||
import {
|
||||
DepthChartDatum,
|
||||
DepthChartPoint,
|
||||
DepthChartSeries,
|
||||
SERIES_KEY_FOR_ORDER_SIDE,
|
||||
} from '@/constants/charts';
|
||||
import { StringGetterFunction } from '@/constants/localization';
|
||||
|
||||
import { useBreakpoints } from '@/hooks';
|
||||
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
||||
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
|
||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||
|
||||
import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents';
|
||||
@@ -21,24 +27,25 @@ import {
|
||||
darkTheme,
|
||||
DataProvider,
|
||||
EventEmitterProvider,
|
||||
type EventHandlerParams,
|
||||
} from '@visx/xychart';
|
||||
import { LinearGradient } from '@visx/gradient';
|
||||
import { curveStepAfter } from '@visx/curve';
|
||||
import type { Point } from '@visx/point';
|
||||
import { Point } from '@visx/point';
|
||||
import Tooltip from '@/components/visx/XYChartTooltipWithBounds';
|
||||
import { TooltipContent } from '@/components/visx/TooltipContent';
|
||||
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
|
||||
|
||||
import { Details } from '@/components/Details';
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { OutputType } from '@/components/Output';
|
||||
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
import { DepthChartTooltipContent } from './Tooltip';
|
||||
|
||||
// @ts-ignore
|
||||
const theme = buildChartTheme({
|
||||
...darkTheme,
|
||||
colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s
|
||||
colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s
|
||||
});
|
||||
|
||||
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
|
||||
@@ -58,30 +65,6 @@ const formatNumber = (n: number, selectedLocale: string, isCompact: boolean = n
|
||||
: formattedNumber;
|
||||
};
|
||||
|
||||
enum DepthChartSeries {
|
||||
Asks = 'Asks',
|
||||
Bids = 'Bids',
|
||||
MidMarket = 'MidMarket',
|
||||
}
|
||||
|
||||
type DepthChartDatum = {
|
||||
size: number;
|
||||
price: number;
|
||||
depth: number;
|
||||
seriesKey: DepthChartSeries;
|
||||
};
|
||||
|
||||
const seriesKeyForOrderSide = {
|
||||
[OrderSide.BUY]: DepthChartSeries.Bids,
|
||||
[OrderSide.SELL]: DepthChartSeries.Asks,
|
||||
};
|
||||
|
||||
type DepthChartPoint = {
|
||||
side: OrderSide;
|
||||
price: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export const DepthChart = ({
|
||||
onChartClick,
|
||||
stringGetter,
|
||||
@@ -96,8 +79,6 @@ export const DepthChart = ({
|
||||
const { isMobile } = useBreakpoints();
|
||||
|
||||
// Chart data
|
||||
|
||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
||||
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
||||
const { stepSizeDecimals, tickSizeDecimals } =
|
||||
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
|
||||
@@ -112,43 +93,15 @@ export const DepthChart = ({
|
||||
midMarketPrice,
|
||||
spread,
|
||||
spreadPercent,
|
||||
} = 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,
|
||||
} = useOrderbookValuesForDepthChart();
|
||||
|
||||
// Chart state
|
||||
|
||||
const [isPointerPressed, setIsPointerPressed] = useState(false);
|
||||
const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>();
|
||||
|
||||
const isEditingOrder = isPointerPressed && chartPointAtPointer;
|
||||
const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer);
|
||||
|
||||
const [zoomDomain, setZoomDomain] = useState<undefined | number>();
|
||||
|
||||
@@ -193,12 +146,24 @@ export const DepthChart = ({
|
||||
}, [orderbook, zoomDomain]);
|
||||
|
||||
const getChartPoint = useCallback(
|
||||
({ x: price, y: size }: Point) =>
|
||||
({
|
||||
side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL,
|
||||
(point: Point | EventHandlerParams<object>) => {
|
||||
let price, size;
|
||||
if (point instanceof Point) {
|
||||
const { x, y } = point as Point;
|
||||
price = x;
|
||||
size = y;
|
||||
} else {
|
||||
const { svgPoint: { x, y } = {} } = point as EventHandlerParams<object>;
|
||||
price = x;
|
||||
size = y;
|
||||
}
|
||||
|
||||
return {
|
||||
side: MustBigNumber(price).lt(midMarketPrice!) ? OrderSide.BUY : OrderSide.SELL,
|
||||
price,
|
||||
size,
|
||||
} as DepthChartPoint),
|
||||
} as DepthChartPoint;
|
||||
},
|
||||
[midMarketPrice]
|
||||
);
|
||||
|
||||
@@ -256,7 +221,7 @@ export const DepthChart = ({
|
||||
}}
|
||||
onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))}
|
||||
onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))}
|
||||
onPointerPressedChange={setIsPointerPressed}
|
||||
onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)}
|
||||
>
|
||||
<Axis
|
||||
orientation="bottom"
|
||||
@@ -363,7 +328,7 @@ export const DepthChart = ({
|
||||
<Styled.XAxisLabelOutput
|
||||
type={OutputType.Fiat}
|
||||
value={
|
||||
isEditingOrder
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? chartPointAtPointer.price
|
||||
: tooltipData!.nearestDatum?.datum.price
|
||||
}
|
||||
@@ -374,8 +339,8 @@ export const DepthChart = ({
|
||||
[DepthChartSeries.Bids]: 'var(--color-positive)',
|
||||
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
|
||||
}[
|
||||
isEditingOrder
|
||||
? seriesKeyForOrderSide[chartPointAtPointer.side]
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
|
||||
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
|
||||
]
|
||||
}
|
||||
@@ -389,7 +354,7 @@ export const DepthChart = ({
|
||||
<Styled.YAxisLabelOutput
|
||||
type={OutputType.Asset}
|
||||
value={
|
||||
isEditingOrder
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? chartPointAtPointer.size
|
||||
: tooltipData!.nearestDatum?.datum.depth
|
||||
}
|
||||
@@ -400,8 +365,8 @@ export const DepthChart = ({
|
||||
[DepthChartSeries.Bids]: 'var(--color-positive)',
|
||||
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
|
||||
}[
|
||||
isEditingOrder
|
||||
? seriesKeyForOrderSide[chartPointAtPointer.side]
|
||||
isEditingOrder && chartPointAtPointer
|
||||
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
|
||||
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
|
||||
]
|
||||
}
|
||||
@@ -410,181 +375,22 @@ export const DepthChart = ({
|
||||
}
|
||||
snapTooltipToDatumX={!isEditingOrder}
|
||||
snapTooltipToDatumY={isEditingOrder ? false : isMobile}
|
||||
renderTooltip={({ tooltipData, colorScale }) => {
|
||||
const { nearestDatum } = tooltipData || {};
|
||||
|
||||
if (!isEditingOrder && !nearestDatum?.datum) return null;
|
||||
|
||||
return (
|
||||
<TooltipContent
|
||||
accentColor={colorScale?.(
|
||||
isEditingOrder
|
||||
? seriesKeyForOrderSide[chartPointAtPointer.side]
|
||||
: nearestDatum.key
|
||||
)}
|
||||
>
|
||||
<h4>
|
||||
{isEditingOrder
|
||||
? 'Release mouse to edit order'
|
||||
: {
|
||||
[DepthChartSeries.Bids]: 'Bids',
|
||||
[DepthChartSeries.Asks]: 'Asks',
|
||||
[DepthChartSeries.MidMarket]: 'Mid-Market',
|
||||
}[nearestDatum.key]}
|
||||
</h4>
|
||||
|
||||
<Details
|
||||
layout="column"
|
||||
items={
|
||||
isEditingOrder
|
||||
? [
|
||||
{
|
||||
key: 'side',
|
||||
label: stringGetter({ key: STRING_KEYS.SIDE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Text}
|
||||
value={
|
||||
{
|
||||
[OrderSide.BUY]: stringGetter({
|
||||
key: STRING_KEYS.BUY,
|
||||
}),
|
||||
[OrderSide.SELL]: stringGetter({
|
||||
key: STRING_KEYS.SELL,
|
||||
}),
|
||||
}[chartPointAtPointer.side]
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'limitPrice',
|
||||
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={chartPointAtPointer.price}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'size',
|
||||
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Asset}
|
||||
value={chartPointAtPointer.size}
|
||||
fractionDigits={stepSizeDecimals}
|
||||
tag={id}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: nearestDatum?.key === DepthChartSeries.MidMarket
|
||||
? [
|
||||
{
|
||||
key: 'midMarketPrice',
|
||||
// label: stringGetter({ key: STRING_KEYS.ORDERBOOK_MID_MARKET_PRICE }),
|
||||
label: stringGetter({ key: STRING_KEYS.PRICE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={midMarketPrice}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'spread',
|
||||
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
|
||||
value: (
|
||||
<>
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={spread}
|
||||
fractionDigits={tickSizeDecimals}
|
||||
useGrouping={false}
|
||||
/>
|
||||
<Output
|
||||
type={OutputType.SmallPercent}
|
||||
value={spreadPercent}
|
||||
withParentheses
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: 'price',
|
||||
label: stringGetter({ key: STRING_KEYS.PRICE }),
|
||||
value: (
|
||||
<>
|
||||
{nearestDatum &&
|
||||
{
|
||||
[DepthChartSeries.Bids]: '≥',
|
||||
[DepthChartSeries.Asks]: '≤',
|
||||
}[nearestDatum.key]}
|
||||
<Output
|
||||
type={OutputType.Fiat}
|
||||
value={nearestDatum?.datum.price}
|
||||
useGrouping={false}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'depth',
|
||||
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Asset}
|
||||
value={nearestDatum?.datum.depth}
|
||||
fractionDigits={stepSizeDecimals}
|
||||
tag={id}
|
||||
useGrouping={false}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'cost',
|
||||
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
|
||||
value: (
|
||||
<Output
|
||||
useGrouping
|
||||
type={OutputType.Fiat}
|
||||
value={nearestDatum?.datum.price * nearestDatum?.datum.depth}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'priceImpact',
|
||||
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
|
||||
value: (
|
||||
<Output
|
||||
useGrouping
|
||||
type={OutputType.Percent}
|
||||
value={{
|
||||
[DepthChartSeries.Asks]: () =>
|
||||
MustBigNumber(nearestDatum.datum.price)
|
||||
.minus(lowestAsk.price)
|
||||
.div(nearestDatum.datum.price),
|
||||
[DepthChartSeries.Bids]: () =>
|
||||
MustBigNumber(highestBid.price)
|
||||
.minus(nearestDatum.datum.price)
|
||||
.div(highestBid.price),
|
||||
}[nearestDatum.key]()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</TooltipContent>
|
||||
);
|
||||
}}
|
||||
renderTooltip={({ tooltipData, colorScale }) =>
|
||||
chartPointAtPointer && (
|
||||
<DepthChartTooltipContent
|
||||
{...{
|
||||
tooltipData,
|
||||
colorScale,
|
||||
isEditingOrder,
|
||||
chartPointAtPointer,
|
||||
stringGetter,
|
||||
selectedLocale,
|
||||
stepSizeDecimals,
|
||||
tickSizeDecimals,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</XYChartWithPointerEvents>
|
||||
</EventEmitterProvider>
|
||||
@@ -0,0 +1,107 @@
|
||||
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
|
||||
import { TooltipContent } from '@/components/visx/TooltipContent';
|
||||
|
||||
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { FundingDirection } from '@/constants/markets';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { Details, DetailsItem } from '@/components/Details';
|
||||
import { Output, OutputType, ShowSign } from '@/components/Output';
|
||||
|
||||
type FundingChartTooltipProps = {
|
||||
fundingRateView: FundingRateResolution;
|
||||
latestDatum: FundingChartDatum;
|
||||
} & Pick<RenderTooltipParams<FundingChartDatum>, 'tooltipData'>;
|
||||
|
||||
export const FundingChartTooltipContent = ({
|
||||
fundingRateView,
|
||||
latestDatum,
|
||||
tooltipData,
|
||||
}: FundingChartTooltipProps) => {
|
||||
const { nearestDatum } = tooltipData || {};
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
|
||||
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
|
||||
|
||||
return (
|
||||
<TooltipContent
|
||||
accentColor={
|
||||
{
|
||||
[FundingDirection.ToLong]: 'var(--color-negative)',
|
||||
[FundingDirection.ToShort]: 'var(--color-positive)',
|
||||
[FundingDirection.None]: 'var(--color-layer-6)',
|
||||
}[tooltipDatum.direction]
|
||||
}
|
||||
>
|
||||
<h4>
|
||||
{isShowingCurrentFundingRate
|
||||
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
|
||||
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
|
||||
</h4>
|
||||
|
||||
<Details
|
||||
layout="column"
|
||||
items={
|
||||
[
|
||||
{
|
||||
key: 'direction',
|
||||
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Text}
|
||||
value={
|
||||
{
|
||||
[FundingDirection.ToLong]: `${stringGetter({
|
||||
key: STRING_KEYS.SHORT_POSITION_SHORT,
|
||||
})} → ${stringGetter({
|
||||
key: STRING_KEYS.LONG_POSITION_SHORT,
|
||||
})}`,
|
||||
[FundingDirection.ToShort]: `${stringGetter({
|
||||
key: STRING_KEYS.LONG_POSITION_SHORT,
|
||||
})} → ${stringGetter({
|
||||
key: STRING_KEYS.SHORT_POSITION_SHORT,
|
||||
})}`,
|
||||
[FundingDirection.None]: undefined,
|
||||
}[tooltipDatum.direction]
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'fundingRate',
|
||||
label: stringGetter({
|
||||
key: {
|
||||
[FundingRateResolution.OneHour]: STRING_KEYS.RATE_1H,
|
||||
[FundingRateResolution.EightHour]: STRING_KEYS.RATE_8H,
|
||||
[FundingRateResolution.Annualized]: STRING_KEYS.ANNUALIZED,
|
||||
}[fundingRateView],
|
||||
}),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.SmallPercent}
|
||||
value={
|
||||
{
|
||||
[FundingRateResolution.OneHour]: tooltipDatum.fundingRate,
|
||||
[FundingRateResolution.EightHour]: tooltipDatum.fundingRate * 8,
|
||||
[FundingRateResolution.Annualized]: tooltipDatum.fundingRate * (24 * 365),
|
||||
}[fundingRateView]
|
||||
}
|
||||
showSign={ShowSign.Both}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
label: isShowingCurrentFundingRate
|
||||
? 'Time Remaining'
|
||||
: stringGetter({ key: STRING_KEYS.TIME }),
|
||||
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
|
||||
},
|
||||
].filter(Boolean) as Array<DetailsItem>
|
||||
}
|
||||
/>
|
||||
</TooltipContent>
|
||||
);
|
||||
};
|
||||
@@ -3,39 +3,27 @@ import { shallowEqual, useSelector } from 'react-redux';
|
||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
import { curveMonotoneX, curveStepAfter } from '@visx/curve';
|
||||
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
|
||||
import { FundingDirection } from '@/constants/markets';
|
||||
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
import { Details, DetailsItem } from '@/components/Details';
|
||||
import { Output, OutputType, ShowSign } from '@/components/Output';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
import { ToggleGroup } from '@/components/ToggleGroup';
|
||||
|
||||
import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
|
||||
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
|
||||
import { TooltipContent } from '@/components/visx/TooltipContent';
|
||||
import type { TooltipContextType } from '@visx/xychart';
|
||||
|
||||
import { calculateFundingRateHistory } from '@/state/perpetualsCalculators';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
enum FundingRateResolution {
|
||||
OneHour = 'OneHour',
|
||||
EightHour = 'EightHour',
|
||||
Annualized = 'Annualized',
|
||||
}
|
||||
|
||||
type FundingChartDatum = {
|
||||
time: number;
|
||||
fundingRate: number;
|
||||
direction: FundingDirection;
|
||||
};
|
||||
import { FundingChartTooltipContent } from './Tooltip';
|
||||
|
||||
const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
@@ -116,114 +104,20 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => {
|
||||
{
|
||||
[FundingDirection.ToLong]: 'var(--color-negative)',
|
||||
[FundingDirection.ToShort]: 'var(--color-positive)',
|
||||
[FundingDirection.None]: 'var(--color-layer-6)',
|
||||
}[tooltipDatum.direction]
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
renderTooltip={({ tooltipData }) => {
|
||||
const { nearestDatum } = tooltipData || {};
|
||||
|
||||
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
|
||||
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
|
||||
|
||||
return (
|
||||
<TooltipContent
|
||||
accentColor={
|
||||
{
|
||||
[FundingDirection.ToLong]: 'var(--color-negative)',
|
||||
[FundingDirection.ToShort]: 'var(--color-positive)',
|
||||
}[tooltipDatum.direction]
|
||||
}
|
||||
>
|
||||
<h4>
|
||||
{isShowingCurrentFundingRate
|
||||
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
|
||||
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
|
||||
</h4>
|
||||
|
||||
<Details
|
||||
layout="column"
|
||||
items={
|
||||
[
|
||||
{
|
||||
key: 'direction',
|
||||
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.Text}
|
||||
value={
|
||||
{
|
||||
[FundingDirection.ToLong]: `${stringGetter({
|
||||
key: STRING_KEYS.SHORT_POSITION_SHORT,
|
||||
})} → ${stringGetter({
|
||||
key: STRING_KEYS.LONG_POSITION_SHORT,
|
||||
})}`,
|
||||
[FundingDirection.ToShort]: `${stringGetter({
|
||||
key: STRING_KEYS.LONG_POSITION_SHORT,
|
||||
})} → ${stringGetter({
|
||||
key: STRING_KEYS.SHORT_POSITION_SHORT,
|
||||
})}`,
|
||||
}[tooltipDatum.direction]
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'fundingRate1h',
|
||||
label: stringGetter({ key: STRING_KEYS.RATE_1H }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.SmallPercent}
|
||||
value={tooltipDatum.fundingRate}
|
||||
showSign={ShowSign.Both}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'fundingRate8h',
|
||||
label: stringGetter({ key: STRING_KEYS.RATE_8H }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.SmallPercent}
|
||||
value={tooltipDatum.fundingRate * 8}
|
||||
// value={
|
||||
// Math.sign(tooltipDatum.fundingRate) *
|
||||
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** 8 - 1)
|
||||
// }
|
||||
showSign={ShowSign.Both}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'fundingRateAnnualized',
|
||||
label: stringGetter({ key: STRING_KEYS.ANNUALIZED }),
|
||||
value: (
|
||||
<Output
|
||||
type={OutputType.SmallPercent}
|
||||
value={tooltipDatum.fundingRate * (24 * 365)}
|
||||
// value={
|
||||
// Math.sign(tooltipDatum.fundingRate) *
|
||||
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** (24 * 365) - 1)
|
||||
// }
|
||||
showSign={ShowSign.Both}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
label: isShowingCurrentFundingRate
|
||||
? 'Time Remaining'
|
||||
: stringGetter({ key: STRING_KEYS.TIME }),
|
||||
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
|
||||
},
|
||||
].filter(Boolean) as Array<DetailsItem>
|
||||
}
|
||||
/>
|
||||
</TooltipContent>
|
||||
);
|
||||
}}
|
||||
onTooltipContext={setTooltipContext}
|
||||
renderTooltip={({ tooltipData }) => (
|
||||
<FundingChartTooltipContent
|
||||
fundingRateView={fundingRateView}
|
||||
tooltipData={tooltipData}
|
||||
latestDatum={latestDatum}
|
||||
/>
|
||||
)}
|
||||
onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)}
|
||||
minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4}
|
||||
numGridLines={1}
|
||||
slotEmpty={<LoadingSpace id="funding-chart-loading" />}
|
||||
@@ -290,6 +184,7 @@ Styled.FundingRateToggle = styled.div`
|
||||
Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>`
|
||||
place-self: start center;
|
||||
padding: clamp(1.5rem, 9rem - 15%, 4rem);
|
||||
pointer-events: none;
|
||||
|
||||
font: var(--font-large-book);
|
||||
|
||||
@@ -108,20 +108,6 @@ export const DepositButtonAndReceipt = ({
|
||||
|
||||
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
|
||||
|
||||
const { toAmount, toAmountMin } = useMemo(() => {
|
||||
if (isCctp) {
|
||||
return {
|
||||
toAmount: summary?.toAmount,
|
||||
toAmountMin: summary?.toAmountMin,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
toAmount: summary?.toAmount && formatUnits(BigInt(summary.toAmount), usdcDecimals),
|
||||
toAmountMin: summary?.toAmountMin && formatUnits(BigInt(summary.toAmountMin), usdcDecimals),
|
||||
};
|
||||
}
|
||||
}, [isCctp, summary]);
|
||||
|
||||
const submitButtonReceipt = [
|
||||
{
|
||||
key: 'expected-deposit-amount',
|
||||
@@ -130,7 +116,7 @@ export const DepositButtonAndReceipt = ({
|
||||
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
|
||||
</span>
|
||||
),
|
||||
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmount} />,
|
||||
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />,
|
||||
subitems: [
|
||||
{
|
||||
key: 'minimum-deposit-amount',
|
||||
@@ -140,7 +126,7 @@ export const DepositButtonAndReceipt = ({
|
||||
</span>
|
||||
),
|
||||
value: (
|
||||
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmountMin} />
|
||||
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} />
|
||||
),
|
||||
tooltip: 'minimum-deposit-amount',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user