@rango-dev/widget-embedded 0.0.0-experimental-1624fbb4-20260708 → 0.0.0-experimental-a84f27a7-20260921

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/QueueManager.d.ts.map +1 -1
  3. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts +1 -1
  4. package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts.map +1 -1
  5. package/dist/components/CustomDestination/CustomDestination.styles.d.ts +4 -4
  6. package/dist/components/CustomDestination/CustomDestination.styles.d.ts.map +1 -1
  7. package/dist/components/CustomTokenModal/CustomTokenModal.styles.d.ts +169 -169
  8. package/dist/components/CustomTokenModal/CustomTokenModal.styles.d.ts.map +1 -1
  9. package/dist/components/FilterSelector/FilterSelector.style.d.ts +1 -1
  10. package/dist/components/FilterSelector/FilterSelector.style.d.ts.map +1 -1
  11. package/dist/components/HeaderButtons/HeaderButtons.styles.d.ts +1 -1
  12. package/dist/components/HeaderButtons/HeaderButtons.styles.d.ts.map +1 -1
  13. package/dist/components/NamespaceItem/NamespaceItem.d.ts.map +1 -1
  14. package/dist/components/NamespaceItem/NamespaceItem.styles.d.ts +1 -1
  15. package/dist/components/NamespaceItem/NamespaceItem.styles.d.ts.map +1 -1
  16. package/dist/components/NamespaceItem/NamespaceItem.types.d.ts +3 -3
  17. package/dist/components/NamespaceItem/NamespaceItem.types.d.ts.map +1 -1
  18. package/dist/components/Quote/Quote.d.ts.map +1 -1
  19. package/dist/components/Quote/Quote.styles.d.ts +171 -171
  20. package/dist/components/Quote/Quote.styles.d.ts.map +1 -1
  21. package/dist/components/Quote/Quote.types.d.ts +0 -2
  22. package/dist/components/Quote/Quote.types.d.ts.map +1 -1
  23. package/dist/components/Quote/QuoteCostDetails.d.ts.map +1 -1
  24. package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.styles.d.ts +169 -169
  25. package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.styles.d.ts.map +1 -1
  26. package/dist/components/RefreshModal/RefreshModal.styles.d.ts +168 -168
  27. package/dist/components/RefreshModal/RefreshModal.styles.d.ts.map +1 -1
  28. package/dist/components/StatefulConnectModal/StatefulConnectModal.d.ts.map +1 -1
  29. package/dist/components/SwapDetails/SwapDetails.d.ts.map +1 -1
  30. package/dist/components/SwapDetails/SwapDetails.styles.d.ts +1 -1
  31. package/dist/components/SwapDetails/SwapDetails.styles.d.ts.map +1 -1
  32. package/dist/components/SwapDetailsModal/SwapDetailsModal.ConnectWallet.d.ts.map +1 -1
  33. package/dist/components/SwapDetailsModal/SwapDetailsModal.InstallWallet.d.ts.map +1 -1
  34. package/dist/components/SwapDetailsModal/SwapDetailsModal.types.d.ts +3 -3
  35. package/dist/components/SwapDetailsModal/SwapDetailsModal.types.d.ts.map +1 -1
  36. package/dist/components/SwapMetrics/SwapMetrics.styles.d.ts +1 -1
  37. package/dist/components/SwapMetrics/SwapMetrics.styles.d.ts.map +1 -1
  38. package/dist/components/TokenList/TokenList.styles.d.ts +2 -2
  39. package/dist/components/TokenList/TokenList.styles.d.ts.map +1 -1
  40. package/dist/components/WalletStatefulConnect/DerivationPath.d.ts.map +1 -1
  41. package/dist/components/WalletStatefulConnect/DerivationPath.helpers.d.ts +7 -3
  42. package/dist/components/WalletStatefulConnect/DerivationPath.helpers.d.ts.map +1 -1
  43. package/dist/components/WalletStatefulConnect/DerivationPath.helpers.test.d.ts +2 -0
  44. package/dist/components/WalletStatefulConnect/DerivationPath.helpers.test.d.ts.map +1 -0
  45. package/dist/components/WalletStatefulConnect/DerivationPath.styles.d.ts +169 -169
  46. package/dist/components/WalletStatefulConnect/DerivationPath.styles.d.ts.map +1 -1
  47. package/dist/components/WalletStatefulConnect/Detached.d.ts.map +1 -1
  48. package/dist/components/WalletStatefulConnect/NamespaceDetachedItem.d.ts.map +1 -1
  49. package/dist/components/WalletStatefulConnect/Namespaces.d.ts.map +1 -1
  50. package/dist/components/WalletStatefulConnect/Namespaces.styles.d.ts +168 -168
  51. package/dist/components/WalletStatefulConnect/Namespaces.styles.d.ts.map +1 -1
  52. package/dist/components/WalletStatefulConnect/Namespaces.types.d.ts +3 -3
  53. package/dist/components/WalletStatefulConnect/Namespaces.types.d.ts.map +1 -1
  54. package/dist/constants/errors.d.ts +1 -0
  55. package/dist/constants/errors.d.ts.map +1 -1
  56. package/dist/containers/Wallets/Wallets.d.ts.map +1 -1
  57. package/dist/containers/Wallets/Wallets.helpers.d.ts +2 -2
  58. package/dist/containers/Wallets/Wallets.helpers.d.ts.map +1 -1
  59. package/dist/containers/Wallets/Wallets.types.d.ts +4 -4
  60. package/dist/containers/Wallets/Wallets.types.d.ts.map +1 -1
  61. package/dist/containers/Wallets/useUpdates.d.ts +1 -1
  62. package/dist/containers/Wallets/useUpdates.d.ts.map +1 -1
  63. package/dist/containers/WidgetProvider/WidgetProvider.d.ts.map +1 -1
  64. package/dist/containers/WidgetProvider/WidgetProvider.helpers.d.ts +5 -0
  65. package/dist/containers/WidgetProvider/WidgetProvider.helpers.d.ts.map +1 -0
  66. package/dist/containers/WidgetProvider/WidgetProvider.types.d.ts +1 -1
  67. package/dist/containers/WidgetProvider/WidgetProvider.types.d.ts.map +1 -1
  68. package/dist/hooks/useStatefulConnect/useStatefulConnect.d.ts.map +1 -1
  69. package/dist/hooks/useStatefulConnect/useStatefulConnect.types.d.ts +5 -4
  70. package/dist/hooks/useStatefulConnect/useStatefulConnect.types.d.ts.map +1 -1
  71. package/dist/hooks/useWalletList.d.ts.map +1 -1
  72. package/dist/hooks/useWalletProviders/useWalletProviders.d.ts +2 -2
  73. package/dist/hooks/useWalletProviders/useWalletProviders.d.ts.map +1 -1
  74. package/dist/hooks/useWalletProviders/useWalletProviders.helpers.d.ts +2 -3
  75. package/dist/hooks/useWalletProviders/useWalletProviders.helpers.d.ts.map +1 -1
  76. package/dist/index.d.ts +10 -7
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -2
  79. package/dist/index.js.map +4 -4
  80. package/dist/store/AppStore.d.ts +2 -2
  81. package/dist/store/AppStore.d.ts.map +1 -1
  82. package/dist/store/app.d.ts +2 -2
  83. package/dist/store/app.d.ts.map +1 -1
  84. package/dist/store/slices/config.d.ts +5 -5
  85. package/dist/store/slices/config.d.ts.map +1 -1
  86. package/dist/test-utils/fixtures.d.ts +2 -2
  87. package/dist/test-utils/fixtures.d.ts.map +1 -1
  88. package/dist/types/config.d.ts +25 -4
  89. package/dist/types/config.d.ts.map +1 -1
  90. package/dist/types/wallets.d.ts +1 -1
  91. package/dist/types/wallets.d.ts.map +1 -1
  92. package/dist/utils/common.d.ts +1 -0
  93. package/dist/utils/common.d.ts.map +1 -1
  94. package/dist/utils/connectionReporting.d.ts +24 -0
  95. package/dist/utils/connectionReporting.d.ts.map +1 -0
  96. package/dist/utils/connectionReporting.test.d.ts +2 -0
  97. package/dist/utils/connectionReporting.test.d.ts.map +1 -0
  98. package/dist/utils/errors.d.ts +14 -0
  99. package/dist/utils/errors.d.ts.map +1 -0
  100. package/dist/utils/errors.test.d.ts +2 -0
  101. package/dist/utils/errors.test.d.ts.map +1 -0
  102. package/dist/utils/providers.d.ts +3 -6
  103. package/dist/utils/providers.d.ts.map +1 -1
  104. package/dist/utils/swap.d.ts +1 -1
  105. package/dist/utils/swap.d.ts.map +1 -1
  106. package/dist/utils/wallets.d.ts +2 -1
  107. package/dist/utils/wallets.d.ts.map +1 -1
  108. package/package.json +16 -16
  109. package/src/QueueManager.tsx +17 -8
  110. package/src/components/NamespaceItem/NamespaceItem.styles.ts +1 -0
  111. package/src/components/NamespaceItem/NamespaceItem.tsx +5 -1
  112. package/src/components/NamespaceItem/NamespaceItem.types.ts +3 -3
  113. package/src/components/Quote/Quote.tsx +0 -2
  114. package/src/components/Quote/Quote.types.ts +0 -2
  115. package/src/components/Quote/QuoteCostDetails.tsx +15 -4
  116. package/src/components/StatefulConnectModal/StatefulConnectModal.tsx +8 -2
  117. package/src/components/SwapDetails/SwapDetails.tsx +18 -6
  118. package/src/components/SwapDetailsModal/SwapDetailsModal.ConnectWallet.tsx +23 -23
  119. package/src/components/SwapDetailsModal/SwapDetailsModal.InstallWallet.tsx +8 -2
  120. package/src/components/SwapDetailsModal/SwapDetailsModal.types.ts +3 -3
  121. package/src/components/TokenList/LoadingTokenList.tsx +2 -2
  122. package/src/components/WalletStatefulConnect/DerivationPath.helpers.test.ts +98 -0
  123. package/src/components/WalletStatefulConnect/DerivationPath.helpers.ts +14 -10
  124. package/src/components/WalletStatefulConnect/DerivationPath.tsx +14 -7
  125. package/src/components/WalletStatefulConnect/Detached.tsx +17 -8
  126. package/src/components/WalletStatefulConnect/NamespaceDetachedItem.tsx +2 -1
  127. package/src/components/WalletStatefulConnect/Namespaces.tsx +6 -12
  128. package/src/components/WalletStatefulConnect/Namespaces.types.tsx +3 -3
  129. package/src/constants/errors.ts +3 -0
  130. package/src/containers/Wallets/Wallets.helpers.ts +4 -4
  131. package/src/containers/Wallets/Wallets.tsx +20 -3
  132. package/src/containers/Wallets/Wallets.types.ts +4 -7
  133. package/src/containers/Wallets/useUpdates.ts +4 -119
  134. package/src/containers/WidgetProvider/WidgetProvider.helpers.ts +10 -0
  135. package/src/containers/WidgetProvider/WidgetProvider.tsx +12 -1
  136. package/src/containers/WidgetProvider/WidgetProvider.types.ts +1 -1
  137. package/src/hooks/useStatefulConnect/useStatefulConnect.ts +30 -31
  138. package/src/hooks/useStatefulConnect/useStatefulConnect.types.ts +6 -3
  139. package/src/hooks/useWalletList.ts +10 -7
  140. package/src/hooks/useWalletProviders/useWalletProviders.helpers.ts +3 -10
  141. package/src/hooks/useWalletProviders/useWalletProviders.ts +2 -2
  142. package/src/index.ts +10 -11
  143. package/src/store/AppStore.tsx +4 -2
  144. package/src/store/app.ts +7 -2
  145. package/src/store/slices/config.ts +36 -25
  146. package/src/store/slices/data.test.ts +2 -2
  147. package/src/test-utils/fixtures.ts +6 -2
  148. package/src/types/config.ts +31 -4
  149. package/src/types/wallets.ts +1 -1
  150. package/src/utils/common.ts +6 -0
  151. package/src/utils/connectionReporting.test.ts +379 -0
  152. package/src/utils/connectionReporting.ts +94 -0
  153. package/src/utils/errors.test.ts +125 -0
  154. package/src/utils/errors.ts +88 -0
  155. package/src/utils/providers.ts +16 -50
  156. package/src/utils/swap.ts +8 -1
  157. package/src/utils/wallets.ts +12 -15
  158. package/dist/widget-embedded.build.json +0 -1
@@ -1,13 +1,20 @@
1
1
  import type { DataSlice } from './data';
2
2
  import type { SettingsSlice } from './settings';
3
- import type { WidgetConfig } from '../../types';
3
+ import type {
4
+ WidgetConfig,
5
+ WidgetConfigWithoutLegacyProviders,
6
+ } from '../../types';
4
7
  import type { StateCreatorWithInitialData } from '../app';
5
8
  import type { VersionedProviders } from '@hub3js/core/utils';
6
9
 
7
- import { allProviders as getAllProviders } from '@rango-dev/provider-all';
10
+ import {
11
+ allProviders as getAllProviders,
12
+ WalletTypes,
13
+ } from '@rango-dev/provider-all';
8
14
 
9
15
  import { cacheService } from '../../services/cacheService';
10
16
  import {
17
+ configWalletsToWalletName,
11
18
  matchAndGenerateProviders,
12
19
  type ProvidersOptions,
13
20
  } from '../../utils/providers';
@@ -27,7 +34,7 @@ function makeProvidersOptionsFromConfig(
27
34
  return options;
28
35
  }
29
36
 
30
- export const DEFAULT_CONFIG: WidgetConfig = {
37
+ export const DEFAULT_CONFIG: WidgetConfigWithoutLegacyProviders = {
31
38
  apiKey: '',
32
39
  title: undefined,
33
40
  multiWallets: true,
@@ -62,13 +69,13 @@ const DEFAULT_CAMPAIGN_MODE: CampaignMode = {
62
69
 
63
70
  export interface ConfigSlice {
64
71
  // What user can set directly.
65
- config: WidgetConfig;
72
+ config: WidgetConfigWithoutLegacyProviders;
66
73
  campaignMode: CampaignMode;
67
74
  isInCampaignMode: () => boolean;
68
75
  getLiquiditySources: () => string[];
69
76
  getDisabledLiquiditySources: () => string[];
70
77
  excludeLiquiditySources: () => boolean;
71
- updateConfig: (config: WidgetConfig) => void;
78
+ updateConfig: (config: WidgetConfigWithoutLegacyProviders) => void;
72
79
  updateCampaignMode: <K extends keyof CampaignMode>(
73
80
  name: K,
74
81
  value: CampaignMode[K]
@@ -84,36 +91,40 @@ export interface ConfigSlice {
84
91
  getAvailableProviders: () => VersionedProviders[];
85
92
  }
86
93
 
87
- function generateProviders(config: WidgetConfig) {
88
- const options = makeProvidersOptionsFromConfig(config);
89
- const envs = {
90
- walletconnect2: {
91
- WC_PROJECT_ID: options?.walletConnectProjectId || '',
92
- DISABLE_MODAL_AND_OPEN_LINK:
93
- options?.walletConnectListedDesktopWalletLink,
94
- },
95
- selectedProviders: config.wallets,
96
- tonConnect: options?.tonConnect?.manifestUrl
97
- ? { manifestUrl: options?.tonConnect.manifestUrl }
98
- : undefined,
99
- };
100
- const allProviders = getAllProviders(envs);
94
+ function generateProviders(
95
+ config: WidgetConfigWithoutLegacyProviders
96
+ ): VersionedProviders[] {
97
+ const allProviders = getAllProviders();
101
98
  const allBuiltProviders = allProviders.map((build) => build());
102
99
 
103
- return allBuiltProviders;
100
+ const providerNames = configWalletsToWalletName(allBuiltProviders);
101
+
102
+ const filteredProviders = allBuiltProviders.filter((_, index) => {
103
+ if (
104
+ providerNames[index] === WalletTypes.LEDGER_WALLET &&
105
+ (!config.ledgerWallet?.apiKey || !config.ledgerWallet?.dAppIdentifier)
106
+ ) {
107
+ return false;
108
+ }
109
+
110
+ return true;
111
+ });
112
+
113
+ return filteredProviders;
104
114
  }
105
115
 
106
116
  export const createConfigSlice: StateCreatorWithInitialData<
107
- WidgetConfig,
117
+ WidgetConfigWithoutLegacyProviders,
108
118
  ConfigSlice & SettingsSlice & DataSlice,
109
119
  ConfigSlice
110
120
  > = (initialData, set, get) => {
111
- const allBuiltProviders = generateProviders({
121
+ const config: WidgetConfigWithoutLegacyProviders = {
112
122
  ...DEFAULT_CONFIG,
113
123
  ...initialData,
114
- });
124
+ };
125
+ const allBuiltProviders = generateProviders(config);
115
126
  return {
116
- config: { ...DEFAULT_CONFIG, ...initialData },
127
+ config,
117
128
  iframe: DEFAULT_IFRAME_CONFIGS,
118
129
  campaignMode: DEFAULT_CAMPAIGN_MODE,
119
130
  allProviders: allBuiltProviders,
@@ -141,7 +152,7 @@ export const createConfigSlice: StateCreatorWithInitialData<
141
152
  },
142
153
 
143
154
  // Actions
144
- updateConfig: (nextConfig: WidgetConfig) => {
155
+ updateConfig: (nextConfig: WidgetConfigWithoutLegacyProviders) => {
145
156
  const currentConfig = get().config;
146
157
  const {
147
158
  _tokensMapByTokenHash: tokensMapByTokenHash,
@@ -1,4 +1,4 @@
1
- import type { WidgetConfig } from '../../types';
1
+ import type { WidgetConfigWithoutLegacyProviders } from '../../types';
2
2
  import type { AppStoreState } from '../app';
3
3
  import type { EvmBlockchainMeta, Token } from 'rango-sdk';
4
4
 
@@ -18,7 +18,7 @@ let appStoreState: AppStoreState;
18
18
  let customTokens: [Token, Token, Token];
19
19
  let rangoBlockchain: EvmBlockchainMeta;
20
20
 
21
- const DEFAULT_CONFIG: WidgetConfig = {
21
+ const DEFAULT_CONFIG: WidgetConfigWithoutLegacyProviders = {
22
22
  apiKey: '',
23
23
  walletConnectProjectId: 'e24844c5deb5193c1c14840a7af6a40b',
24
24
  title: undefined,
@@ -1,5 +1,9 @@
1
1
  import type { AppStoreState } from '../store/app';
2
- import type { Blockchain, TokenHash, WidgetConfig } from '../types';
2
+ import type {
3
+ Blockchain,
4
+ TokenHash,
5
+ WidgetConfigWithoutLegacyProviders,
6
+ } from '../types';
3
7
  import type { BlockchainMeta, EvmBlockchainMeta, Token } from 'rango-sdk';
4
8
 
5
9
  import { faker } from '@faker-js/faker';
@@ -216,7 +220,7 @@ export function createInitialAppStore() {
216
220
 
217
221
  export function updateAppStoreConfig(
218
222
  appStoreState: AppStoreState,
219
- config: Partial<WidgetConfig>
223
+ config: Partial<WidgetConfigWithoutLegacyProviders>
220
224
  ): AppStoreState {
221
225
  appStoreState.config = {
222
226
  ...appStoreState.config,
@@ -1,7 +1,6 @@
1
- import type { Provider } from '@hub3js/core';
1
+ import type { Provider, WalletType } from '@hub3js/core';
2
2
  import type { Language, theme } from '@rango-dev/ui';
3
- import type { LegacyProviderInterface } from '@rango-dev/wallets-core/legacy';
4
- import type { WalletType } from '@rango-dev/wallets-shared';
3
+ import type { ProviderInterface } from '@rango-dev/wallets-react';
5
4
  import type { Asset } from 'rango-sdk';
6
5
  import type { ReactElement } from 'react';
7
6
 
@@ -186,6 +185,21 @@ export type TonConnectConfig = {
186
185
  manifestUrl?: string;
187
186
  };
188
187
 
188
+ /**
189
+ * Configuration for the Ledger wallet integration.
190
+ *
191
+ * @property {string} dAppIdentifier - Unique identifier of your dApp, registered with Ledger to authorize the integration.
192
+ * @property {string} apiKey - API key issued by Ledger, used to authenticate requests from your dApp.
193
+ * @property {'fatal' | 'error' | 'warn' | 'info' | 'debug'} [loggerLevel] - Verbosity of the Ledger logger.
194
+ * @property {boolean} [hideButton] - When `true`, hides the built-in Ledger floating button.
195
+ */
196
+ export type LedgerWalletConfig = {
197
+ dAppIdentifier: string;
198
+ apiKey: string;
199
+ loggerLevel?: 'fatal' | 'error' | 'warn' | 'info' | 'debug';
200
+ hideButton?: boolean;
201
+ };
202
+
189
203
  /**
190
204
  * The type WidgetConfig defines the configuration options for a widget, including API key, affiliate
191
205
  * reference, amount, blockchain and token configurations, liquidity sources, wallet types, language,
@@ -254,13 +268,18 @@ export type WidgetConfig = {
254
268
  title?: string;
255
269
  walletConnectProjectId?: string;
256
270
  trezorManifest?: TrezorManifest;
271
+ ledgerWallet?: LedgerWalletConfig;
257
272
  tonConnect?: TonConnectConfig;
258
273
  affiliate?: WidgetAffiliate;
259
274
  amount?: number;
260
275
  from?: BlockchainAndTokenConfig;
261
276
  to?: BlockchainAndTokenConfig;
262
277
  liquiditySources?: string[];
263
- wallets?: (WalletType | LegacyProviderInterface | Provider)[];
278
+ /**
279
+ * Passing a `ProviderInterface` is deprecated. Pass a hub `Provider` instead.
280
+ * Legacy providers will be removed in future versions.
281
+ */
282
+ wallets?: (WalletType | ProviderInterface | Provider)[];
264
283
  multiWallets?: boolean;
265
284
  customDestination?: boolean;
266
285
  defaultCustomDestinations?: { [blockchain: string]: string };
@@ -289,3 +308,11 @@ export type WidgetConfig = {
289
308
  };
290
309
  routing?: Routing;
291
310
  };
311
+
312
+ // TODO: remove this when ProviderInterface gets removed
313
+ export type WidgetConfigWithoutLegacyProviders = WidgetConfig & {
314
+ wallets?: Exclude<
315
+ NonNullable<WidgetConfig['wallets']>[number],
316
+ ProviderInterface
317
+ >[];
318
+ };
@@ -1,5 +1,5 @@
1
+ import type { WalletType } from '@hub3js/core';
1
2
  import type { WalletInfo } from '@rango-dev/ui';
2
- import type { WalletType } from '@rango-dev/wallets-shared';
3
3
 
4
4
  export interface Wallet {
5
5
  chain: string;
@@ -294,3 +294,9 @@ export function filterBlockchainsWithAtLeastOneToken(
294
294
  blockchainsWithAtLeastOneToken.has(blockchain.name)
295
295
  );
296
296
  }
297
+
298
+ export function detectMobileScreens(): boolean {
299
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
300
+ navigator.userAgent
301
+ );
302
+ }
@@ -0,0 +1,379 @@
1
+ import type { ConnectionReportContext } from './connectionReporting';
2
+
3
+ import { WalletConnectionError } from '@hub3js/std/utils';
4
+ import { error as logError } from '@rango-dev/logging-core';
5
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
6
+
7
+ import {
8
+ CONNECTION_TIMEOUT_MS,
9
+ reportConnectionError,
10
+ trackConnection,
11
+ } from './connectionReporting';
12
+
13
+ vi.mock('@rango-dev/logging-core', () => ({ error: vi.fn() }));
14
+
15
+ const context: ConnectionReportContext = {
16
+ trigger: 'wallet-modal',
17
+ source: 'user',
18
+ walletType: 'phantom',
19
+ };
20
+
21
+ const namespaceState = {
22
+ network: null,
23
+ connected: false,
24
+ connecting: true,
25
+ connectArgs: null,
26
+ };
27
+
28
+ function createConnectionError(
29
+ type: 'rejected' | 'locked' | 'timeout' | 'unknown',
30
+ cause?: unknown
31
+ ) {
32
+ return new WalletConnectionError({
33
+ walletType: 'phantom',
34
+ namespace: 'solana',
35
+ type,
36
+ state: namespaceState,
37
+ cause,
38
+ });
39
+ }
40
+
41
+ function catchThrown(run: () => unknown): unknown {
42
+ try {
43
+ run();
44
+ } catch (error) {
45
+ return error;
46
+ }
47
+
48
+ return undefined;
49
+ }
50
+
51
+ function createPendingAttempt() {
52
+ let resolve!: (value: string) => void;
53
+ let reject!: (reason: unknown) => void;
54
+ const attempt = new Promise<string>((res, rej) => {
55
+ resolve = res;
56
+ reject = rej;
57
+ });
58
+
59
+ return { attempt, resolve, reject };
60
+ }
61
+
62
+ function reportedEvent(call = 0) {
63
+ const [error, data] = vi.mocked(logError).mock.calls[call] ?? [];
64
+
65
+ return { error, data };
66
+ }
67
+
68
+ beforeEach(() => {
69
+ vi.mocked(logError).mockReset();
70
+ });
71
+
72
+ describe('reporting a connection error', () => {
73
+ test('drop a rejected connection failure without any event', () => {
74
+ catchThrown(() =>
75
+ reportConnectionError(createConnectionError('rejected'), context)
76
+ );
77
+
78
+ expect(logError).not.toHaveBeenCalled();
79
+ });
80
+
81
+ test('drop a locked wallet without any event', () => {
82
+ catchThrown(() =>
83
+ reportConnectionError(createConnectionError('locked'), context)
84
+ );
85
+
86
+ expect(logError).not.toHaveBeenCalled();
87
+ });
88
+
89
+ test('log an unknown connection failure once', () => {
90
+ catchThrown(() =>
91
+ reportConnectionError(createConnectionError('unknown'), context)
92
+ );
93
+
94
+ expect(logError).toHaveBeenCalledTimes(1);
95
+ });
96
+
97
+ test('log a timeout connection failure once', () => {
98
+ catchThrown(() =>
99
+ reportConnectionError(createConnectionError('timeout'), context)
100
+ );
101
+
102
+ expect(logError).toHaveBeenCalledTimes(1);
103
+ });
104
+
105
+ test('log the error itself with its own tags and context', () => {
106
+ const connectionError = createConnectionError('unknown');
107
+
108
+ catchThrown(() => reportConnectionError(connectionError, context));
109
+
110
+ const { error, data } = reportedEvent();
111
+ expect(error).toBe(connectionError);
112
+ expect(data).toEqual({
113
+ tags: {
114
+ name: 'WalletConnectionError',
115
+ type: 'unknown',
116
+ trigger: 'wallet-modal',
117
+ source: 'user',
118
+ walletType: 'phantom',
119
+ namespace: 'solana',
120
+ },
121
+ context: {
122
+ walletType: 'phantom',
123
+ namespace: 'solana',
124
+ state: namespaceState,
125
+ },
126
+ });
127
+ });
128
+
129
+ test('keep the wallet raw error as the cause of the event', () => {
130
+ const walletError = { code: -32603, message: 'Internal error' };
131
+
132
+ catchThrown(() =>
133
+ reportConnectionError(
134
+ createConnectionError('unknown', walletError),
135
+ context
136
+ )
137
+ );
138
+
139
+ expect(reportedEvent().error).toHaveProperty('cause', walletError);
140
+ });
141
+
142
+ test('keep the trigger and source a failure already carries', () => {
143
+ const connectionError = createConnectionError('unknown');
144
+ connectionError.trigger = 'swap-details';
145
+ connectionError.source = 'auto-connect';
146
+
147
+ catchThrown(() => reportConnectionError(connectionError, context));
148
+
149
+ expect(reportedEvent().data).toMatchObject({
150
+ tags: { trigger: 'swap-details', source: 'auto-connect' },
151
+ });
152
+ });
153
+
154
+ test('wrap an unexpected error keeping its real message', () => {
155
+ const unexpected = new TypeError('Cannot read properties of undefined');
156
+
157
+ catchThrown(() => reportConnectionError(unexpected, context));
158
+
159
+ const { error, data } = reportedEvent();
160
+ expect(error).toMatchObject({
161
+ name: 'RangoError',
162
+ type: 'unknown',
163
+ message: 'Cannot read properties of undefined',
164
+ cause: unexpected,
165
+ });
166
+ expect(data).toMatchObject({
167
+ tags: {
168
+ name: 'RangoError',
169
+ type: 'unknown',
170
+ trigger: 'wallet-modal',
171
+ source: 'user',
172
+ walletType: 'phantom',
173
+ },
174
+ });
175
+ });
176
+
177
+ test('log a thrown value that is not an error', () => {
178
+ catchThrown(() => reportConnectionError('boom', context));
179
+
180
+ expect(logError).toHaveBeenCalledTimes(1);
181
+ expect(reportedEvent().error).toMatchObject({ message: 'boom' });
182
+ });
183
+
184
+ test('rethrow the original error as the same object', () => {
185
+ const rejected = createConnectionError('rejected');
186
+ const unknown = createConnectionError('unknown');
187
+ const unexpected = new TypeError('boom');
188
+
189
+ expect(catchThrown(() => reportConnectionError(rejected, context))).toBe(
190
+ rejected
191
+ );
192
+ expect(catchThrown(() => reportConnectionError(unknown, context))).toBe(
193
+ unknown
194
+ );
195
+ expect(catchThrown(() => reportConnectionError(unexpected, context))).toBe(
196
+ unexpected
197
+ );
198
+ });
199
+
200
+ test('rethrow the original error when logging fails', () => {
201
+ vi.mocked(logError).mockImplementationOnce(() => {
202
+ throw new Error('the log sink is down');
203
+ });
204
+ const connectionError = createConnectionError('unknown');
205
+
206
+ expect(
207
+ catchThrown(() => reportConnectionError(connectionError, context))
208
+ ).toBe(connectionError);
209
+ });
210
+ });
211
+
212
+ describe('tracking a connection attempt', () => {
213
+ beforeEach(() => {
214
+ vi.useFakeTimers();
215
+ });
216
+
217
+ afterEach(() => {
218
+ vi.useRealTimers();
219
+ });
220
+
221
+ test('resolve with the value of a successful attempt without any event', async () => {
222
+ const { attempt, resolve } = createPendingAttempt();
223
+
224
+ const tracked = trackConnection(attempt, context);
225
+ resolve('connected');
226
+
227
+ await expect(tracked).resolves.toBe('connected');
228
+ expect(logError).not.toHaveBeenCalled();
229
+ });
230
+
231
+ test('report a failed attempt and rethrow the same error', async () => {
232
+ const { attempt, reject } = createPendingAttempt();
233
+ const connectionError = createConnectionError('unknown');
234
+
235
+ const tracked = trackConnection(attempt, context);
236
+ reject(connectionError);
237
+
238
+ await expect(tracked).rejects.toBe(connectionError);
239
+ expect(logError).toHaveBeenCalledTimes(1);
240
+ });
241
+
242
+ test('rethrow a rejection without any event', async () => {
243
+ const { attempt, reject } = createPendingAttempt();
244
+ const connectionError = createConnectionError('rejected');
245
+
246
+ const tracked = trackConnection(attempt, context);
247
+ reject(connectionError);
248
+
249
+ await expect(tracked).rejects.toBe(connectionError);
250
+ expect(logError).not.toHaveBeenCalled();
251
+ });
252
+
253
+ test('send one timeout event when the attempt is still pending after three minutes', async () => {
254
+ const { attempt } = createPendingAttempt();
255
+
256
+ void trackConnection(attempt, { ...context, namespaces: ['solana'] });
257
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS);
258
+
259
+ expect(logError).toHaveBeenCalledTimes(1);
260
+ const { error, data } = reportedEvent();
261
+ expect(error).toBeInstanceOf(WalletConnectionError);
262
+ expect(error).toMatchObject({
263
+ type: 'timeout',
264
+ message: "Your wallet didn't respond.",
265
+ });
266
+ expect(data).toMatchObject({
267
+ tags: {
268
+ name: 'WalletConnectionError',
269
+ type: 'timeout',
270
+ trigger: 'wallet-modal',
271
+ source: 'user',
272
+ walletType: 'phantom',
273
+ namespace: 'solana',
274
+ },
275
+ });
276
+ });
277
+
278
+ test('wait for the full three minutes before the timeout event', async () => {
279
+ const { attempt } = createPendingAttempt();
280
+
281
+ void trackConnection(attempt, context);
282
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS - 1);
283
+
284
+ expect(logError).not.toHaveBeenCalled();
285
+ });
286
+
287
+ test('send at most one timeout event per attempt', async () => {
288
+ const { attempt } = createPendingAttempt();
289
+
290
+ void trackConnection(attempt, context);
291
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS * 2);
292
+
293
+ expect(logError).toHaveBeenCalledTimes(1);
294
+ });
295
+
296
+ test('name every requested namespace on the timeout event', async () => {
297
+ const { attempt } = createPendingAttempt();
298
+
299
+ void trackConnection(attempt, {
300
+ ...context,
301
+ namespaces: ['solana', 'evm', 'solana'],
302
+ });
303
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS);
304
+
305
+ expect(reportedEvent().data).toMatchObject({
306
+ tags: { namespace: 'evm,solana' },
307
+ });
308
+ });
309
+
310
+ test('send no timeout event when the attempt succeeds first', async () => {
311
+ const { attempt, resolve } = createPendingAttempt();
312
+
313
+ const tracked = trackConnection(attempt, context);
314
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS - 1);
315
+ resolve('connected');
316
+ await tracked;
317
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS * 2);
318
+
319
+ expect(logError).not.toHaveBeenCalled();
320
+ });
321
+
322
+ test('send no timeout event when the attempt fails first', async () => {
323
+ const { attempt, reject } = createPendingAttempt();
324
+
325
+ const tracked = trackConnection(attempt, context);
326
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS - 1);
327
+ reject(createConnectionError('rejected'));
328
+ await tracked.catch(() => undefined);
329
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS * 2);
330
+
331
+ expect(logError).not.toHaveBeenCalled();
332
+ });
333
+
334
+ test('clear the timer once the attempt settles', async () => {
335
+ const { attempt, resolve } = createPendingAttempt();
336
+
337
+ const tracked = trackConnection(attempt, context);
338
+ expect(vi.getTimerCount()).toBe(1);
339
+ resolve('connected');
340
+ await tracked;
341
+
342
+ expect(vi.getTimerCount()).toBe(0);
343
+ });
344
+
345
+ test('send no timeout event for auto connect', async () => {
346
+ const { attempt } = createPendingAttempt();
347
+
348
+ void trackConnection(attempt, { ...context, source: 'auto-connect' });
349
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS * 2);
350
+
351
+ expect(vi.getTimerCount()).toBe(0);
352
+ expect(logError).not.toHaveBeenCalled();
353
+ });
354
+
355
+ test('keep the attempt going after the timeout event', async () => {
356
+ const { attempt, resolve } = createPendingAttempt();
357
+
358
+ const tracked = trackConnection(attempt, context);
359
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS);
360
+ resolve('connected');
361
+
362
+ await expect(tracked).resolves.toBe('connected');
363
+ expect(logError).toHaveBeenCalledTimes(1);
364
+ });
365
+
366
+ test('report a real failure that arrives after the timeout event', async () => {
367
+ const { attempt, reject } = createPendingAttempt();
368
+ const connectionError = createConnectionError('unknown');
369
+
370
+ const tracked = trackConnection(attempt, context);
371
+ await vi.advanceTimersByTimeAsync(CONNECTION_TIMEOUT_MS);
372
+ reject(connectionError);
373
+
374
+ await expect(tracked).rejects.toBe(connectionError);
375
+ expect(logError).toHaveBeenCalledTimes(2);
376
+ expect(reportedEvent(0).error).toMatchObject({ type: 'timeout' });
377
+ expect(reportedEvent(1).error).toBe(connectionError);
378
+ });
379
+ });