Compare commits

..
Author SHA1 Message Date
jaredvu 5a91e56ac3 🚧 WAGMI 2 2024-01-09 13:28:21 -08:00
43 changed files with 4935 additions and 1397 deletions
+6 -5
View File
@@ -39,9 +39,9 @@
"@cosmjs/proto-signing": "^0.31.0", "@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0", "@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0", "@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.2.3", "@dydxprotocol/v4-abacus": "^1.1.32",
"@dydxprotocol/v4-client-js": "^1.0.11", "@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.11", "@dydxprotocol/v4-localization": "^1.1.6",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3", "@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
@@ -72,6 +72,7 @@
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@scure/bip32": "^1.3.0", "@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0", "@scure/bip39": "^1.2.0",
"@tanstack/react-query": "^5.17.9",
"@types/lodash": "^4.14.195", "@types/lodash": "^4.14.195",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@visx/axis": "^3.1.0", "@visx/axis": "^3.1.0",
@@ -104,15 +105,15 @@
"react-aria": "^3.25.0", "react-aria": "^3.25.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-number-format": "^5.2.2", "react-number-format": "^5.2.2",
"react-query": "^3.39.3",
"react-redux": "^8.1.1", "react-redux": "^8.1.1",
"react-router-dom": "^6.14.0", "react-router-dom": "^6.14.0",
"react-stately": "^3.23.0", "react-stately": "^3.23.0",
"reselect": "^4.1.8", "reselect": "^4.1.8",
"stream-browserify": "^3.0.0",
"styled-components": "^5.3.11", "styled-components": "^5.3.11",
"use-latest": "^1.2.1", "use-latest": "^1.2.1",
"viem": "^1.20.0", "viem": "^2.0.0",
"wagmi": "^1.4.12" "wagmi": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.5",
+4198 -551
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,8 +1,8 @@
import { lazy, Suspense } from 'react'; 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 { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { GrazProvider } from 'graz'; import { GrazProvider } from 'graz';
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes'; import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
@@ -120,9 +120,9 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
}; };
const providers = [ const providers = [
wrapProvider(WagmiProvider, { config }),
wrapProvider(QueryClientProvider, { client: queryClient }), wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider), wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider), wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider), wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider), wrapProvider(DydxProvider),
-26
View File
@@ -1,26 +0,0 @@
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 -13
View File
@@ -3,8 +3,6 @@ import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion'; import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
import { PlusIcon } from '@/icons'; import { PlusIcon } from '@/icons';
export type AccordionItem = { export type AccordionItem = {
@@ -12,7 +10,7 @@ export type AccordionItem = {
content: React.ReactNode; content: React.ReactNode;
}; };
export type AccordionProps = { type AccordionProps = {
items: AccordionItem[]; items: AccordionItem[];
className?: string; className?: string;
}; };
@@ -38,13 +36,6 @@ export const Accordion = ({ items, className }: AccordionProps) => (
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.Root = styled(Root)` Styled.Root = styled(Root)`
--accordion-paddingY: 1rem;
--accordion-paddingX: 1rem;
@media ${breakpoints.notTablet} {
--accordion-paddingX: 1.5rem;
}
> *:not(:last-child) { > *:not(:last-child) {
border-bottom: var(--border-width) solid var(--border-color); border-bottom: var(--border-width) solid var(--border-color);
} }
@@ -76,8 +67,7 @@ Styled.Icon = styled.div`
Styled.Trigger = styled(Trigger)` Styled.Trigger = styled(Trigger)`
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
width: 100%; width: 100%;
padding: var(--accordion-paddingY) var(--accordion-paddingX); padding: 1rem 0.75rem;
gap: 0.5rem;
color: var(--color-text-1); color: var(--color-text-1);
text-align: start; text-align: start;
@@ -101,7 +91,7 @@ Styled.Trigger = styled(Trigger)`
Styled.Content = styled(Content)` Styled.Content = styled(Content)`
overflow: hidden; overflow: hidden;
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY); margin: 0 0.75rem 1rem;
color: var(--color-text-0); color: var(--color-text-0);
+3
View File
@@ -24,6 +24,7 @@ import {
CoinsIcon, CoinsIcon,
CommentIcon, CommentIcon,
CopyIcon, CopyIcon,
CubeIcon,
DepositIcon, DepositIcon,
DepthChartIcon, DepthChartIcon,
DiscordIcon, DiscordIcon,
@@ -98,6 +99,7 @@ export enum IconName {
Coins = 'Coins', Coins = 'Coins',
Comment = 'Comment', Comment = 'Comment',
Copy = 'Copy', Copy = 'Copy',
Cube = 'Cube',
Deposit = 'Deposit', Deposit = 'Deposit',
DepthChart = 'DepthChart', DepthChart = 'DepthChart',
Discord = 'Discord', Discord = 'Discord',
@@ -173,6 +175,7 @@ const icons = {
[IconName.Coins]: CoinsIcon, [IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon, [IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon, [IconName.Copy]: CopyIcon,
[IconName.Cube]: CubeIcon,
[IconName.Deposit]: DepositIcon, [IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon, [IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon, [IconName.Discord]: DiscordIcon,
-7
View File
@@ -4,7 +4,6 @@ import { Link } from 'react-router-dom';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = { type PanelProps = {
slotHeaderContent?: string; slotHeaderContent?: string;
@@ -68,12 +67,6 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
--panel-content-paddingY: var(--panel-paddingY); --panel-content-paddingY: var(--panel-paddingY);
--panel-content-paddingX: var(--panel-paddingX); --panel-content-paddingX: var(--panel-paddingX);
@media ${breakpoints.notTablet} {
--panel-paddingX: 1.5rem;
--panel-paddingY: 1.25rem;
--panel-content-paddingY: 1rem;
}
${layoutMixins.row} ${layoutMixins.row}
background-color: var(--color-layer-3); background-color: var(--color-layer-3);
@@ -1,33 +1,26 @@
import React, { PropsWithChildren, useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { Point } from '@visx/point'; import { Point } from '@visx/point';
import { localPoint } from '@visx/event'; import { localPoint } from '@visx/event';
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart'; import { XYChart, DataContext } from '@visx/xychart';
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth'; import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
export const XYChartWithPointerEvents = ({ export const XYChartWithPointerEvents = ({
onPointerMove, onPointerMove, onPointerUp, onPointerPressedChange, ...props
onPointerUp,
onPointerPressedChange,
...props
}: { }: {
onPointerMove?: (point: Point | EventHandlerParams<object>) => void; onPointerMove?: (point: Point) => void;
onPointerUp?: (point: Point | EventHandlerParams<object>) => void; onPointerUp?: (point: Point) => void;
onPointerPressedChange?: (isPointerPressed: boolean) => void; onPointerPressedChange?: (isPointerPressed: boolean) => void;
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => { } & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
const { xScale, yScale } = useContext(DataContext); const { xScale, yScale } = useContext(DataContext);
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>(); const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null; const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
const pointerChartPosition = const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
xScale &&
yScale &&
pointerContainerPosition &&
new Point({ new Point({
// @ts-expect-error invert supposedly doesn't exist on AxisScale
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2), 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), y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
}); });
@@ -36,13 +29,15 @@ export const XYChartWithPointerEvents = ({
{...props} {...props}
onPointerMove={({ event }) => { onPointerMove={({ event }) => {
setLastPointerMoveEvent(event as React.PointerEvent); setLastPointerMoveEvent(event as React.PointerEvent);
if (pointerChartPosition) onPointerMove?.(pointerChartPosition); if (pointerChartPosition)
onPointerMove?.(pointerChartPosition);
}} }}
onPointerOut={() => setLastPointerMoveEvent(undefined)} onPointerOut={() => setLastPointerMoveEvent(undefined)}
onPointerDown={() => onPointerPressedChange?.(true)} onPointerDown={() => onPointerPressedChange?.(true)}
onPointerUp={() => { onPointerUp={() => {
onPointerPressedChange?.(false); onPointerPressedChange?.(false);
if (pointerChartPosition) onPointerUp?.(pointerChartPosition); if (pointerChartPosition)
onPointerUp?.(pointerChartPosition);
}} }}
> >
{props.children} {props.children}
+3 -3
View File
@@ -142,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number]; export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
// ------ Transfer Items ------ // // ------ Transfer Items ------ //
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField; export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
const transferInputFields = [...TransferInputField.values()] as const; const transferInputFields = [...TransferInputField.values()] as const;
export type TransferInputFields = (typeof transferInputFields)[number]; export type TransferInputFields = (typeof transferInputFields)[number];
@@ -151,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
export type TransferTypes = (typeof transferTypes)[number]; export type TransferTypes = (typeof transferTypes)[number];
// ------ Trade Items ------ // // ------ Trade Items ------ //
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField; export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
const tradeInputFields = [...TradeInputField.values()] as const; const tradeInputFields = [...TradeInputField.values()] as const;
export type TradeInputFields = (typeof tradeInputFields)[number]; export type TradeInputFields = (typeof tradeInputFields)[number];
@@ -162,7 +162,7 @@ export type TradeState<T> = {
}; };
export const ClosePositionInputField = export const ClosePositionInputField =
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField; Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
const closePositionInputFields = [...ClosePositionInputField.values()] as const; const closePositionInputFields = [...ClosePositionInputField.values()] as const;
export type ClosePositionInputFields = (typeof closePositionInputFields)[number]; export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
-1
View File
@@ -96,7 +96,6 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
elapsedTime?: number; elapsedTime?: number;
blockHeight?: number; blockHeight?: number;
indexerBlockHeight?: number; indexerBlockHeight?: number;
trailingBlocks?: number;
} }
: // Navigation : // Navigation
T extends AnalyticsEvent.NavigatePage T extends AnalyticsEvent.NavigatePage
-40
View File
@@ -1,40 +0,0 @@
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;
};
-1
View File
@@ -25,5 +25,4 @@ export const DEFAULT_MARKETID = 'ETH-USD';
export enum FundingDirection { export enum FundingDirection {
ToShort = 'ToShort', ToShort = 'ToShort',
ToLong = 'ToLong', ToLong = 'ToLong',
None = 'None',
} }
+5
View File
@@ -0,0 +1,5 @@
export enum QueryKeys {
ACCOUNT_BALANCE = 'ACCOUNT_BALANCE',
LAUNCH_INCENTIVES = 'LAUNCH_INCENTIVES',
TRANSACTION_STATUS = 'TRANSACTION_STATUS',
}
+13 -2
View File
@@ -52,7 +52,7 @@ type WalletConnectionTypeConfig = {
wagmiConnectorId?: string; wagmiConnectorId?: string;
}; };
export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectionTypeConfig> = { export const WALLET_CONNECTION_TYPES: Record<WalletConnectionType, WalletConnectionTypeConfig> = {
[WalletConnectionType.CoinbaseWalletSdk]: { [WalletConnectionType.CoinbaseWalletSdk]: {
name: 'Coinbase Wallet SDK', name: 'Coinbase Wallet SDK',
wagmiConnectorId: 'coinbaseWallet', wagmiConnectorId: 'coinbaseWallet',
@@ -110,7 +110,7 @@ export const WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS = Object.values(
WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS
); );
type WalletConfig = { export type WalletConfig = {
type: WalletType; type: WalletType;
stringKey: string; stringKey: string;
icon: string; icon: string;
@@ -274,6 +274,17 @@ export type WalletConnection = {
provider?: ExternalProvider; provider?: ExternalProvider;
}; };
export type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
};
// dYdX Chain wallets // dYdX Chain wallets
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0"; export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
+27 -11
View File
@@ -1,11 +1,12 @@
import { useCallback } from 'react'; import { useCallback, useEffect } from 'react';
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import { useBalance } from 'wagmi'; import { useBalance, useBlockNumber } from 'wagmi';
import { StargateClient } from '@cosmjs/stargate'; import { StargateClient } from '@cosmjs/stargate';
import { useQuery } from 'react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { formatUnits } from 'viem'; import { formatUnits } from 'viem';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QueryKeys } from '@/constants/queries';
import { EvmAddress } from '@/constants/wallets'; import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils'; import { convertBech32Address } from '@/lib/addressUtils';
@@ -52,15 +53,27 @@ export const useAccountBalance = ({
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId); const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const stakingBalances = useSelector(getStakingBalances, shallowEqual); const stakingBalances = useSelector(getStakingBalances, shallowEqual);
const evmQuery = useBalance({ const queryClient = useQueryClient();
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')), const { data: blockNumber } = useBlockNumber({ watch: true });
const {
data: evmQuery,
status: evmBalanceStatus,
fetchStatus: evmBalanceFetchStatus,
queryKey,
} = useBalance({
query: {
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
},
address: evmAddress, address: evmAddress,
chainId: typeof chainId === 'number' ? chainId : Number(evmChainId), chainId: typeof chainId === 'number' ? chainId : Number(evmChainId),
token: token:
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress), addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress),
watch: true,
}); });
useEffect(() => {
queryClient.invalidateQueries({ queryKey });
}, [blockNumber]);
const cosmosQueryFn = useCallback(async () => { const cosmosQueryFn = useCallback(async () => {
if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) { if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) {
const address = convertBech32Address({ const address = convertBech32Address({
@@ -78,7 +91,7 @@ export const useAccountBalance = ({
const cosmosQuery = useQuery({ const cosmosQuery = useQuery({
enabled: Boolean(isCosmosChain && dydxAddress && bech32AddrPrefix && rpc && addressOrDenom), enabled: Boolean(isCosmosChain && dydxAddress && bech32AddrPrefix && rpc && addressOrDenom),
queryKey: `accountBalances_${chainId}_${addressOrDenom}`, queryKey: [QueryKeys.ACCOUNT_BALANCE, chainId, addressOrDenom],
queryFn: cosmosQueryFn, queryFn: cosmosQueryFn,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnMount: false, refetchOnMount: false,
@@ -87,8 +100,11 @@ export const useAccountBalance = ({
staleTime: 10_000, staleTime: 10_000,
}); });
const { formatted: evmBalance } = evmQuery.data || {}; const balance = isCosmosChain
const balance = isCosmosChain ? cosmosQuery.data : evmBalance; ? cosmosQuery.data
: evmQuery
? formatUnits(evmQuery?.value, evmQuery?.decimals)
: undefined;
const nativeTokenCoinBalance = balances?.[chainTokenDenom]; const nativeTokenCoinBalance = balances?.[chainTokenDenom];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount); const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
@@ -104,7 +120,7 @@ export const useAccountBalance = ({
nativeTokenBalance, nativeTokenBalance,
nativeStakingBalance, nativeStakingBalance,
usdcBalance, usdcBalance,
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status, queryStatus: isCosmosChain ? cosmosQuery.status : evmBalanceStatus,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching', isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmBalanceFetchStatus === 'fetching',
}; };
}; };
-3
View File
@@ -39,7 +39,6 @@ const useAccountsContext = () => {
walletConnectionType, walletConnectionType,
selectWalletType, selectWalletType,
selectedWalletType, selectedWalletType,
selectedWalletError,
evmAddress, evmAddress,
signerWagmi, signerWagmi,
publicClientWagmi, publicClientWagmi,
@@ -300,9 +299,7 @@ const useAccountsContext = () => {
walletConnectionType, walletConnectionType,
// Wallet selection // Wallet selection
selectWalletType,
selectedWalletType, selectedWalletType,
selectedWalletError,
// Wallet connection (EVM) // Wallet connection (EVM)
evmAddress, evmAddress,
+1 -2
View File
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
}, []); }, []);
// AnalyticsEvent.NetworkStatus // AnalyticsEvent.NetworkStatus
const { height, indexerHeight, status, trailingBlocks} = useApiState(); const { height, indexerHeight, status } = useApiState();
useEffect(() => { useEffect(() => {
if (status) { if (status) {
@@ -114,7 +114,6 @@ export const useAnalytics = () => {
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery, elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
blockHeight: height ?? undefined, blockHeight: height ?? undefined,
indexerBlockHeight: indexerHeight ?? undefined, indexerBlockHeight: indexerHeight ?? undefined,
trailingBlocks: trailingBlocks ?? undefined
}); });
} }
}, [status]); }, [status]);
+1 -1
View File
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
export const useApiState = () => { export const useApiState = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const apiState = useSelector(getApiState, shallowEqual); const apiState = useSelector(getApiState, shallowEqual);
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {}; const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter }); const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
const indexerHeight = getIndexerHeight(apiState); const indexerHeight = getIndexerHeight(apiState);
+24 -13
View File
@@ -1,8 +1,9 @@
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react'; import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from '@tanstack/react-query';
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage'; import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications'; import { type TransferNotifcation } from '@/constants/notifications';
import { QueryKeys } from '@/constants/queries';
import { useAccounts } from '@/hooks/useAccounts'; import { useAccounts } from '@/hooks/useAccounts';
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid'; import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
@@ -68,8 +69,8 @@ const useLocalNotificationsContext = () => {
[transferNotifications] [transferNotifications]
); );
useQuery({ const { data: newTransferNotifications } = useQuery({
queryKey: 'getTransactionStatus', queryKey: [QueryKeys.TRANSACTION_STATUS],
queryFn: async () => { queryFn: async () => {
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => { const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
const newTransferNotifications = await Promise.all( const newTransferNotifications = await Promise.all(
@@ -84,10 +85,11 @@ const useLocalNotificationsContext = () => {
status: currentStatus, status: currentStatus,
} = transferNotification; } = transferNotification;
// @ts-ignore status.errors is not in the type definition but can be returned const hasErrors =
// also error can some time come back as an empty object so we need to ignore for that // @ts-ignore status.errors is not in the type definition but can be returned
const hasErrors = !!currentStatus?.errors || // also error can some time come back as an empty object so we need to ignore for that
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0); !!currentStatus?.errors ||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
if ( if (
!hasErrors && !hasErrors &&
@@ -95,11 +97,14 @@ const useLocalNotificationsContext = () => {
currentStatus?.squidTransactionStatus === 'ongoing') currentStatus?.squidTransactionStatus === 'ongoing')
) { ) {
try { try {
const status = await fetchSquidStatus({ const status = await fetchSquidStatus(
transactionId: txHash, {
toChainId, transactionId: txHash,
fromChainId, toChainId,
}, isCctp); fromChainId,
},
isCctp
);
if (status) { if (status) {
transferNotification.status = status; transferNotification.status = status;
@@ -121,12 +126,18 @@ const useLocalNotificationsContext = () => {
return newTransferNotifications; return newTransferNotifications;
}; };
const newTransferNotifications = await processTransferNotifications(transferNotifications); const newTransferNotifications = await processTransferNotifications(transferNotifications);
setTransferNotifications(newTransferNotifications); return newTransferNotifications;
}, },
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL, refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
}); });
useEffect(() => {
if (!newTransferNotifications) return;
setTransferNotifications(newTransferNotifications);
}, [newTransferNotifications]);
return { return {
transferNotifications, transferNotifications,
addTransferNotification, addTransferNotification,
+13 -6
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useNetwork, useSwitchNetwork } from 'wagmi'; import { UseSwitchChainReturnType, useAccount, useSwitchChain } from 'wagmi';
export const useMatchingEvmNetwork = ({ export const useMatchingEvmNetwork = ({
chainId, chainId,
@@ -8,10 +8,10 @@ export const useMatchingEvmNetwork = ({
}: { }: {
chainId?: string | number; chainId?: string | number;
switchAutomatically?: boolean; switchAutomatically?: boolean;
onError?: (error: Error) => void; onError?: Parameters<UseSwitchChainReturnType['switchChainAsync']>[1]['onError'];
}) => { }) => {
const { chain } = useNetwork(); const { chain } = useAccount();
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError }); const { switchChainAsync, isPending } = useSwitchChain();
// 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 +21,14 @@ export const useMatchingEvmNetwork = ({
const matchNetwork = useCallback(async () => { const matchNetwork = useCallback(async () => {
if (!isMatchingNetwork) { if (!isMatchingNetwork) {
await switchNetworkAsync?.(Number(chainId)); await switchChainAsync?.(
{
chainId: Number(chainId),
},
{
onError,
}
);
} }
}, [chainId, chain]); }, [chainId, chain]);
@@ -34,6 +41,6 @@ export const useMatchingEvmNetwork = ({
return { return {
isMatchingNetwork, isMatchingNetwork,
matchNetwork, matchNetwork,
isSwitchingNetwork: isLoading, isSwitchingNetwork: isPending,
}; };
}; };
-44
View File
@@ -1,44 +0,0 @@
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]);
};
+14 -10
View File
@@ -104,12 +104,13 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType] [walletConnectConfig, walletType, walletConnectionType]
); );
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector }) const { connectAsync: connectWagmi } = useConnectWagmi();
const { suggestAndConnect: connectGraz } = useConnectGraz(); const { suggestAndConnect: connectGraz } = useConnectGraz();
const connectWallet = useCallback( const connectWallet = useCallback(
async ({ walletType }: { walletType: WalletType }) => { async ({ walletType }: { walletType: WalletType }) => {
const walletConnection = getWalletConnection({ walletType }); const walletConnection = getWalletConnection({ walletType });
console.log({ walletConnection });
try { try {
if (!walletConnection) { if (!walletConnection) {
@@ -133,13 +134,17 @@ export const useWalletConnection = () => {
} }
} else { } else {
if (!isConnectedWagmi) { if (!isConnectedWagmi) {
await connectWagmi({ const connector = resolveWagmiConnector({
connector: resolveWagmiConnector({ walletType,
walletType, walletConnection,
walletConnection, walletConnectConfig,
walletConnectConfig,
}),
}); });
if (connector) {
await connectWagmi({
connector,
});
}
} }
} }
} catch (error) { } catch (error) {
@@ -156,7 +161,7 @@ export const useWalletConnection = () => {
walletConnectionType: walletConnection.type, walletConnectionType: walletConnection.type,
}; };
}, },
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi] [isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, walletConnectConfig]
); );
const disconnectWallet = useCallback(async () => { const disconnectWallet = useCallback(async () => {
@@ -204,10 +209,9 @@ export const useWalletConnection = () => {
})(); })();
}, [selectedWalletType, signerWagmi, signerGraz]); }, [selectedWalletType, signerWagmi, signerGraz]);
const selectWalletType = async (walletType: WalletType | undefined) => { const selectWalletType = (walletType: WalletType | undefined) => {
if (selectedWalletType) { if (selectedWalletType) {
setSelectedWalletType(undefined); setSelectedWalletType(undefined);
await new Promise(requestAnimationFrame);
} }
setSelectedWalletType(walletType); setSelectedWalletType(walletType);
+3 -1
View File
@@ -1 +1,3 @@
<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> <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>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 205 B

+3
View File
@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 969 B

+1
View File
@@ -19,6 +19,7 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
export { default as CoinsIcon } from './coins.svg'; export { default as CoinsIcon } from './coins.svg';
export { default as CommentIcon } from './comment.svg'; export { default as CommentIcon } from './comment.svg';
export { default as CopyIcon } from './copy.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 DepositIcon } from './deposit.svg';
export { default as DepthChartIcon } from './depth-chart.svg'; export { default as DepthChartIcon } from './depth-chart.svg';
export { default as DiscordIcon } from './discord.svg'; export { default as DiscordIcon } from './discord.svg';
+110 -121
View File
@@ -1,7 +1,6 @@
import { createConfig, configureChains, mainnet, Chain } from 'wagmi'; import { createConfig, createConnector, http, usePublicClient } from 'wagmi';
import { goerli } from 'wagmi/chains';
import { import {
type Chain,
arbitrum, arbitrum,
arbitrumGoerli, arbitrumGoerli,
avalanche, avalanche,
@@ -26,34 +25,30 @@ import {
fantomTestnet, fantomTestnet,
celo, celo,
celoAlfajores, celoAlfajores,
scroll, mainnet,
kava, goerli,
sepolia, sepolia,
} from 'viem/chains'; } from 'wagmi/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy'; import { coinbaseWallet, injected, metaMask, walletConnect } from 'wagmi/connectors';
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'; import type { ExternalProvider } from '@ethersproject/providers';
import { publicProvider } from 'wagmi/providers/public';
import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { MetaMaskConnector } from 'wagmi/connectors/metaMask';
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect';
import { import {
type WalletConnectConfig,
type WalletConnection, type WalletConnection,
WalletConnectionType, WalletConnectionType,
type WalletType, WalletType,
walletConnectionTypes, WALLET_CONNECTION_TYPES,
wallets, wallets,
WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS, WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
WalletConfig,
} from '@/constants/wallets'; } from '@/constants/wallets';
import { isTruthy } from './isTruthy'; import { isTruthy } from './isTruthy';
import { log } from './telemetry';
// Config // Config
const WAGMI_SUPPORTED_CHAINS: Parameters<typeof createConfig>[0]['chains'] = [
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
mainnet, mainnet,
goerli, goerli,
sepolia, sepolia,
@@ -81,122 +76,106 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
fantomTestnet, fantomTestnet,
celo, celo,
celoAlfajores, celoAlfajores,
scroll,
kava,
]; ];
const { chains, publicClient, webSocketPublicClient } = configureChains( export const config = createConfig({
WAGMI_SUPPORTED_CHAINS, chains: WAGMI_SUPPORTED_CHAINS,
[ transports: Object.fromEntries(
import.meta.env.VITE_ALCHEMY_API_KEY && Object.values(WAGMI_SUPPORTED_CHAINS).map(({ id }) => [id, http()])
alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }), ),
jsonRpcProvider({ });
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }),
}),
publicProvider(),
].filter(isTruthy)
);
const injectedConnectorOptions = { const createdInjectedConnectorWithProvider = ({ provider }: { provider: ExternalProvider }) => {
chains, console.log(provider);
options: { return injected({
name: 'Injected', target() {
return {
id: 'windowProvider',
name: 'Injected',
provider,
};
},
shimDisconnect: true, shimDisconnect: true,
shimChainChangedDisconnect: false, });
},
}; };
type WalletConnectConfig = { const getConnector = ({
client: { walletConnectionType,
name: string; walletConnectConfig,
description: string; }: {
iconUrl: string; walletConnectionType: WalletConnectionType;
}; walletConnectConfig: WalletConnectConfig;
v2: { }) => {
projectId: string; console.log(walletConnectionType);
}; switch (walletConnectionType) {
case WalletConnectionType.WalletConnect2:
return [
walletConnect(
getConnectorInfoForWc2({
walletConnectConfig,
})
),
];
case WalletConnectionType.CoinbaseWalletSdk:
return [
coinbaseWallet({
appName: 'dYdX',
appLogoUrl: walletConnectConfig.client.iconUrl,
reloadOnDisconnect: false,
darkMode: true,
}),
];
case WalletConnectionType.InjectedEip1193:
return injected({
target() {
return {
id: 'windowProvider',
name: 'Injected',
provider: window.ethereum,
};
},
shimDisconnect: true,
});
case WalletConnectionType.CosmosSigner:
default: {
return undefined;
}
}
}; };
const getWalletconnect2ConnectorOptions = ( const getConnectorInfoForWc2 = ({
config: WalletConnectConfig walletConnectId,
): ConstructorParameters<typeof WalletConnectConnector>[0] => ({ walletConnectConfig,
chains, }: {
options: { walletConnectId?: string;
projectId: config.v2.projectId, walletConnectConfig: WalletConnectConfig;
}) => {
const explorerRecommendedWalletIds = walletConnectId
? [walletConnectId]
: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS;
return {
projectId: walletConnectConfig.v2.projectId,
metadata: { metadata: {
name: config.client.name, name: walletConnectConfig.client.name,
description: config.client.description, description: walletConnectConfig.client.description,
url: import.meta.env.VITE_BASE_URL, url: import.meta.env.VITE_BASE_URL,
icons: [config.client.iconUrl], icons: [walletConnectConfig.client.iconUrl],
}, },
showQrModal: true, showQrModal: true,
qrModalOptions: { qrModalOptions: {
themeMode: 'dark' as const, themeMode: 'dark' as const,
themeVariables: { themeVariables: {
'--wcm-accent-color': '#5973fe', '--wcm-accent-color': 'var(--color-accent)',
'--wcm-font-family': 'var(--fontFamily-base)', '--wcm-font-family': 'var(--fontFamily-base)',
'--wcm-background-color': 'var(--color-accent)',
}, },
explorerRecommendedWalletIds: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS, explorerRecommendedWalletIds,
}, },
}, };
});
const getConnectors = (walletConnectConfig: WalletConnectConfig) => [
new MetaMaskConnector({
chains,
options: {
shimDisconnect: true,
},
}),
new CoinbaseWalletConnector({
chains,
options: {
appName: 'dYdX',
reloadOnDisconnect: false,
},
}),
new WalletConnectConnector(getWalletconnect2ConnectorOptions(walletConnectConfig)),
new InjectedConnector(injectedConnectorOptions),
];
export const config = createConfig({
autoConnect: true,
// connectors,
publicClient,
webSocketPublicClient,
});
// Custom connectors
import type { ExternalProvider } from '@ethersproject/providers';
// Create a custom wagmi InjectedConnector using a specific injected EIP-1193 provider (instead of wagmi's default detection logic)
const createInjectedConnectorWithProvider = (provider: ExternalProvider) =>
new (class extends InjectedConnector {
getProvider = async () =>
provider as unknown as Awaited<ReturnType<InjectedConnector['getProvider']>>;
})(injectedConnectorOptions) as InjectedConnector;
const createWalletConnect2ConnectorWithId = (
walletconnectId: string,
walletConnectConfig: WalletConnectConfig
) => {
const walletconnect2ConnectorOptions = getWalletconnect2ConnectorOptions(walletConnectConfig);
return new WalletConnectConnector({
...walletconnect2ConnectorOptions,
options: {
...walletconnect2ConnectorOptions.options,
qrModalOptions: {
...walletconnect2ConnectorOptions.options.qrModalOptions,
explorerRecommendedWalletIds: [walletconnectId],
explorerExcludedWalletIds: 'ALL',
},
},
});
}; };
// Custom connector from wallet selection // Custom connector from wallet selection
export const resolveWagmiConnector = ({ export const resolveWagmiConnector = ({
walletType, walletType,
walletConnection, walletConnection,
@@ -207,13 +186,23 @@ export const resolveWagmiConnector = ({
walletConnectConfig: WalletConnectConfig; walletConnectConfig: WalletConnectConfig;
}) => { }) => {
const walletConfig = wallets[walletType]; const walletConfig = wallets[walletType];
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider if (walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider) {
? createInjectedConnectorWithProvider(walletConnection.provider) if (walletType === WalletType.MetaMask) {
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id return metaMask();
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig) }
: getConnectors(walletConnectConfig).find( return createdInjectedConnectorWithProvider({ provider: walletConnection.provider });
({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId } else if (
); walletConnection.type === WalletConnectionType.WalletConnect2 &&
walletConfig.walletconnect2Id
) {
return walletConnect(
getConnectorInfoForWc2({
walletConnectId: walletConfig.walletconnect2Id,
walletConnectConfig,
})
);
} else {
return getConnector({ walletConnectionType: walletConnection.type, walletConnectConfig });
}
}; };
+13 -29
View File
@@ -30,9 +30,6 @@ 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 { MigratePanel } from './rewards/MigratePanel';
const ENS_CHAIN_ID = 1; // Ethereum const ENS_CHAIN_ID = 1; // Ethereum
const Profile = () => { const Profile = () => {
@@ -178,27 +175,8 @@ const Profile = () => {
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))} onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
/> />
</Styled.EqualGrid> </Styled.EqualGrid>
<MigratePanel />
<DYDXBalancePanel />
<Styled.EqualGrid> <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 <Panel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })} slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`} href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
@@ -213,6 +191,19 @@ const Profile = () => {
layout="grid" layout="grid"
/> />
</Panel> </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.EqualGrid>
<Styled.TablePanel <Styled.TablePanel
@@ -336,13 +327,6 @@ Styled.Details = styled(Details)`
`; `;
Styled.RewardsPanel = styled(Panel)` Styled.RewardsPanel = styled(Panel)`
align-self: flex-start;
&,
> * {
height: 100%;
}
dl { dl {
--details-grid-numColumns: 1; --details-grid-numColumns: 1;
} }
+1 -1
View File
@@ -118,7 +118,7 @@ export default () => {
}, },
{ {
value: PortfolioRoute.Positions, value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Positions} />, slotBefore: <Styled.Icon iconName={IconName.Cube} />,
label: stringGetter({ key: STRING_KEYS.POSITIONS }), label: stringGetter({ key: STRING_KEYS.POSITIONS }),
href: PortfolioRoute.Positions, href: PortfolioRoute.Positions,
}, },
+12 -2
View File
@@ -2,6 +2,7 @@ import type { ElementType } from 'react';
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks'; import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
@@ -32,7 +33,7 @@ export const DYDXBalancePanel = () => {
const { chainTokenLabel } = useTokenConfigs(); const { chainTokenLabel } = useTokenConfigs();
return ( return (
<Panel <Styled.Panel
slotHeader={ slotHeader={
<Styled.Header> <Styled.Header>
<Styled.Title> <Styled.Title>
@@ -108,12 +109,21 @@ export const DYDXBalancePanel = () => {
]} ]}
/> />
</Styled.Content> </Styled.Content>
</Panel> </Styled.Panel>
); );
}; };
const Styled: Record<string, AnyStyledComponent> = {}; 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` Styled.Header = styled.div`
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
gap: 1rem; gap: 1rem;
+18 -5
View File
@@ -1,11 +1,12 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useQuery } from 'react-query'; import { useQuery } from '@tanstack/react-query';
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons'; import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { QueryKeys } from '@/constants/queries';
import breakpoints from '@/styles/breakpoints'; import breakpoints from '@/styles/breakpoints';
import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks'; import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks';
@@ -68,9 +69,9 @@ const EstimatedRewards = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts(); const { dydxAddress } = useAccounts();
const { data, isLoading } = useQuery({ const { data, isLoading, status, error } = useQuery({
enabled: !!dydxAddress, enabled: !!dydxAddress,
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`, queryKey: [QueryKeys.LAUNCH_INCENTIVES, dydxAddress, SEASON_NUMBER],
queryFn: async () => { queryFn: async () => {
if (!dydxAddress) return undefined; if (!dydxAddress) return undefined;
const resp = await fetch( const resp = await fetch(
@@ -78,9 +79,14 @@ const EstimatedRewards = () => {
); );
return (await resp.json())?.incentivePoints; return (await resp.json())?.incentivePoints;
}, },
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
}); });
useEffect(() => {
if (status === 'error') {
log('LaunchIncentives/fetchPoints', error);
}
}, [status]);
return ( return (
<Styled.EstimatedRewardsCard> <Styled.EstimatedRewardsCard>
<Styled.EstimatedRewardsCardContent> <Styled.EstimatedRewardsCardContent>
@@ -153,8 +159,15 @@ const LaunchIncentivesContent = () => {
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)` Styled.Panel = styled(Panel)`
--panel-paddingY: 1rem;
--panel-paddingX: 1.5rem;
background-color: var(--color-layer-4); background-color: var(--color-layer-4);
width: 100%; width: 100%;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
}
`; `;
Styled.ForV4 = styled.span` Styled.ForV4 = styled.span`
@@ -218,7 +231,7 @@ Styled.Column = styled.div`
Styled.EstimatedRewardsCard = styled.div` Styled.EstimatedRewardsCard = styled.div`
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
min-width: 19rem; min-width: 21.25rem;
height: calc(100% - calc(1.5rem * 2)); height: calc(100% - calc(1.5rem * 2));
margin: 1.5rem; margin: 1.5rem;
+11 -5
View File
@@ -7,6 +7,8 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks'; import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details'; import { Details } from '@/components/Details';
@@ -26,7 +28,7 @@ import { MustBigNumber } from '@/lib/numbers';
const TOKEN_MIGRATION_LEARN_MORE_LINK = const TOKEN_MIGRATION_LEARN_MORE_LINK =
'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx'; 'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx';
export const MigratePanel = ({ className }: { className?: string }) => { export const MigratePanel = () => {
const { isNotTablet } = useBreakpoints(); const { isNotTablet } = useBreakpoints();
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
@@ -43,7 +45,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
return isNotTablet ? ( return isNotTablet ? (
<Styled.MigratePanel <Styled.MigratePanel
className={className}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>} slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
slotRight={ slotRight={
<Styled.MigrateAction> <Styled.MigrateAction>
@@ -73,7 +74,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
</Styled.MigratePanel> </Styled.MigratePanel>
) : ( ) : (
<Styled.MigratePanel <Styled.MigratePanel
className={className}
slotHeader={ slotHeader={
<Styled.MobileMigrateHeader> <Styled.MobileMigrateHeader>
<h3> <h3>
@@ -134,11 +134,17 @@ export const MigratePanel = ({ className }: { className?: string }) => {
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.MigratePanel = styled(Panel)` Styled.MigratePanel = styled(Panel)`
--panel-paddingX: 1.5rem;
width: 100%; width: 100%;
background-image: url('/dots-background.svg'); background-image: url('/dots-background.svg');
background-position: right; background-position: right;
background-repeat: no-repeat; background-repeat: no-repeat;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`; `;
Styled.Title = styled.h3` Styled.Title = styled.h3`
@@ -151,7 +157,7 @@ Styled.Title = styled.h3`
Styled.MigrateAction = styled.div` Styled.MigrateAction = styled.div`
${layoutMixins.flexEqualColumns} ${layoutMixins.flexEqualColumns}
align-items: center; align-items: center;
margin: 1rem; margin-right: 1rem;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
width: 100%; width: 100%;
@@ -191,7 +197,7 @@ Styled.MobileMigrateHeader = styled.div`
font: var(--font-small-book); font: var(--font-small-book);
color: var(--color-text-0); color: var(--color-text-0);
padding: 1rem 1rem 0; padding: 1rem 1.5rem 0;
h3 { h3 {
${layoutMixins.inlineRow} ${layoutMixins.inlineRow}
+1 -7
View File
@@ -1,6 +1,5 @@
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { breakpoints } from '@/styles';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks'; import { useStringGetter } from '@/hooks';
@@ -52,7 +51,6 @@ export const RewardsHelpPanel = () => {
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.HelpCard = styled(Panel)` Styled.HelpCard = styled(Panel)`
--panel-content-paddingX: 0;
--panel-content-paddingY: 0; --panel-content-paddingY: 0;
width: 100%; width: 100%;
height: max-content; height: max-content;
@@ -66,15 +64,11 @@ Styled.Header = styled.div`
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
gap: 1ch; gap: 1ch;
padding: 1rem 1rem; padding: 1.25rem 1.5rem;
border-bottom: var(--border-width) solid var(--border-color); border-bottom: var(--border-width) solid var(--border-color);
font: var(--font-small-book); font: var(--font-small-book);
@media ${breakpoints.notTablet} {
padding: 1.5rem 1.25rem;
}
h3 { h3 {
font: var(--font-medium-book); font: var(--font-medium-book);
color: var(--color-text-2); color: var(--color-text-2);
+17 -39
View File
@@ -1,18 +1,15 @@
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
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 { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { AppRoute } from '@/constants/routes';
import { useBreakpoints, useStringGetter, useURLConfigs } 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 { Panel } from '@/components/Panel'; import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon'; import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton'; import { IconButton } from '@/components/IconButton';
@@ -23,14 +20,12 @@ 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';
const RewardsPage = () => { const RewardsPage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { governanceLearnMore, stakingLearnMore } = useURLConfigs(); const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
const { isTablet, isNotTablet } = useBreakpoints(); const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate();
const panelArrow = ( const panelArrow = (
<Styled.Arrow> <Styled.Arrow>
@@ -44,16 +39,13 @@ const RewardsPage = () => {
return ( return (
<Styled.Page> <Styled.Page>
{isTablet && ( {import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
<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 ? ( {isTablet ? (
<LaunchIncentivesPanel /> <>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</>
) : ( ) : (
<Styled.PanelRowIncentivesAndBalance> <Styled.PanelRowIncentivesAndBalance>
<LaunchIncentivesPanel /> <LaunchIncentivesPanel />
@@ -63,9 +55,7 @@ const RewardsPage = () => {
<Styled.PanelRow> <Styled.PanelRow>
<Styled.Panel <Styled.Panel
slotHeaderContent={ slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
slotRight={panelArrow} slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
> >
@@ -78,9 +68,7 @@ const RewardsPage = () => {
</Styled.Panel> </Styled.Panel>
<Styled.Panel <Styled.Panel
slotHeaderContent={ slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
}
slotRight={panelArrow} slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
> >
@@ -92,8 +80,6 @@ const RewardsPage = () => {
</Styled.Description> </Styled.Description>
</Styled.Panel> </Styled.Panel>
</Styled.PanelRow> </Styled.PanelRow>
<RewardsHelpPanel />
</Styled.Page> </Styled.Page>
); );
}; };
@@ -109,41 +95,33 @@ Styled.Page = styled.div`
align-items: center; align-items: center;
> * { > * {
--content-max-width: 80rem; --content-max-width: 70rem;
max-width: min(calc(100vw - 4rem), var(--content-max-width)); max-width: min(calc(100vw - 4rem), var(--content-max-width));
} }
@media ${breakpoints.tablet} { @media ${breakpoints.tablet} {
--stickyArea-topHeight: var(--page-header-height-mobile); padding: 1.25rem;
padding: 0 1rem 1rem;
> * { > * {
max-width: calc(100vw - 2rem); max-width: calc(100vw - 2.5rem);
width: 100%; width: 100%;
} }
} }
`; `;
Styled.MobileHeader = styled.header`
${layoutMixins.contentSectionDetachedScrollable}
${layoutMixins.stickyHeader}
z-index: 2;
padding: 1.25rem 0;
margin-bottom: -1.5rem;
font: var(--font-large-medium);
color: var(--color-text-2);
background-color: var(--color-layer-2);
`;
Styled.Panel = styled(Panel)` Styled.Panel = styled(Panel)`
height: fit-content; --panel-paddingX: 1.5rem;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`; `;
Styled.Title = styled.h3` Styled.Title = styled.h3`
font: var(--font-medium-book); font: var(--font-medium-book);
color: var(--color-text-2); color: var(--color-text-2);
margin-bottom: -1rem; padding: 1rem 1.5rem 0;
`; `;
Styled.Description = styled.div` Styled.Description = styled.div`
+1 -6
View File
@@ -26,12 +26,7 @@ export const calculateFundingRateHistory = createSelector(
return data.map(({ effectiveAtMilliseconds, rate }) => ({ return data.map(({ effectiveAtMilliseconds, rate }) => ({
time: effectiveAtMilliseconds, time: effectiveAtMilliseconds,
fundingRate: rate, fundingRate: rate,
direction: direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort,
rate === 0
? FundingDirection.None
: rate < 0
? FundingDirection.ToLong
: FundingDirection.ToShort,
})); }));
} }
); );
+6
View File
@@ -119,6 +119,12 @@ export const MarketDetails: React.FC = () => {
tooltip: 'initial-margin-fraction', tooltip: 'initial-margin-fraction',
value: <Output useGrouping value={initialMarginFraction} type={OutputType.SmallPercent} />, 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 ( return (
@@ -1,20 +1,14 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import styled, { AnyStyledComponent, css, keyframes } from 'styled-components'; import styled, { AnyStyledComponent, css, keyframes } from 'styled-components';
import { useSelector, shallowEqual } from 'react-redux'; import { useSelector, shallowEqual } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { import { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { StringGetterFunction } from '@/constants/localization';
import { useBreakpoints } from '@/hooks'; import { useBreakpoints } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import { MustBigNumber } from '@/lib/numbers';
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors'; import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents'; import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents';
@@ -27,25 +21,24 @@ import {
darkTheme, darkTheme,
DataProvider, DataProvider,
EventEmitterProvider, EventEmitterProvider,
type EventHandlerParams,
} from '@visx/xychart'; } from '@visx/xychart';
import { LinearGradient } from '@visx/gradient'; import { LinearGradient } from '@visx/gradient';
import { curveStepAfter } from '@visx/curve'; import { curveStepAfter } from '@visx/curve';
import { Point } from '@visx/point'; import type { Point } from '@visx/point';
import Tooltip from '@/components/visx/XYChartTooltipWithBounds'; import Tooltip from '@/components/visx/XYChartTooltipWithBounds';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput'; import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { Details } from '@/components/Details';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner'; import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { OutputType } from '@/components/Output'; import { Output, OutputType } from '@/components/Output';
import { MustBigNumber } from '@/lib/numbers'; import { OrderSide } from '@dydxprotocol/v4-client-js';
import { DepthChartTooltipContent } from './Tooltip';
// @ts-ignore // @ts-ignore
const theme = buildChartTheme({ const theme = buildChartTheme({
...darkTheme, ...darkTheme,
colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s
}); });
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n)); const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
@@ -65,6 +58,30 @@ const formatNumber = (n: number, selectedLocale: string, isCompact: boolean = n
: formattedNumber; : 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 = ({ export const DepthChart = ({
onChartClick, onChartClick,
stringGetter, stringGetter,
@@ -79,6 +96,8 @@ export const DepthChart = ({
const { isMobile } = useBreakpoints(); const { isMobile } = useBreakpoints();
// Chart data // Chart data
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {}; const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { stepSizeDecimals, tickSizeDecimals } = const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {}; useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
@@ -93,15 +112,43 @@ export const DepthChart = ({
midMarketPrice, midMarketPrice,
spread, spread,
spreadPercent, spreadPercent,
orderbook, } = useMemo(() => {
} = useOrderbookValuesForDepthChart(); 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]);
// Chart state // Chart state
const [isPointerPressed, setIsPointerPressed] = useState(false); const [isPointerPressed, setIsPointerPressed] = useState(false);
const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>(); const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>();
const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer); const isEditingOrder = isPointerPressed && chartPointAtPointer;
const [zoomDomain, setZoomDomain] = useState<undefined | number>(); const [zoomDomain, setZoomDomain] = useState<undefined | number>();
@@ -146,24 +193,12 @@ export const DepthChart = ({
}, [orderbook, zoomDomain]); }, [orderbook, zoomDomain]);
const getChartPoint = useCallback( const getChartPoint = useCallback(
(point: Point | EventHandlerParams<object>) => { ({ x: price, y: size }: Point) =>
let price, size; ({
if (point instanceof Point) { side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL,
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, price,
size, size,
} as DepthChartPoint; } as DepthChartPoint),
},
[midMarketPrice] [midMarketPrice]
); );
@@ -221,7 +256,7 @@ export const DepthChart = ({
}} }}
onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))} onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))}
onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))} onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))}
onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)} onPointerPressedChange={setIsPointerPressed}
> >
<Axis <Axis
orientation="bottom" orientation="bottom"
@@ -328,7 +363,7 @@ export const DepthChart = ({
<Styled.XAxisLabelOutput <Styled.XAxisLabelOutput
type={OutputType.Fiat} type={OutputType.Fiat}
value={ value={
isEditingOrder && chartPointAtPointer isEditingOrder
? chartPointAtPointer.price ? chartPointAtPointer.price
: tooltipData!.nearestDatum?.datum.price : tooltipData!.nearestDatum?.datum.price
} }
@@ -339,8 +374,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)', [DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)', [DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[ }[
isEditingOrder && chartPointAtPointer isEditingOrder
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side] ? seriesKeyForOrderSide[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries) : (tooltipData!.nearestDatum?.key as DepthChartSeries)
] ]
} }
@@ -354,7 +389,7 @@ export const DepthChart = ({
<Styled.YAxisLabelOutput <Styled.YAxisLabelOutput
type={OutputType.Asset} type={OutputType.Asset}
value={ value={
isEditingOrder && chartPointAtPointer isEditingOrder
? chartPointAtPointer.size ? chartPointAtPointer.size
: tooltipData!.nearestDatum?.datum.depth : tooltipData!.nearestDatum?.datum.depth
} }
@@ -365,8 +400,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)', [DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)', [DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[ }[
isEditingOrder && chartPointAtPointer isEditingOrder
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side] ? seriesKeyForOrderSide[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries) : (tooltipData!.nearestDatum?.key as DepthChartSeries)
] ]
} }
@@ -375,22 +410,181 @@ export const DepthChart = ({
} }
snapTooltipToDatumX={!isEditingOrder} snapTooltipToDatumX={!isEditingOrder}
snapTooltipToDatumY={isEditingOrder ? false : isMobile} snapTooltipToDatumY={isEditingOrder ? false : isMobile}
renderTooltip={({ tooltipData, colorScale }) => renderTooltip={({ tooltipData, colorScale }) => {
chartPointAtPointer && ( const { nearestDatum } = tooltipData || {};
<DepthChartTooltipContent
{...{ if (!isEditingOrder && !nearestDatum?.datum) return null;
tooltipData,
colorScale, return (
isEditingOrder, <TooltipContent
chartPointAtPointer, accentColor={colorScale?.(
stringGetter, isEditingOrder
selectedLocale, ? seriesKeyForOrderSide[chartPointAtPointer.side]
stepSizeDecimals, : nearestDatum.key
tickSizeDecimals, )}
}} >
/> <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>
);
}}
/> />
</XYChartWithPointerEvents> </XYChartWithPointerEvents>
</EventEmitterProvider> </EventEmitterProvider>
-220
View File
@@ -1,220 +0,0 @@
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>
);
};
@@ -3,27 +3,39 @@ import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css } from 'styled-components'; import styled, { type AnyStyledComponent, css } from 'styled-components';
import { curveMonotoneX, curveStepAfter } from '@visx/curve'; import { curveMonotoneX, curveStepAfter } from '@visx/curve';
import { ButtonSize } from '@/constants/buttons';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { FundingDirection } from '@/constants/markets';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { useBreakpoints, useStringGetter } from '@/hooks'; import { useBreakpoints, useStringGetter } from '@/hooks';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { FundingDirection } from '@/constants/markets';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { Output, OutputType } from '@/components/Output'; import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner'; import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { ToggleGroup } from '@/components/ToggleGroup'; import { ToggleGroup } from '@/components/ToggleGroup';
import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart'; import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput'; import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { TooltipContent } from '@/components/visx/TooltipContent';
import type { TooltipContextType } from '@visx/xychart'; import type { TooltipContextType } from '@visx/xychart';
import { calculateFundingRateHistory } from '@/state/perpetualsCalculators'; import { calculateFundingRateHistory } from '@/state/perpetualsCalculators';
import { MustBigNumber } from '@/lib/numbers'; import { MustBigNumber } from '@/lib/numbers';
import { FundingChartTooltipContent } from './Tooltip';
enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour
@@ -104,20 +116,114 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => {
{ {
[FundingDirection.ToLong]: 'var(--color-negative)', [FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)', [FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction] }[tooltipDatum.direction]
} }
/> />
); );
}} }}
renderTooltip={({ tooltipData }) => ( renderTooltip={({ tooltipData }) => {
<FundingChartTooltipContent const { nearestDatum } = tooltipData || {};
fundingRateView={fundingRateView}
tooltipData={tooltipData} const tooltipDatum = nearestDatum?.datum ?? latestDatum;
latestDatum={latestDatum} const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
/>
)} return (
onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)} <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}
minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4} minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4}
numGridLines={1} numGridLines={1}
slotEmpty={<LoadingSpace id="funding-chart-loading" />} slotEmpty={<LoadingSpace id="funding-chart-loading" />}
@@ -184,7 +290,6 @@ Styled.FundingRateToggle = styled.div`
Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>` Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>`
place-self: start center; place-self: start center;
padding: clamp(1.5rem, 9rem - 15%, 4rem); padding: clamp(1.5rem, 9rem - 15%, 4rem);
pointer-events: none;
font: var(--font-large-book); font: var(--font-large-book);
-107
View File
@@ -1,107 +0,0 @@
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,19 +3,18 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization';
import { WalletType, wallets } from '@/constants/wallets';
import { ButtonAction, ButtonSize } from '@/constants/buttons'; import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { WalletType, wallets } from '@/constants/wallets';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { AlertMessage } from '@/components/AlertMessage'; import { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button'; 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 { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -25,7 +24,7 @@ export const ChooseWallet = () => {
const displayedWallets = useDisplayedWallets(); const displayedWallets = useDisplayedWallets();
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts(); const { selectWalletType, selectedWalletType, selectedWalletError } = useWalletConnection();
return ( return (
<> <>
@@ -86,13 +86,7 @@ export const GenerateKeys = ({
].includes(status); ].includes(status);
const signTypedData = getSignTypedData(selectedNetwork); const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData({ const { signTypedDataAsync } = useSignTypedData();
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY; const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY;
@@ -103,7 +97,13 @@ export const GenerateKeys = ({
// 1. First signature // 1. First signature
setStatus(EvmDerivedAccountStatus.Deriving); setStatus(EvmDerivedAccountStatus.Deriving);
const signature = await signTypedDataAsync(); const signature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature }); const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature });
// 2. Ensure signature is deterministic // 2. Ensure signature is deterministic
@@ -121,7 +121,13 @@ export const GenerateKeys = ({
setStatus(EvmDerivedAccountStatus.EnsuringDeterminism); setStatus(EvmDerivedAccountStatus.EnsuringDeterminism);
// Second signature // Second signature
const additionalSignature = await signTypedDataAsync(); const additionalSignature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
if (signature !== additionalSignature) { if (signature !== additionalSignature) {
throw new Error( throw new Error(
@@ -108,6 +108,20 @@ export const DepositButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0); 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 = [ const submitButtonReceipt = [
{ {
key: 'expected-deposit-amount', key: 'expected-deposit-amount',
@@ -116,7 +130,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: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />, value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmount} />,
subitems: [ subitems: [
{ {
key: 'minimum-deposit-amount', key: 'minimum-deposit-amount',
@@ -126,7 +140,7 @@ export const DepositButtonAndReceipt = ({
</span> </span>
), ),
value: ( value: (
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} /> <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmountMin} />
), ),
tooltip: 'minimum-deposit-amount', tooltip: 'minimum-deposit-amount',
}, },
@@ -34,7 +34,6 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps)
selectedToken && onSelectToken(selectedToken); selectedToken && onSelectToken(selectedToken);
}, },
slotBefore: <Styled.Img src={token.iconUrl} alt="" />, slotBefore: <Styled.Img src={token.iconUrl} alt="" />,
tag: resources?.tokenResources?.get(token.type)?.symbol,
})); }));
return ( return (