Compare commits

..
Author SHA1 Message Date
Aleka Cheung d60d7840b3 localize trading reward strings 2024-01-11 14:47:18 -05:00
Aleka Cheung ce84b4c21b nits + localization 2024-01-11 14:47:16 -05:00
Aleka Cheung 85cf0ecd77 nits + localization 2024-01-11 14:46:37 -05:00
Aleka Cheung 7eae69137b reward history table, polish etc 2024-01-09 17:06:33 -05:00
Aleka Cheung 340e124016 add period 2024-01-09 17:03:41 -05:00
Aleka Cheung a6c1aa5fdc add help panel 2024-01-09 17:03:41 -05:00
Aleka Cheung fc00e1d2a9 set up trading rewards layout and data 2024-01-09 17:03:39 -05:00
aleka b2234cb969 reorganize rewards vs. profile, update panel and accordion styling (#221)
* reorganize rewards vs. profile, update panel and accordion styling

* feedback
2024-01-09 16:57:02 -05:00
Bill 4bcb1f64ac Add Kava support for onboarding, and add token symbol to token select (#220)
* Add Kava support for onboarding, and add token symbol to token select

* bump abacus
2024-01-09 11:51:31 -08:00
37 changed files with 1655 additions and 4682 deletions
+5 -6
View File
@@ -39,9 +39,9 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.1.32",
"@dydxprotocol/v4-abacus": "^1.1.33",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.6",
"@dydxprotocol/v4-localization": "^1.1.12",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
@@ -72,7 +72,6 @@
"@reduxjs/toolkit": "^1.9.5",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@tanstack/react-query": "^5.17.9",
"@types/lodash": "^4.14.195",
"@types/styled-components": "^5.1.26",
"@visx/axis": "^3.1.0",
@@ -105,15 +104,15 @@
"react-aria": "^3.25.0",
"react-dom": "^18.2.0",
"react-number-format": "^5.2.2",
"react-query": "^3.39.3",
"react-redux": "^8.1.1",
"react-router-dom": "^6.14.0",
"react-stately": "^3.23.0",
"reselect": "^4.1.8",
"stream-browserify": "^3.0.0",
"styled-components": "^5.3.11",
"use-latest": "^1.2.1",
"viem": "^2.0.0",
"wagmi": "^2.0.3"
"viem": "^1.20.0",
"wagmi": "^1.4.12"
},
"devDependencies": {
"@babel/core": "^7.22.5",
+555 -4202
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -39,7 +39,7 @@
"environments": {
"dydxprotocol-dev": {
"name": "v4 Dev",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -113,7 +113,7 @@
},
"dydxprotocol-dev-2": {
"name": "v4 Dev 2",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -186,7 +186,7 @@
},
"dydxprotocol-dev-4": {
"name": "v4 Dev 4",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -259,7 +259,7 @@
},
"dydxprotocol-dev-5": {
"name": "v4 Dev 5",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -332,7 +332,7 @@
},
"dydxprotocol-staging": {
"name": "v4 Staging",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -407,7 +407,7 @@
},
"dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -480,7 +480,7 @@
},
"dydxprotocol-staging-west": {
"name": "v4 Staging West",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -555,7 +555,7 @@
},
"dydxprotocol-testnet": {
"name": "v4 Public Testnet",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -634,7 +634,7 @@
},
"dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -710,7 +710,7 @@
},
"dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -786,7 +786,7 @@
},
"dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -862,7 +862,7 @@
},
"dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -938,7 +938,7 @@
},
"dydxprotocol-testnet-polkachu": {
"name": "v4 Public Testnet/Polkahcu",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -1005,7 +1005,7 @@
},
"dydxprotocol-testnet-bware": {
"name": "v4 Public Testnet/BWare",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
+3 -3
View File
@@ -1,8 +1,8 @@
import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WagmiConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from 'react-query';
import { GrazProvider } from 'graz';
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
@@ -120,9 +120,9 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
};
const providers = [
wrapProvider(WagmiProvider, { config }),
wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider),
+26
View File
@@ -0,0 +1,26 @@
import type { Story } from '@ladle/react';
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
import { StoryWrapper } from '.ladle/components';
export const Accordion: Story<AccordionProps> = (args) => {
return (
<StoryWrapper>
<AccordionComponent {...args} />
</StoryWrapper>
);
};
Accordion.args = {
items: [
{
header: 'Question 1?',
content: 'Answer 1.',
},
{
header: 'Question 2?',
content: 'Answer 2.',
},
],
};
+13 -3
View File
@@ -3,6 +3,8 @@ import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
import { PlusIcon } from '@/icons';
export type AccordionItem = {
@@ -10,7 +12,7 @@ export type AccordionItem = {
content: React.ReactNode;
};
type AccordionProps = {
export type AccordionProps = {
items: AccordionItem[];
className?: string;
};
@@ -36,6 +38,13 @@ export const Accordion = ({ items, className }: AccordionProps) => (
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Root = styled(Root)`
--accordion-paddingY: 1rem;
--accordion-paddingX: 1rem;
@media ${breakpoints.notTablet} {
--accordion-paddingX: 1.5rem;
}
> *:not(:last-child) {
border-bottom: var(--border-width) solid var(--border-color);
}
@@ -67,7 +76,8 @@ Styled.Icon = styled.div`
Styled.Trigger = styled(Trigger)`
${layoutMixins.spacedRow}
width: 100%;
padding: 1rem 0.75rem;
padding: var(--accordion-paddingY) var(--accordion-paddingX);
gap: 0.5rem;
color: var(--color-text-1);
text-align: start;
@@ -91,7 +101,7 @@ Styled.Trigger = styled(Trigger)`
Styled.Content = styled(Content)`
overflow: hidden;
margin: 0 0.75rem 1rem;
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
color: var(--color-text-0);
+10 -1
View File
@@ -65,6 +65,9 @@ type ElementProps = {
resolution?: number;
stripRelativeWords?: boolean;
};
timeOptions?: {
useUTC?: boolean;
};
tag?: React.ReactNode;
withParentheses?: boolean;
locale?: string;
@@ -89,6 +92,7 @@ export const Output = ({
relativeTimeFormatOptions = {
format: 'singleCharacter',
},
timeOptions,
tag,
withParentheses,
locale = navigator.language || 'en-US',
@@ -165,16 +169,21 @@ export const Output = ({
if ((typeof value !== 'string' && typeof value !== 'number') || !value) return null;
const date = new Date(value);
const dateString = {
[OutputType.Date]: date.toLocaleString(selectedLocale, { dateStyle: 'medium' }),
[OutputType.Date]: date.toLocaleString(selectedLocale, {
dateStyle: 'medium',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
[OutputType.DateTime]: date.toLocaleString(selectedLocale, {
dateStyle: 'short',
timeStyle: 'short',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
[OutputType.Time]: date.toLocaleString(selectedLocale, {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
}[type];
+7
View File
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = {
slotHeaderContent?: string;
@@ -67,6 +68,12 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
--panel-content-paddingY: var(--panel-paddingY);
--panel-content-paddingX: var(--panel-paddingX);
@media ${breakpoints.notTablet} {
--panel-paddingX: 1.5rem;
--panel-paddingY: 1.25rem;
--panel-content-paddingY: 1rem;
}
${layoutMixins.row}
background-color: var(--color-layer-3);
+83 -62
View File
@@ -33,17 +33,19 @@ import {
import { useAsyncList } from 'react-stately';
import { useBreakpoints } from '@/hooks';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
import { Checkbox } from '@/components/Checkbox';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { STRING_KEYS } from '@/constants/localization';
import { CaretIcon } from '@/icons';
import { Icon, IconName } from './Icon';
import { Tag } from './Tag';
import { MustBigNumber } from '@/lib/numbers';
import { Button } from './Button';
export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader';
@@ -92,6 +94,7 @@ type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey ext
selectionBehavior?: 'replace' | 'toggle';
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
slotEmpty?: React.ReactNode;
initialNumRowsToShow?: number;
// collection: TableCollection<string>;
// children: React.ReactNode;
};
@@ -121,6 +124,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
selectionMode = 'single',
selectionBehavior = 'toggle',
slotEmpty,
initialNumRowsToShow = data.length,
// shouldRowRender,
// collection,
@@ -136,6 +140,8 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
style,
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
const stringGetter = useStringGetter();
const currentBreakpoints = useBreakpoints();
const shownColumns = columns.filter(
@@ -188,67 +194,77 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
const isEmpty = data.length === 0;
return (
<Styled.TableWrapper
className={className}
style={style}
isEmpty={isEmpty}
withGradientCardRows={withGradientCardRows}
withOuterBorder={withOuterBorder}
>
{!isEmpty ? (
<TableRoot
aria-label={label}
sortDescriptor={list.sortDescriptor}
onSortChange={list.sort}
selectedKeys={selectedKeys}
setSelectedKeys={setSelectedKeys}
selectionMode={selectionMode}
selectionBehavior={selectionBehavior}
getRowAttributes={getRowAttributes}
onRowAction={
onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
}
// shouldRowRender={shouldRowRender}
hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows}
withFocusStickyRows={withFocusStickyRows}
withOuterBorder={withOuterBorder}
withInnerBorders={withInnerBorders}
withScrollSnapColumns={withScrollSnapColumns}
withScrollSnapRows={withScrollSnapRows}
>
<TableHeader columns={shownColumns}>
{(column) => (
<Column
key={column.columnKey}
childColumns={column.childColumns}
allowsSorting={column.allowsSorting ?? true}
allowsResizing={column.allowsResizing}
width={column.width}
>
{column.label}
{column.tag && <Tag>{column.tag}</Tag>}
</Column>
)}
</TableHeader>
<>
<Styled.TableWrapper
className={className}
style={style}
isEmpty={isEmpty}
withGradientCardRows={withGradientCardRows}
withOuterBorder={withOuterBorder}
>
{!isEmpty ? (
<TableRoot
aria-label={label}
sortDescriptor={list.sortDescriptor}
onSortChange={list.sort}
selectedKeys={selectedKeys}
setSelectedKeys={setSelectedKeys}
selectionMode={selectionMode}
selectionBehavior={selectionBehavior}
getRowAttributes={getRowAttributes}
onRowAction={
onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
}
// shouldRowRender={shouldRowRender}
hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows}
withFocusStickyRows={withFocusStickyRows}
withOuterBorder={withOuterBorder}
withInnerBorders={withInnerBorders}
withScrollSnapColumns={withScrollSnapColumns}
withScrollSnapRows={withScrollSnapRows}
>
<TableHeader columns={shownColumns}>
{(column) => (
<Column
key={column.columnKey}
childColumns={column.childColumns}
allowsSorting={column.allowsSorting ?? true}
allowsResizing={column.allowsResizing}
width={column.width}
>
{column.label}
{column.tag && <Tag>{column.tag}</Tag>}
</Column>
)}
</TableHeader>
<TableBody items={list.items}>
{(item) => (
<Row key={getRowKey(item)}>
{(columnKey) => (
<Cell key={`${getRowKey(item)}-${columnKey}`}>
{columns.find((column) => column.columnKey === columnKey)?.renderCell?.(item)}
</Cell>
)}
</Row>
)}
</TableBody>
</TableRoot>
) : (
<Styled.Empty withOuterBorder={withOuterBorder}>{slotEmpty}</Styled.Empty>
<TableBody items={list.items.slice(0, numRowsToShow)}>
{(item) => (
<Row key={getRowKey(item)}>
{(columnKey) => (
<Cell key={`${getRowKey(item)}-${columnKey}`}>
{columns.find((column) => column.columnKey === columnKey)?.renderCell?.(item)}
</Cell>
)}
</Row>
)}
</TableBody>
</TableRoot>
) : (
<Styled.Empty withOuterBorder={withOuterBorder}>{slotEmpty}</Styled.Empty>
)}
</Styled.TableWrapper>
{numRowsToShow !== undefined && numRowsToShow < data.length && (
<Styled.ViewMoreButton
onClick={() => setNumRowsToShow(data.length)}
slotRight={<CaretIcon />}
>
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
)}
</Styled.TableWrapper>
</>
);
};
@@ -922,3 +938,8 @@ Styled.Row = styled.div`
${layoutMixins.inlineRow}
padding: var(--tableCell-padding);
`;
Styled.ViewMoreButton = styled(Button)`
--button-backgroundColor: var(--color-layer-2);
width: 100%;
`;
+17
View File
@@ -119,6 +119,13 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel
// ------ Wallet ------ //
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
export type TradingRewards = Abacus.exchange.dydx.abacus.output.TradingRewards;
export type HistoricalTradingReward = Abacus.exchange.dydx.abacus.output.HistoricalTradingReward;
export const HistoricaTradingRewardsPeriod =
Abacus.exchange.dydx.abacus.state.manager.HistoricaTradingRewardsPeriod;
const historicalTradingRewardsPeriod = [...HistoricaTradingRewardsPeriod.values()] as const;
export type HistoricaTradingRewardsPeriods = (typeof historicalTradingRewardsPeriod)[number];
export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
@@ -236,6 +243,16 @@ export const HISTORICAL_PNL_PERIODS: Record<
[HistoricalPnlPeriod.Period90d.name]: HistoricalPnlPeriod.Period90d,
};
export const HISTORICAL_TRADING_REWARDS_PERIODS: Record<
KotlinIrEnumValues<typeof HistoricaTradingRewardsPeriod>,
HistoricaTradingRewardsPeriods
> = {
[HistoricaTradingRewardsPeriod.MONTHLY.name]: HistoricaTradingRewardsPeriod.MONTHLY,
[HistoricaTradingRewardsPeriod.WEEKLY.name]: HistoricaTradingRewardsPeriod.WEEKLY,
[HistoricaTradingRewardsPeriod.DAILY.name]: HistoricaTradingRewardsPeriod.DAILY,
[HistoricaTradingRewardsPeriod.BLOCK.name]: HistoricaTradingRewardsPeriod.BLOCK,
};
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
-5
View File
@@ -1,5 +0,0 @@
export enum QueryKeys {
ACCOUNT_BALANCE = 'ACCOUNT_BALANCE',
LAUNCH_INCENTIVES = 'LAUNCH_INCENTIVES',
TRANSACTION_STATUS = 'TRANSACTION_STATUS',
}
+2 -13
View File
@@ -52,7 +52,7 @@ type WalletConnectionTypeConfig = {
wagmiConnectorId?: string;
};
export const WALLET_CONNECTION_TYPES: Record<WalletConnectionType, WalletConnectionTypeConfig> = {
export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectionTypeConfig> = {
[WalletConnectionType.CoinbaseWalletSdk]: {
name: 'Coinbase Wallet SDK',
wagmiConnectorId: 'coinbaseWallet',
@@ -110,7 +110,7 @@ export const WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS = Object.values(
WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS
);
export type WalletConfig = {
type WalletConfig = {
type: WalletType;
stringKey: string;
icon: string;
@@ -274,17 +274,6 @@ export type WalletConnection = {
provider?: ExternalProvider;
};
export type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
};
// dYdX Chain wallets
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
+11 -27
View File
@@ -1,12 +1,11 @@
import { useCallback, useEffect } from 'react';
import { useCallback } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { useBalance, useBlockNumber } from 'wagmi';
import { useBalance } from 'wagmi';
import { StargateClient } from '@cosmjs/stargate';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useQuery } from 'react-query';
import { formatUnits } from 'viem';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QueryKeys } from '@/constants/queries';
import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils';
@@ -53,27 +52,15 @@ export const useAccountBalance = ({
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const stakingBalances = useSelector(getStakingBalances, shallowEqual);
const queryClient = useQueryClient();
const { data: blockNumber } = useBlockNumber({ watch: true });
const {
data: evmQuery,
status: evmBalanceStatus,
fetchStatus: evmBalanceFetchStatus,
queryKey,
} = useBalance({
query: {
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
},
const evmQuery = useBalance({
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
address: evmAddress,
chainId: typeof chainId === 'number' ? chainId : Number(evmChainId),
token:
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress),
watch: true,
});
useEffect(() => {
queryClient.invalidateQueries({ queryKey });
}, [blockNumber]);
const cosmosQueryFn = useCallback(async () => {
if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) {
const address = convertBech32Address({
@@ -91,7 +78,7 @@ export const useAccountBalance = ({
const cosmosQuery = useQuery({
enabled: Boolean(isCosmosChain && dydxAddress && bech32AddrPrefix && rpc && addressOrDenom),
queryKey: [QueryKeys.ACCOUNT_BALANCE, chainId, addressOrDenom],
queryKey: `accountBalances_${chainId}_${addressOrDenom}`,
queryFn: cosmosQueryFn,
refetchOnWindowFocus: false,
refetchOnMount: false,
@@ -100,11 +87,8 @@ export const useAccountBalance = ({
staleTime: 10_000,
});
const balance = isCosmosChain
? cosmosQuery.data
: evmQuery
? formatUnits(evmQuery?.value, evmQuery?.decimals)
: undefined;
const { formatted: evmBalance } = evmQuery.data || {};
const balance = isCosmosChain ? cosmosQuery.data : evmBalance;
const nativeTokenCoinBalance = balances?.[chainTokenDenom];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
@@ -120,7 +104,7 @@ export const useAccountBalance = ({
nativeTokenBalance,
nativeStakingBalance,
usdcBalance,
queryStatus: isCosmosChain ? cosmosQuery.status : evmBalanceStatus,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmBalanceFetchStatus === 'fetching',
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
};
};
+3
View File
@@ -39,6 +39,7 @@ const useAccountsContext = () => {
walletConnectionType,
selectWalletType,
selectedWalletType,
selectedWalletError,
evmAddress,
signerWagmi,
publicClientWagmi,
@@ -299,7 +300,9 @@ const useAccountsContext = () => {
walletConnectionType,
// Wallet selection
selectWalletType,
selectedWalletType,
selectedWalletError,
// Wallet connection (EVM)
evmAddress,
+13 -24
View File
@@ -1,9 +1,8 @@
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useQuery } from 'react-query';
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications';
import { QueryKeys } from '@/constants/queries';
import { useAccounts } from '@/hooks/useAccounts';
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
@@ -69,8 +68,8 @@ const useLocalNotificationsContext = () => {
[transferNotifications]
);
const { data: newTransferNotifications } = useQuery({
queryKey: [QueryKeys.TRANSACTION_STATUS],
useQuery({
queryKey: 'getTransactionStatus',
queryFn: async () => {
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
const newTransferNotifications = await Promise.all(
@@ -85,11 +84,10 @@ const useLocalNotificationsContext = () => {
status: currentStatus,
} = transferNotification;
const hasErrors =
// @ts-ignore status.errors is not in the type definition but can be returned
// also error can some time come back as an empty object so we need to ignore for that
!!currentStatus?.errors ||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
// @ts-ignore status.errors is not in the type definition but can be returned
// also error can some time come back as an empty object so we need to ignore for that
const hasErrors = !!currentStatus?.errors ||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
if (
!hasErrors &&
@@ -97,14 +95,11 @@ const useLocalNotificationsContext = () => {
currentStatus?.squidTransactionStatus === 'ongoing')
) {
try {
const status = await fetchSquidStatus(
{
transactionId: txHash,
toChainId,
fromChainId,
},
isCctp
);
const status = await fetchSquidStatus({
transactionId: txHash,
toChainId,
fromChainId,
}, isCctp);
if (status) {
transferNotification.status = status;
@@ -126,18 +121,12 @@ const useLocalNotificationsContext = () => {
return newTransferNotifications;
};
const newTransferNotifications = await processTransferNotifications(transferNotifications);
return newTransferNotifications;
setTransferNotifications(newTransferNotifications);
},
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
});
useEffect(() => {
if (!newTransferNotifications) return;
setTransferNotifications(newTransferNotifications);
}, [newTransferNotifications]);
return {
transferNotifications,
addTransferNotification,
+6 -13
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react';
import { UseSwitchChainReturnType, useAccount, useSwitchChain } from 'wagmi';
import { useNetwork, useSwitchNetwork } from 'wagmi';
export const useMatchingEvmNetwork = ({
chainId,
@@ -8,10 +8,10 @@ export const useMatchingEvmNetwork = ({
}: {
chainId?: string | number;
switchAutomatically?: boolean;
onError?: Parameters<UseSwitchChainReturnType['switchChainAsync']>[1]['onError'];
onError?: (error: Error) => void;
}) => {
const { chain } = useAccount();
const { switchChainAsync, isPending } = useSwitchChain();
const { chain } = useNetwork();
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
// If chainId is not a number, we can assume it is a non EVM compatible chain
const isMatchingNetwork = useMemo(
@@ -21,14 +21,7 @@ export const useMatchingEvmNetwork = ({
const matchNetwork = useCallback(async () => {
if (!isMatchingNetwork) {
await switchChainAsync?.(
{
chainId: Number(chainId),
},
{
onError,
}
);
await switchNetworkAsync?.(Number(chainId));
}
}, [chainId, chain]);
@@ -41,6 +34,6 @@ export const useMatchingEvmNetwork = ({
return {
isMatchingNetwork,
matchNetwork,
isSwitchingNetwork: isPending,
isSwitchingNetwork: isLoading,
};
};
+10 -14
View File
@@ -104,13 +104,12 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType]
);
const { connectAsync: connectWagmi } = useConnectWagmi();
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
const { suggestAndConnect: connectGraz } = useConnectGraz();
const connectWallet = useCallback(
async ({ walletType }: { walletType: WalletType }) => {
const walletConnection = getWalletConnection({ walletType });
console.log({ walletConnection });
try {
if (!walletConnection) {
@@ -134,17 +133,13 @@ export const useWalletConnection = () => {
}
} else {
if (!isConnectedWagmi) {
const connector = resolveWagmiConnector({
walletType,
walletConnection,
walletConnectConfig,
await connectWagmi({
connector: resolveWagmiConnector({
walletType,
walletConnection,
walletConnectConfig,
}),
});
if (connector) {
await connectWagmi({
connector,
});
}
}
}
} catch (error) {
@@ -161,7 +156,7 @@ export const useWalletConnection = () => {
walletConnectionType: walletConnection.type,
};
},
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, walletConnectConfig]
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
);
const disconnectWallet = useCallback(async () => {
@@ -209,9 +204,10 @@ export const useWalletConnection = () => {
})();
}, [selectedWalletType, signerWagmi, signerGraz]);
const selectWalletType = (walletType: WalletType | undefined) => {
const selectWalletType = async (walletType: WalletType | undefined) => {
if (selectedWalletType) {
setSelectedWalletType(undefined);
await new Promise(requestAnimationFrame);
}
setSelectedWalletType(walletType);
+1 -3
View File
@@ -1,3 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
</svg>
<svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m.321829 1.09389c.200562-.192959.469529-.298401.747791-.29315.27827.00525.54307.120763.73621.32115l5.194 5.5132 5.19397-5.5132c.0944-.10408.2088-.188152.3363-.247234.1275-.059081.2655-.091968.406-.096713.1404-.004746.2804.018748.4116.069088s.2509.126504.3522.223979c.1012.09747.1818.21427.2371.34347.0552.12921.084.26819.0845.40871.0006.14052-.0271.27972-.0813.40936s-.1339.24707-.2344.34534l-5.94997 6.3c-.09795.10163-.21538.18245-.34527.23766-.1299.05521-.26959.08367-.41073.08367s-.28083-.02846-.41073-.08367c-.12989-.05521-.24732-.13603-.34527-.23766l-5.950001-6.3c-.192962-.20056-.29840394-.46953-.29315364-.74779.00525031-.27827.12076264-.54307.32115364-.73621z" fill="currentColor" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 841 B

+12 -5
View File
@@ -9,6 +9,8 @@ import type {
TransferInputFields,
HistoricalPnlPeriods,
ParsingError,
HistoricaTradingRewardsPeriods,
HistoricaTradingRewardsPeriod,
} from '@/constants/abacus';
import {
@@ -218,6 +220,12 @@ class AbacusStateManager {
this.stateManager.historicalPnlPeriod = period;
};
setHistoricalTradingRewardPeriod = (
period: (typeof HistoricaTradingRewardsPeriod)[keyof typeof HistoricaTradingRewardsPeriod]
) => {
this.stateManager.historicalTradingRewardPeriod = period;
};
switchNetwork = (network: DydxNetwork) => {
this.stateManager.environmentId = network;
@@ -262,17 +270,16 @@ class AbacusStateManager {
) => this.stateManager.cancelOrder(orderId, callback);
cctpWithdraw = (
callback: (
success: boolean,
parsingError: Nullable<ParsingError>,
data: string,
) => void
callback: (success: boolean, parsingError: Nullable<ParsingError>, data: string) => void
): void => this.stateManager.commitCCTPWithdraw(callback);
// ------ Utils ------ //
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
this.stateManager.historicalPnlPeriod;
getHistoricalTradingRewardPeriod = (): HistoricaTradingRewardsPeriods =>
this.stateManager.historicalTradingRewardPeriod;
handleCandlesSubscription = ({
channelId,
subscribe,
+7
View File
@@ -29,6 +29,7 @@ import {
setSubaccount,
setTransfers,
setWallet,
setTradingRewards,
} from '@/state/account';
import { setApiState } from '@/state/app';
@@ -96,6 +97,12 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
}
}
if (changes.has(Changes.tradingRewards)) {
if (updatedState.account?.tradingRewards) {
dispatch(setTradingRewards(updatedState.account?.tradingRewards));
}
}
if (changes.has(Changes.configs)) {
dispatch(setConfigs(updatedState.configs));
}
+122 -112
View File
@@ -1,6 +1,7 @@
import { createConfig, createConnector, http, usePublicClient } from 'wagmi';
import { createConfig, configureChains, mainnet, Chain, Connector, usePublicClient } from 'wagmi';
import { goerli } from 'wagmi/chains';
import {
type Chain,
arbitrum,
arbitrumGoerli,
avalanche,
@@ -25,33 +26,36 @@ import {
fantomTestnet,
celo,
celoAlfajores,
mainnet,
goerli,
sepolia,
} from 'wagmi/chains';
scroll,
kava,
} from 'viem/chains';
import { coinbaseWallet, injected, metaMask, walletConnect } from 'wagmi/connectors';
import type { ExternalProvider } from '@ethersproject/providers';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc';
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 {
type WalletConnectConfig,
type WalletConnection,
WalletConnectionType,
WalletType,
WALLET_CONNECTION_TYPES,
type WalletType,
walletConnectionTypes,
wallets,
WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
WalletConfig,
} from '@/constants/wallets';
import { isTruthy } from './isTruthy';
import { log } from './telemetry';
// Config
const WAGMI_SUPPORTED_CHAINS: Parameters<typeof createConfig>[0]['chains'] = [
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
mainnet,
goerli,
sepolia,
arbitrum,
arbitrumGoerli,
avalanche,
@@ -76,106 +80,122 @@ const WAGMI_SUPPORTED_CHAINS: Parameters<typeof createConfig>[0]['chains'] = [
fantomTestnet,
celo,
celoAlfajores,
scroll,
kava,
];
export const config = createConfig({
chains: WAGMI_SUPPORTED_CHAINS,
transports: Object.fromEntries(
Object.values(WAGMI_SUPPORTED_CHAINS).map(({ id }) => [id, http()])
),
});
const { chains, publicClient, webSocketPublicClient } = configureChains(
WAGMI_SUPPORTED_CHAINS,
[
import.meta.env.VITE_ALCHEMY_API_KEY &&
alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }),
jsonRpcProvider({
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }),
}),
publicProvider(),
].filter(isTruthy)
);
const createdInjectedConnectorWithProvider = ({ provider }: { provider: ExternalProvider }) => {
console.log(provider);
return injected({
target() {
return {
id: 'windowProvider',
name: 'Injected',
provider,
};
},
const injectedConnectorOptions = {
chains,
options: {
name: 'Injected',
shimDisconnect: true,
});
shimChainChangedDisconnect: false,
},
};
const getConnector = ({
walletConnectionType,
walletConnectConfig,
}: {
walletConnectionType: WalletConnectionType;
walletConnectConfig: WalletConnectConfig;
}) => {
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;
}
}
type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
};
const getConnectorInfoForWc2 = ({
walletConnectId,
walletConnectConfig,
}: {
walletConnectId?: string;
walletConnectConfig: WalletConnectConfig;
}) => {
const explorerRecommendedWalletIds = walletConnectId
? [walletConnectId]
: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS;
return {
projectId: walletConnectConfig.v2.projectId,
const getWalletconnect2ConnectorOptions = (
config: WalletConnectConfig
): ConstructorParameters<typeof WalletConnectConnector>[0] => ({
chains,
options: {
projectId: config.v2.projectId,
metadata: {
name: walletConnectConfig.client.name,
description: walletConnectConfig.client.description,
name: config.client.name,
description: config.client.description,
url: import.meta.env.VITE_BASE_URL,
icons: [walletConnectConfig.client.iconUrl],
icons: [config.client.iconUrl],
},
showQrModal: true,
qrModalOptions: {
themeMode: 'dark' as const,
themeVariables: {
'--wcm-accent-color': 'var(--color-accent)',
'--wcm-accent-color': '#5973fe',
'--wcm-font-family': 'var(--fontFamily-base)',
'--wcm-background-color': 'var(--color-accent)',
},
explorerRecommendedWalletIds,
explorerRecommendedWalletIds: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
},
};
},
});
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
export const resolveWagmiConnector = ({
walletType,
walletConnection,
@@ -186,23 +206,13 @@ export const resolveWagmiConnector = ({
walletConnectConfig: WalletConnectConfig;
}) => {
const walletConfig = wallets[walletType];
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
if (walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider) {
if (walletType === WalletType.MetaMask) {
return metaMask();
}
return createdInjectedConnectorWithProvider({ provider: walletConnection.provider });
} else if (
walletConnection.type === WalletConnectionType.WalletConnect2 &&
walletConfig.walletconnect2Id
) {
return walletConnect(
getConnectorInfoForWc2({
walletConnectId: walletConfig.walletconnect2Id,
walletConnectConfig,
})
);
} else {
return getConnector({ walletConnectionType: walletConnection.type, walletConnectConfig });
}
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider
? createInjectedConnectorWithProvider(walletConnection.provider)
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig)
: getConnectors(walletConnectConfig).find(
({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId
);
};
+35 -14
View File
@@ -26,10 +26,14 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { getOnboardingState } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
import { MigratePanel } from './rewards/MigratePanel';
import { GovernancePanel } from './rewards/GovernancePanel';
import { StakingPanel } from './rewards/StakingPanel';
const ENS_CHAIN_ID = 1; // Ethereum
const Profile = () => {
@@ -176,7 +180,26 @@ const Profile = () => {
/>
</Styled.EqualGrid>
<MigratePanel />
<DYDXBalancePanel />
<Styled.EqualGrid>
<Styled.RewardsPanel
slotHeaderContent="Trading Rewards"
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{
key: 'week-rewards',
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
value: '-',
},
]}
layout="grid"
/>
</Styled.RewardsPanel>
<Panel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
@@ -191,19 +214,6 @@ const Profile = () => {
layout="grid"
/>
</Panel>
<Styled.RewardsPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.REWARDS })}
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.YOU_WILL_EARN }), value: '-' },
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.EPOCH_ENDS_IN }), value: '-' },
]}
layout="grid"
/>
</Styled.RewardsPanel>
</Styled.EqualGrid>
<Styled.TablePanel
@@ -221,6 +231,9 @@ const Profile = () => {
withInnerBorders={false}
/>
</Styled.TablePanel>
<GovernancePanel />
<StakingPanel />
</Styled.MobileProfileLayout>
);
};
@@ -234,6 +247,7 @@ Styled.MobileProfileLayout = styled.div`
gap: 1rem;
padding: 1.25rem 0.9rem;
max-width: 100vw;
`;
Styled.Header = styled.header`
@@ -327,6 +341,13 @@ Styled.Details = styled(Details)`
`;
Styled.RewardsPanel = styled(Panel)`
align-self: flex-start;
&,
> * {
height: 100%;
}
dl {
--details-grid-numColumns: 1;
}
+2 -12
View File
@@ -2,7 +2,6 @@ import type { ElementType } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
@@ -33,7 +32,7 @@ export const DYDXBalancePanel = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<Styled.Panel
<Panel
slotHeader={
<Styled.Header>
<Styled.Title>
@@ -109,21 +108,12 @@ export const DYDXBalancePanel = () => {
]}
/>
</Styled.Content>
</Styled.Panel>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1rem;
+79
View File
@@ -0,0 +1,79 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const GovernancePanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { governanceLearnMore } = useURLConfigs();
return (
<Styled.Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
height: fit-content;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+5 -18
View File
@@ -1,12 +1,11 @@
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useQuery } from '@tanstack/react-query';
import { useQuery } from 'react-query';
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { QueryKeys } from '@/constants/queries';
import breakpoints from '@/styles/breakpoints';
import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks';
@@ -69,9 +68,9 @@ const EstimatedRewards = () => {
const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts();
const { data, isLoading, status, error } = useQuery({
const { data, isLoading } = useQuery({
enabled: !!dydxAddress,
queryKey: [QueryKeys.LAUNCH_INCENTIVES, dydxAddress, SEASON_NUMBER],
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`,
queryFn: async () => {
if (!dydxAddress) return undefined;
const resp = await fetch(
@@ -79,14 +78,9 @@ const EstimatedRewards = () => {
);
return (await resp.json())?.incentivePoints;
},
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
});
useEffect(() => {
if (status === 'error') {
log('LaunchIncentives/fetchPoints', error);
}
}, [status]);
return (
<Styled.EstimatedRewardsCard>
<Styled.EstimatedRewardsCardContent>
@@ -159,15 +153,8 @@ const LaunchIncentivesContent = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingY: 1rem;
--panel-paddingX: 1.5rem;
background-color: var(--color-layer-4);
width: 100%;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
}
`;
Styled.ForV4 = styled.span`
@@ -231,7 +218,7 @@ Styled.Column = styled.div`
Styled.EstimatedRewardsCard = styled.div`
${layoutMixins.spacedRow}
padding: 1rem 1.25rem;
min-width: 21.25rem;
min-width: 19rem;
height: calc(100% - calc(1.5rem * 2));
margin: 1.5rem;
+6 -11
View File
@@ -7,8 +7,6 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
@@ -28,7 +26,7 @@ import { MustBigNumber } from '@/lib/numbers';
const TOKEN_MIGRATION_LEARN_MORE_LINK =
'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx';
export const MigratePanel = () => {
export const MigratePanel = ({ className }: { className?: string }) => {
const { isNotTablet } = useBreakpoints();
const stringGetter = useStringGetter();
@@ -45,6 +43,7 @@ export const MigratePanel = () => {
return isNotTablet ? (
<Styled.MigratePanel
className={className}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
slotRight={
<Styled.MigrateAction>
@@ -74,6 +73,7 @@ export const MigratePanel = () => {
</Styled.MigratePanel>
) : (
<Styled.MigratePanel
className={className}
slotHeader={
<Styled.MobileMigrateHeader>
<h3>
@@ -134,17 +134,11 @@ export const MigratePanel = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.MigratePanel = styled(Panel)`
--panel-paddingX: 1.5rem;
width: 100%;
background-image: url('/dots-background.svg');
background-position: right;
background-repeat: no-repeat;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Title = styled.h3`
@@ -152,14 +146,15 @@ Styled.Title = styled.h3`
color: var(--color-text-2);
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
margin-bottom: -0.5rem;
`;
Styled.MigrateAction = styled.div`
${layoutMixins.flexEqualColumns}
align-items: center;
margin-right: 1rem;
gap: 1rem;
padding: 1rem;
margin: 1rem;
width: 100%;
background-color: var(--color-layer-2);
@@ -197,7 +192,7 @@ Styled.MobileMigrateHeader = styled.div`
font: var(--font-small-book);
color: var(--color-text-0);
padding: 1rem 1.5rem 0;
padding: 1rem 1rem 0;
h3 {
${layoutMixins.inlineRow}
+107
View File
@@ -0,0 +1,107 @@
import { useCallback, useState } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks';
import {
HISTORICAL_TRADING_REWARDS_PERIODS,
HistoricaTradingRewardsPeriod,
HistoricaTradingRewardsPeriods,
} from '@/constants/abacus';
import { ComingSoon } from '@/components/ComingSoon';
import { Panel } from '@/components/Panel';
import { ToggleGroup } from '@/components/ToggleGroup';
import { TradingRewardHistoryTable } from '@/views/tables/TradingRewardHistoryTable';
import { getHistoricalTradingRewards } from '@/state/accountSelectors';
import abacusStateManager from '@/lib/abacus';
import { STRING_KEYS } from '@/constants/localization';
export const RewardHistoryPanel = () => {
const stringGetter = useStringGetter();
const historicalTradingRewards = useSelector(getHistoricalTradingRewards, shallowEqual);
const [selectedPeriod, setSelectedPeriod] = useState<HistoricaTradingRewardsPeriods>(
abacusStateManager.getHistoricalTradingRewardPeriod() || HistoricaTradingRewardsPeriod.WEEKLY
);
const onSelectPeriod = useCallback(
(periodName: string) => {
setSelectedPeriod(
HISTORICAL_TRADING_REWARDS_PERIODS[
periodName as keyof typeof HISTORICAL_TRADING_REWARDS_PERIODS
]
);
},
[setSelectedPeriod, selectedPeriod]
);
return (
<Panel
slotHeader={
<Styled.Header>
<Styled.Title>
<h3>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY })}</h3>
<span>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION })}</span>
</Styled.Title>
<ToggleGroup
items={[
{
value: HistoricaTradingRewardsPeriod.MONTHLY.name,
label: stringGetter({ key: STRING_KEYS.MONTHLY }),
},
{
value: HistoricaTradingRewardsPeriod.WEEKLY.name,
label: stringGetter({ key: STRING_KEYS.WEEKLY }),
},
{
value: HistoricaTradingRewardsPeriod.DAILY.name,
label: stringGetter({ key: STRING_KEYS.DAILY }),
},
]}
value={selectedPeriod.name}
onValueChange={onSelectPeriod}
/>
</Styled.Header>
}
>
{historicalTradingRewards ? (
<TradingRewardHistoryTable period={selectedPeriod} />
) : (
<ComingSoon />
)}
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Header = styled.div`
${layoutMixins.spacedRow}
padding: 1rem 1rem 0;
margin-bottom: -0.5rem;
@media ${breakpoints.notTablet} {
padding: 1.25rem 1.5rem 0;
}
`;
Styled.Title = styled.div`
color: var(--color-text-0);
font: var(--font-small-book);
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
}
`;
Styled.Content = styled.div`
${layoutMixins.flexColumn}
gap: 0.75rem;
`;
+13 -9
View File
@@ -1,5 +1,6 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { breakpoints } from '@/styles';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks';
@@ -29,18 +30,16 @@ export const RewardsHelpPanel = () => {
<Accordion
items={[
{
header: 'Who is eligible for trading rewards?',
content: 'All traders are eligible for trading rewards.',
header: stringGetter({ key: STRING_KEYS.FAQ_WHO_IS_ELIGIBLE_QUESTION }),
content: stringGetter({ key: STRING_KEYS.FAQ_WHO_IS_ELIGIBLE_ANSWER }),
},
{
header: 'How do trading rewards work?',
content:
'Immediately after each fill, trading rewards are sent directly to the traders dYdX Chain address, based on the amount of fees paid by the trader.',
header: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_TRADING_REWARDS_WORK_QUESTION }),
content: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_TRADING_REWARDS_WORK_ANSWER }),
},
{
header: 'How do I claim my rewards?',
content:
'Each block, trading rewards are automatically sent directly to the traders dYdX Chain address.',
header: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_QUESTION }),
content: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_ANSWER }),
},
]}
/>
@@ -51,6 +50,7 @@ export const RewardsHelpPanel = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.HelpCard = styled(Panel)`
--panel-content-paddingX: 0;
--panel-content-paddingY: 0;
width: 100%;
height: max-content;
@@ -64,11 +64,15 @@ Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1ch;
padding: 1.25rem 1.5rem;
padding: 1rem 1rem;
border-bottom: var(--border-width) solid var(--border-color);
font: var(--font-small-book);
@media ${breakpoints.notTablet} {
padding: 1.5rem 1.25rem;
}
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
+91 -91
View File
@@ -1,85 +1,68 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { AppRoute } from '@/constants/routes';
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
import { DYDXBalancePanel } from './DYDXBalancePanel';
import { MigratePanel } from './MigratePanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { RewardsHelpPanel } from './RewardsHelpPanel';
import { TradingRewardsSummaryPanel } from './TradingRewardsSummaryPanel';
import { RewardHistoryPanel } from './RewardHistoryPanel';
import { GovernancePanel } from './GovernancePanel';
import { StakingPanel } from './StakingPanel';
const RewardsPage = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
const { isTablet, isNotTablet } = useBreakpoints();
const panelArrow = (
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
);
const navigate = useNavigate();
return (
<Styled.Page>
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
{isTablet ? (
<>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</>
) : (
<Styled.PanelRowIncentivesAndBalance>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</Styled.PanelRowIncentivesAndBalance>
{isTablet && (
<Styled.MobileHeader>
<BackButton onClick={() => navigate(AppRoute.Profile)} />
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
</Styled.MobileHeader>
)}
<Styled.GridLayout>
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
<Styled.PanelRow>
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
{isTablet ? (
<Styled.LaunchIncentivesPanel />
) : (
<>
<Styled.LaunchIncentivesPanel />
<Styled.DYDXBalancePanel />
</>
)}
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
</Styled.PanelRow>
<Styled.TradingRewardsColumn>
<TradingRewardsSummaryPanel />
{isTablet && <RewardsHelpPanel />}
<RewardHistoryPanel />
</Styled.TradingRewardsColumn>
{isNotTablet && (
<Styled.OtherColumn>
<GovernancePanel />
<StakingPanel />
<RewardsHelpPanel />
</Styled.OtherColumn>
)}
</Styled.GridLayout>
</Styled.Page>
);
};
@@ -90,71 +73,88 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.Page = styled.div`
${layoutMixins.contentContainerPage}
gap: 1.5rem;
padding: 2rem;
align-items: center;
> * {
--content-max-width: 70rem;
--content-max-width: 80rem;
max-width: min(calc(100vw - 4rem), var(--content-max-width));
}
@media ${breakpoints.tablet} {
padding: 1.25rem;
--stickyArea-topHeight: var(--page-header-height-mobile);
padding: 0 1rem 1rem;
> * {
max-width: calc(100vw - 2.5rem);
max-width: calc(100vw - 2rem);
width: 100%;
}
}
`;
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
Styled.MobileHeader = styled.header`
${layoutMixins.contentSectionDetachedScrollable}
${layoutMixins.stickyHeader}
z-index: 2;
padding: 1.25rem 0;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
font: var(--font-large-medium);
color: var(--color-text-2);
padding: 1rem 1.5rem 0;
background-color: var(--color-layer-2);
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
Styled.GridLayout = styled.div`
--gap: 1.5rem;
display: grid;
grid-template-columns: 2fr 1fr;
gap: var(--gap);
a {
display: inline;
::before {
content: ' ';
}
> * {
gap: var(--gap);
}
`;
Styled.PanelRow = styled.div`
${layoutMixins.gridEqualColumns}
gap: 1.5rem;
grid-template-areas:
'migrate migrate'
'incentives balance'
'rewards other';
@media ${breakpoints.tablet} {
grid-auto-flow: row;
--gap: 1rem;
grid-template-columns: 1fr;
grid-template-areas:
'incentives'
'rewards';
}
`;
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
grid-template-columns: 2fr 1fr;
Styled.MigratePanel = styled(MigratePanel)`
grid-area: migrate;
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
Styled.LaunchIncentivesPanel = styled(LaunchIncentivesPanel)`
grid-area: incentives;
`;
Styled.Arrow = styled.div`
padding: 1rem;
Styled.DYDXBalancePanel = styled(DYDXBalancePanel)`
grid-area: balance;
`;
Styled.TradingRewardsColumn = styled.div`
grid-area: rewards;
${layoutMixins.flexColumn}
`;
Styled.OtherColumn = styled.div`
grid-area: other;
${layoutMixins.flexColumn}
`;
Styled.RewardHistoryHeader = styled.div`
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
}
padding: 1rem 1.5rem 0;
margin-bottom: -0.5rem;
`;
+77
View File
@@ -0,0 +1,77 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const StakingPanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { stakingLearnMore } = useURLConfigs();
return (
<Styled.Panel
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
height: fit-content;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
@@ -0,0 +1,133 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { ComingSoon } from '@/components/ComingSoon';
import { Details } from '@/components/Details';
import { Output, OutputType } from '@/components/Output';
import { Panel } from '@/components/Panel';
import { getHistoricalTradingRewards } from '@/state/accountSelectors';
export const TradingRewardsSummaryPanel = () => {
const stringGetter = useStringGetter();
const historicalTradingRewards = useSelector(getHistoricalTradingRewards, shallowEqual);
const currentWeekTradingReward = historicalTradingRewards?.get('WEEKLY')?.firstOrNull();
const { chainTokenLabel } = useTokenConfigs();
return (
<Panel
slotHeader={
<Styled.Header>{stringGetter({ key: STRING_KEYS.TRADING_REWARDS_SUMMARY })}</Styled.Header>
}
>
<Styled.Content>
{currentWeekTradingReward ? (
<Styled.TradingRewardsDetails
layout="grid"
items={[
{
key: 'week',
label: (
<Styled.Label>
<h4>{stringGetter({ key: STRING_KEYS.THIS_WEEK })}</h4>
</Styled.Label>
),
value: (
<Styled.Column>
<Output
slotRight={<Styled.AssetIcon symbol={chainTokenLabel} />}
type={OutputType.Asset}
value={currentWeekTradingReward.amount}
/>
<Styled.TimePeriod>
<Output
type={OutputType.Date}
value={currentWeekTradingReward.startedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
<Output
type={OutputType.Date}
value={currentWeekTradingReward.endedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
</Styled.TimePeriod>
</Styled.Column>
),
},
// TODO(@aforaleka): add all-time when supported
]}
/>
) : (
<ComingSoon />
)}
</Styled.Content>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Header = styled.div`
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
font: var(--font-medium-book);
color: var(--color-text-2);
`;
Styled.Content = styled.div`
${layoutMixins.flexColumn}
gap: 0.75rem;
`;
Styled.TradingRewardsDetails = styled(Details)`
--details-item-backgroundColor: var(--color-layer-6);
grid-template-columns: 1fr; // TODO(@aforaleka): change to 1fr 1fr when all-time is supported
gap: 1rem;
> div {
gap: 0.5rem;
padding: 1rem;
border-radius: 0.75em;
background-color: var(--color-layer-5);
}
dt {
width: 100%;
}
output {
color: var(--color-text-2);
font: var(--font-large-book);
}
`;
Styled.Label = styled.div`
${layoutMixins.spacedRow}
font: var(--font-base-book);
color: var(--color-text-1);
`;
Styled.TimePeriod = styled.div`
${layoutMixins.inlineRow}
&, output {
color: var(--color-text-0);
font: var(--font-small-book);
}
`;
Styled.Column = styled.div`
${layoutMixins.flexColumn}
gap: 0.33rem;
`;
Styled.AssetIcon = styled(AssetIcon)`
margin-left: 0.5ch;
`;
+6
View File
@@ -13,6 +13,7 @@ import type {
HistoricalPnlPeriods,
SubAccountHistoricalPNLs,
UsageRestriction,
TradingRewards,
} from '@/constants/abacus';
import { OnboardingGuard, OnboardingState } from '@/constants/account';
@@ -24,6 +25,7 @@ import { getLocalStorage } from '@/lib/localStorage';
export type AccountState = {
balances?: Record<string, AccountBalance>;
stakingBalances?: Record<string, AccountBalance>;
tradingRewards?: TradingRewards;
wallet?: Nullable<Wallet>;
walletType?: WalletType;
@@ -179,6 +181,9 @@ export const accountSlice = createSlice({
setStakingBalances: (state, action: PayloadAction<Record<string, AccountBalance>>) => {
state.stakingBalances = action.payload;
},
setTradingRewards: (state, action: PayloadAction<TradingRewards>) => {
state.tradingRewards = action.payload;
},
addUncommittedOrderClientId: (state, action: PayloadAction<number>) => {
state.uncommittedOrderClientIds.push(action.payload);
},
@@ -206,6 +211,7 @@ export const {
viewedOrders,
setBalances,
setStakingBalances,
setTradingRewards,
addUncommittedOrderClientId,
removeUncommittedOrderClientId,
} = accountSlice.actions;
+11
View File
@@ -343,6 +343,17 @@ export const getBalances = (state: RootState) => state.account?.balances;
* */
export const getStakingBalances = (state: RootState) => state.account?.stakingBalances;
/**
* @returns account all time trading rewards
*/
export const getTotalTradingRewards = (state: RootState) => state.account?.tradingRewards?.total;
/**
* @returns account trading rewards aggregated by period
*/
export const getHistoricalTradingRewards = (state: RootState) =>
state.account?.tradingRewards?.historical;
/**
* @returns UsageRestriction of the current session
*/
@@ -3,18 +3,19 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { AlertType } from '@/constants/alerts';
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 { ButtonAction, ButtonSize } from '@/constants/buttons';
import { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button';
import { Icon } from '@/components/Icon';
import { Link } from '@/components/Link';
import { useAccounts, useStringGetter, useURLConfigs } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -24,7 +25,7 @@ export const ChooseWallet = () => {
const displayedWallets = useDisplayedWallets();
const { selectWalletType, selectedWalletType, selectedWalletError } = useWalletConnection();
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
return (
<>
@@ -86,7 +86,13 @@ export const GenerateKeys = ({
].includes(status);
const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData();
const { signTypedDataAsync } = useSignTypedData({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY;
@@ -97,13 +103,7 @@ export const GenerateKeys = ({
// 1. First signature
setStatus(EvmDerivedAccountStatus.Deriving);
const signature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const signature = await signTypedDataAsync();
const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature });
// 2. Ensure signature is deterministic
@@ -121,13 +121,7 @@ export const GenerateKeys = ({
setStatus(EvmDerivedAccountStatus.EnsuringDeterminism);
// Second signature
const additionalSignature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const additionalSignature = await signTypedDataAsync();
if (signature !== additionalSignature) {
throw new Error(
@@ -34,6 +34,7 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps)
selectedToken && onSelectToken(selectedToken);
},
slotBefore: <Styled.Img src={token.iconUrl} alt="" />,
tag: resources?.tokenResources?.get(token.type)?.symbol,
}));
return (
@@ -0,0 +1,154 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { HistoricaTradingRewardsPeriods } from '@/constants/abacus';
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { AssetIcon } from '@/components/AssetIcon';
import { Output, OutputType } from '@/components/Output';
import { Table, TableCell, type ColumnDef } from '@/components/Table';
import { getHistoricalTradingRewards } from '@/state/accountSelectors';
export enum TradingRewardHistoryTableColumnKey {
Event = 'Event',
Earned = 'Earned',
}
const getTradingRewardHistoryTableColumnDef = ({
key,
chainTokenLabel,
stringGetter,
}: {
key: TradingRewardHistoryTableColumnKey;
chainTokenLabel: string;
stringGetter: StringGetterFunction;
}): ColumnDef<any> => ({
...(
{
[TradingRewardHistoryTableColumnKey.Event]: {
columnKey: TradingRewardHistoryTableColumnKey.Event,
getCellValue: (row) => row.startedAtInMilliseconds,
label: 'Event',
renderCell: ({ startedAtInMilliseconds, endedAtInMilliseconds }) => (
<TableCell stacked>
<Styled.Rewarded>{stringGetter({ key: STRING_KEYS.REWARDED })}</Styled.Rewarded>
<Styled.TimePeriod>
{stringGetter({
key: STRING_KEYS.FOR_TRADING,
params: {
PERIOD: (
<>
<Output
type={OutputType.Date}
value={startedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
<Output
type={OutputType.Date}
value={endedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
</>
),
},
})}
</Styled.TimePeriod>
</TableCell>
),
},
[TradingRewardHistoryTableColumnKey.Earned]: {
columnKey: TradingRewardHistoryTableColumnKey.Earned,
getCellValue: (row) => row.amount,
label: stringGetter({ key: STRING_KEYS.EARNED }),
renderCell: ({ amount }) => (
<Output
type={OutputType.Asset}
value={amount}
slotRight={<Styled.AssetIcon symbol={chainTokenLabel} />}
/>
),
},
} as Record<TradingRewardHistoryTableColumnKey, ColumnDef<any>>
)[key],
});
type ElementProps = {
columnKeys?: TradingRewardHistoryTableColumnKey[];
period: HistoricaTradingRewardsPeriods;
};
type StyleProps = {
withOuterBorder?: boolean;
withInnerBorders?: boolean;
};
export const TradingRewardHistoryTable = ({
period,
columnKeys = Object.values(TradingRewardHistoryTableColumnKey),
withOuterBorder,
withInnerBorders = true,
}: ElementProps & StyleProps) => {
const stringGetter = useStringGetter();
const historicalTradingRewards = useSelector(getHistoricalTradingRewards, shallowEqual);
const periodTradingRewards = historicalTradingRewards?.get(period.name) ?? [];
const { chainTokenLabel } = useTokenConfigs();
return (
<Styled.Table
label="Reward History"
data={periodTradingRewards}
getRowKey={(row: any) => row.startedAtInMilliseconds}
columns={columnKeys.map((key: TradingRewardHistoryTableColumnKey) =>
getTradingRewardHistoryTableColumnDef({
key,
chainTokenLabel,
stringGetter,
})
)}
selectionBehavior="replace"
withOuterBorder={withOuterBorder}
withInnerBorders={withInnerBorders}
initialNumRowsToShow={5}
withScrollSnapColumns
withScrollSnapRows
/>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Table = styled(Table)`
--tableCell-padding: 0.5rem 0;
--tableHeader-backgroundColor: var(--color-layer-3);
--tableRow-backgroundColor: var(--color-layer-3);
tbody {
font: var(--font-medium-book);
}
`;
Styled.Rewarded = styled.span`
color: var(--color-text-2);
`;
Styled.TimePeriod = styled.div`
${layoutMixins.inlineRow}
&& {
color: var(--color-text-0);
font: var(--font-base-book);
}
output {
color: var(--color-text-1);
font: var(--font-base-book);
}
`;
Styled.AssetIcon = styled(AssetIcon)`
margin-left: 0.5ch;
`;