@meshconnect/uwc-react 0.9.4 → 0.10.0

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 (65) hide show
  1. package/dist/hooks/useConnection.d.ts.map +1 -1
  2. package/dist/hooks/useConnection.js +15 -6
  3. package/dist/hooks/useConnection.js.map +1 -1
  4. package/dist/hooks/useConnectionContext.d.ts +15 -0
  5. package/dist/hooks/useConnectionContext.d.ts.map +1 -0
  6. package/dist/hooks/useConnectionContext.js +22 -0
  7. package/dist/hooks/useConnectionContext.js.map +1 -0
  8. package/dist/hooks/useDetectedWallets.d.ts.map +1 -1
  9. package/dist/hooks/useDetectedWallets.js +6 -6
  10. package/dist/hooks/useDetectedWallets.js.map +1 -1
  11. package/dist/hooks/useDisconnect.d.ts.map +1 -1
  12. package/dist/hooks/useDisconnect.js +6 -6
  13. package/dist/hooks/useDisconnect.js.map +1 -1
  14. package/dist/hooks/useNetworks.d.ts.map +1 -1
  15. package/dist/hooks/useNetworks.js +2 -6
  16. package/dist/hooks/useNetworks.js.map +1 -1
  17. package/dist/hooks/useSession.d.ts.map +1 -1
  18. package/dist/hooks/useSession.js +2 -6
  19. package/dist/hooks/useSession.js.map +1 -1
  20. package/dist/hooks/useSignMessage.d.ts.map +1 -1
  21. package/dist/hooks/useSignMessage.js +4 -7
  22. package/dist/hooks/useSignMessage.js.map +1 -1
  23. package/dist/hooks/useSignSolanaTransaction.d.ts.map +1 -1
  24. package/dist/hooks/useSignSolanaTransaction.js +4 -7
  25. package/dist/hooks/useSignSolanaTransaction.js.map +1 -1
  26. package/dist/hooks/useSignTypedData.d.ts.map +1 -1
  27. package/dist/hooks/useSignTypedData.js +4 -7
  28. package/dist/hooks/useSignTypedData.js.map +1 -1
  29. package/dist/hooks/useSwitchNetwork.d.ts.map +1 -1
  30. package/dist/hooks/useSwitchNetwork.js +9 -6
  31. package/dist/hooks/useSwitchNetwork.js.map +1 -1
  32. package/dist/hooks/useTransaction.d.ts.map +1 -1
  33. package/dist/hooks/useTransaction.js +4 -7
  34. package/dist/hooks/useTransaction.js.map +1 -1
  35. package/dist/hooks/useWallet.d.ts.map +1 -1
  36. package/dist/hooks/useWallet.js +2 -6
  37. package/dist/hooks/useWallet.js.map +1 -1
  38. package/dist/hooks/useWalletCapabilities.d.ts.map +1 -1
  39. package/dist/hooks/useWalletCapabilities.js +6 -8
  40. package/dist/hooks/useWalletCapabilities.js.map +1 -1
  41. package/dist/hooks/useWallets.d.ts.map +1 -1
  42. package/dist/hooks/useWallets.js +2 -6
  43. package/dist/hooks/useWallets.js.map +1 -1
  44. package/dist/providers/ConnectionProvider.d.ts +10 -2
  45. package/dist/providers/ConnectionProvider.d.ts.map +1 -1
  46. package/dist/providers/ConnectionProvider.js +32 -5
  47. package/dist/providers/ConnectionProvider.js.map +1 -1
  48. package/package.json +3 -3
  49. package/src/hooks/useConnection.ts +18 -8
  50. package/src/hooks/useConnectionContext.ts +25 -0
  51. package/src/hooks/useDetectedWallets.ts +6 -10
  52. package/src/hooks/useDisconnect.ts +6 -8
  53. package/src/hooks/useNetworks.ts +2 -8
  54. package/src/hooks/useSession.ts +2 -8
  55. package/src/hooks/useSignMessage.ts +4 -9
  56. package/src/hooks/useSignSolanaTransaction.ts +4 -11
  57. package/src/hooks/useSignTypedData.ts +4 -9
  58. package/src/hooks/useSwitchNetwork.ts +10 -8
  59. package/src/hooks/useTransaction.ts +4 -9
  60. package/src/hooks/useWallet.ts +2 -8
  61. package/src/hooks/useWalletCapabilities.ts +6 -12
  62. package/src/hooks/useWallets.ts +2 -8
  63. package/src/providers/ConnectionProvider.emptyWallets.test.tsx +105 -0
  64. package/src/providers/ConnectionProvider.singleton.test.tsx +18 -7
  65. package/src/providers/ConnectionProvider.tsx +46 -7
@@ -1,8 +1,16 @@
1
1
  import React from 'react';
2
2
  import { UniversalWalletConnector } from '@meshconnect/uwc-core';
3
3
  import type { Session, Network, WalletMetadata, WalletConnectConfig, TonConnectConfig, TronConnectorConfig } from '@meshconnect/uwc-types';
4
- interface ConnectionContextValue {
5
- connector: UniversalWalletConnector;
4
+ export interface ConnectionContextValue {
5
+ /**
6
+ * `null` when the provider is mounted without wallets. `@meshconnect/uwc-core`
7
+ * requires at least one wallet to construct a connector, so a wallet-less
8
+ * session (e.g. a CEX-only catalog) legitimately has none. Hooks treat a null
9
+ * connector as "no wallet session" and return inert values instead of
10
+ * throwing, so a host that mounts this provider for every session — wallet or
11
+ * not — does not crash.
12
+ */
13
+ connector: UniversalWalletConnector | null;
6
14
  session: Session;
7
15
  wallets: WalletMetadata[];
8
16
  networks: Network[];
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectionProvider.d.ts","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,KAAK,EACV,OAAO,EACP,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,wBAAwB,CAAA;AAK/B,UAAU,sBAAsB;IAC9B,SAAS,EAAE,wBAAwB,CAAA;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,QAAA,MAAM,iBAAiB,8CAAqD,CAAA;AAE5E,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;CAC1C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,sBAA8B,EAC9B,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACpB,EAAE,uBAAuB,2CAuCzB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"ConnectionProvider.d.ts","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,KAAK,EACV,OAAO,EACP,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,wBAAwB,CAAA;AAwB/B,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,SAAS,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC1C,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,QAAA,MAAM,iBAAiB,8CAAqD,CAAA;AAE5E,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;CAC1C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,sBAA8B,EAC9B,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACpB,EAAE,uBAAuB,2CAmDzB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
@@ -3,15 +3,42 @@ import React, { createContext, useEffect, useState } from 'react';
3
3
  import { UniversalWalletConnector } from '@meshconnect/uwc-core';
4
4
  // Guard against double initialization
5
5
  let sharedConnector = null;
6
+ /**
7
+ * Session served before any wallet is connected — and while the provider is
8
+ * mounted without wallets (a wallet-less session; see `connector` below). Every
9
+ * field is at its natural empty per the `Session` type, so the read-only hooks
10
+ * (`useSession`/`useNetworks`/`useWallets`) work unchanged and the sign/transfer
11
+ * hooks short-circuit on their existing `activeAddress` guard.
12
+ */
13
+ const EMPTY_SESSION = {
14
+ connectionMode: null,
15
+ activeNetwork: null,
16
+ activeWalletCapabilities: null,
17
+ activeNetworkWalletCapabilities: null,
18
+ activeAddress: null,
19
+ publicKey: null,
20
+ availableNetworks: [],
21
+ activeWallet: null,
22
+ availableAddresses: []
23
+ };
6
24
  const ConnectionContext = createContext(null);
7
25
  export function ConnectionProvider({ children, networks, wallets, usingIntegratedBrowser = false, walletConnectConfig, tonConnectConfig, tronConnectorConfig }) {
8
- if (!sharedConnector) {
26
+ // Core rejects an empty wallet list, so only construct once there is at least
27
+ // one wallet. A wallet-less session keeps `connector` null; the effect below
28
+ // adopts the connector if wallets arrive later (async catalog population).
29
+ if (!sharedConnector && wallets.length > 0) {
9
30
  sharedConnector = new UniversalWalletConnector(networks, wallets, usingIntegratedBrowser, walletConnectConfig, tonConnectConfig, tronConnectorConfig);
10
31
  }
11
32
  const connector = sharedConnector;
12
- const [session, setSession] = useState(() => connector.getSession());
13
- const [isReady, setIsReady] = useState(() => connector.isReady());
33
+ const [session, setSession] = useState(() => connector?.getSession() ?? EMPTY_SESSION);
34
+ const [isReady, setIsReady] = useState(() => connector?.isReady() ?? false);
14
35
  useEffect(() => {
36
+ if (!connector)
37
+ return;
38
+ // Sync once on adoption — covers the case where the connector was created
39
+ // after the first render (wallets populated asynchronously).
40
+ setSession(connector.getSession());
41
+ setIsReady(connector.isReady());
15
42
  const unsubscribe = connector.subscribe(() => {
16
43
  setSession(connector.getSession());
17
44
  setIsReady(connector.isReady());
@@ -21,8 +48,8 @@ export function ConnectionProvider({ children, networks, wallets, usingIntegrate
21
48
  const value = {
22
49
  connector,
23
50
  session,
24
- wallets: connector.getWallets(),
25
- networks: connector.getNetworks(),
51
+ wallets: connector ? connector.getWallets() : wallets,
52
+ networks: connector ? connector.getNetworks() : networks,
26
53
  isReady
27
54
  };
28
55
  return (_jsx(ConnectionContext.Provider, { value: value, children: children }));
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectionProvider.js","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAUhE,sCAAsC;AACtC,IAAI,eAAe,GAAoC,IAAI,CAAA;AAU3D,MAAM,iBAAiB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAA;AAY5E,MAAM,UAAU,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,sBAAsB,GAAG,KAAK,EAC9B,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACK;IACxB,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,eAAe,GAAG,IAAI,wBAAwB,CAC5C,QAAQ,EACR,OAAO,EACP,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,CACpB,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,eAAe,CAAA;IAEjC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAU,GAAG,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAA;IAC7E,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAA;IAEjE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,WAAW,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE;YAC3C,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAA;YAClC,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAA;QACjC,CAAC,CAAC,CAAA;QAEF,OAAO,WAAW,CAAA;IACpB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;IAEf,MAAM,KAAK,GAA2B;QACpC,SAAS;QACT,OAAO;QACP,OAAO,EAAE,SAAS,CAAC,UAAU,EAAE;QAC/B,QAAQ,EAAE,SAAS,CAAC,WAAW,EAAE;QACjC,OAAO;KACR,CAAA;IAED,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACrC,QAAQ,GACkB,CAC9B,CAAA;AACH,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"ConnectionProvider.js","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAUhE,sCAAsC;AACtC,IAAI,eAAe,GAAoC,IAAI,CAAA;AAE3D;;;;;;GAMG;AACH,MAAM,aAAa,GAAY;IAC7B,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,IAAI;IACnB,wBAAwB,EAAE,IAAI;IAC9B,+BAA+B,EAAE,IAAI;IACrC,aAAa,EAAE,IAAI;IACnB,SAAS,EAAE,IAAI;IACf,iBAAiB,EAAE,EAAE;IACrB,YAAY,EAAE,IAAI;IAClB,kBAAkB,EAAE,EAAE;CACvB,CAAA;AAkBD,MAAM,iBAAiB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAA;AAY5E,MAAM,UAAU,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,sBAAsB,GAAG,KAAK,EAC9B,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACK;IACxB,8EAA8E;IAC9E,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,CAAC,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,eAAe,GAAG,IAAI,wBAAwB,CAC5C,QAAQ,EACR,OAAO,EACP,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,CACpB,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,eAAe,CAAA;IAEjC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CACpC,GAAG,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,aAAa,CAC/C,CAAA;IACD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,KAAK,CAAC,CAAA;IAE3E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS;YAAE,OAAM;QAEtB,0EAA0E;QAC1E,6DAA6D;QAC7D,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAA;QAClC,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAA;QAE/B,MAAM,WAAW,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE;YAC3C,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAA;YAClC,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAA;QACjC,CAAC,CAAC,CAAA;QAEF,OAAO,WAAW,CAAA;IACpB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;IAEf,MAAM,KAAK,GAA2B;QACpC,SAAS;QACT,OAAO;QACP,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,OAAO;QACrD,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ;QACxD,OAAO;KACR,CAAA;IAED,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACrC,QAAQ,GACkB,CAC9B,CAAA;AACH,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshconnect/uwc-react",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "React hooks and components for Universal Wallet Connector",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,8 +16,8 @@
16
16
  "src"
17
17
  ],
18
18
  "dependencies": {
19
- "@meshconnect/uwc-core": "1.0.2",
20
- "@meshconnect/uwc-types": "0.16.1"
19
+ "@meshconnect/uwc-types": "0.16.1",
20
+ "@meshconnect/uwc-core": "1.0.2"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "react": "^18.0.0",
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState, useEffect } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState, useEffect } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  NetworkId,
5
5
  UseConnectionReturn,
@@ -26,12 +26,7 @@ import type {
26
26
  * ```
27
27
  */
28
28
  export function useConnection(walletId: string): UseConnectionReturn {
29
- const context = useContext(ConnectionContext)
30
-
31
- if (!context) {
32
- throw new Error('useConnection must be used within a ConnectionProvider')
33
- }
34
-
29
+ const context = useConnectionContext('useConnection')
35
30
  const { connector, isReady } = context
36
31
 
37
32
  const [walletConnectLoading, setWalletConnectLoading] = useState(false)
@@ -52,6 +47,9 @@ export function useConnection(walletId: string): UseConnectionReturn {
52
47
 
53
48
  // Update connection URI and availability when they change
54
49
  useEffect(() => {
50
+ // No connector (wallet-less session): nothing to connect or track.
51
+ if (!connector) return
52
+
55
53
  const checkState = () => {
56
54
  const uri = connector.getConnectionURI()
57
55
 
@@ -89,6 +87,10 @@ export function useConnection(walletId: string): UseConnectionReturn {
89
87
 
90
88
  const connectWalletConnect = useCallback(
91
89
  async (networkId?: NetworkId) => {
90
+ if (!connector) {
91
+ throw new Error('No wallet connected')
92
+ }
93
+
92
94
  setWalletConnectLoading(true)
93
95
  setWalletConnectError(undefined)
94
96
  setWalletConnectURI(undefined)
@@ -123,6 +125,10 @@ export function useConnection(walletId: string): UseConnectionReturn {
123
125
 
124
126
  const connectInjected = useCallback(
125
127
  async (networkId?: NetworkId) => {
128
+ if (!connector) {
129
+ throw new Error('No wallet connected')
130
+ }
131
+
126
132
  setInjectedLoading(true)
127
133
  setInjectedError(undefined)
128
134
  try {
@@ -140,6 +146,10 @@ export function useConnection(walletId: string): UseConnectionReturn {
140
146
 
141
147
  const connectTonConnect = useCallback(
142
148
  async (networkId?: NetworkId) => {
149
+ if (!connector) {
150
+ throw new Error('No wallet connected')
151
+ }
152
+
143
153
  setTonConnectLoading(true)
144
154
  setTonConnectError(undefined)
145
155
  setTonConnectURI(undefined)
@@ -0,0 +1,25 @@
1
+ import { useContext } from 'react'
2
+ import {
3
+ ConnectionContext,
4
+ type ConnectionContextValue
5
+ } from '../providers/ConnectionProvider'
6
+
7
+ /**
8
+ * Reads the connection context, throwing the canonical
9
+ * "must be used within a ConnectionProvider" error when the hook is used
10
+ * outside the provider tree. Centralizes the guard every hook needs so the
11
+ * check and its message live in one place.
12
+ *
13
+ * Note: a non-null context can still carry a `null` connector (a wallet-less
14
+ * session — see `ConnectionContextValue.connector`). Callers that need the
15
+ * connector must handle that case; this guard only covers "no provider".
16
+ *
17
+ * @param hookName - Name used in the error, e.g. `'useSession'`.
18
+ */
19
+ export function useConnectionContext(hookName: string): ConnectionContextValue {
20
+ const context = useContext(ConnectionContext)
21
+ if (!context) {
22
+ throw new Error(`${hookName} must be used within a ConnectionProvider`)
23
+ }
24
+ return context
25
+ }
@@ -1,24 +1,20 @@
1
- import { useContext, useEffect, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useEffect, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  UseDetectedWalletsReturn,
5
5
  WalletMetadata
6
6
  } from '@meshconnect/uwc-types'
7
7
 
8
8
  export function useDetectedWallets(): UseDetectedWalletsReturn {
9
- const context = useContext(ConnectionContext)
10
-
11
- if (!context) {
12
- throw new Error(
13
- 'useInjectedAvailableWallets must be used within a ConnectionProvider'
14
- )
15
- }
16
-
9
+ const context = useConnectionContext('useInjectedAvailableWallets')
17
10
  const { connector, wallets, isReady } = context
18
11
 
19
12
  const [detectedWallets, setDetectedWallets] = useState<WalletMetadata[]>([])
20
13
 
21
14
  useEffect(() => {
15
+ // No connector (wallet-less session): nothing can be detected.
16
+ if (!connector) return
17
+
22
18
  const checkState = () => {
23
19
  const newDetectedWallets = wallets.filter(wallet =>
24
20
  connector.isConnectionModeAvailable('injected', wallet.id)
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
 
4
4
  interface UseDisconnectReturn {
5
5
  disconnect: () => Promise<void>
@@ -27,16 +27,14 @@ interface UseDisconnectReturn {
27
27
  * ```
28
28
  */
29
29
  export function useDisconnect(): UseDisconnectReturn {
30
- const context = useContext(ConnectionContext)
31
-
32
- if (!context) {
33
- throw new Error('useDisconnect must be used within a ConnectionProvider')
34
- }
35
-
30
+ const context = useConnectionContext('useDisconnect')
36
31
  const { connector } = context
37
32
  const [isLoading, setIsLoading] = useState(false)
38
33
 
39
34
  const disconnect = useCallback(async () => {
35
+ // No connector (wallet-less session) means nothing to disconnect.
36
+ if (!connector) return
37
+
40
38
  setIsLoading(true)
41
39
  try {
42
40
  await connector.disconnect()
@@ -1,5 +1,4 @@
1
- import { useContext } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useConnectionContext } from './useConnectionContext'
3
2
  import type { Network } from '@meshconnect/uwc-types'
4
3
 
5
4
  /**
@@ -16,11 +15,6 @@ import type { Network } from '@meshconnect/uwc-types'
16
15
  * ```
17
16
  */
18
17
  export function useNetworks(): Network[] {
19
- const context = useContext(ConnectionContext)
20
-
21
- if (!context) {
22
- throw new Error('useNetworks must be used within a ConnectionProvider')
23
- }
24
-
18
+ const context = useConnectionContext('useNetworks')
25
19
  return context.networks
26
20
  }
@@ -1,5 +1,4 @@
1
- import { useContext } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useConnectionContext } from './useConnectionContext'
3
2
  import type { UseSessionReturn } from '@meshconnect/uwc-types'
4
3
 
5
4
  /**
@@ -18,12 +17,7 @@ import type { UseSessionReturn } from '@meshconnect/uwc-types'
18
17
  * ```
19
18
  */
20
19
  export function useSession(): UseSessionReturn {
21
- const context = useContext(ConnectionContext)
22
-
23
- if (!context) {
24
- throw new Error('useSession must be used within a ConnectionProvider')
25
- }
26
-
20
+ const context = useConnectionContext('useSession')
27
21
  const { session } = context
28
22
 
29
23
  return {
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  SignatureType,
5
5
  UseSignMessageReturn,
@@ -26,12 +26,7 @@ import type {
26
26
  * ```
27
27
  */
28
28
  export function useSignMessage(): UseSignMessageReturn {
29
- const context = useContext(ConnectionContext)
30
-
31
- if (!context) {
32
- throw new Error('useSignMessage must be used within a ConnectionProvider')
33
- }
34
-
29
+ const context = useConnectionContext('useSignMessage')
35
30
  const { connector, session } = context
36
31
  const [isLoading, setIsLoading] = useState(false)
37
32
  const [signature, setSignature] = useState<SignatureType | undefined>()
@@ -39,7 +34,7 @@ export function useSignMessage(): UseSignMessageReturn {
39
34
 
40
35
  const signMessage = useCallback(
41
36
  async (message: string): Promise<SignatureType> => {
42
- if (!session.activeAddress) {
37
+ if (!connector || !session.activeAddress) {
43
38
  throw new Error('No wallet connected')
44
39
  }
45
40
 
@@ -1,26 +1,19 @@
1
- import { useContext, useCallback, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  UseSignSolanaTransactionReturn,
5
5
  WalletError
6
6
  } from '@meshconnect/uwc-types'
7
7
 
8
8
  export function useSignSolanaTransaction(): UseSignSolanaTransactionReturn {
9
- const context = useContext(ConnectionContext)
10
-
11
- if (!context) {
12
- throw new Error(
13
- 'useSignSolanaTransaction must be used within a ConnectionProvider'
14
- )
15
- }
16
-
9
+ const context = useConnectionContext('useSignSolanaTransaction')
17
10
  const { connector, session } = context
18
11
  const [isLoading, setIsLoading] = useState(false)
19
12
  const [error, setError] = useState<WalletError | undefined>()
20
13
 
21
14
  const signSolanaTransaction = useCallback(
22
15
  async (serializedTx: Uint8Array): Promise<Uint8Array> => {
23
- if (!session.activeAddress) {
16
+ if (!connector || !session.activeAddress) {
24
17
  throw new Error('No wallet connected')
25
18
  }
26
19
 
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  EIP712TypedData,
5
5
  UseSignTypedDataReturn,
@@ -30,12 +30,7 @@ import type {
30
30
  * ```
31
31
  */
32
32
  export function useSignTypedData(): UseSignTypedDataReturn {
33
- const context = useContext(ConnectionContext)
34
-
35
- if (!context) {
36
- throw new Error('useSignTypedData must be used within a ConnectionProvider')
37
- }
38
-
33
+ const context = useConnectionContext('useSignTypedData')
39
34
  const { connector, session } = context
40
35
  const [isLoading, setIsLoading] = useState(false)
41
36
  const [signature, setSignature] = useState<string | undefined>()
@@ -43,7 +38,7 @@ export function useSignTypedData(): UseSignTypedDataReturn {
43
38
 
44
39
  const signTypedData = useCallback(
45
40
  async (typedData: EIP712TypedData): Promise<string> => {
46
- if (!session.activeAddress) {
41
+ if (!connector || !session.activeAddress) {
47
42
  throw new Error('No wallet connected')
48
43
  }
49
44
 
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState, useEffect } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState, useEffect } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  NetworkId,
5
5
  UseSwitchNetworkReturn,
@@ -32,12 +32,7 @@ import type {
32
32
  * ```
33
33
  */
34
34
  export function useSwitchNetwork(): UseSwitchNetworkReturn {
35
- const context = useContext(ConnectionContext)
36
-
37
- if (!context) {
38
- throw new Error('useSwitchNetwork must be used within a ConnectionProvider')
39
- }
40
-
35
+ const context = useConnectionContext('useSwitchNetwork')
41
36
  const { connector } = context
42
37
  const [isLoading, setIsLoading] = useState(false)
43
38
  const [isWaitingForUserApproval, setIsWaitingForUserApproval] =
@@ -46,6 +41,9 @@ export function useSwitchNetwork(): UseSwitchNetworkReturn {
46
41
 
47
42
  // Subscribe to loading state changes from the core
48
43
  useEffect(() => {
44
+ // No connector (wallet-less session): no switch state to track.
45
+ if (!connector) return
46
+
49
47
  const updateLoadingState = () => {
50
48
  const state = connector.getNetworkSwitchLoadingState()
51
49
  setIsLoading(state.isLoading)
@@ -63,6 +61,10 @@ export function useSwitchNetwork(): UseSwitchNetworkReturn {
63
61
 
64
62
  const switchNetwork = useCallback(
65
63
  async (networkId: NetworkId) => {
64
+ if (!connector) {
65
+ throw new Error('No wallet connected')
66
+ }
67
+
66
68
  setError(undefined)
67
69
  try {
68
70
  await connector.switchNetwork(networkId)
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  TransactionRequest,
5
5
  TransactionResult,
@@ -30,12 +30,7 @@ import type {
30
30
  * ```
31
31
  */
32
32
  export function useTransaction(): UseTransactionReturn {
33
- const context = useContext(ConnectionContext)
34
-
35
- if (!context) {
36
- throw new Error('useTransaction must be used within a ConnectionProvider')
37
- }
38
-
33
+ const context = useConnectionContext('useTransaction')
39
34
  const { connector, session } = context
40
35
  const [isLoading, setIsLoading] = useState(false)
41
36
  const [transactionResult, setTransactionResult] = useState<
@@ -45,7 +40,7 @@ export function useTransaction(): UseTransactionReturn {
45
40
 
46
41
  const sendTransaction = useCallback(
47
42
  async (request: TransactionRequest): Promise<TransactionResult> => {
48
- if (!session.activeAddress) {
43
+ if (!connector || !session.activeAddress) {
49
44
  throw new Error('No wallet connected')
50
45
  }
51
46
 
@@ -1,5 +1,4 @@
1
- import { useContext } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useConnectionContext } from './useConnectionContext'
3
2
  import type { WalletMetadata, UseWalletReturn } from '@meshconnect/uwc-types'
4
3
  import { WalletConnectorError } from '@meshconnect/uwc-types'
5
4
 
@@ -18,12 +17,7 @@ import { WalletConnectorError } from '@meshconnect/uwc-types'
18
17
  * }
19
18
  */
20
19
  export function useWallet(walletId: string): UseWalletReturn {
21
- const context = useContext(ConnectionContext)
22
-
23
- if (!context) {
24
- throw new Error('useWallet must be used within a ConnectionProvider')
25
- }
26
-
20
+ const context = useConnectionContext('useWallet')
27
21
  const selectedWallet = context.wallets.find(
28
22
  (wallet: WalletMetadata) => wallet.id === walletId
29
23
  )
@@ -1,5 +1,5 @@
1
- import { useContext, useCallback, useState } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useCallback, useState } from 'react'
2
+ import { useConnectionContext } from './useConnectionContext'
3
3
  import type {
4
4
  UseWalletCapabilitiesReturn,
5
5
  WalletError
@@ -27,14 +27,7 @@ import type {
27
27
  * ```
28
28
  */
29
29
  export function useWalletCapabilities(): UseWalletCapabilitiesReturn {
30
- const context = useContext(ConnectionContext)
31
-
32
- if (!context) {
33
- throw new Error(
34
- 'useWalletCapabilities must be used within a ConnectionProvider'
35
- )
36
- }
37
-
30
+ const context = useConnectionContext('useWalletCapabilities')
38
31
  const { connector, session } = context
39
32
 
40
33
  const [isLoading, setIsLoading] = useState(false)
@@ -42,8 +35,9 @@ export function useWalletCapabilities(): UseWalletCapabilitiesReturn {
42
35
 
43
36
  // Manual fetch function to be called from the app
44
37
  const fetchCapabilities = useCallback(async () => {
45
- // If no active session or address, clear capabilities
46
- if (!session?.activeAddress || !session?.connectionMode) {
38
+ // No connector (wallet-less session) or no active session/address: nothing
39
+ // to fetch.
40
+ if (!connector || !session?.activeAddress || !session?.connectionMode) {
47
41
  return
48
42
  }
49
43
 
@@ -1,5 +1,4 @@
1
- import { useContext } from 'react'
2
- import { ConnectionContext } from '../providers/ConnectionProvider'
1
+ import { useConnectionContext } from './useConnectionContext'
3
2
  import type { WalletMetadata } from '@meshconnect/uwc-types'
4
3
 
5
4
  /**
@@ -16,11 +15,6 @@ import type { WalletMetadata } from '@meshconnect/uwc-types'
16
15
  * ```
17
16
  */
18
17
  export function useWallets(): WalletMetadata[] {
19
- const context = useContext(ConnectionContext)
20
-
21
- if (!context) {
22
- throw new Error('useWallets must be used within a ConnectionProvider')
23
- }
24
-
18
+ const context = useConnectionContext('useWallets')
25
19
  return context.wallets
26
20
  }
@@ -0,0 +1,105 @@
1
+ import { useContext, act } from 'react'
2
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
3
+ import { createRoot } from 'react-dom/client'
4
+
5
+ /**
6
+ * A wallet-less session (e.g. a CEX-only catalog) mounts the provider with an
7
+ * empty wallet list. `@meshconnect/uwc-core` rejects an empty wallet list, so
8
+ * the provider must NOT construct a connector — it serves a null connector and
9
+ * an empty session, and the hooks stay inert instead of crashing. This guards
10
+ * the exact regression where bumping core to a version that throws on empty
11
+ * wallets took down the whole widget for CEX-only sessions.
12
+ */
13
+ describe('ConnectionProvider — wallet-less session', () => {
14
+ beforeEach(() => {
15
+ vi.resetModules()
16
+ })
17
+
18
+ it('does not construct a connector and exposes a null connector + empty session', async () => {
19
+ vi.doMock('@meshconnect/uwc-core', () => {
20
+ class UniversalWalletConnector {
21
+ static calls = 0
22
+ constructor() {
23
+ UniversalWalletConnector.calls++
24
+ }
25
+ }
26
+ return { UniversalWalletConnector }
27
+ })
28
+
29
+ const { ConnectionProvider, ConnectionContext } =
30
+ await import('./ConnectionProvider')
31
+ const { useSession } = await import('../hooks/useSession')
32
+ const { UniversalWalletConnector } = await import('@meshconnect/uwc-core')
33
+
34
+ let connector: unknown = 'unset'
35
+ let activeAddress: unknown = 'unset'
36
+ let availableNetworks: unknown = 'unset'
37
+
38
+ function Capture() {
39
+ const ctx = useContext(ConnectionContext)
40
+ connector = ctx?.connector
41
+ const session = useSession()
42
+ activeAddress = session.activeAddress
43
+ availableNetworks = session.availableNetworks
44
+ return null
45
+ }
46
+
47
+ const container = document.createElement('div')
48
+ document.body.appendChild(container)
49
+ const root = createRoot(container)
50
+
51
+ await act(async () => {
52
+ root.render(
53
+ <ConnectionProvider networks={[]} wallets={[]}>
54
+ <Capture />
55
+ </ConnectionProvider>
56
+ )
57
+ })
58
+
59
+ // Core was never constructed (its guard would have thrown on []).
60
+ // @ts-expect-error test-only property
61
+ expect(UniversalWalletConnector.calls).toBe(0)
62
+ expect(connector).toBeNull()
63
+ // Read-only session hooks return the empty session, not a crash.
64
+ expect(activeAddress).toBeNull()
65
+ expect(availableNetworks).toEqual([])
66
+
67
+ await act(async () => {
68
+ root.unmount()
69
+ })
70
+ })
71
+
72
+ it('keeps action hooks inert — switchNetwork rejects with "No wallet connected"', async () => {
73
+ const { ConnectionProvider } = await import('./ConnectionProvider')
74
+ const { useSwitchNetwork } = await import('../hooks/useSwitchNetwork')
75
+
76
+ let switchNetwork:
77
+ | ((networkId: `${string}:${string}`) => Promise<void>)
78
+ | undefined
79
+
80
+ function Capture() {
81
+ switchNetwork = useSwitchNetwork().switchNetwork
82
+ return null
83
+ }
84
+
85
+ const container = document.createElement('div')
86
+ document.body.appendChild(container)
87
+ const root = createRoot(container)
88
+
89
+ await act(async () => {
90
+ root.render(
91
+ <ConnectionProvider networks={[]} wallets={[]}>
92
+ <Capture />
93
+ </ConnectionProvider>
94
+ )
95
+ })
96
+
97
+ await expect(switchNetwork?.('eip155:1')).rejects.toThrow(
98
+ 'No wallet connected'
99
+ )
100
+
101
+ await act(async () => {
102
+ root.unmount()
103
+ })
104
+ })
105
+ })