Compare commits

..
Author SHA1 Message Date
James Jia - Test 8dfe36998d update LICENSE 2023-08-31 17:55:45 -07:00
63 changed files with 2065 additions and 1413 deletions
+3 -3
View File
@@ -30,16 +30,16 @@
},
"packageManager": "pnpm@8.6.6",
"dependencies": {
"@0xsquid/sdk": "^1.10.0",
"@0xsquid/sdk": "^1.7.2",
"@cosmjs/amino": "^0.31.0",
"@cosmjs/crypto": "^0.31.0",
"@cosmjs/encoding": "^0.31.0",
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/abacus": "^0.4.8",
"@dydxprotocol/abacus": "^0.4.1",
"@dydxprotocol/v4-client-js": "^0.32.0",
"@dydxprotocol/v4-localization": "^0.0.25",
"@dydxprotocol/v4-localization": "^0.0.21",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-collapsible": "^1.0.3",
+1780 -201
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

+25 -30
View File
@@ -13,9 +13,8 @@ import { AccountsProvider } from '@/hooks/useAccounts';
import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
import { LocaleProvider } from './hooks/useLocaleSeparators';
import { NotificationsProvider } from './hooks/useNotifications';
import { LocalNotificationsProvider } from './hooks/useLocalNotifications';
import { SubaccountProvider } from './hooks/useSubaccount';
import { SquidProvider } from '@/hooks/useSquid';
import { SquidProvider } from '@/hooks/useSquidRouter';
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
@@ -99,31 +98,29 @@ const Content = () => {
);
};
const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
return ({ children }: { children: React.ReactNode }) => (
<Component {...props}>{children}</Component>
);
};
const providers = [
wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider),
wrapProvider(DydxProvider),
wrapProvider(AccountsProvider),
wrapProvider(SubaccountProvider),
wrapProvider(SquidProvider),
wrapProvider(LocalNotificationsProvider),
wrapProvider(NotificationsProvider),
wrapProvider(DialogAreaProvider),
];
const App = () => {
return [...providers].reverse().reduce((children, Provider) => {
return <Provider>{children}</Provider>;
}, <Content />);
};
const App = () => (
<QueryClientProvider client={queryClient}>
<GrazProvider>
<WagmiConfig config={config}>
<LocaleProvider>
<DydxProvider>
<AccountsProvider>
<SubaccountProvider>
<NotificationsProvider>
<DialogAreaProvider>
<SquidProvider>
<Content />
</SquidProvider>
</DialogAreaProvider>
</NotificationsProvider>
</SubaccountProvider>
</AccountsProvider>
</DydxProvider>
</LocaleProvider>
</WagmiConfig>
</GrazProvider>
</QueryClientProvider>
);
const Styled: Record<string, AnyStyledComponent> = {};
@@ -195,9 +192,7 @@ Styled.NotificationsToastArea = styled(NotificationsToastArea)`
`;
Styled.DialogArea = styled.aside`
position: fixed;
height: 100vh;
z-index: 1;
position: absolute;
inset: 0;
overflow: clip;
${layoutMixins.noPointerEvents}
-222
View File
@@ -1,222 +0,0 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
]
+8 -14
View File
@@ -80,9 +80,9 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
{group.items.map((item) => (
<Fragment key={item.value}>
<Styled.Item
value={[group.groupLabel, item.value, item.description, item.label, item.tag]
.filter(Boolean)
.join('|')}
// value={item.value} // search by both value and textContent
// value={[group.groupLabel, item.label, item.tag].filter(Boolean).join('|')} // exclude item.value from searchable terms (not guaranteed to be unique)
value={[group.groupLabel, item.value, item.description, item.label, item.tag].filter(Boolean).join('|')}
onSelect={() => {
if (item.subitems) {
// open submenu
@@ -114,15 +114,9 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
item.subitems?.map((subitem) => (
<Fragment key={subitem.value}>
<Styled.Item
value={[
group.groupLabel,
item.value,
item.description,
item.label,
item.tag,
]
.filter(Boolean)
.join('|')}
// value={subitem.value} // search by both value and textContent
// value={[group.groupLabel, item.label, subitem.label, subitem.tag].filter(Boolean).join('|')}
value={[group.groupLabel, item.value, item.description, item.label, item.tag].filter(Boolean).join('|')}
onSelect={() => {
subitem.onSelect?.(subitem.value);
onItemSelected?.();
@@ -174,7 +168,7 @@ Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
background-color: var(--comboboxMenu-backgroundColor);
border-radius: inherit;
input:focus-visible {
input:focus {
outline: none;
}
@@ -278,7 +272,7 @@ Styled.Item = styled(Command.Item)`
align-items: center;
gap: 0.5rem;
&[aria-disabled='true'] {
&[aria-disabled="true"] {
opacity: 0.75;
cursor: not-allowed;
}
+5 -2
View File
@@ -90,7 +90,10 @@ export const Dialog = ({
return (
<Root modal={showOverlay} open={isOpen} onOpenChange={setIsOpen}>
{slotTrigger && <Trigger asChild>{slotTrigger}</Trigger>}
<DialogPortal withPortal={placement !== DialogPlacement.Inline} container={portalContainer}>
<DialogPortal
withPortal={placement !== DialogPlacement.Inline}
container={portalContainer}
>
{showOverlay && <Styled.Overlay />}
<Styled.Container
placement={placement}
@@ -422,7 +425,7 @@ Styled.Close = styled(Close)`
}
&:hover,
&:focus-visible {
&:focus {
color: var(--color-text-2);
}
+1 -1
View File
@@ -137,7 +137,7 @@ Styled.ComboboxMenu = styled(ComboboxMenu)`
--stickyArea1-topHeight: var(--form-input-height);
input:focus-visible {
input:focus {
outline: none;
}
+2 -2
View File
@@ -743,7 +743,7 @@ Styled.Tr = styled.tr<{
cursor: pointer;
&:hover,
&:focus-visible,
&:focus,
&:focus-within {
--tableRow-currentBackgroundColor: var(--tableRow-hover-backgroundColor);
filter: brightness(1.1);
@@ -753,7 +753,7 @@ Styled.Tr = styled.tr<{
${({ withFocusStickyRows }) =>
withFocusStickyRows &&
css`
&:focus-visible,
&:focus,
&:focus-within {
${layoutMixins.sticky}
z-index: 1;
+11 -13
View File
@@ -17,7 +17,6 @@ type ElementProps = {
slotIcon?: React.ReactNode;
slotTitle?: React.ReactNode;
slotDescription?: React.ReactNode;
slotCustomContent?: React.ReactNode;
slotAction?: React.ReactNode;
actionDescription?: string;
actionAltText?: string;
@@ -34,7 +33,6 @@ export const Toast = ({
slotIcon,
slotTitle,
slotDescription,
slotCustomContent,
slotAction,
actionDescription = '',
actionAltText = actionDescription,
@@ -80,13 +78,12 @@ export const Toast = ({
<$Title>{slotTitle}</$Title>
</$Header>
{!slotCustomContent && <$Description>{slotDescription}</$Description>}
{slotCustomContent}
{actionDescription && (
<$Action asChild altText={actionAltText}>
{slotAction}
</$Action>
)}
<$Description>{slotDescription}</$Description>
<$Action asChild altText={actionAltText}>
{slotAction}
</$Action>
</$Container>
</div>
</$Root>
@@ -121,7 +118,8 @@ const $Root = styled(Root)`
align-items: end;
transform-origin: left bottom;
animation: ${keyframes`
animation:
${keyframes`
from {
/* scale: 0; */
grid-template-rows: 0fr; // height transition
@@ -152,7 +150,7 @@ const $Root = styled(Root)`
}
}
&:focus-visible {
&:focus {
outline: none;
}
@@ -205,8 +203,8 @@ const $Container = styled.div`
// Rules
${popoverMixins.popover}
padding: 1rem;
box-shadow: 0 0 0 var(--border-width) var(--color-border),
// border
box-shadow:
0 0 0 var(--border-width) var(--color-border), // border
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
${$Root}:focus:not([data-swipe='end']) & {
+1 -1
View File
@@ -91,7 +91,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.Content = styled(Content)`
z-index: 1;
&:focus-visible {
&:focus {
outline: none;
}
`;
+1 -4
View File
@@ -40,10 +40,7 @@ export type AbacusFileSystemProtocol = Omit<
Abacus.exchange.dydx.abacus.protocols.FileSystemProtocol,
'__doNotUseOrImplementIt'
>;
export type AbacusTrackingProtocol = Omit<
Abacus.exchange.dydx.abacus.protocols.TrackingProtocol,
'__doNotUseOrImplementIt'
>;
export type FileLocation = Abacus.exchange.dydx.abacus.protocols.FileLocation;
export type ThreadingType = Abacus.exchange.dydx.abacus.protocols.ThreadingType;
export const CoroutineTimer = Abacus.exchange.dydx.abacus.utils.CoroutineTimer;
+2 -2
View File
@@ -49,11 +49,11 @@ export enum EvmDerivedAccountStatus {
Derived,
}
import type { DydxAddress, EvmAddress } from './wallets';
import type { DydxAddress, EthereumAddress } from './wallets';
export type EvmDerivedAddresses = {
version?: string;
[EvmAddress: EvmAddress]: {
[EthereumAddress: EthereumAddress]: {
encryptedSignature?: string;
dydxAddress?: DydxAddress;
};
+2 -3
View File
@@ -1,7 +1,7 @@
import type { SupportedLocales } from './localization';
import type { DydxNetwork } from './networks';
import type { OnboardingState, OnboardingSteps } from './account';
import type { DydxAddress, WalletType, WalletConnectionType, EvmAddress } from './wallets';
import type { DydxAddress, WalletType, WalletConnectionType, EthereumAddress } from './wallets';
import type { DialogTypes } from './dialogs';
import type { TradeTypes } from './trade';
import type { AbacusApiStatus, HumanReadablePlaceOrderPayload } from './abacus';
@@ -40,7 +40,7 @@ export type AnalyticsUserPropertyValue<T extends AnalyticsUserProperty> =
: T extends AnalyticsUserProperty.WalletConnectionType
? WalletConnectionType | undefined
: T extends AnalyticsUserProperty.WalletAddress
? EvmAddress | DydxAddress | undefined
? EthereumAddress | DydxAddress | undefined
: // Account
T extends AnalyticsUserProperty.DydxAddress
? DydxAddress | undefined
@@ -67,7 +67,6 @@ export enum AnalyticsEvent {
// Onboarding
OnboardingStepChanged = 'OnboardingStepChanged',
OnboardingAccountDerived = 'OnboardingAccountDerived',
OnboardingWalletIsNonDeterministic = 'OnboardingWalletIsNonDeterministic',
// Transfers
TransferFaucet = 'TransferFaucet',
-1
View File
@@ -12,7 +12,6 @@ export enum LocalStorageKey {
NotificationsLastUpdated = 'dydx.NotificationsLastUpdated',
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
TransferNotifications = 'dydx.TransferNotifications',
// UI State
LastViewedMarket = 'dydx.LastViewedMarket',
+13 -15
View File
@@ -34,8 +34,6 @@ export const STRING_KEYS = {
...WARNINGS_STRING_KEYS,
};
export type StringKey = keyof typeof STRING_KEYS;
export type LocaleData = typeof EN_LOCALE_DATA;
export type StringGetterFunction = (a: {
@@ -90,17 +88,17 @@ export type TooltipStrings = {
};
export const ORDER_ERROR_CODE_MAP: { [key: number]: string } = {
2000: STRING_KEYS['2000_FILL_OR_KILL_ORDER_COULD_NOT_BE_FULLY_FILLED'],
2001: STRING_KEYS['2001_REDUCE_ONLY_WOULD_INCREASE_POSITION_SIZE'],
2002: STRING_KEYS['2002_REDUCE_ONLY_WOULD_CHANGE_POSITION_SIDE'],
2003: STRING_KEYS['2003_POST_ONLY_WOULD_CROSS_MAKER_ORDER'],
3000: STRING_KEYS['3000_INVALID_ORDER_FLAGS'],
3001: STRING_KEYS['3001_INVALID_STATEFUL_ORDER_GOOD_TIL_BLOCK_TIME'],
3002: STRING_KEYS['3002_STATEFUL_ORDERS_CANNOT_REQUIRE_IMMEDIATE_EXECUTION'],
3003: STRING_KEYS['3003_TIME_EXCEEDS_GOOD_TIL_BLOCK_TIME'],
3004: STRING_KEYS['3004_GOOD_TIL_BLOCK_TIME_EXCEEDS_STATEFUL_ORDER_TIME_WINDOW'],
3005: STRING_KEYS['3005_STATEFUL_ORDER_ALREADY_EXISTS'],
3006: STRING_KEYS['3006_STATEFUL_ORDER_DOES_NOT_EXIST'],
3007: STRING_KEYS['3007_STATEFUL_ORDER_COLLATERALIZATION_CHECK_FAILED'],
3008: STRING_KEYS['3008_STATEFUL_ORDER_PREVIOUSLY_CANCELLED'],
2000: '2000_FILL_OR_KILL_ORDER_COULD_NOT_BE_FULLY_FILLED',
2001: '2001_REDUCE_ONLY_WOULD_INCREASE_POSITION_SIZE',
2002: '2002_REDUCE_ONLY_WOULD_CHANGE_POSITION_SIDE',
2003: '2003_POST_ONLY_WOULD_CROSS_MAKER_ORDER',
3000: '3000_INVALID_ORDER_FLAGS',
3001: '3001_INVALID_STATEFUL_ORDER_GOOD_TIL_BLOCK_TIME',
3002: '3002_STATEFUL_ORDERS_CANNOT_REQUIRE_IMMEDIATE_EXECUTION',
3003: '3003_TIME_EXCEEDS_GOOD_TIL_BLOCK_TIME',
3004: '3004_GOOD_TIL_BLOCK_TIME_EXCEEDS_STATEFUL_ORDER_TIME_WINDOW',
3005: '3005_STATEFUL_ORDER_ALREADY_EXISTS',
3006: '3006_STATEFUL_ORDER_DOES_NOT_EXIST',
3007: '3007_STATEFUL_ORDER_COLLATERALIZATION_CHECK_FAILED',
3008: '3008_STATEFUL_ORDER_PREVIOUSLY_CANCELLED',
};
+12 -41
View File
@@ -1,49 +1,37 @@
import type { ReactNode } from 'react';
import type { StatusResponse } from '@0xsquid/sdk';
/** implemented in useNotificationTypes */
export enum NotificationType {
OrderStatusChanged = 'OrderStatusChanged',
SquidTransfer = 'SquidTransfer',
}
export enum NotificationComponentType {}
export type NotificationId = string | number;
export type NotificationId = string | number
export type NotificationTypeConfig<
_NotificationId extends NotificationId = string,
NotificationUpdateKey = any
> = {
export type NotificationTypeConfig<_NotificationId extends NotificationId = string, NotificationUpdateKey = any> = {
type: NotificationType;
/** React hook to trigger notifications based on app state */
/** React hook to trigger notifications based on app state */
useTrigger: (_: {
trigger: ({
id,
displayData,
updateKey,
isNew,
}: {
trigger: (
/** Unique ID for the triggered notification */
id: _NotificationId;
id: _NotificationId,
/** Display data for the triggered notification */
displayData: NotificationDisplayData;
displayData: NotificationDisplayData,
/**
* JSON-serializable key.
* Re-triggers the notification if passed a different value from the last trigger() call (even from a previous browser session).
* Suggested usage: data dependency array
*/
updateKey?: NotificationUpdateKey;
updateKey?: NotificationUpdateKey,
/**
* @param true (default): Notification initialized with status NotificationStatus.Triggered
* @param false: Notification initialized with status NotificationStatus.Cleared
*/
isNew?: boolean;
}) => void;
isNew?: boolean,
) => void;
lastUpdated: number;
}) => void;
@@ -70,10 +58,7 @@ export enum NotificationStatus {
}
/** Notification state. Serialized and cached into localStorage. */
export type Notification<
_NotificationId extends NotificationId = string,
NotificationUpdateKey = any
> = {
export type Notification<_NotificationId extends NotificationId = string, NotificationUpdateKey = any> = {
id: _NotificationId;
type: NotificationType;
status: NotificationStatus;
@@ -85,15 +70,11 @@ export type Notifications = Record<NotificationId, Notification<any>>;
/** Notification display data derived from app state at runtime. */
export type NotificationDisplayData = {
icon?: React.ReactElement<any, 'svg'>;
icon?: React.ReactNode;
title?: string;
description?: ReactNode;
customContent?: ReactNode;
customMenuContent?: ReactNode;
description?: string;
actionDescription?: string;
@@ -120,13 +101,3 @@ export type NotificationDisplayData = {
*/
toastDuration?: number;
};
// Notification types
export type TransferNotifcation = {
txHash: string;
toChainId?: string;
fromChainId?: string;
toAmount?: number;
triggeredAt?: number;
status?: StatusResponse;
};
+1 -13
View File
@@ -32,18 +32,6 @@ export enum WalletConnectionType {
WalletConnect2 = 'walletConnect2',
}
export enum WalletErrorType {
// General
ChainMismatch,
UserCanceled,
// Non-Deterministic
NonDeterministicWallet,
// Misc
Unknown,
}
type WalletConnectionTypeConfig = {
name: string;
wagmiConnectorId?: string;
@@ -337,7 +325,7 @@ export const SIGN_TYPED_DATA = {
export type PrivateInformation = ReturnType<typeof onboarding.deriveHDKeyFromEthereumSignature>;
export type EvmAddress = `0x${string}`;
export type EthereumAddress = `0x${string}`;
export type DydxAddress = `dydx${string}`;
export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
+2 -7
View File
@@ -7,7 +7,7 @@ import { formatUnits } from 'viem';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { EvmAddress } from '@/constants/wallets';
import { EthereumAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils';
import { MustBigNumber } from '@/lib/numbers';
@@ -16,7 +16,6 @@ import { getSelectedNetwork } from '@/state/appSelectors';
import { useAccounts } from './useAccounts';
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
import { usePollUSDCBalance } from './usePollUSDCBalance';
type UseAccountBalanceProps = {
// Token Items
@@ -59,7 +58,7 @@ export const useAccountBalance = ({
token:
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS
? undefined
: (addressOrDenom as EvmAddress),
: (addressOrDenom as EthereumAddress),
watch: true,
});
@@ -97,13 +96,9 @@ export const useAccountBalance = ({
.div(QUANTUM_MULTIPLIER)
.toNumber();
const usdcCoinBalance = usePollUSDCBalance({ dydxAddress });
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber();
return {
balance,
nativeTokenBalance,
usdcBalance,
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
};
+3 -3
View File
@@ -6,7 +6,7 @@ import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-clien
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
import { DydxAddress, EthereumAddress, PrivateInformation } from '@/constants/wallets';
import {
setOnboardingState,
@@ -82,7 +82,7 @@ const useAccountsContext = () => {
evmAddress,
dydxAddress,
}: {
evmAddress: EvmAddress;
evmAddress: EthereumAddress;
dydxAddress?: DydxAddress;
}) => {
saveEvmDerivedAddresses({
@@ -192,7 +192,7 @@ const useAccountsContext = () => {
} catch (error) {
log('useAccounts/setLocalDydxWallet', error);
}
} else if (evmAddress) {
} else if (evmAddress && signerWagmi) {
if (!localDydxWallet) {
dispatch(setOnboardingState(OnboardingState.WalletConnected));
+4 -7
View File
@@ -57,8 +57,8 @@ const useDydxClientContext = () => {
networkConfig?.indexerSocketUrl &&
networkConfig?.validatorUrl
) {
try {
const initializedClient = await CompositeClient.connect(
setCompositeClient(
await CompositeClient.connect(
new Network(
selectedNetwork,
new IndexerConfig(networkConfig.indexerUrl, networkConfig.indexerSocketUrl),
@@ -67,11 +67,8 @@ const useDydxClientContext = () => {
broadcastTimeoutMs: 60_000,
})
)
);
setCompositeClient(initializedClient);
} catch (error) {
log('useDydxClient/initializeCompositeClient', error);
}
)
);
} else {
setCompositeClient(undefined);
}
-98
View File
@@ -1,98 +0,0 @@
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'react-query';
import type { StatusResponse } from '@0xsquid/sdk';
import { LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications';
import { useAccounts } from '@/hooks/useAccounts';
import { useSquid } from '@/hooks/useSquid';
import { useLocalStorage } from './useLocalStorage';
const LocalNotificationsContext = createContext<
ReturnType<typeof useLocalNotificationsContext> | undefined
>(undefined);
LocalNotificationsContext.displayName = 'LocalNotifications';
export const LocalNotificationsProvider = ({ ...props }) => (
<LocalNotificationsContext.Provider value={useLocalNotificationsContext()} {...props} />
);
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
const useLocalNotificationsContext = () => {
// transfer notifications
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
[key: `dydx${string}`]: TransferNotifcation[];
}>({
key: LocalStorageKey.TransferNotifications,
defaultValue: {},
});
const { dydxAddress } = useAccounts();
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
const setTransferNotifications = useCallback(
(notifications: TransferNotifcation[]) => {
if (!dydxAddress) return;
const updatedNotifications = { ...allTransferNotifications };
updatedNotifications[dydxAddress] = notifications;
setAllTransferNotifications(updatedNotifications);
},
[setAllTransferNotifications, dydxAddress]
);
const addTransferNotification = useCallback(
(notification: TransferNotifcation) =>
setTransferNotifications([...transferNotifications, notification]),
[transferNotifications]
);
const squid = useSquid();
const { data: transferStatuses } = useQuery({
queryKey: ['getTransactionStatus', transferNotifications],
queryFn: async () => {
const statuses: { [key: string]: StatusResponse } = {};
for (const {
txHash,
toChainId,
fromChainId,
status: currentStatus,
} of transferNotifications) {
try {
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
if (status) statuses[txHash] = status;
} catch (error) {
console.error(error);
}
}
return statuses;
},
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
});
useEffect(() => {
if (!transferStatuses) return;
const newTransferNotifications = transferNotifications.map((notification) => {
const status = transferStatuses[notification.txHash];
if (!status) return notification;
return {
...notification,
status,
};
});
setTransferNotifications(newTransferNotifications);
}, [transferStatuses]);
return {
transferNotifications,
addTransferNotification,
};
};
+31 -85
View File
@@ -1,26 +1,18 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useEffect, useMemo } from 'react';
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import { groupBy } from 'lodash';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { AbacusOrderStatus, ORDER_SIDES, ORDER_STATUS_STRINGS } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
import { ORDER_SIDE_STRINGS } from '@/constants/trade';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output';
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
import { TransferStatusToast } from '@/views/TransferStatus';
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade';
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
import { useStringGetter } from './useStringGetter';
export const notificationTypes = [
@@ -58,33 +50,34 @@ export const notificationTypes = [
}
: undefined);
if (order) console.log(order);
trigger({
id: order.id,
displayData: {
icon: <AssetIcon symbol={order.marketId.split('-')[0]} />, //<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />,
title: `${stringGetter({
key: order.resources.typeStringKey ?? '',
})} ${
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
}`,
description: `${stringGetter({
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
})} ${order.size} ${order.marketId} @ $${order.price}`,
actionDescription: 'View Order',
actionAltText: 'View this order in the Orders tab or the Notifications menu.',
toastSensitivity:
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
toastDuration: Infinity,
},
updateKey: [order.status.name, order.size],
isNew: !order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated,
});
if (order)
trigger(
order.id,
{
icon: (
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
),
title: `${stringGetter({
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey,
})} ${
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
}`,
description: `${stringGetter({
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
})} ${order.size} ${order.marketId} @ $${order.price}`,
actionDescription: 'View Order',
actionAltText: 'View this order in the Orders tab or the Notifications menu.',
toastSensitivity:
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
toastDuration: 5000,
},
[order.status.name, order.size],
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated
);
}
}, [orderIds, stringGetter]);
}, [orderIds]);
},
useNotificationAction: () => {
@@ -100,51 +93,4 @@ export const notificationTypes = [
};
},
} as NotificationTypeConfig<string, [string, number]>,
{
type: NotificationType.SquidTransfer,
useTrigger: ({ trigger }) => {
const stringGetter = useStringGetter();
const { transferNotifications } = useLocalNotifications();
const getTitleStringKey = useCallback((type: 'deposit' | 'withdraw', finished: boolean) => {
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
if (type === 'withdraw' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
return STRING_KEYS.WITHDRAW;
}, []);
useEffect(() => {
for (const transfer of transferNotifications) {
const { toChainId, status, txHash, toAmount } = transfer;
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw';
trigger({
id: txHash,
displayData: {
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
title: stringGetter({ key: getTitleStringKey(type, finished) }),
// TODO: confirm with design what the description should be
description: (
<>
<span>{type === 'deposit' ? 'Deposit of ' : 'Withdraw of'}</span>
<Output type={OutputType.Fiat} value={toAmount} />
</>
),
customContent: (
<TransferStatusToast
toAmount={transfer.toAmount}
triggeredAt={transfer.triggeredAt}
status={transfer.status}
/>
),
customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />,
toastSensitivity: 'foreground',
},
updateKey: [],
});
}
}, [transferNotifications, stringGetter]);
},
} as NotificationTypeConfig<string, []>,
] satisfies NotificationTypeConfig[];
+25 -24
View File
@@ -92,7 +92,7 @@ const useNotificationsContext = () => {
for (const { type, useTrigger } of notificationTypes)
useTrigger({
trigger: useCallback(
({ id, displayData, updateKey, isNew = true }) => {
(id, displayData, updateKey, isNew = true) => {
const key = getKey({ type, id });
const notification = notifications[key];
@@ -106,10 +106,7 @@ const useNotificationsContext = () => {
updateKey,
} as Notification);
updateStatus(
notification,
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
);
updateStatus(notification, isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared);
}
// updateKey changed - update existing notification
@@ -144,9 +141,7 @@ const useNotificationsContext = () => {
// Push notifications
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
key: LocalStorageKey.PushNotificationsEnabled,
defaultValue: Boolean(
globalThis.Notification && globalThis.Notification.permission === 'granted'
),
defaultValue: Boolean(globalThis.Notification && globalThis.Notification.permission === 'granted'),
});
const [isEnablingPush, setIsEnablingPush] = useState(false);
@@ -182,23 +177,28 @@ const useNotificationsContext = () => {
const iconUrl =
displayData.icon && (await renderSvgToDataUrl(displayData.icon).catch(() => undefined));
if (displayData.title) {
const pushNotification = new globalThis.Notification(displayData.title, {
renotify: true,
tag: getKey(notification),
data: notification,
body: displayData.description ?? '',
icon: (iconUrl as string) ?? '/favicon.svg',
badge: (iconUrl as string) ?? '/favicon.svg',
image: (iconUrl as string) ?? '/favicon.svg',
requireInteraction: displayData.toastDuration === Infinity,
});
const pushNotification = new globalThis.Notification(displayData.title, {
renotify: true,
tag: getKey(notification),
data: notification,
body: displayData.description,
icon: iconUrl ?? '/favicon.svg',
badge: iconUrl ?? '/favicon.svg',
image: iconUrl ?? '/favicon.svg',
vibrate: displayData.toastSensitivity === 'foreground',
requireInteraction: displayData.toastDuration === Infinity,
// actions: [
// {
// action: displayData.actionDescription,
// title: displayData.actionDescription,
// }
// ].slice(0, globalThis.Notification.maxActions),
});
pushNotification.addEventListener('click', () => {
onNotificationAction(notification);
markSeen(notification);
});
}
pushNotification.addEventListener('click', () => {
onNotificationAction(notification);
markSeen(notification);
});
}
setPushNotificationsLastUpdated(Date.now());
@@ -236,3 +236,4 @@ const useNotificationsContext = () => {
setIsMenuOpen,
};
};
// }
@@ -1,13 +1,17 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { Squid } from '@0xsquid/sdk';
import { CLIENT_NETWORK_CONFIGS, DydxV4Network, isDydxV4Network } from '@/constants/networks';
import { DydxV4Network, isDydxV4Network } from '@/constants/networks';
import { getSelectedNetwork } from '@/state/appSelectors';
export const NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const SQUID_BASE_URL: Record<DydxV4Network, string | undefined> = {
[DydxV4Network.V4Testnet2]: 'https://squid-api-git-feat-cosmos-maintestnet-0xsquid.vercel.app',
[DydxV4Network.V4Staging]: undefined,
[DydxV4Network.V4Local]: undefined,
[DydxV4Network.V4Mainnet]: 'https://api.0xsquid.com',
};
const useSquidContext = () => {
const selectedNetwork = useSelector(getSelectedNetwork);
@@ -23,7 +27,7 @@ const useSquidContext = () => {
const squid = useMemo(
() =>
isDydxV4Network(selectedNetwork)
? new Squid({ baseUrl: CLIENT_NETWORK_CONFIGS[selectedNetwork]?.endpoints['0xsquid'] })
? new Squid({ baseUrl: SQUID_BASE_URL[selectedNetwork as DydxV4Network] })
: undefined,
[selectedNetwork]
);
+1 -2
View File
@@ -551,8 +551,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
onSuccess?.();
} catch (error) {
const errorCode: number | undefined = error?.code;
const errorStringKey = errorCode ? ORDER_ERROR_CODE_MAP[errorCode] : undefined;
const errorStringKey = error?.code && ORDER_ERROR_CODE_MAP[error.code as number];
onError?.({ errorStringKey });
log('useSubaccount/placeOrder', error, {
+10 -19
View File
@@ -4,20 +4,17 @@ import { LocalStorageKey } from '@/constants/localStorage';
import {
type DydxAddress,
type EvmAddress,
type EthereumAddress,
WalletConnectionType,
WalletType,
wallets,
DYDX_CHAIN_INFO,
} from '@/constants/wallets';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import {
useConnect as useConnectWagmi,
useAccount as useAccountWagmi,
useDisconnect as useDisconnectWagmi,
usePublicClient as usePublicClientWagmi,
useWalletClient as useWalletClientWagmi,
} from 'wagmi';
import {
@@ -29,21 +26,21 @@ import {
} from 'graz';
import { resolveWagmiConnector } from '@/lib/wagmi';
import { getWalletConnection, parseWalletError } from '@/lib/wallet';
import { getWalletConnection } from '@/lib/wallet';
import { log } from '@/lib/telemetry';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { useStringGetter } from './useStringGetter';
export const useWalletConnection = () => {
const stringGetter = useStringGetter();
// EVM wallet connection
const [evmAddress, saveEvmAddress] = useLocalStorage<EvmAddress | undefined>({
const [evmAddress, saveEvmAddress] = useLocalStorage<EthereumAddress | undefined>({
key: LocalStorageKey.EvmAddress,
defaultValue: undefined,
});
const { address: evmAddressWagmi, isConnected: isConnectedWagmi } = useAccountWagmi();
const publicClientWagmi = usePublicClientWagmi();
const { data: signerWagmi } = useWalletClientWagmi();
const { disconnectAsync: disconnectWagmi } = useDisconnectWagmi();
@@ -132,6 +129,8 @@ export const useWalletConnection = () => {
}
}
} catch (error) {
log('useWalletConnection/connectWallet', error);
throw Object.assign(
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
{
@@ -159,7 +158,7 @@ export const useWalletConnection = () => {
// Wallet selection
const [selectedWalletType, setSelectedWalletType] = useState<WalletType | undefined>(walletType);
const [selectedWalletError, setSelectedWalletError] = useState<string>();
const [selectedWalletError, setSelectedWalletError] = useState<Error>();
useEffect(() => {
(async () => {
@@ -174,15 +173,8 @@ export const useWalletConnection = () => {
setWalletType(walletType);
setWalletConnectionType(walletConnectionType);
} catch (error) {
const { walletErrorType, message } = parseWalletError({
error,
stringGetter,
});
if (message) {
log('useWalletConnection/connectWallet', error, { walletErrorType });
setSelectedWalletError(message);
}
log('useWalletConnection/connectWallet', error);
setSelectedWalletError(error);
}
} else {
setWalletType(undefined);
@@ -216,8 +208,7 @@ export const useWalletConnection = () => {
evmAddress,
evmAddressWagmi,
signerWagmi,
publicClientWagmi,
// Wallet connection (Cosmos)
dydxAddress,
dydxAddressGraz,
+1 -1
View File
@@ -54,7 +54,7 @@ button:disabled {
cursor: pointer;
}
:focus-visible{
:focus {
outline: var(--color-accent) 1px solid;
outline-offset: -1px;
/* outline-offset: -2px; */
-1
View File
@@ -41,7 +41,6 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
slotIcon={displayData.icon}
slotTitle={displayData.title}
slotDescription={displayData.description}
slotCustomContent={displayData.customContent}
slotAction={
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
{displayData.actionDescription}
+1 -3
View File
@@ -36,7 +36,6 @@ import AbacusStateNotifier from './stateNotification';
import AbacusLocalizer from './localizer';
import AbacusFormatter from './formatter';
import AbacusThreading from './threading';
import AbacusTracking from './tracking';
import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem';
import { LocaleSeparators } from '../numbers';
class AbacusStateManager {
@@ -60,7 +59,7 @@ class AbacusStateManager {
new AbacusRest(),
this.websocket,
new AbacusChainTransaction(),
new AbacusTracking(),
null,
new AbacusThreading(),
new CoroutineTimer(),
new AbacusFileSystem()
@@ -154,7 +153,6 @@ class AbacusStateManager {
// ------ Set Data ------ //
setStore = (store: RootStore) => {
this.store = store;
this.stateNotifier.setStore(store);
};
-9
View File
@@ -1,9 +0,0 @@
import type { AbacusTrackingProtocol, Nullable } from '@/constants/abacus';
class AbacusTracking implements AbacusTrackingProtocol {
log(event: string, data: Nullable<string>) {
console.log({ event, data });
}
}
export default AbacusTracking;
+2 -4
View File
@@ -21,10 +21,8 @@ const applyComputedStyles = (html: string) => {
const toDataUrl = (bytes: string, type = 'image/svg+xml') =>
new Promise<string | ArrayBuffer | null>((resolve, reject) => {
Object.assign(new FileReader(), {
onload: (e: {
target: { result: string | ArrayBuffer | PromiseLike<string | ArrayBuffer | null> | null };
}) => resolve(e.target.result),
onerror: (e: { target: { error: Error } }) => reject(e.target.error),
onload: (e) => resolve(e.target.result),
onerror: (e) => reject(e.target.error),
}).readAsDataURL(new File([bytes], '', { type }));
});
-6
View File
@@ -72,9 +72,3 @@ export const getTimeTillNextUnit = (unit: 'minute' | 'hour' | 'day') => {
};
export const getTimeString = (time: number) => time.toString().padStart(2, '0');
export const formatSeconds = (seconds: number) => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${getTimeString(minutes)}:${getTimeString(remainingSeconds)}`;
}
-66
View File
@@ -1,10 +1,7 @@
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
import {
type WalletConnection,
wallets,
WalletConnectionType,
WalletErrorType,
WalletType,
} from '@/constants/wallets';
@@ -71,66 +68,3 @@ export const getWalletConnection = ({
}
}
};
export const getWalletErrorType = ({ error }: { error: Error }) => {
const { message } = error;
const messageLower = message.toLowerCase();
// General - Cancelled
if (
messageLower.includes('connection request reset') ||
messageLower.includes('rejected') ||
messageLower.includes('reject') ||
messageLower.includes('cancelled') ||
messageLower.includes('canceled') ||
messageLower.includes('user denied')
) {
return WalletErrorType.UserCanceled;
}
if (messageLower.includes('chain mismatch')) {
return WalletErrorType.ChainMismatch;
}
// ImToken - User canceled
if (messageLower.includes('用户取消了操作')) {
return WalletErrorType.UserCanceled;
}
if (messageLower.includes('does not support deterministic signing')) {
return WalletErrorType.NonDeterministicWallet;
}
return WalletErrorType.Unknown;
};
export const parseWalletError = ({
error,
stringGetter,
}: {
error: Error;
stringGetter: StringGetterFunction;
}) => {
const walletErrorType = getWalletErrorType({ error });
let message;
switch (walletErrorType) {
case WalletErrorType.ChainMismatch:
case WalletErrorType.UserCanceled: {
break;
}
default: {
message = stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: {
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
},
});
}
}
return {
walletErrorType,
message,
};
};
+1 -1
View File
@@ -49,7 +49,7 @@ export const DYDXBalancePanel = () => {
size={ButtonSize.Small}
onClick={() => dispatch(openDialog({ type: DialogTypes.Receive }))}
>
{stringGetter({ key: STRING_KEYS.RECEIVE })}
{stringGetter({ key: STRING_KEYS.EXCHANGE_RECEIVED })}
</Styled.ReceiveButton>
<Button
slotLeft={<Icon iconName={IconName.Send} />}
+1 -1
View File
@@ -214,7 +214,7 @@ export const popoverMixins = {
/* &:hover, */
/* :not(:has(* > [data-highlighted])) > &:hover, */
&[data-radix-collection-item]:hover, // @radix-ui/react-navigation-menu
/* &:focus-visible, */
/* &:focus, */
&[aria-selected="true"], // cmdk
&[data-highlighted] // @radix-ui
{
+1 -1
View File
@@ -304,7 +304,7 @@ Styled.Popover = styled(Popover)`
}
`} 0.2s;
}
&:focus-visible {
&:focus {
outline: none;
}
`;
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState, useCallback } from 'react';
import { useInterval } from '@/hooks';
import { getTimeTillNextUnit, formatSeconds } from '@/lib/timeUtils';
import { getTimeTillNextUnit, getTimeString } from '@/lib/timeUtils';
import { Output, OutputType } from '@/components/Output';
export const NextFundingTimer = () => {
@@ -18,7 +18,7 @@ export const NextFundingTimer = () => {
type={OutputType.Text}
value={
secondsLeft !== undefined
? formatSeconds(secondsLeft)
? `${getTimeString(Math.floor(secondsLeft / 60))}:${getTimeString(secondsLeft % 60)}`
: undefined
}
/>
-156
View File
@@ -1,156 +0,0 @@
import { useCallback, useState, useMemo } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { Root, Trigger, Content } from '@radix-ui/react-collapsible';
import { StatusResponse } from '@0xsquid/sdk';
import { useInterval, useStringGetter } from '@/hooks';
import { STRING_KEYS } from '@/constants/localization';
import { formatSeconds } from '@/lib/timeUtils';
import { Output, OutputType } from '@/components/Output';
import { WithReceipt } from '@/components/WithReceipt';
import { Icon, IconName } from '@/components/Icon';
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
import { LoadingDots } from '@/components/Loading/LoadingDots';
import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
toAmount?: number;
triggeredAt?: number;
status?: StatusResponse;
};
export const TransferStatusToast = ({
toAmount,
triggeredAt = Date.now(),
status,
}: ElementProps) => {
const stringGetter = useStringGetter();
const [open, setOpen] = useState<boolean>(false);
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
const type = useMemo(
() => (status?.toChain?.chainData?.chainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal'),
[status]
);
const updateSecondsLeft = useCallback(() => {
const fromChainEta = (status?.fromChain?.chainData?.estimatedRouteDuration || 0) * 1000;
const toChainEta = (status?.toChain?.chainData?.estimatedRouteDuration || 0) * 1000;
setSecondsLeft(Math.floor((triggeredAt + fromChainEta + toChainEta - Date.now()) / 1000));
}, [status]);
useInterval({ callback: updateSecondsLeft });
if (!status) return <LoadingDots size={3} />;
return (
<Styled.Root open={open} onOpenChange={setOpen}>
<WithReceipt
hideReceipt={!open}
side="bottom"
slotReceipt={
<Styled.Receipt>
<TransferStatusSteps status={status} />
</Styled.Receipt>
}
>
<Styled.BridgingStatus>
<Styled.Status>
{stringGetter({
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_STATUS : STRING_KEYS.WITHDRAW_STATUS,
params: {
AMOUNT_USD: <Styled.InlineOutput type={OutputType.Fiat} value={toAmount} />,
ESTIMATED_DURATION: (
<Styled.InlineOutput
type={OutputType.Text}
value={formatSeconds(Math.max(secondsLeft || 0, 0))}
/>
),
},
})}
</Styled.Status>
<Styled.Trigger>
<Styled.TriggerIcon>
<Icon iconName={IconName.Caret} />
</Styled.TriggerIcon>
{stringGetter({ key: open ? STRING_KEYS.HIDE_DETAILS : STRING_KEYS.VIEW_DETAILS })}
</Styled.Trigger>
</Styled.BridgingStatus>
</WithReceipt>
</Styled.Root>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Root = styled(Root)`
margin: 0.5rem -1rem -1rem -1rem;
color: var(--color-text-0);
font: var(--font-small-book);
`;
Styled.Receipt = styled.div`
padding: 0 1rem;
`;
Styled.BridgingStatus = styled.div`
${layoutMixins.flexColumn};
background-color: var(--color-layer-3);
gap: 0.5rem;
padding: 0 1rem 1rem 1rem;
border-radius: 0.5rem;
`;
Styled.Status = styled.div`
color: var(--color-text-0);
font-size: 0.875rem;
`;
Styled.InlineOutput = styled(Output)`
display: inline-block;
color: var(--color-text-1);
`;
Styled.Content = styled(Content)`
${layoutMixins.flexColumn};
gap: 0.5rem;
padding: 1rem;
`;
Styled.Step = styled.div`
${layoutMixins.row};
gap: 0.5rem;
`;
Styled.Trigger = styled(Trigger)`
display: flex;
align-items: center;
gap: 0.5em;
color: var(--color-accent);
user-select: none;
&:focus {
outline: none;
}
`;
Styled.TriggerIcon = styled.span`
width: 0.75em;
display: inline-flex;
transition: transform 0.3s var(--ease-out-expo);
${Styled.Trigger}[data-state='open'] & {
rotate: -0.5turn;
}
`;
-175
View File
@@ -1,175 +0,0 @@
import { useMemo } from 'react';
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
import { StatusResponse } from '@0xsquid/sdk';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { useStringGetter } from '@/hooks';
import { Link } from '@/components/Link';
import { Icon, IconName } from '@/components/Icon';
import { LoadingDots } from '@/components/Loading/LoadingDots';
import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
import { layoutMixins } from '@/styles/layoutMixins';
import { STRING_KEYS } from '@/constants/localization';
type ElementProps = {
status?: StatusResponse;
};
enum TransferStatusStep {
FromChain,
Bridge,
ToChain,
Complete,
}
export const TransferStatusSteps = ({ status }: ElementProps) => {
const stringGetter = useStringGetter();
const { currentStep, steps, type } = useMemo(() => {
const routeStatus = status?.routeStatus;
const fromChain = status?.fromChain?.chainData?.chainId;
const toChain = status?.toChain?.chainData?.chainId;
const type = toChain === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal';
const steps = [
{
label: stringGetter({
key:
type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
}),
step: TransferStatusStep.FromChain,
link: status?.fromChain?.transactionUrl,
},
{
label: stringGetter({ key: STRING_KEYS.BRIDGING_TOKENS }),
step: TransferStatusStep.Bridge,
link: status?.axelarTransactionUrl,
},
{
label: stringGetter({
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_TO_CHAIN : STRING_KEYS.WITHDRAW_TO_CHAIN,
params: {
CHAIN: status?.toChain?.chainData?.chainName,
},
}),
step: TransferStatusStep.ToChain,
link: status?.toChain?.transactionUrl,
},
];
const currentStatus = routeStatus[routeStatus?.length - 1];
let currentStep = TransferStatusStep.Bridge;
if (!routeStatus?.length) {
currentStep = TransferStatusStep.FromChain;
} else if (currentStatus.chainId === toChain) {
currentStep =
currentStatus.status !== 'success'
? TransferStatusStep.ToChain
: TransferStatusStep.Complete;
} else if (currentStatus.chainId === fromChain && currentStatus.status !== 'success') {
currentStep = TransferStatusStep.FromChain;
}
return {
currentStep,
steps,
type,
};
}, [status, stringGetter]);
if (!status) return <LoadingDots size={3} />;
return (
<Styled.BridgingStatus>
{steps.map((step) => (
<Styled.Step key={step.step}>
<Styled.row>
{step.step === currentStep ? (
<Styled.Icon>
<Styled.Spinner />
</Styled.Icon>
) : step.step < currentStep ? (
<Styled.Icon state="complete">
<Icon iconName={IconName.Check} />
</Styled.Icon>
) : (
<Styled.Icon state="default">{step.step + 1}</Styled.Icon>
)}
{step.link && currentStep >= step.step ? (
<Link href={step.link}>
<Styled.Label highlighted={currentStep >= step.step}>
{step.label}
<Icon iconName={IconName.LinkOut} />
</Styled.Label>
</Link>
) : (
<Styled.Label highlighted={currentStep >= step.step}>{step.label}</Styled.Label>
)}
</Styled.row>
</Styled.Step>
))}
</Styled.BridgingStatus>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.BridgingStatus = styled.div`
${layoutMixins.flexColumn};
gap: 1rem;
padding: 1rem 0;
`;
Styled.Step = styled.div`
${layoutMixins.spacedRow};
`;
Styled.row = styled.div`
${layoutMixins.inlineRow};
gap: 0.5rem;
`;
Styled.Icon = styled.div<{ state: 'complete' | 'default' }>`
display: flex;
align-items: center;
justify-content: center;
height: 2rem;
width: 2rem;
border-radius: 50%;
background-color: var(--color-layer-3);
${({ state }) =>
({
['complete']: css`
color: var(--color-positive);
`,
['default']: css`
color: var(--color-text-0);
`,
}[state])}
`;
Styled.Spinner = styled(LoadingSpinner)`
--spinner-width: 1.25rem;
color: var(--color-accent);
`;
Styled.Label = styled(Styled.row)<{ highlighted?: boolean }>`
${({ highlighted }) =>
highlighted
? css`
color: var(--color-text-2);
`
: css`
color: var(--color-text-0);
`}
`;
@@ -7,7 +7,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { AbacusOrderStatus, AbacusOrderTypes, type Nullable } from '@/constants/abacus';
import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS, StringKey } from '@/constants/localization';
import { STRING_KEYS } from '@/constants/localization';
import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button';
@@ -38,9 +38,6 @@ type ElementProps = {
setIsOpen: (open: boolean) => void;
};
const isStringKey = (key: Nullable<string>): key is StringKey =>
key != null && STRING_KEYS.hasOwnProperty(key);
export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
@@ -50,7 +47,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
const { cancelOrder } = useSubaccount();
const {
asset,
asset = {},
cancelReason,
createdAtMilliseconds,
expiresAtMilliseconds,
@@ -60,7 +57,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
price,
reduceOnly,
totalFilled,
resources,
resources = {},
size,
status,
stepSizeDecimals,
@@ -112,9 +109,9 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
<Styled.Row>
<Styled.StatusIcon iconName={statusIcon} color={statusIconColor} />
<Styled.Status>
{isStringKey(statusStringKey)
{statusStringKey
? stringGetter({ key: statusStringKey })
: isStringKey(resources.statusStringKey)
: resources.statusStringKey
? stringGetter({ key: resources.statusStringKey })
: undefined}
</Styled.Status>
@@ -124,9 +121,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
{
key: 'cancel-reason',
label: stringGetter({ key: STRING_KEYS.CANCEL_REASON }),
value: isStringKey(cancelReason)
? stringGetter({ key: STRING_KEYS[cancelReason] })
: undefined,
value: cancelReason ? stringGetter({ key: STRING_KEYS[cancelReason] }) : undefined,
},
{
key: 'amount',
@@ -32,12 +32,8 @@ export const ChooseWallet = () => {
<>
{selectedWalletType && selectedWalletError && (
<Styled.AlertMessage type={AlertType.Error}>
{
<h4>
Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.
</h4>
}
{selectedWalletError}
{<h4>Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.</h4>}
<code>{selectedWalletError.message}</code>
</Styled.AlertMessage>
)}
@@ -25,12 +25,11 @@ import { Switch } from '@/components/Switch';
import { WithReceipt } from '@/components/WithReceipt';
import { WithTooltip } from '@/components/WithTooltip';
import { getSelectedNetwork } from '@/state/appSelectors';
import { track } from '@/lib/analytics';
import { isTruthy } from '@/lib/isTruthy';
import { log } from '@/lib/telemetry';
import { parseWalletError } from '@/lib/wallet';
import { getSelectedNetwork } from '@/state/appSelectors';
type ElementProps = {
status: EvmDerivedAccountStatus;
@@ -67,12 +66,8 @@ export const GenerateKeys = ({
try {
await matchNetwork?.();
} catch (error) {
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
if (message) {
log('GenerateKeys/switchNetwork', error, { walletErrorType });
setError(message);
}
setError(error.message);
log('GenerateKeys/switchNetwork', error);
}
};
@@ -129,12 +124,7 @@ export const GenerateKeys = ({
}
}
} catch (error) {
const { message } = parseWalletError({ error, stringGetter });
if (message) {
track(AnalyticsEvent.OnboardingWalletIsNonDeterministic);
setError(message);
}
log('GenerateKeys/getSubaccounts', error);
}
await setWalletFromEvmSignature(signature);
@@ -150,48 +140,48 @@ export const GenerateKeys = ({
setStatus(EvmDerivedAccountStatus.Derived);
} catch (error) {
setStatus(EvmDerivedAccountStatus.NotDerived);
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
setError(error?.message);
if (message) {
setError(message);
log('GenerateKeys/deriveKeys', error, { walletErrorType });
}
log('GenerateKeys/deriveKeys', error);
throw error;
}
};
return (
<>
<Styled.StatusCardsContainer>
{[
{
status: EvmDerivedAccountStatus.Deriving,
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
},
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
status: EvmDerivedAccountStatus.EnsuringDeterminism,
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
},
]
.filter(isTruthy)
.map((step) => (
<Styled.StatusCard key={step.status} active={status === step.status}>
{status < step.status ? (
<LoadingSpinner disabled />
) : status === step.status ? (
<LoadingSpinner />
) : (
<Styled.GreenCheckCircle />
)}
<div>
<h3>{step.title}</h3>
<p>{step.description}</p>
</div>
</Styled.StatusCard>
))}
</Styled.StatusCardsContainer>
{isMobile && (
<Styled.MobileStatusCards>
{[
{
status: EvmDerivedAccountStatus.Deriving,
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
},
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
status: EvmDerivedAccountStatus.EnsuringDeterminism,
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
},
]
.filter(isTruthy)
.map((step) => (
<Styled.StatusCard key={step.status} active={status === step.status}>
{status < step.status ? (
<LoadingSpinner disabled />
) : status === step.status ? (
<LoadingSpinner />
) : (
<Styled.GreenCheckCircle />
)}
<div>
<h3>{step.title}</h3>
<p>{step.description}</p>
</div>
</Styled.StatusCard>
))}
</Styled.MobileStatusCards>
)}
<Styled.Footer>
<Styled.RememberMe htmlFor="remember-me">
<WithTooltip withIcon tooltip="remember-me">
@@ -261,7 +251,7 @@ export const GenerateKeys = ({
const Styled: Record<string, AnyStyledComponent> = {};
Styled.StatusCardsContainer = styled.div`
Styled.MobileStatusCards = styled.div`
display: grid;
margin-top: 1rem;
gap: 1rem;
@@ -3,21 +3,16 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type NumberFormatValues } from 'react-number-format';
import { shallowEqual, useSelector } from 'react-redux';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { parseUnits } from 'viem'
import { ethers } from 'ethers';
import erc20 from '@/abi/erc20.json';
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign } from '@/constants/numbers';
import type { EvmAddress } from '@/constants/wallets';
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
import { useAccountBalance } from '@/hooks/useAccountBalance';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { NATIVE_TOKEN_ADDRESS, useSquid } from '@/hooks/useSquid';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
@@ -37,7 +32,6 @@ import { getTransferInputs } from '@/state/inputsSelectors';
import abacusStateManager from '@/lib/abacus';
import { MustBigNumber } from '@/lib/numbers';
import { log } from '@/lib/telemetry';
import { ChainSelectMenu } from './ChainSelectMenu';
import { TokenSelectMenu } from './TokenSelectMenu';
@@ -54,17 +48,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false);
const { evmAddress, signerWagmi } = useAccounts();
const { publicClientWagmi } = useWalletConnection();
const { addTransferNotification } = useLocalNotifications();
const { signerWagmi } = useAccounts();
const {
requestPayload,
token,
chain: chainIdStr,
resources,
summary,
} = useSelector(getTransferInputs, shallowEqual) || {};
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
@@ -74,16 +64,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
[token, resources]
);
const sourceChain = useMemo(
() => (chainIdStr ? resources?.chainResources?.get(chainIdStr) : undefined),
[chainId, resources]
);
const [fromAmount, setFromAmount] = useState('');
const [slippage, setSlippage] = useState(0.01); // 1% slippage
const debouncedAmount = useDebounce<string>(fromAmount, 500);
// Async Data
const [transactionHash, setTransactionHash] = useState<string>();
const { balance, queryStatus, isQueryFetching } = useAccountBalance({
addressOrDenom: sourceToken?.address || undefined,
assetSymbol: sourceToken?.symbol || undefined,
@@ -170,39 +157,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}
}, [balance, setFromAmount]);
const validateTokenApproval = useCallback(async () => {
if (!signerWagmi || !publicClientWagmi) throw new Error('Missing signer');
if (!sourceToken?.address || !sourceToken.decimals) throw new Error('Missing source token address');
if (!sourceChain?.rpc) throw new Error('Missing source chain rpc');
if (!requestPayload?.targetAddress) throw new Error('Missing target address');
if (!requestPayload?.value) throw new Error('Missing transaction value');
if (sourceToken?.address === NATIVE_TOKEN_ADDRESS) return;
const allowance = await publicClientWagmi.readContract({
address: sourceToken.address as EvmAddress,
abi: erc20,
functionName: 'allowance',
args: [evmAddress as EvmAddress, requestPayload.targetAddress as EvmAddress]
});
const sourceAmountBN = parseUnits(debouncedAmount, sourceToken.decimals);
if (sourceAmountBN > (allowance as bigint)) {
const { request } = await publicClientWagmi.simulateContract({
account: evmAddress,
address: sourceToken.address as EvmAddress,
abi: erc20,
functionName: 'approve',
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
})
const approveTx = await signerWagmi.writeContract(request);
await publicClientWagmi.waitForTransactionReceipt({
hash: approveTx,
})
}
}, [signerWagmi, sourceToken, sourceChain, requestPayload, publicClientWagmi]);
const onSubmit = useCallback(
async (e: FormEvent) => {
try {
@@ -224,32 +178,27 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
setIsLoading(true);
await validateTokenApproval();
let tx = {
to: requestPayload.targetAddress as EvmAddress,
data: requestPayload.data as EvmAddress,
gasLimit: BigInt(requestPayload.gasLimit),
to: requestPayload.targetAddress as `0x${string}`,
data: requestPayload.data as `0x${string}`,
gasLimit: ethers.toBigInt(requestPayload.gasLimit),
value:
requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
requestPayload.routeType !== 'SEND' ? ethers.toBigInt(requestPayload.value) : undefined,
};
const txHash = await signerWagmi.sendTransaction(tx);
onDeposit?.();
if (txHash) {
addTransferNotification({
txHash: txHash,
setTransactionHash(txHash);
abacusStateManager.setTransferStatus({
hash: txHash,
toChainId: TESTNET_CHAIN_ID,
fromChainId: chainIdStr || undefined,
toAmount: summary?.usdcSize || undefined,
triggeredAt: Date.now(),
fromChainId: chainId?.toString(),
});
abacusStateManager.clearTransferInputValues();
setFromAmount('');
}
} catch (error) {
log('DepositForm/onSubmit', error);
setError(error);
} finally {
setIsLoading(false);
@@ -337,7 +286,17 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}
/>
</Styled.WithDetailsReceipt>
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
{errorMessage ? (
<AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>
) : (
transactionHash && (
<AlertMessage type={AlertType.Success}>
<Styled.TransactionInfo>
{stringGetter({ key: STRING_KEYS.DEPOSIT_IN_PROGRESS })}
</Styled.TransactionInfo>
</AlertMessage>
)
)}
<DepositButtonAndReceipt
isDisabled={isDisabled}
isLoading={isLoading}
@@ -12,7 +12,6 @@ import { STRING_KEYS } from '@/constants/localization';
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { useDebounce, useStringGetter, useSubaccount } from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
@@ -64,9 +63,9 @@ export const WithdrawForm = () => {
[token, resources]
);
const { addTransferNotification } = useLocalNotifications();
// Async Data
const [transactionHash, setTransactionHash] = useState<string>();
const debouncedAmountBN = MustBigNumber(debouncedAmount);
const withdrawAmountBN = MustBigNumber(withdrawAmount);
const freeCollateralBN = MustBigNumber(freeCollateral?.current);
@@ -134,12 +133,11 @@ export const WithdrawForm = () => {
if (txHash?.hash) {
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
addTransferNotification({
txHash: hash,
setTransactionHash(hash);
abacusStateManager.setTransferStatus({
hash,
fromChainId: TESTNET_CHAIN_ID,
toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(),
});
abacusStateManager.clearTransferInputValues();
setWithdrawAmount('');
@@ -150,7 +148,7 @@ export const WithdrawForm = () => {
setIsLoading(false);
}
},
[requestPayload, debouncedAmountBN, chainIdStr]
[setTransactionHash, requestPayload, debouncedAmountBN, chainIdStr]
);
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
@@ -293,7 +291,17 @@ export const WithdrawForm = () => {
}
/>
</Styled.WithDetailsReceipt>
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
{errorMessage ? (
<AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>
) : (
transactionHash && (
<AlertMessage type={AlertType.Success}>
<Styled.TransactionInfo>
{stringGetter({ key: STRING_KEYS.WITHDRAW_IN_PROGRESS })}
</Styled.TransactionInfo>
</AlertMessage>
)
)}
<WithdrawButtonAndReceipt
isDisabled={isDisabled}
isLoading={isLoading}
+1 -12
View File
@@ -84,7 +84,7 @@ export const TransferForm = ({
const { dydxAddress } = useAccounts();
const { address: recipientAddress, size } = useSelector(getTransferInputs, shallowEqual) || {};
const { transfer, simulateTransfer } = useSubaccount();
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const { nativeTokenBalance } = useAccountBalance();
const { selectedNetwork } = useSelectedNetwork();
// User Input
@@ -104,8 +104,6 @@ export const TransferForm = ({
.toNumber();
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
const showNotEnoughGasWarning = fees && asset === DydxChainAsset.USDC && usdcBalance < fees;
// BN
const amountBN = MustBigNumber(amount);
const balanceBN = MustBigNumber(balance);
@@ -361,15 +359,6 @@ export const TransferForm = ({
/>
</WithDetailsReceipt>
{showNotEnoughGasWarning && (
<AlertMessage type={AlertType.Warning}>
{stringGetter({
key: STRING_KEYS.TRANSFER_INSUFFICIENT_GAS,
params: { USDC_BALANCE: `(${usdcBalance})` },
})}
</AlertMessage>
)}
{error && <AlertMessage type={AlertType.Error}>{error.message}</AlertMessage>}
<Styled.Footer>
+11 -13
View File
@@ -1,12 +1,10 @@
import React, { useMemo } from 'react';
import { groupBy } from 'lodash';
import styled from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { groupBy } from 'lodash';
import { type Notification, NotificationStatus } from '@/constants/notifications';
import { useStringGetter } from '@/hooks';
import { useNotifications } from '@/hooks/useNotifications';
import { CloseIcon } from '@/icons';
import { Button } from '@/components/Button';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
@@ -15,7 +13,9 @@ import { DialogPlacement } from '@/components/Dialog';
import { Output, OutputType } from '@/components/Output';
import { IconButton } from '@/components/IconButton';
import { Toolbar } from '@/components/Toolbar';
import { CloseIcon } from '@/icons';
import styled from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
@@ -27,8 +27,6 @@ export const NotificationsMenu = ({
slotTrigger,
placement = DialogPlacement.Sidebar,
}: ElementProps) => {
const stringGetter = useStringGetter();
const {
notifications,
getDisplayData,
@@ -71,7 +69,7 @@ export const NotificationsMenu = ({
.map(([status, notifications]) => ({
group: status,
groupLabel: {
[NotificationStatus.Triggered]: stringGetter({ key: STRING_KEYS.NEW }),
[NotificationStatus.Triggered]: 'New',
// [NotificationStatus.Updated]: 'Updates',
[NotificationStatus.Seen]: 'Seen',
[NotificationStatus.Cleared]: 'Archived',
@@ -91,9 +89,9 @@ export const NotificationsMenu = ({
.map(({ notification, key, displayData }) => ({
value: key,
label: displayData.title ?? '',
description: displayData.customMenuContent || displayData.description,
slotBefore: !displayData.customMenuContent && displayData.icon,
slotAfter: !displayData.customMenuContent && (
description: displayData.description,
slotBefore: displayData.icon,
slotAfter: (
<>
<$Output
type={OutputType.RelativeTime}
@@ -137,7 +135,7 @@ export const NotificationsMenu = ({
isOpen={isMenuOpen || placement === DialogPlacement.Inline}
setIsOpen={setIsMenuOpen}
items={items}
title={stringGetter({ key: STRING_KEYS.NOTIFICATIONS })}
title="Notifications"
slotTrigger={
<$TriggerContainer>
{slotTrigger}
@@ -170,7 +168,7 @@ export const NotificationsMenu = ({
),
}}
>
{stringGetter({ key: STRING_KEYS.CLEAR })}
Clear All
</Button>
</$FooterToolbar>
}
+1 -1
View File
@@ -510,7 +510,7 @@ Styled.OrderbookTable = styled(OrderbookTradesTable)<StyleProps>`
content: '';
}
&:not(:active):is(:focus-visible, :focus-within) {
&:not(:active):is(:focus, :focus-within) {
${orderbookMixins.scrollSnapItem}
z-index: 2;
+2 -2
View File
@@ -72,8 +72,8 @@ export const OrderbookTradesTable = styled(Table)<{ histogramSide: 'left' | 'rig
}
tbody tr:hover,
tr:focus-visible,
td:focus-visible {
tr:focus,
td:focus {
color: var(--color-text-2);
background-color: var(--color-layer-3);
}