diff --git a/src/App.tsx b/src/App.tsx index db877bb..e357cc4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -192,7 +192,9 @@ Styled.NotificationsToastArea = styled(NotificationsToastArea)` `; Styled.DialogArea = styled.aside` - position: absolute; + position: fixed; + height: 100vh; + z-index: 1; inset: 0; overflow: clip; ${layoutMixins.noPointerEvents} diff --git a/src/constants/analytics.ts b/src/constants/analytics.ts index bf117f3..b6a1732 100644 --- a/src/constants/analytics.ts +++ b/src/constants/analytics.ts @@ -67,6 +67,7 @@ export enum AnalyticsEvent { // Onboarding OnboardingStepChanged = 'OnboardingStepChanged', OnboardingAccountDerived = 'OnboardingAccountDerived', + OnboardingWalletIsNonDeterministic = 'OnboardingWalletIsNonDeterministic', // Transfers TransferFaucet = 'TransferFaucet', diff --git a/src/constants/wallets.ts b/src/constants/wallets.ts index 654ef88..a6bfcef 100644 --- a/src/constants/wallets.ts +++ b/src/constants/wallets.ts @@ -32,6 +32,18 @@ export enum WalletConnectionType { WalletConnect2 = 'walletConnect2', } +export enum WalletErrorType { + // General + ChainMismatch, + UserCanceled, + + // Non-Deterministic + NonDeterministicWallet, + + // Misc + Unknown, +} + type WalletConnectionTypeConfig = { name: string; wagmiConnectorId?: string; diff --git a/src/hooks/useDydxClient.tsx b/src/hooks/useDydxClient.tsx index b0126f9..1cc2871 100644 --- a/src/hooks/useDydxClient.tsx +++ b/src/hooks/useDydxClient.tsx @@ -57,8 +57,8 @@ const useDydxClientContext = () => { networkConfig?.indexerSocketUrl && networkConfig?.validatorUrl ) { - setCompositeClient( - await CompositeClient.connect( + try { + const initializedClient = await CompositeClient.connect( new Network( selectedNetwork, new IndexerConfig(networkConfig.indexerUrl, networkConfig.indexerSocketUrl), @@ -67,8 +67,11 @@ const useDydxClientContext = () => { broadcastTimeoutMs: 60_000, }) ) - ) - ); + ); + setCompositeClient(initializedClient); + } catch (error) { + log('useDydxClient/initializeCompositeClient', error); + } } else { setCompositeClient(undefined); } diff --git a/src/hooks/useWalletConnection.ts b/src/hooks/useWalletConnection.ts index 610c997..c8cbfb4 100644 --- a/src/hooks/useWalletConnection.ts +++ b/src/hooks/useWalletConnection.ts @@ -11,6 +11,8 @@ import { DYDX_CHAIN_INFO, } from '@/constants/wallets'; +import { useLocalStorage } from '@/hooks/useLocalStorage'; + import { useConnect as useConnectWagmi, useAccount as useAccountWagmi, @@ -26,10 +28,9 @@ import { } from 'graz'; import { resolveWagmiConnector } from '@/lib/wagmi'; -import { getWalletConnection } from '@/lib/wallet'; +import { getWalletConnection, parseWalletError } from '@/lib/wallet'; import { log } from '@/lib/telemetry'; -import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useStringGetter } from './useStringGetter'; export const useWalletConnection = () => { @@ -129,8 +130,6 @@ export const useWalletConnection = () => { } } } catch (error) { - log('useWalletConnection/connectWallet', error); - throw Object.assign( new Error([error.message, error.cause?.message].filter(Boolean).join('\n')), { @@ -158,7 +157,7 @@ export const useWalletConnection = () => { // Wallet selection const [selectedWalletType, setSelectedWalletType] = useState(walletType); - const [selectedWalletError, setSelectedWalletError] = useState(); + const [selectedWalletError, setSelectedWalletError] = useState(); useEffect(() => { (async () => { @@ -173,8 +172,15 @@ export const useWalletConnection = () => { setWalletType(walletType); setWalletConnectionType(walletConnectionType); } catch (error) { - log('useWalletConnection/connectWallet', error); - setSelectedWalletError(error); + const { walletErrorType, message } = parseWalletError({ + error, + stringGetter, + }); + + if (message) { + log('useWalletConnection/connectWallet', error, { walletErrorType }); + setSelectedWalletError(message); + } } } else { setWalletType(undefined); diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index f2beb36..c8619d2 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -1,7 +1,10 @@ +import { STRING_KEYS, StringGetterFunction } from '@/constants/localization'; + import { type WalletConnection, wallets, WalletConnectionType, + WalletErrorType, WalletType, } from '@/constants/wallets'; @@ -68,3 +71,66 @@ 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, + }; +}; diff --git a/src/views/dialogs/OnboardingDialog/ChooseWallet.tsx b/src/views/dialogs/OnboardingDialog/ChooseWallet.tsx index 13f3021..0d283ed 100644 --- a/src/views/dialogs/OnboardingDialog/ChooseWallet.tsx +++ b/src/views/dialogs/OnboardingDialog/ChooseWallet.tsx @@ -32,8 +32,12 @@ export const ChooseWallet = () => { <> {selectedWalletType && selectedWalletError && ( - {

Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.

} - {selectedWalletError.message} + { +

+ Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}. +

+ } + {selectedWalletError}
)} diff --git a/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx b/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx index 84a704d..805fcb6 100644 --- a/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx +++ b/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx @@ -25,11 +25,12 @@ 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 { getSelectedNetwork } from '@/state/appSelectors'; +import { parseWalletError } from '@/lib/wallet'; type ElementProps = { status: EvmDerivedAccountStatus; @@ -66,8 +67,12 @@ export const GenerateKeys = ({ try { await matchNetwork?.(); } catch (error) { - setError(error.message); - log('GenerateKeys/switchNetwork', error); + const { message, walletErrorType } = parseWalletError({ error, stringGetter }); + + if (message) { + log('GenerateKeys/switchNetwork', error, { walletErrorType }); + setError(message); + } } }; @@ -124,7 +129,12 @@ export const GenerateKeys = ({ } } } catch (error) { - log('GenerateKeys/getSubaccounts', error); + const { message } = parseWalletError({ error, stringGetter }); + + if (message) { + track(AnalyticsEvent.OnboardingWalletIsNonDeterministic); + setError(message); + } } await setWalletFromEvmSignature(signature); @@ -140,48 +150,48 @@ export const GenerateKeys = ({ setStatus(EvmDerivedAccountStatus.Derived); } catch (error) { setStatus(EvmDerivedAccountStatus.NotDerived); - setError(error?.message); + const { message, walletErrorType } = parseWalletError({ error, stringGetter }); - log('GenerateKeys/deriveKeys', error); - - throw error; + if (message) { + setError(message); + log('GenerateKeys/deriveKeys', error, { walletErrorType }); + } } }; return ( <> - {isMobile && ( - - {[ - { - 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) => ( - - {status < step.status ? ( - - ) : status === step.status ? ( - - ) : ( - - )} -
-

{step.title}

-

{step.description}

-
-
- ))} -
- )} + + {[ + { + 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) => ( + + {status < step.status ? ( + + ) : status === step.status ? ( + + ) : ( + + )} +
+

{step.title}

+

{step.description}

+
+
+ ))} +
+ @@ -251,7 +261,7 @@ export const GenerateKeys = ({ const Styled: Record = {}; -Styled.MobileStatusCards = styled.div` +Styled.StatusCardsContainer = styled.div` display: grid; margin-top: 1rem; gap: 1rem;