@account-kit/privy-integration 4.75.4 → 4.76.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 (94) hide show
  1. package/README.md +165 -6
  2. package/dist/esm/Provider.d.ts +4 -71
  3. package/dist/esm/Provider.js +4 -109
  4. package/dist/esm/Provider.js.map +1 -1
  5. package/dist/esm/Provider.native.d.ts +6 -0
  6. package/dist/esm/Provider.native.js +13 -0
  7. package/dist/esm/Provider.native.js.map +1 -0
  8. package/dist/esm/adapters/react-native.d.ts +6 -0
  9. package/dist/esm/adapters/react-native.js +132 -0
  10. package/dist/esm/adapters/react-native.js.map +1 -0
  11. package/dist/esm/adapters/types.d.ts +56 -0
  12. package/dist/esm/adapters/types.js +2 -0
  13. package/dist/esm/adapters/types.js.map +1 -0
  14. package/dist/esm/adapters/web.d.ts +6 -0
  15. package/dist/esm/adapters/web.js +71 -0
  16. package/dist/esm/adapters/web.js.map +1 -0
  17. package/dist/esm/adapters/web.native.d.ts +5 -0
  18. package/dist/esm/adapters/web.native.js +6 -0
  19. package/dist/esm/adapters/web.native.js.map +1 -0
  20. package/dist/esm/context/AlchemyContext.d.ts +71 -0
  21. package/dist/esm/context/AlchemyContext.js +115 -0
  22. package/dist/esm/context/AlchemyContext.js.map +1 -0
  23. package/dist/esm/hooks/internal/useEmbeddedWallet.d.ts +3 -4
  24. package/dist/esm/hooks/internal/useEmbeddedWallet.js +6 -13
  25. package/dist/esm/hooks/internal/useEmbeddedWallet.js.map +1 -1
  26. package/dist/esm/hooks/useAlchemyClient.js +17 -24
  27. package/dist/esm/hooks/useAlchemyClient.js.map +1 -1
  28. package/dist/esm/hooks/useAlchemySendTransaction.js +1 -1
  29. package/dist/esm/hooks/useAlchemySendTransaction.js.map +1 -1
  30. package/dist/esm/hooks/useAlchemySolanaTransaction.js +1 -1
  31. package/dist/esm/hooks/useAlchemySolanaTransaction.js.map +1 -1
  32. package/dist/esm/providers/ReactNativeProvider.d.ts +37 -0
  33. package/dist/esm/providers/ReactNativeProvider.js +41 -0
  34. package/dist/esm/providers/ReactNativeProvider.js.map +1 -0
  35. package/dist/esm/providers/WebProvider.d.ts +37 -0
  36. package/dist/esm/providers/WebProvider.js +41 -0
  37. package/dist/esm/providers/WebProvider.js.map +1 -0
  38. package/dist/esm/providers/WebProvider.native.d.ts +5 -0
  39. package/dist/esm/providers/WebProvider.native.js +9 -0
  40. package/dist/esm/providers/WebProvider.native.js.map +1 -0
  41. package/dist/esm/react-native.d.ts +11 -0
  42. package/dist/esm/react-native.js +13 -0
  43. package/dist/esm/react-native.js.map +1 -0
  44. package/dist/esm/types.d.ts +6 -0
  45. package/dist/esm/types.js.map +1 -1
  46. package/dist/esm/version.d.ts +1 -1
  47. package/dist/esm/version.js +1 -1
  48. package/dist/esm/version.js.map +1 -1
  49. package/dist/types/Provider.d.ts +4 -71
  50. package/dist/types/Provider.d.ts.map +1 -1
  51. package/dist/types/Provider.native.d.ts +7 -0
  52. package/dist/types/Provider.native.d.ts.map +1 -0
  53. package/dist/types/adapters/react-native.d.ts +7 -0
  54. package/dist/types/adapters/react-native.d.ts.map +1 -0
  55. package/dist/types/adapters/types.d.ts +57 -0
  56. package/dist/types/adapters/types.d.ts.map +1 -0
  57. package/dist/types/adapters/web.d.ts +7 -0
  58. package/dist/types/adapters/web.d.ts.map +1 -0
  59. package/dist/types/adapters/web.native.d.ts +6 -0
  60. package/dist/types/adapters/web.native.d.ts.map +1 -0
  61. package/dist/types/context/AlchemyContext.d.ts +72 -0
  62. package/dist/types/context/AlchemyContext.d.ts.map +1 -0
  63. package/dist/types/hooks/internal/useEmbeddedWallet.d.ts +3 -4
  64. package/dist/types/hooks/internal/useEmbeddedWallet.d.ts.map +1 -1
  65. package/dist/types/hooks/useAlchemyClient.d.ts.map +1 -1
  66. package/dist/types/providers/ReactNativeProvider.d.ts +38 -0
  67. package/dist/types/providers/ReactNativeProvider.d.ts.map +1 -0
  68. package/dist/types/providers/WebProvider.d.ts +38 -0
  69. package/dist/types/providers/WebProvider.d.ts.map +1 -0
  70. package/dist/types/providers/WebProvider.native.d.ts +6 -0
  71. package/dist/types/providers/WebProvider.native.d.ts.map +1 -0
  72. package/dist/types/react-native.d.ts +12 -0
  73. package/dist/types/react-native.d.ts.map +1 -0
  74. package/dist/types/types.d.ts +6 -0
  75. package/dist/types/types.d.ts.map +1 -1
  76. package/dist/types/version.d.ts +1 -1
  77. package/package.json +27 -4
  78. package/src/Provider.native.tsx +18 -0
  79. package/src/Provider.tsx +6 -0
  80. package/src/adapters/react-native.ts +210 -0
  81. package/src/adapters/types.ts +71 -0
  82. package/src/adapters/web.native.ts +6 -0
  83. package/src/adapters/web.ts +113 -0
  84. package/src/context/AlchemyContext.tsx +185 -0
  85. package/src/hooks/internal/useEmbeddedWallet.ts +6 -20
  86. package/src/hooks/useAlchemyClient.ts +24 -38
  87. package/src/hooks/useAlchemySendTransaction.ts +1 -1
  88. package/src/hooks/useAlchemySolanaTransaction.ts +1 -1
  89. package/src/providers/ReactNativeProvider.tsx +49 -0
  90. package/src/providers/WebProvider.native.tsx +11 -0
  91. package/src/providers/WebProvider.tsx +49 -0
  92. package/src/react-native.ts +29 -0
  93. package/src/types.ts +7 -0
  94. package/src/version.ts +1 -1
@@ -0,0 +1,71 @@
1
+ import type { Address, Authorization } from "viem";
2
+ import type { AuthorizationRequest } from "@aa-sdk/core";
3
+
4
+ /**
5
+ * Platform-agnostic embedded wallet interface
6
+ * Abstracts differences between @privy-io/react-auth and @privy-io/expo
7
+ */
8
+ export interface EmbeddedWallet {
9
+ /** Wallet address */
10
+ address: Address;
11
+
12
+ /** Chain ID as a string (may be CAIP-2 format like "eip155:1" or numeric string like "1") */
13
+ chainId: string;
14
+
15
+ /** Get EVM provider for the wallet */
16
+ getEthereumProvider(): Promise<any>;
17
+ }
18
+
19
+ /**
20
+ * Platform-agnostic Privy auth state
21
+ */
22
+ export interface PrivyAuthState {
23
+ /** Whether user is authenticated */
24
+ authenticated: boolean;
25
+
26
+ /** User object (platform-specific, used for cache invalidation) */
27
+ user: any;
28
+ }
29
+
30
+ /**
31
+ * Adapter interface that each platform must implement
32
+ * Provides platform-specific Privy functionality
33
+ */
34
+ export interface PrivyAdapter {
35
+ /**
36
+ * Hook to get embedded wallet
37
+ * Must be called as a React hook (follows rules of hooks)
38
+ *
39
+ * @param preferredAddress - Optional address to find a specific wallet
40
+ */
41
+ useEmbeddedWallet(preferredAddress?: string): () => EmbeddedWallet;
42
+
43
+ /**
44
+ * Hook to get Privy authentication state
45
+ * Must be called as a React hook (follows rules of hooks)
46
+ */
47
+ usePrivyAuth(): PrivyAuthState;
48
+
49
+ /**
50
+ * Hook to get current wallet address (for cache invalidation)
51
+ * Returns undefined if no wallet is available
52
+ * Must be called as a React hook (follows rules of hooks)
53
+ *
54
+ * @param preferredAddress - Optional address to find a specific wallet
55
+ */
56
+ useWalletAddress(preferredAddress?: string): string | undefined;
57
+
58
+ /**
59
+ * Hook to get EIP-7702 authorization signer (optional, web only)
60
+ * Must be called as a React hook (follows rules of hooks)
61
+ *
62
+ * @param preferredAddress - Optional address to find a specific wallet
63
+ */
64
+ useAuthorizationSigner?(
65
+ preferredAddress?: string,
66
+ ):
67
+ | ((
68
+ auth: AuthorizationRequest<number>,
69
+ ) => Promise<Authorization<number, true>>)
70
+ | null;
71
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * React Native stub for web adapter
3
+ * This file prevents Metro from importing @privy-io/react-auth
4
+ */
5
+
6
+ export const webAdapter = null;
@@ -0,0 +1,113 @@
1
+ import { useCallback } from "react";
2
+ import {
3
+ useWallets,
4
+ usePrivy,
5
+ useSign7702Authorization,
6
+ type ConnectedWallet as PrivyWallet,
7
+ } from "@privy-io/react-auth";
8
+ import { isAddressEqual, type Authorization } from "viem";
9
+ import type { AuthorizationRequest } from "@aa-sdk/core";
10
+ import type { PrivyAdapter, EmbeddedWallet, PrivyAuthState } from "./types.js";
11
+
12
+ /**
13
+ * Web adapter for @privy-io/react-auth
14
+ * Implements platform-specific hooks for React web applications
15
+ */
16
+ export const webAdapter: PrivyAdapter = {
17
+ useEmbeddedWallet(preferredAddress?: string) {
18
+ const { wallets } = useWallets();
19
+
20
+ const getEmbeddedWallet = useCallback((): EmbeddedWallet => {
21
+ const privyWallets = wallets.filter(
22
+ (w) => w.walletClientType === "privy",
23
+ );
24
+
25
+ if (privyWallets.length === 0) {
26
+ throw new Error(
27
+ "Privy embedded wallet not found. Please ensure the user is authenticated.",
28
+ );
29
+ }
30
+
31
+ // If a preferred address is specified, find that wallet
32
+ const embedded = preferredAddress
33
+ ? privyWallets.find((w) =>
34
+ isAddressEqual(
35
+ w.address as `0x${string}`,
36
+ preferredAddress as `0x${string}`,
37
+ ),
38
+ )
39
+ : privyWallets[0];
40
+
41
+ if (!embedded) {
42
+ throw new Error(
43
+ preferredAddress
44
+ ? `Privy embedded wallet with address ${preferredAddress} not found.`
45
+ : "Privy embedded wallet not found. Please ensure the user is authenticated.",
46
+ );
47
+ }
48
+
49
+ return adaptWebWallet(embedded);
50
+ }, [wallets, preferredAddress]);
51
+
52
+ return getEmbeddedWallet;
53
+ },
54
+
55
+ usePrivyAuth(): PrivyAuthState {
56
+ const { user } = usePrivy();
57
+ return { authenticated: !!user, user };
58
+ },
59
+
60
+ useWalletAddress(preferredAddress?: string): string | undefined {
61
+ const { wallets } = useWallets();
62
+ const privyWallets = wallets.filter((w) => w.walletClientType === "privy");
63
+
64
+ // If a preferred address is specified, find that wallet
65
+ if (preferredAddress) {
66
+ const wallet = privyWallets.find((w) =>
67
+ isAddressEqual(
68
+ w.address as `0x${string}`,
69
+ preferredAddress as `0x${string}`,
70
+ ),
71
+ );
72
+ return wallet?.address;
73
+ }
74
+
75
+ // Otherwise return the first embedded wallet
76
+ return privyWallets[0]?.address;
77
+ },
78
+
79
+ useAuthorizationSigner(_preferredAddress?: string) {
80
+ const { signAuthorization } = useSign7702Authorization();
81
+
82
+ return useCallback(
83
+ async (
84
+ unsignedAuth: AuthorizationRequest<number>,
85
+ ): Promise<Authorization<number, true>> => {
86
+ const signature = await signAuthorization({
87
+ ...unsignedAuth,
88
+ contractAddress: unsignedAuth.address ?? unsignedAuth.contractAddress,
89
+ });
90
+
91
+ return {
92
+ ...unsignedAuth,
93
+ ...signature,
94
+ };
95
+ },
96
+ [signAuthorization],
97
+ );
98
+ },
99
+ };
100
+
101
+ /**
102
+ * Adapts a Privy web wallet to the common EmbeddedWallet interface
103
+ *
104
+ * @param {PrivyWallet} wallet - The Privy web wallet to adapt
105
+ * @returns {EmbeddedWallet} The adapted wallet following the common interface
106
+ */
107
+ function adaptWebWallet(wallet: PrivyWallet): EmbeddedWallet {
108
+ return {
109
+ address: wallet.address as `0x${string}`,
110
+ chainId: wallet.chainId || "1",
111
+ getEthereumProvider: () => wallet.getEthereumProvider(),
112
+ };
113
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ type PropsWithChildren,
3
+ createContext,
4
+ useContext,
5
+ useRef,
6
+ useEffect,
7
+ } from "react";
8
+ import type { SmartWalletClient } from "@account-kit/wallet-client";
9
+ import type { SmartContractAccount } from "@aa-sdk/core";
10
+ import type { AlchemyProviderConfig } from "../types.js";
11
+ import type { PrivyAdapter } from "../adapters/types.js";
12
+
13
+ /**
14
+ * Normalized config with defaults applied
15
+ *
16
+ * @internal
17
+ */
18
+ export type NormalizedAlchemyConfig = AlchemyProviderConfig &
19
+ Required<Pick<AlchemyProviderConfig, "accountAuthMode">>;
20
+
21
+ /**
22
+ * Context for Alchemy configuration
23
+ */
24
+ const AlchemyConfigContext = createContext<NormalizedAlchemyConfig | null>(
25
+ null,
26
+ );
27
+
28
+ /**
29
+ * Context for the platform adapter
30
+ */
31
+ const AdapterContext = createContext<PrivyAdapter | null>(null);
32
+
33
+ /**
34
+ * Client cache stored in React tree (similar to QueryClient in React Query)
35
+ *
36
+ * @internal
37
+ */
38
+ interface ClientCache {
39
+ client: SmartWalletClient | null;
40
+ account: SmartContractAccount | null;
41
+ cacheKey: string | null;
42
+ }
43
+
44
+ const ClientCacheContext = createContext<ClientCache | null>(null);
45
+
46
+ /**
47
+ * Props for AlchemyContextProvider
48
+ */
49
+ interface AlchemyContextProviderProps extends PropsWithChildren {
50
+ config: AlchemyProviderConfig;
51
+ adapter: PrivyAdapter;
52
+ }
53
+
54
+ /**
55
+ * Internal provider component that manages Alchemy context
56
+ * Used by both web and React Native providers
57
+ *
58
+ * @internal
59
+ * @param {AlchemyContextProviderProps} props - Component props
60
+ * @param {React.ReactNode} props.children - React children to wrap with context
61
+ * @param {AlchemyProviderConfig} props.config - Alchemy configuration
62
+ * @param {PrivyAdapter} props.adapter - Platform adapter
63
+ * @returns {JSX.Element} Context provider component
64
+ */
65
+ export function AlchemyContextProvider({
66
+ children,
67
+ config,
68
+ adapter,
69
+ }: AlchemyContextProviderProps) {
70
+ const { authenticated } = adapter.usePrivyAuth();
71
+ const walletAddress = adapter.useWalletAddress(config.walletAddress);
72
+
73
+ // Normalize config with default values
74
+ const normalizedConfig: NormalizedAlchemyConfig = {
75
+ ...config,
76
+ accountAuthMode: config.accountAuthMode ?? "eip7702",
77
+ };
78
+
79
+ // Store cache in a ref - persists across renders but scoped to this component instance
80
+ // This makes it SSR-safe (each request gets its own cache) and React StrictMode-safe
81
+ const cache = useRef<ClientCache>({
82
+ client: null,
83
+ account: null,
84
+ cacheKey: null,
85
+ });
86
+
87
+ // Track previous state to detect logout and wallet changes
88
+ const prevAuthenticatedRef = useRef(authenticated);
89
+ const prevWalletAddressRef = useRef(walletAddress);
90
+
91
+ // Automatically reset cache when user logs out or switches wallets
92
+ useEffect(() => {
93
+ const wasAuthenticated = prevAuthenticatedRef.current;
94
+ const prevWalletAddress = prevWalletAddressRef.current;
95
+ const currentWalletAddress = walletAddress;
96
+
97
+ // Reset cache on logout
98
+ if (wasAuthenticated && !authenticated) {
99
+ cache.current.client = null;
100
+ cache.current.account = null;
101
+ cache.current.cacheKey = null;
102
+ }
103
+
104
+ // Reset cache on wallet address change (account switching)
105
+ if (
106
+ authenticated &&
107
+ prevWalletAddress &&
108
+ currentWalletAddress &&
109
+ prevWalletAddress !== currentWalletAddress
110
+ ) {
111
+ cache.current.client = null;
112
+ cache.current.account = null;
113
+ cache.current.cacheKey = null;
114
+ }
115
+
116
+ // Update refs for next render
117
+ prevAuthenticatedRef.current = authenticated;
118
+ prevWalletAddressRef.current = currentWalletAddress;
119
+ }, [authenticated, walletAddress]);
120
+
121
+ return (
122
+ <AlchemyConfigContext.Provider value={normalizedConfig}>
123
+ <AdapterContext.Provider value={adapter}>
124
+ <ClientCacheContext.Provider value={cache.current}>
125
+ {children}
126
+ </ClientCacheContext.Provider>
127
+ </AdapterContext.Provider>
128
+ </AlchemyConfigContext.Provider>
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Hook to access Alchemy provider configuration
134
+ * Must be used within an <AlchemyProvider> component
135
+ *
136
+ * @returns {NormalizedAlchemyConfig} The current Alchemy configuration with defaults applied
137
+ * @throws {Error} If used outside of AlchemyProvider
138
+ *
139
+ * @example
140
+ * ```tsx
141
+ * const config = useAlchemyConfig();
142
+ * console.log('Policy ID:', config.policyId);
143
+ * console.log('Auth Mode:', config.accountAuthMode); // Always defined
144
+ * ```
145
+ */
146
+ export function useAlchemyConfig(): NormalizedAlchemyConfig {
147
+ const context = useContext(AlchemyConfigContext);
148
+ if (!context) {
149
+ throw new Error("useAlchemyConfig must be used within <AlchemyProvider />");
150
+ }
151
+ return context;
152
+ }
153
+
154
+ /**
155
+ * Hook to access the platform adapter
156
+ * Must be used within an <AlchemyProvider> component
157
+ *
158
+ * @internal
159
+ * @returns {PrivyAdapter} The platform adapter
160
+ */
161
+ export function useAdapter(): PrivyAdapter {
162
+ const context = useContext(AdapterContext);
163
+ if (!context) {
164
+ throw new Error(
165
+ "useAdapter must be used within <AlchemyProvider />. Make sure AlchemyProvider is nested inside PrivyProvider.",
166
+ );
167
+ }
168
+ return context;
169
+ }
170
+
171
+ /**
172
+ * Hook to access the client cache (internal use only)
173
+ *
174
+ * @internal
175
+ * @returns {ClientCache} The client cache object
176
+ */
177
+ export function useClientCache(): ClientCache {
178
+ const context = useContext(ClientCacheContext);
179
+ if (!context) {
180
+ throw new Error(
181
+ "useClientCache must be used within <AlchemyProvider />. Make sure AlchemyProvider is nested inside PrivyProvider.",
182
+ );
183
+ }
184
+ return context;
185
+ }
@@ -1,29 +1,15 @@
1
- import { useCallback } from "react";
2
- import {
3
- useWallets,
4
- type ConnectedWallet as PrivyWallet,
5
- } from "@privy-io/react-auth";
1
+ import { useAdapter, useAlchemyConfig } from "../../context/AlchemyContext.js";
6
2
 
7
3
  /**
8
4
  * Internal hook to get the Privy embedded wallet
9
- * Shared across multiple hooks to avoid duplication
5
+ * Uses the platform adapter to abstract differences between web and React Native
10
6
  *
11
7
  * @internal
12
- * @returns {() => PrivyWallet} Function that returns the embedded wallet
8
+ * @returns {() => EmbeddedWallet} Function that returns the embedded wallet
13
9
  * @throws {Error} If embedded wallet is not found
14
10
  */
15
11
  export function useEmbeddedWallet() {
16
- const { wallets } = useWallets();
17
-
18
- const getEmbeddedWallet = useCallback((): PrivyWallet => {
19
- const embedded = wallets.find((w) => w.walletClientType === "privy");
20
- if (!embedded) {
21
- throw new Error(
22
- "Privy embedded wallet not found. Please ensure the user is authenticated.",
23
- );
24
- }
25
- return embedded;
26
- }, [wallets]);
27
-
28
- return getEmbeddedWallet;
12
+ const adapter = useAdapter();
13
+ const config = useAlchemyConfig();
14
+ return adapter.useEmbeddedWallet(config.walletAddress);
29
15
  }
@@ -1,23 +1,20 @@
1
1
  import { useCallback } from "react";
2
2
  import {
3
3
  WalletClientSigner,
4
- type AuthorizationRequest,
5
4
  ConnectionConfigSchema,
6
5
  type SmartContractAccount,
7
6
  } from "@aa-sdk/core";
8
- import {
9
- createWalletClient,
10
- custom,
11
- type Address,
12
- type Authorization,
13
- } from "viem";
14
- import { useSign7702Authorization } from "@privy-io/react-auth";
7
+ import { createWalletClient, custom, type Address } from "viem";
15
8
  import {
16
9
  createSmartWalletClient,
17
10
  type SmartWalletClient,
18
11
  } from "@account-kit/wallet-client";
19
12
  import { alchemy } from "@account-kit/infra";
20
- import { useAlchemyConfig, useClientCache } from "../Provider.js";
13
+ import {
14
+ useAlchemyConfig,
15
+ useClientCache,
16
+ useAdapter,
17
+ } from "../context/AlchemyContext.js";
21
18
  import { getChain } from "../util/getChain.js";
22
19
  import { useEmbeddedWallet } from "./internal/useEmbeddedWallet.js";
23
20
 
@@ -40,42 +37,43 @@ export type AlchemyClientResult = {
40
37
  * ```
41
38
  */
42
39
  export function useAlchemyClient() {
43
- const { signAuthorization } = useSign7702Authorization();
40
+ const adapter = useAdapter();
44
41
  const config = useAlchemyConfig();
42
+ const signAuthorizationFn =
43
+ adapter.useAuthorizationSigner?.(config.walletAddress) || null;
45
44
  const cache = useClientCache();
46
45
  const getEmbeddedWallet = useEmbeddedWallet();
47
46
 
48
- const getEmbeddedWalletChain = useCallback(() => {
49
- const embedded = getEmbeddedWallet();
47
+ const getClient = useCallback(async (): Promise<AlchemyClientResult> => {
48
+ const embeddedWallet = getEmbeddedWallet();
49
+
50
+ // IMPORTANT: Get provider FIRST to ensure chain ID is updated
51
+ // The provider fetch triggers chain ID update in the adapter
52
+ const provider = await embeddedWallet.getEthereumProvider();
53
+
54
+ // NOW get the chain from the SAME wallet instance with updated chain ID
50
55
  // Handle CAIP-2 format like "eip155:1"
51
- const chainIdStr = embedded.chainId?.toString();
56
+ const chainIdStr = embeddedWallet.chainId?.toString();
52
57
 
53
58
  if (!chainIdStr) {
54
59
  throw new Error(
55
60
  "Embedded wallet chainId is not set. Please ensure the wallet is connected to a network.",
56
61
  );
57
62
  }
58
-
59
63
  const numericChainId = chainIdStr.includes(":")
60
64
  ? chainIdStr.split(":")[1]
61
65
  : chainIdStr;
62
-
63
66
  const parsedChainId = Number(numericChainId);
64
-
65
67
  if (isNaN(parsedChainId)) {
66
68
  throw new Error(
67
69
  `Failed to parse chainId from embedded wallet. Received: ${chainIdStr}`,
68
70
  );
69
71
  }
70
72
 
71
- return getChain(parsedChainId);
72
- }, [getEmbeddedWallet]);
73
-
74
- const getClient = useCallback(async (): Promise<AlchemyClientResult> => {
75
- const embeddedWallet = getEmbeddedWallet();
76
- const chain = getEmbeddedWalletChain();
73
+ const chain = getChain(parsedChainId);
77
74
 
78
75
  // Generate a cache key based on configuration and wallet address
76
+ // IMPORTANT: Include whether authorization signing is supported in cache key
79
77
  const currentCacheKey = JSON.stringify({
80
78
  address: embeddedWallet.address,
81
79
  chainId: chain.id,
@@ -84,6 +82,7 @@ export function useAlchemyClient() {
84
82
  rpcUrl: config.rpcUrl,
85
83
  policyId: config.policyId,
86
84
  accountAuthMode: config.accountAuthMode,
85
+ supportsSignAuthorization: !!signAuthorizationFn,
87
86
  });
88
87
 
89
88
  // Return cached client and account if configuration hasn't changed
@@ -91,9 +90,6 @@ export function useAlchemyClient() {
91
90
  return { client: cache.client, account: cache.account };
92
91
  }
93
92
 
94
- // Configuration changed or no cache exists, create new client
95
- const provider = await embeddedWallet.getEthereumProvider();
96
-
97
93
  // Create base signer from Privy wallet
98
94
  const baseSigner = new WalletClientSigner(
99
95
  createWalletClient({
@@ -106,19 +102,10 @@ export function useAlchemyClient() {
106
102
 
107
103
  // Optionally extend signer with EIP-7702 authorization support
108
104
  const signer =
109
- config.accountAuthMode === "eip7702"
105
+ config.accountAuthMode === "eip7702" && !!signAuthorizationFn
110
106
  ? {
111
107
  ...baseSigner,
112
- signAuthorization: async (
113
- unsignedAuth: AuthorizationRequest<number>,
114
- ): Promise<Authorization<number, true>> => {
115
- const signature = await signAuthorization({
116
- ...unsignedAuth,
117
- contractAddress:
118
- unsignedAuth.address ?? unsignedAuth.contractAddress,
119
- });
120
- return { ...unsignedAuth, ...signature };
121
- },
108
+ signAuthorization: signAuthorizationFn,
122
109
  }
123
110
  : baseSigner;
124
111
 
@@ -166,8 +153,7 @@ export function useAlchemyClient() {
166
153
  return { client: cache.client, account: cache.account };
167
154
  }, [
168
155
  getEmbeddedWallet,
169
- getEmbeddedWalletChain,
170
- signAuthorization,
156
+ signAuthorizationFn,
171
157
  config.apiKey,
172
158
  config.jwt,
173
159
  config.rpcUrl,
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useState } from "react";
2
2
  import { type Hex, isHex } from "viem";
3
3
  import { useAlchemyClient } from "./useAlchemyClient.js";
4
- import { useAlchemyConfig } from "../Provider.js";
4
+ import { useAlchemyConfig } from "../context/AlchemyContext.js";
5
5
  import type {
6
6
  UnsignedTransactionRequest,
7
7
  SendTransactionOptions,
@@ -7,7 +7,7 @@ import {
7
7
  TransactionInstruction,
8
8
  VersionedTransaction,
9
9
  } from "@solana/web3.js";
10
- import { useAlchemyConfig } from "../Provider.js";
10
+ import { useAlchemyConfig } from "../context/AlchemyContext.js";
11
11
  import { createSolanaSponsoredTransaction } from "../util/createSolanaSponsoredTransaction.js";
12
12
  import { useSignTransaction, useWallets } from "@privy-io/react-auth/solana";
13
13
  import { createSolanaTransaction } from "../util/createSolanaTransaction.js";
@@ -0,0 +1,49 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import { AlchemyContextProvider } from "../context/AlchemyContext.js";
3
+ import { reactNativeAdapter } from "../adapters/react-native.js";
4
+ import type { AlchemyProviderConfig } from "../types.js";
5
+
6
+ /**
7
+ * Provider component for React Native (Expo) applications
8
+ * Must be nested INSIDE PrivyProvider from @privy-io/expo
9
+ *
10
+ * @param {PropsWithChildren<AlchemyProviderConfig>} props - Component props
11
+ * @param {React.ReactNode} props.children - React children to wrap with Alchemy configuration
12
+ * @param {string} [props.apiKey] - Your Alchemy API key
13
+ * @param {string} [props.jwt] - JWT token for authentication
14
+ * @param {string} [props.rpcUrl] - Custom RPC URL for EVM chains
15
+ * @param {string} [props.solanaRpcUrl] - Custom RPC URL for Solana
16
+ * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s) for EVM chains
17
+ * @param {string | string[]} [props.solanaPolicyId] - Gas Manager policy ID(s) for Solana
18
+ * @param {boolean} [props.disableSponsorship] - Set to true to disable sponsorship by default (default: false)
19
+ * @param {'eip7702' | 'owner'} [props.accountAuthMode] - Authorization mode for EVM smart accounts (default: 'eip7702')
20
+ * @param {string} [props.walletAddress] - Optional: Specify which wallet address to use (defaults to first wallet in array)
21
+ * @returns {JSX.Element} Provider component
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * import { PrivyProvider } from '@privy-io/expo';
26
+ * import { AlchemyProvider } from '@account-kit/privy-integration/react-native';
27
+ *
28
+ * <PrivyProvider appId="..." clientId="...">
29
+ * <AlchemyProvider
30
+ * apiKey="your-alchemy-api-key"
31
+ * policyId="your-gas-policy-id"
32
+ * accountAuthMode="eip7702"
33
+ * walletAddress="0x123..." // Optional: specify which wallet to use
34
+ * >
35
+ * <YourApp />
36
+ * </AlchemyProvider>
37
+ * </PrivyProvider>
38
+ * ```
39
+ */
40
+ export function AlchemyProvider({
41
+ children,
42
+ ...config
43
+ }: PropsWithChildren<AlchemyProviderConfig>) {
44
+ return (
45
+ <AlchemyContextProvider config={config} adapter={reactNativeAdapter}>
46
+ {children}
47
+ </AlchemyContextProvider>
48
+ );
49
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * React Native stub for WebProvider
3
+ * This file prevents Metro from importing web-specific code
4
+ */
5
+
6
+ export function AlchemyProvider() {
7
+ throw new Error(
8
+ "This module requires @privy-io/react-auth which is not available in React Native. " +
9
+ 'Import from "@account-kit/privy-integration/react-native" instead.',
10
+ );
11
+ }
@@ -0,0 +1,49 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import { AlchemyContextProvider } from "../context/AlchemyContext.js";
3
+ import { webAdapter } from "../adapters/web.js";
4
+ import type { AlchemyProviderConfig } from "../types.js";
5
+
6
+ /**
7
+ * Provider component for React web applications
8
+ * Must be nested INSIDE PrivyProvider from @privy-io/react-auth
9
+ *
10
+ * @param {PropsWithChildren<AlchemyProviderConfig>} props - Component props
11
+ * @param {React.ReactNode} props.children - React children to wrap with Alchemy configuration
12
+ * @param {string} [props.apiKey] - Your Alchemy API key
13
+ * @param {string} [props.jwt] - JWT token for authentication
14
+ * @param {string} [props.rpcUrl] - Custom RPC URL for EVM chains
15
+ * @param {string} [props.solanaRpcUrl] - Custom RPC URL for Solana
16
+ * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s) for EVM chains
17
+ * @param {string | string[]} [props.solanaPolicyId] - Gas Manager policy ID(s) for Solana
18
+ * @param {boolean} [props.disableSponsorship] - Set to true to disable sponsorship by default (default: false)
19
+ * @param {'eip7702' | 'owner'} [props.accountAuthMode] - Authorization mode for EVM smart accounts (default: 'eip7702')
20
+ * @param {string} [props.walletAddress] - Optional: Specify which wallet address to use (defaults to first embedded wallet)
21
+ * @returns {JSX.Element} Provider component
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * import { PrivyProvider } from '@privy-io/react-auth';
26
+ * import { AlchemyProvider } from '@account-kit/privy-integration';
27
+ *
28
+ * <PrivyProvider appId="...">
29
+ * <AlchemyProvider
30
+ * apiKey="your-alchemy-api-key"
31
+ * policyId="your-gas-policy-id"
32
+ * accountAuthMode="eip7702"
33
+ * walletAddress="0x123..." // Optional: specify which wallet to use
34
+ * >
35
+ * <YourApp />
36
+ * </AlchemyProvider>
37
+ * </PrivyProvider>
38
+ * ```
39
+ */
40
+ export function AlchemyProvider({
41
+ children,
42
+ ...config
43
+ }: PropsWithChildren<AlchemyProviderConfig>) {
44
+ return (
45
+ <AlchemyContextProvider config={config} adapter={webAdapter}>
46
+ {children}
47
+ </AlchemyContextProvider>
48
+ );
49
+ }