diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 07732cd..1345e6e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,8 +27,8 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/abacus':
- specifier: file:/Users/jaredvu/Documents/v4-abacus/build/packages/dydxprotocol-abacus-0.3.7.tgz
- version: file:../v4-abacus/build/packages/dydxprotocol-abacus-0.3.7.tgz
+ specifier: ^0.3.7
+ version: 0.3.7
'@dydxprotocol/v4-client':
specifier: ^0.29.0
version: 0.29.0
@@ -1143,6 +1143,10 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
+ /@dydxprotocol/abacus@0.3.7:
+ resolution: {integrity: sha512-wYGdsFNnVulwVqsNpMrS3j02lOyw/qg5QrB2DdlWtj4YeNZqvdM/vHC6HgU+U4McdVJW4STiEKW2Fl0rF/qbhQ==}
+ dev: false
+
/@dydxprotocol/dydxjs@0.3.0:
resolution: {integrity: sha512-ygNeBs0f3H7sJk1qLUjfNwaXgc5TmjD+qZf7BFDbi7+boHJI9xuOWrGpjfBo7OTUZxS6O5jZG323CgU5XqMSPw==}
dependencies:
@@ -15836,12 +15840,6 @@ packages:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
- file:../v4-abacus/build/packages/dydxprotocol-abacus-0.3.7.tgz:
- resolution: {integrity: sha512-omUiVHcT/tHWeXKgZTk1WW8LDVHI+2yxXNM6+B756liRY/t1JwUkRzwplp09U2+mNo/FenejHFtWZ0qd+W79iw==, tarball: file:../v4-abacus/build/packages/dydxprotocol-abacus-0.3.7.tgz}
- name: '@dydxprotocol/abacus'
- version: 0.3.7
- dev: false
-
github.com/release-it/conventional-changelog/a4acfeac76369937a6566d698a5ae9180eceb413(release-it@15.11.0):
resolution: {tarball: https://codeload.github.com/release-it/conventional-changelog/tar.gz/a4acfeac76369937a6566d698a5ae9180eceb413}
id: github.com/release-it/conventional-changelog/a4acfeac76369937a6566d698a5ae9180eceb413
diff --git a/src/App.tsx b/src/App.tsx
index 1190db9..1869616 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,10 +10,10 @@ import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
import { useBreakpoints, useInitializePage, useShouldShowFooter, useAnalytics } from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts';
-import { SubaccountProvider } from './hooks/useSubaccount';
import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
+import { LocaleProvider } from './hooks/useLocaleSeparators';
import { NotificationsProvider } from './hooks/useNotifications';
-
+import { SubaccountProvider } from './hooks/useSubaccount';
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
import MarketsPage from '@/pages/markets/Markets';
@@ -100,17 +100,19 @@ const App = () => (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/CopyButton.stories.tsx b/src/components/CopyButton.stories.tsx
new file mode 100644
index 0000000..71f4070
--- /dev/null
+++ b/src/components/CopyButton.stories.tsx
@@ -0,0 +1,28 @@
+import type { Story } from '@ladle/react';
+
+import { CopyButton, type CopyButtonProps } from '@/components/CopyButton';
+
+import { StoryWrapper } from '.ladle/components';
+
+export const CopyButtonStory: Story = (args) => (
+
+
+
+);
+
+CopyButtonStory.args = {
+ value: 'some text to copy',
+};
+
+CopyButtonStory.argTypes = {
+ shownAsText: {
+ options: [true, false],
+ control: { type: 'select' },
+ defaultValue: false,
+ },
+ children: {
+ options: ['some text to copy'],
+ control: { type: 'select' },
+ defaultValue: undefined,
+ }
+};
diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx
new file mode 100644
index 0000000..5da154e
--- /dev/null
+++ b/src/components/CopyButton.tsx
@@ -0,0 +1,65 @@
+import { useState } from 'react';
+import styled, { css, type AnyStyledComponent } from 'styled-components';
+
+import { ButtonAction } from '@/constants/buttons';
+import { STRING_KEYS } from '@/constants/localization';
+
+import { useStringGetter } from '@/hooks';
+import { layoutMixins } from '@/styles/layoutMixins';
+
+import { Button, ButtonProps } from './Button';
+import { Icon, IconName } from './Icon';
+
+export type CopyButtonProps = {
+ value?: string;
+ shownAsText?: boolean;
+ children?: React.ReactNode;
+} & ButtonProps;
+
+export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => {
+ const stringGetter = useStringGetter();
+ const [copied, setCopied] = useState(false);
+
+ const onCopy = () => {
+ if (!value) return;
+
+ setCopied(true);
+ navigator.clipboard.writeText(value);
+ setTimeout(() => setCopied(false), 500);
+ };
+
+ return shownAsText ? (
+
+ {children}
+
+
+ ) : (
+
+ );
+};
+
+const Styled: Record = {};
+
+Styled.InlineRow = styled.div<{ copied: boolean }>`
+ ${layoutMixins.inlineRow}
+ cursor: pointer;
+
+ ${({ copied }) =>
+ copied
+ ? css`
+ filter: brightness(0.8);
+ `
+ : css`
+ &:hover {
+ filter: brightness(1.1);
+ text-decoration: underline;
+ }
+ `}
+`;
diff --git a/src/constants/abacus.ts b/src/constants/abacus.ts
index 39134af..35e05d8 100644
--- a/src/constants/abacus.ts
+++ b/src/constants/abacus.ts
@@ -119,6 +119,8 @@ export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
export type SubaccountFundingPayments =
Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment[];
+export type SubaccountTransfer = Abacus.exchange.dydx.abacus.output.SubaccountTransfer;
+export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.SubaccountTransfer[];
// ------ Historical PnL ------ //
export type SubAccountHistoricalPNL = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL;
diff --git a/src/constants/localization/app.ts b/src/constants/localization/app.ts
index 93914c9..a392340 100644
--- a/src/constants/localization/app.ts
+++ b/src/constants/localization/app.ts
@@ -177,6 +177,7 @@ export const APP_STRING_KEYS = {
RECEIVE: 'GENERAL.RECEIVE',
RECENT: 'GENERAL.RECENT',
RECENT_TRADES_SHORT: 'GENERAL.RECENT_TRADES_SHORT',
+ RECIPIENT: 'GENERAL.RECIPIENT',
REFERRAL_CODE: 'GENERAL.REFERRAL_CODE',
REFERRALS: 'GENERAL.REFERRALS',
REFERRER_PERCENT_OFF: 'GENERAL.REFERRER_PERCENT_OFF',
@@ -188,6 +189,7 @@ export const APP_STRING_KEYS = {
SELECT_NETWORK: 'GENERAL.SELECT_NETWORK',
SELL: 'GENERAL.SELL',
SEND: 'GENERAL.SEND',
+ SENDER: 'GENERAL.SENDER',
SHARE: 'GENERAL.SHARE',
SHORT_POSITION_SHORT: 'GENERAL.SHORT_POSITION_SHORT',
SIDE: 'GENERAL.SIDE',
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index bd8642a..1aaa95b 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -1,3 +1,5 @@
+import { DEFAULT_MARKETID } from './markets';
+
export enum AppRoute {
Markets = '/markets',
Portfolio = '/portfolio',
@@ -5,7 +7,7 @@ export enum AppRoute {
Profile = '/profile',
Alerts = '/alerts',
Settings = '/settings',
- Rewards = '/DYDX',
+ Rewards = '/DV4TNT',
}
export enum PortfolioRoute {
@@ -31,7 +33,7 @@ export enum MobileSettingsRoute {
export const TRADE_ROUTE = `${AppRoute.Trade}/:market`;
export const PORTFOLIO_ROUTE = `${AppRoute.Portfolio}/:subroute`;
export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:subroute`;
-export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/ETH-USD`;
+export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/${DEFAULT_MARKETID}`;
export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`;
export const DEFAULT_DOCUMENT_TITLE = 'dYdX';
diff --git a/src/constants/wallets.ts b/src/constants/wallets.ts
index 6451456..8e5299b 100644
--- a/src/constants/wallets.ts
+++ b/src/constants/wallets.ts
@@ -377,7 +377,7 @@ export const DYDX_CHAIN_INFO: Parameters[0] = {
export enum DydxChainAsset {
USDC = 'USDC',
- DYDX = 'DYDX',
+ DYDX = 'Dv4TNT',
}
export const DYDX_CHAIN_ASSET_COIN_DENOM: Record = {
diff --git a/src/hooks/useCurrentMarketId.ts b/src/hooks/useCurrentMarketId.ts
index 837a860..528364b 100644
--- a/src/hooks/useCurrentMarketId.ts
+++ b/src/hooks/useCurrentMarketId.ts
@@ -1,5 +1,5 @@
-import { useEffect } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
+import { useEffect, useMemo } from 'react';
+import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useMatch, useNavigate } from 'react-router-dom';
import { LocalStorageKey } from '@/constants/localStorage';
@@ -13,6 +13,7 @@ import { setCurrentMarketId } from '@/state/perpetuals';
import abacusStateManager from '@/lib/abacus';
import { useLocalStorage } from './useLocalStorage';
+import { getMarketIds } from '@/state/perpetualsSelectors';
export const useCurrentMarketId = () => {
const navigate = useNavigate();
@@ -20,26 +21,27 @@ export const useCurrentMarketId = () => {
const { marketId } = match?.params ?? {};
const dispatch = useDispatch();
const selectedNetwork = useSelector(getSelectedNetwork);
+ const marketIds = useSelector(getMarketIds, shallowEqual);
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
key: LocalStorageKey.LastViewedMarket,
defaultValue: DEFAULT_MARKETID,
});
+ const validId = useMemo(() => {
+ if (marketIds.length === 0) return marketId ?? lastViewedMarket;
+ if (!marketIds.includes(marketId)) return DEFAULT_MARKETID;
+ return marketId ?? lastViewedMarket;
+ }, [marketIds, marketId]);
+
useEffect(() => {
- setLastViewedMarket(marketId ?? DEFAULT_MARKETID);
- dispatch(setCurrentMarketId(marketId ?? DEFAULT_MARKETID));
+ setLastViewedMarket(validId);
+ dispatch(setCurrentMarketId(validId));
dispatch(closeDialogInTradeBox());
- if (!marketId) {
- navigate(lastViewedMarket ? `${AppRoute.Trade}/${lastViewedMarket}` : DEFAULT_TRADE_ROUTE, {
- replace: true,
- });
- } else {
- navigate(`${AppRoute.Trade}/${marketId}`, {
- replace: true,
- });
- }
- }, [marketId]);
+ navigate(`${AppRoute.Trade}/${validId}`, {
+ replace: true,
+ });
+ }, [validId]);
useEffect(() => {
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
diff --git a/src/lib/abacus/stateNotification.ts b/src/lib/abacus/stateNotification.ts
index 51fd67b..0400575 100644
--- a/src/lib/abacus/stateNotification.ts
+++ b/src/lib/abacus/stateNotification.ts
@@ -22,6 +22,7 @@ import {
setFundingPayments,
setHistoricalPnl,
setSubaccount,
+ setTransfers,
setWallet,
} from '@/state/account';
@@ -113,6 +114,11 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
dispatch(setFundingPayments(fundingPayments));
}
+ if (changes.has(Changes.transfers)) {
+ const transfers = updatedState.subaccountTransfers(subaccountId)?.toArray() || [];
+ dispatch(setTransfers(transfers));
+ }
+
if (changes.has(Changes.historicalPnl)) {
const historicalPnl =
updatedState.subaccountHistoricalPnl(subaccountId)?.toArray() || [];
diff --git a/src/localization/en/app.json b/src/localization/en/app.json
index 41a0027..a492d6b 100644
--- a/src/localization/en/app.json
+++ b/src/localization/en/app.json
@@ -182,6 +182,7 @@
"RECEIVE": "Receive",
"RECENT": "Recent",
"RECENT_TRADES_SHORT": "Trades",
+ "RECIPIENT": "Recipient",
"REFERRAL_CODE": "Referral Code",
"REFERRALS": "Referrals",
"REFERRER_PERCENT_OFF": "{DISCOUNT}% off",
@@ -193,6 +194,7 @@
"SELECT_NETWORK": "Select Network",
"SELL": "Sell",
"SEND": "Send",
+ "SENDER": "Sender",
"SHARE": "Share",
"SHORT_POSITION_SHORT": "Short",
"SIDE": "Side",
diff --git a/src/main.tsx b/src/main.tsx
index 2a29686..e8a6619 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,5 +1,5 @@
import './polyfills';
-import { Fragment, StrictMode } from 'react';
+import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
@@ -11,15 +11,12 @@ import { ErrorBoundary } from './components/ErrorBoundary';
import './index.css';
import App from './App';
-import { LocaleProvider } from './hooks/useLocaleSeparators';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
- } />
-
+ } />
diff --git a/src/pages/portfolio/History.tsx b/src/pages/portfolio/History.tsx
index 72fffa2..78f85aa 100644
--- a/src/pages/portfolio/History.tsx
+++ b/src/pages/portfolio/History.tsx
@@ -28,13 +28,14 @@ export const History = () => {
label: {stringGetter({ key: STRING_KEYS.TRADES })}
,
href: HistoryRoute.Trades,
},
+ {
+ value: HistoryRoute.Transfers,
+ label: {stringGetter({ key: STRING_KEYS.TRANSFERS })}
,
+ href: HistoryRoute.Transfers,
+ tag: 'USDC',
+ },
// TODO - TRCL-1693 -
// {
- // value: HistoryRoute.Transfers,
- // label: {stringGetter({ key: STRING_KEYS.TRANSFERS })}
,
- // href: HistoryRoute.Transfers,
- // },
- // {
// value: HistoryRoute.Payments,
// label: {stringGetter({ key: STRING_KEYS.PAYMENTS })}
,
// href: HistoryRoute.Payments,
diff --git a/src/pages/portfolio/Portfolio.tsx b/src/pages/portfolio/Portfolio.tsx
index 227af41..d8969b8 100644
--- a/src/pages/portfolio/Portfolio.tsx
+++ b/src/pages/portfolio/Portfolio.tsx
@@ -9,6 +9,7 @@ import { useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
+import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable';
import { Icon, IconName } from '@/components/Icon';
import { NavigationMenu } from '@/components/NavigationMenu';
import { WithSidebar } from '@/components/WithSidebar';
@@ -61,7 +62,10 @@ export default () => {
/>
}
/>
- } />
+ }
+ />
}
@@ -118,8 +122,6 @@ export default () => {
},
],
},
- // TODO(aforaleka) Add back subitems when there are clearer designs
- // or when transfers and payments are ready
]}
/>
)
diff --git a/src/pages/portfolio/PortfolioNavMobile.tsx b/src/pages/portfolio/PortfolioNavMobile.tsx
index ebfaf5a..0ce1a65 100644
--- a/src/pages/portfolio/PortfolioNavMobile.tsx
+++ b/src/pages/portfolio/PortfolioNavMobile.tsx
@@ -40,12 +40,12 @@ export const PortfolioNavMobile = () => {
label: stringGetter({ key: STRING_KEYS.TRADES }),
description: stringGetter({ key: STRING_KEYS.TRADES_DESCRIPTION }),
},
- // TODO: TRCL-1693 - re-enable when Payments and Transfers are ready
- // {
- // value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`,
- // label: stringGetter({ key: STRING_KEYS.TRANSFERS }),
- // description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }),
- // },
+ {
+ value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`,
+ label: stringGetter({ key: STRING_KEYS.TRANSFERS }),
+ description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }),
+ },
+ // TODO: TRCL-1693 - re-enable when Payments are ready
// {
// value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Payments}`,
// label: stringGetter({ key: STRING_KEYS.PAYMENTS }),
diff --git a/src/pages/rewards/DYDXBalancePanel.tsx b/src/pages/rewards/DYDXBalancePanel.tsx
index 7393217..5ebf66b 100644
--- a/src/pages/rewards/DYDXBalancePanel.tsx
+++ b/src/pages/rewards/DYDXBalancePanel.tsx
@@ -36,8 +36,8 @@ export const DYDXBalancePanel = () => {
slotHeader={
-
- DYDX
+ {/* */}
+ Dv4TNT
{!canAccountTrade ? (
@@ -106,7 +106,7 @@ export const DYDXBalancePanel = () => {
{
key: 'totalBalance',
label: 'Total balance',
- value: ,
+ value: ,
},
]}
/>
diff --git a/src/state/account.ts b/src/state/account.ts
index b722363..5fab6e3 100644
--- a/src/state/account.ts
+++ b/src/state/account.ts
@@ -9,6 +9,7 @@ import type {
SubaccountFundingPayments,
Wallet,
SubaccountOrder,
+ SubaccountTransfers,
HistoricalPnlPeriods,
SubAccountHistoricalPNLs,
} from '@/constants/abacus';
@@ -22,6 +23,7 @@ import { getLocalStorage } from '@/lib/localStorage';
export type AccountState = {
fills?: SubaccountFills;
fundingPayments?: SubaccountFundingPayments;
+ transfers?: SubaccountTransfers;
clearedOrderIds?: string[];
uncommittedOrderClientIds?: number[];
hasUnseenFillUpdates: boolean;
@@ -38,6 +40,7 @@ export type AccountState = {
const initialState: AccountState = {
fills: undefined,
fundingPayments: undefined,
+ transfers: undefined,
clearedOrderIds: undefined,
uncommittedOrderClientIds: undefined,
hasUnseenFillUpdates: false,
@@ -81,6 +84,9 @@ export const accountSlice = createSlice({
setFundingPayments: (state, action: PayloadAction) => {
state.fundingPayments = action.payload;
},
+ setTransfers: (state, action: PayloadAction) => {
+ state.transfers = action.payload;
+ },
clearOrder: (state, action: PayloadAction) => ({
...state,
clearedOrderIds: [...(state.clearedOrderIds || []), action.payload],
@@ -148,6 +154,7 @@ export const accountSlice = createSlice({
export const {
setFills,
setFundingPayments,
+ setTransfers,
clearOrder,
setOnboardingGuard,
setOnboardingState,
diff --git a/src/state/accountSelectors.ts b/src/state/accountSelectors.ts
index c16f039..29aa72e 100644
--- a/src/state/accountSelectors.ts
+++ b/src/state/accountSelectors.ts
@@ -193,6 +193,12 @@ export const getCurrentMarketFills = createSelector(
!currentMarketId ? [] : marketFills[currentMarketId]
);
+/**
+ * @param state
+ * @returns list of transfers for the currently connected subaccount
+ */
+export const getSubaccountTransfers = (state: RootState) => state.account?.transfers;
+
/**
* @param state
* @returns list of funding payments for the currently connected subaccount
diff --git a/src/styles/headerMixins.ts b/src/styles/headerMixins.ts
index 8efef47..4a8da3d 100644
--- a/src/styles/headerMixins.ts
+++ b/src/styles/headerMixins.ts
@@ -5,7 +5,7 @@ export const headerMixins = {
--trigger-backgroundColor: transparent;
--trigger-textColor: var(--color-text-0);
- --trigger-hover-backgroundColor: var(--color-layer-4);
+ --trigger-hover-backgroundColor: var(--color-layer-3);
--trigger-hover-textColor: var(--color-text-2);
--trigger-open-backgroundColor: var(--color-layer-1);
diff --git a/src/styles/popoverMixins.ts b/src/styles/popoverMixins.ts
index 3f01d3c..47932cf 100644
--- a/src/styles/popoverMixins.ts
+++ b/src/styles/popoverMixins.ts
@@ -185,6 +185,7 @@ export const popoverMixins = {
--item-checked-backgroundColor: var(--color-layer-2);
--item-checked-textColor: currentColor;
+ --item-highlighted-backgroundColor: var(--color-layer-2);
--item-highlighted-textColor: var(--color-text-2);
--item-radius: 0px;
@@ -218,6 +219,7 @@ export const popoverMixins = {
&[data-highlighted] // @radix-ui
{
filter: brightness(1.1);
+ background-color: var(--item-highlighted-backgroundColor);
color: var(--item-highlighted-textColor, var(--trigger-textColor, inherit)) !important;
outline: none;
}
diff --git a/src/styles/tradeViewMixins.ts b/src/styles/tradeViewMixins.ts
index fa64498..a847b81 100644
--- a/src/styles/tradeViewMixins.ts
+++ b/src/styles/tradeViewMixins.ts
@@ -18,5 +18,9 @@ export const tradeViewMixins: Record<
tbody {
font: var(--font-small-book);
}
+
+ thead tr {
+ box-shadow: none;
+ }
`,
};
diff --git a/src/views/dialogs/MnemonicExportDialog.tsx b/src/views/dialogs/MnemonicExportDialog.tsx
index b654170..a53c7e0 100644
--- a/src/views/dialogs/MnemonicExportDialog.tsx
+++ b/src/views/dialogs/MnemonicExportDialog.tsx
@@ -9,7 +9,7 @@ import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { AlertMessage } from '@/components/AlertMessage';
-import { Button } from '@/components/Button';
+import { CopyButton } from '@/components/CopyButton';
import { Dialog } from '@/components/Dialog';
import { Checkbox } from '@/components/Checkbox';
import { Icon, IconName } from '@/components/Icon';
@@ -30,21 +30,12 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => {
const [hasAcknowledged, setHasAcknowledged] = useState(false);
const [currentStep, setCurrentStep] = useState(MnemonicExportStep.AcknowledgeRisk);
const [isShowing, setIsShowing] = useState(false);
- const [copied, setCopied] = useState(false);
const stringGetter = useStringGetter();
const { hdKey } = useAccounts();
const { mnemonic } = hdKey ?? {};
- const onCopy = () => {
- setCopied(true);
- if (mnemonic) {
- navigator.clipboard.writeText(mnemonic);
- }
- setTimeout(() => setCopied(false), 500);
- };
-
const title = {
[MnemonicExportStep.AcknowledgeRisk]: stringGetter({ key: STRING_KEYS.REVEAL_SECRET_PHRASE }),
[MnemonicExportStep.DisplayMnemonic]: stringGetter({ key: STRING_KEYS.EXPORT_SECRET_PHRASE }),
@@ -116,10 +107,7 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => {
}
>
-
+
>
),
diff --git a/src/views/dialogs/ReceiveDialog.tsx b/src/views/dialogs/ReceiveDialog.tsx
index 957bcb6..b9546fa 100644
--- a/src/views/dialogs/ReceiveDialog.tsx
+++ b/src/views/dialogs/ReceiveDialog.tsx
@@ -1,7 +1,6 @@
import { useState } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
-import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DydxChainAsset } from '@/constants/wallets';
@@ -10,9 +9,8 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { useAccounts, useStringGetter } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
-import { Button } from '@/components/Button';
+import { CopyButton } from '@/components/CopyButton';
import { Dialog } from '@/components/Dialog';
-import { Icon, IconName } from '@/components/Icon';
import { QrCode } from '@/components/QrCode';
import { SelectItem, SelectMenu } from '@/components/SelectMenu';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
@@ -22,22 +20,15 @@ import { truncateAddress } from '@/lib/wallet';
import { OnboardingTriggerButton } from './OnboardingTriggerButton';
type ElementProps = {
+ selectedAsset?: DydxChainAsset;
setIsOpen: (open: boolean) => void;
};
-export const ReceiveDialog = ({ setIsOpen }: ElementProps) => {
+export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts();
- const [asset, setAsset] = useState(DydxChainAsset.DYDX);
- const [copied, setCopied] = useState(false);
-
- const onCopy = () => {
- if (!dydxAddress) return;
- setCopied(true);
- navigator.clipboard.writeText(dydxAddress);
- setTimeout(() => setCopied(false), 500);
- };
+ const [asset, setAsset] = useState(selectedAsset);
const assetOptions = [
{
@@ -52,7 +43,8 @@ export const ReceiveDialog = ({ setIsOpen }: ElementProps) => {
value: DydxChainAsset.DYDX,
label: (
- DYDX
+ {/* */}
+ Dv4TNT
),
},
@@ -87,10 +79,7 @@ export const ReceiveDialog = ({ setIsOpen }: ElementProps) => {
>
-
+
>
)}
diff --git a/src/views/dialogs/TransferDialog.tsx b/src/views/dialogs/TransferDialog.tsx
index 4af8e48..fd0bb70 100644
--- a/src/views/dialogs/TransferDialog.tsx
+++ b/src/views/dialogs/TransferDialog.tsx
@@ -1,21 +1,23 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
+import { DydxChainAsset } from '@/constants/wallets';
import { useStringGetter } from '@/hooks';
import { Dialog } from '@/components/Dialog';
import { TransferForm } from '@/views/forms/TransferForm';
type ElementProps = {
+ selectedAsset?: DydxChainAsset;
setIsOpen?: (open: boolean) => void;
};
-export const TransferDialog = ({ setIsOpen }: ElementProps) => {
+export const TransferDialog = ({ selectedAsset, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
- setIsOpen?.(false)} />
+ setIsOpen?.(false)} />
);
};
diff --git a/src/views/forms/TradeForm/AdvancedTradeOptions.tsx b/src/views/forms/TradeForm/AdvancedTradeOptions.tsx
index 477f947..5fa2ef3 100644
--- a/src/views/forms/TradeForm/AdvancedTradeOptions.tsx
+++ b/src/views/forms/TradeForm/AdvancedTradeOptions.tsx
@@ -109,6 +109,7 @@ export const AdvancedTradeOptions = () => {
{executionOptions && (
abacusStateManager.setTradeValue({
value: selectedTimeInForceOption,
diff --git a/src/views/forms/TransferForm.tsx b/src/views/forms/TransferForm.tsx
index 966bea1..2131411 100644
--- a/src/views/forms/TransferForm.tsx
+++ b/src/views/forms/TransferForm.tsx
@@ -48,6 +48,7 @@ import { MustBigNumber } from '@/lib/numbers';
import { log } from '@/lib/telemetry';
type TransferFormProps = {
+ selectedAsset?: DydxChainAsset;
onDone?: () => void;
className?: string;
};
@@ -74,7 +75,11 @@ const debouncedEstimateFee = debounce(
{ trailing: true }
);
-export const TransferForm = ({ onDone, className }: TransferFormProps) => {
+export const TransferForm = ({
+ selectedAsset = DydxChainAsset.DYDX,
+ onDone,
+ className,
+}: TransferFormProps) => {
const stringGetter = useStringGetter();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { dydxAddress } = useAccounts();
@@ -84,7 +89,7 @@ export const TransferForm = ({ onDone, className }: TransferFormProps) => {
const { selectedNetwork } = useSelectedNetwork();
// User Input
- const [asset, setAsset] = useState(DydxChainAsset.DYDX);
+ const [asset, setAsset] = useState(selectedAsset);
// Form states
const [error, setError] = useState();
@@ -214,7 +219,8 @@ export const TransferForm = ({ onDone, className }: TransferFormProps) => {
value: DydxChainAsset.DYDX,
label: (
- DYDX
+ {/* */}
+ Dv4TNT
),
},
diff --git a/src/views/menus/AccountMenu.tsx b/src/views/menus/AccountMenu.tsx
index a1dc135..cbbfd62 100644
--- a/src/views/menus/AccountMenu.tsx
+++ b/src/views/menus/AccountMenu.tsx
@@ -1,11 +1,13 @@
+import { memo } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
+import type { Dispatch } from '@reduxjs/toolkit';
import { OnboardingState } from '@/constants/account';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS, TOOLTIP_STRING_KEYS } from '@/constants/localization';
-import { wallets } from '@/constants/wallets';
+import { DydxChainAsset, wallets } from '@/constants/wallets';
import { useAccounts, useBreakpoints, useStringGetter, useAccountBalance } from '@/hooks';
@@ -103,22 +105,28 @@ export const AccountMenu = () => {
-
- {stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'Dv4TNT' } })}
- {/* */}
-
-
+
+
+ {stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'Dv4TNT' } })}
+ {/* */}
+
+
+
+
-
- {stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'USDC' } })}
-
-
-
+
+
+ {stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'USDC' } })}
+
+
+
+
+
@@ -191,6 +199,29 @@ export const AccountMenu = () => {
);
};
+const AssetActions = memo(({ asset, dispatch }: { asset: DydxChainAsset; dispatch: Dispatch }) => (
+
+ {[
+ // TODO(@rosepuppy): Add withdraw action for USDC
+ {
+ dialogType: DialogTypes.Receive,
+ iconName: IconName.Qr,
+ },
+ { dialogType: DialogTypes.Transfer, iconName: IconName.Send },
+ ].map(({ iconName, dialogType }) => (
+
+ dispatch(openDialog({ type: dialogType, dialogProps: { selectedAsset: asset } }))
+ }
+ />
+ ))}
+
+));
+
const Styled: Record = {};
Styled.AccountInfo = styled.div`
@@ -204,6 +235,10 @@ Styled.Column = styled.div`
${layoutMixins.column}
`;
+Styled.InlineRow = styled.div`
+ ${layoutMixins.inlineRow}
+`;
+
Styled.AddressRow = styled.div`
${layoutMixins.row}
@@ -256,7 +291,7 @@ Styled.Balances = styled.div`
gap: 2px;
> div {
- ${layoutMixins.flexColumn}
+ ${layoutMixins.spacedRow}
gap: 0.5rem;
padding: 0.5rem 1rem;
diff --git a/src/views/tables/TransferHistoryTable.tsx b/src/views/tables/TransferHistoryTable.tsx
new file mode 100644
index 0000000..cf7c9c3
--- /dev/null
+++ b/src/views/tables/TransferHistoryTable.tsx
@@ -0,0 +1,195 @@
+import styled, { type AnyStyledComponent, css } from 'styled-components';
+import { shallowEqual, useDispatch, useSelector } from 'react-redux';
+import type { ColumnSize } from '@react-types/table';
+
+import { type SubaccountTransfer } from '@/constants/abacus';
+import { ButtonAction } from '@/constants/buttons';
+import { DialogTypes } from '@/constants/dialogs';
+import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
+
+import { useBreakpoints, useStringGetter } from '@/hooks';
+
+import { layoutMixins } from '@/styles/layoutMixins';
+import { tradeViewMixins } from '@/styles/tradeViewMixins';
+
+import { Button } from '@/components/Button';
+import { CopyButton } from '@/components/CopyButton';
+import { Icon } from '@/components/Icon';
+import { Link } from '@/components/Link';
+import { Output, OutputType } from '@/components/Output';
+import { Table, TableCell, TableColumnHeader, type ColumnDef } from '@/components/Table';
+import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton';
+
+import { getSubaccountTransfers } from '@/state/accountSelectors';
+import { calculateCanAccountTrade } from '@/state/accountCalculators';
+import { openDialog } from '@/state/dialogs';
+
+import { truncateAddress } from '@/lib/wallet';
+
+const MOBILE_TRANSFERS_PER_PAGE = 50;
+
+export enum TransferHistoryTableColumnKey {
+ Time = 'Time',
+ Action = 'Action',
+ SenderRecipient = 'Sender-Recipient',
+ Amount = 'Amount',
+ TxHash = 'TxHash',
+}
+
+const getTransferHistoryTableColumnDef = ({
+ key,
+ stringGetter,
+ width,
+}: {
+ key: TransferHistoryTableColumnKey;
+ isTablet?: boolean;
+ stringGetter: StringGetterFunction;
+ width?: ColumnSize;
+}): ColumnDef => ({
+ width,
+ ...(
+ {
+ [TransferHistoryTableColumnKey.Time]: {
+ columnKey: TransferHistoryTableColumnKey.Time,
+ getCellValue: (row) => row.updatedAtMilliseconds,
+ label: stringGetter({ key: STRING_KEYS.TIME }),
+ renderCell: ({ updatedAtMilliseconds }) => (
+
+ ),
+ },
+ [TransferHistoryTableColumnKey.Action]: {
+ columnKey: TransferHistoryTableColumnKey.Action,
+ getCellValue: (row) => row.resources.typeStringKey,
+ label: stringGetter({ key: STRING_KEYS.ACTION }),
+ renderCell: ({ resources }) =>
+ resources.typeStringKey && stringGetter({ key: resources.typeStringKey }),
+ },
+ [TransferHistoryTableColumnKey.SenderRecipient]: {
+ columnKey: TransferHistoryTableColumnKey.SenderRecipient,
+ getCellValue: (row) => `${row.fromAddress}-${row.toAddress}`,
+ label: (
+
+ {stringGetter({ key: STRING_KEYS.SENDER })}
+ {stringGetter({ key: STRING_KEYS.RECIPIENT })}
+
+ ),
+ renderCell: ({ fromAddress, toAddress }) => (
+
+
+ {fromAddress ? truncateAddress(fromAddress) : '-'}
+ {' '}
+
+ {toAddress ? truncateAddress(toAddress) : '-'}
+
+
+ ),
+ },
+ [TransferHistoryTableColumnKey.Amount]: {
+ columnKey: TransferHistoryTableColumnKey.Amount,
+ getCellValue: (row) => row.amount,
+ label: stringGetter({ key: STRING_KEYS.AMOUNT }),
+ renderCell: ({ amount }) => ,
+ },
+ [TransferHistoryTableColumnKey.TxHash]: {
+ columnKey: TransferHistoryTableColumnKey.TxHash,
+ getCellValue: (row) => row.transactionHash,
+ label: stringGetter({ key: STRING_KEYS.TRANSACTION }),
+ renderCell: ({ transactionHash, resources }) =>
+ transactionHash ? (
+
+ {truncateAddress(transactionHash, '')}
+
+ ) : (
+ '-'
+ ),
+ },
+ } as Record>
+ )[key],
+});
+
+type ElementProps = {
+ columnKeys?: TransferHistoryTableColumnKey[];
+ columnWidths?: Partial>;
+};
+
+type StyleProps = {
+ withOuterBorder?: boolean;
+ withInnerBorders?: boolean;
+};
+
+export const TransferHistoryTable = ({
+ columnKeys = Object.values(TransferHistoryTableColumnKey),
+ columnWidths,
+ withOuterBorder,
+ withInnerBorders = true,
+}: ElementProps & StyleProps) => {
+ const stringGetter = useStringGetter();
+ const dispatch = useDispatch();
+ const { isMobile, isTablet } = useBreakpoints();
+
+ const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
+
+ const transfers = useSelector(getSubaccountTransfers, shallowEqual) ?? [];
+
+ return (
+ row.id}
+ columns={columnKeys.map((key: TransferHistoryTableColumnKey) =>
+ getTransferHistoryTableColumnDef({
+ key,
+ isTablet,
+ stringGetter,
+ width: columnWidths?.[key],
+ })
+ )}
+ slotEmpty={
+ <>
+ {stringGetter({ key: STRING_KEYS.TRANSFERS_EMPTY_STATE })}
+ {canAccountTrade ? (
+
+ ) : (
+
+ )}
+ >
+ }
+ selectionBehavior="replace"
+ withOuterBorder={withOuterBorder}
+ withInnerBorders={withInnerBorders}
+ withScrollSnapColumns
+ withScrollSnapRows
+ />
+ );
+};
+
+const Styled: Record = {};
+
+Styled.Table = styled(Table)`
+ ${tradeViewMixins.horizontalTable}
+`;
+
+Styled.InlineRow = styled.div`
+ ${layoutMixins.inlineRow}
+`;
+
+Styled.Icon = styled(Icon)`
+ font-size: 3em;
+`;
+
+Styled.TimeOutput = styled(Output)`
+ color: var(--color-text-0);
+`;
+
+Styled.TxHash = styled(Link)`
+ justify-content: flex-end;
+`;