@account-kit/privy-integration 4.71.1 → 4.73.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 (41) hide show
  1. package/README.md +117 -9
  2. package/dist/esm/Provider.d.ts +4 -2
  3. package/dist/esm/Provider.js +4 -2
  4. package/dist/esm/Provider.js.map +1 -1
  5. package/dist/esm/hooks/useAlchemySolanaTransaction.d.ts +163 -0
  6. package/dist/esm/hooks/useAlchemySolanaTransaction.js +233 -0
  7. package/dist/esm/hooks/useAlchemySolanaTransaction.js.map +1 -0
  8. package/dist/esm/solana.d.ts +2 -0
  9. package/dist/esm/solana.js +5 -0
  10. package/dist/esm/solana.js.map +1 -0
  11. package/dist/esm/types.d.ts +5 -1
  12. package/dist/esm/types.js.map +1 -1
  13. package/dist/esm/util/createSolanaSponsoredTransaction.d.ts +12 -0
  14. package/dist/esm/util/createSolanaSponsoredTransaction.js +53 -0
  15. package/dist/esm/util/createSolanaSponsoredTransaction.js.map +1 -0
  16. package/dist/esm/util/createSolanaTransaction.d.ts +11 -0
  17. package/dist/esm/util/createSolanaTransaction.js +21 -0
  18. package/dist/esm/util/createSolanaTransaction.js.map +1 -0
  19. package/dist/esm/version.d.ts +1 -1
  20. package/dist/esm/version.js +1 -1
  21. package/dist/esm/version.js.map +1 -1
  22. package/dist/types/Provider.d.ts +4 -2
  23. package/dist/types/Provider.d.ts.map +1 -1
  24. package/dist/types/hooks/useAlchemySolanaTransaction.d.ts +164 -0
  25. package/dist/types/hooks/useAlchemySolanaTransaction.d.ts.map +1 -0
  26. package/dist/types/solana.d.ts +3 -0
  27. package/dist/types/solana.d.ts.map +1 -0
  28. package/dist/types/types.d.ts +5 -1
  29. package/dist/types/types.d.ts.map +1 -1
  30. package/dist/types/util/createSolanaSponsoredTransaction.d.ts +13 -0
  31. package/dist/types/util/createSolanaSponsoredTransaction.d.ts.map +1 -0
  32. package/dist/types/util/createSolanaTransaction.d.ts +12 -0
  33. package/dist/types/util/createSolanaTransaction.d.ts.map +1 -0
  34. package/dist/types/version.d.ts +1 -1
  35. package/package.json +15 -5
  36. package/src/hooks/useAlchemySolanaTransaction.ts +402 -0
  37. package/src/solana.ts +16 -0
  38. package/src/types.ts +7 -1
  39. package/src/util/createSolanaSponsoredTransaction.ts +75 -0
  40. package/src/util/createSolanaTransaction.ts +31 -0
  41. package/src/version.ts +1 -1
package/README.md CHANGED
@@ -7,9 +7,10 @@ Add gas sponsorship and smart wallet features to your Privy app in under 5 minut
7
7
  If you're already using [Privy](https://privy.io) for authentication, this package lets you upgrade your users' wallets with:
8
8
 
9
9
  - **🔄 EIP-7702 Delegation** - Upgrade EOAs to smart accounts without migration
10
- - **⛽ Gas Sponsorship** - Pay gas fees for your users via Alchemy Gas Manager
10
+ - **⛽ Gas Sponsorship** - Pay gas fees for your users via Alchemy Gas Manager (EVM & Solana)
11
11
  - **💱 Token Swaps** - Execute swaps through Alchemy's swap infrastructure
12
12
  - **🚀 Batched Transactions** - Send multiple operations in a single transaction using `sendTransaction([...])`
13
+ - **☀️ Solana Support** - Send sponsored Solana transactions with Privy's embedded Solana wallets
13
14
 
14
15
  All while keeping Privy as your authentication provider. No need to change your auth flow or migrate user accounts.
15
16
 
@@ -31,6 +32,26 @@ yarn add @account-kit/privy-integration
31
32
  pnpm add @account-kit/privy-integration
32
33
  ```
33
34
 
35
+ ### Optional: Solana Support
36
+
37
+ To use Solana features (like `useAlchemySolanaTransaction`), you'll need to install the Solana Web3.js library:
38
+
39
+ ```bash
40
+ npm install @solana/web3.js
41
+ # or
42
+ yarn add @solana/web3.js
43
+ # or
44
+ pnpm add @solana/web3.js
45
+ ```
46
+
47
+ Then import from the `/solana` export:
48
+
49
+ ```tsx
50
+ import { useAlchemySolanaTransaction } from "@account-kit/privy-integration/solana";
51
+ ```
52
+
53
+ > **Note:** The Solana functionality is completely optional. If you only need EVM features, you don't need to install `@solana/web3.js`.
54
+
34
55
  ## Quick Start
35
56
 
36
57
  ### 1. Wrap Your App with Both Providers
@@ -166,17 +187,81 @@ function SwapButton() {
166
187
  }
167
188
  ```
168
189
 
190
+ ### 4. Send Solana Transactions
191
+
192
+ ```tsx
193
+ import { useAlchemySolanaTransaction } from "@account-kit/privy-integration/solana";
194
+
195
+ function SolanaSendButton() {
196
+ const { sendTransactionAsync, isPending, error, data } =
197
+ useAlchemySolanaTransaction({
198
+ rpcUrl: "https://solana-mainnet.g.alchemy.com/v2/your-api-key",
199
+ policyId: "your-solana-policy-id", // optional, for gas sponsorship
200
+ });
201
+
202
+ const handleTransfer = async () => {
203
+ try {
204
+ // Simple SOL transfer
205
+ const result = await sendTransactionAsync({
206
+ transfer: {
207
+ amount: 1_000_000_000, // 1 SOL in lamports
208
+ toAddress: "recipient-base58-address",
209
+ },
210
+ });
211
+
212
+ console.log("Transaction hash:", result.hash);
213
+ } catch (err) {
214
+ console.error("Transaction failed:", err);
215
+ }
216
+ };
217
+
218
+ const handleCustomInstructions = async () => {
219
+ try {
220
+ // Custom instructions
221
+ import { SystemProgram, PublicKey } from "@solana/web3.js";
222
+
223
+ const instruction = SystemProgram.transfer({
224
+ fromPubkey: new PublicKey(walletAddress),
225
+ toPubkey: new PublicKey(recipientAddress),
226
+ lamports: 1_000_000,
227
+ });
228
+
229
+ const result = await sendTransactionAsync({
230
+ instructions: [instruction],
231
+ });
232
+
233
+ console.log("Transaction hash:", result.hash);
234
+ } catch (err) {
235
+ console.error("Transaction failed:", err);
236
+ }
237
+ };
238
+
239
+ return (
240
+ <>
241
+ <button onClick={handleTransfer} disabled={isPending}>
242
+ {isPending ? "Sending..." : "Send SOL"}
243
+ </button>
244
+ <button onClick={handleCustomInstructions} disabled={isPending}>
245
+ {isPending ? "Sending..." : "Custom Instructions"}
246
+ </button>
247
+ </>
248
+ );
249
+ }
250
+ ```
251
+
169
252
  ## Configuration
170
253
 
171
254
  ### AlchemyProvider Props
172
255
 
173
- | Prop | Type | Required | Description |
174
- | -------------------- | -------------------- | ------------- | ---------------------------------------------------------------------------------------------------- |
175
- | `apiKey` | `string` | Conditional\* | Your Alchemy API key for @account-kit/infra transport |
176
- | `jwt` | `string` | Conditional\* | JWT token for authentication (alternative to `apiKey`) |
177
- | `rpcUrl` | `string` | Conditional\* | Custom RPC URL (can be used alone or with `jwt`) |
178
- | `policyId` | `string \| string[]` | No | Gas Manager policy ID(s) for sponsorship. If array is provided, backend uses first applicable policy |
179
- | `disableSponsorship` | `boolean` | No | Set to `true` to disable gas sponsorship by default (default: `false`) |
256
+ | Prop | Type | Required | Description |
257
+ | -------------------- | -------------------- | ------------- | -------------------------------------------------------------------------------------------------------- |
258
+ | `apiKey` | `string` | Conditional\* | Your Alchemy API key for @account-kit/infra transport |
259
+ | `jwt` | `string` | Conditional\* | JWT token for authentication (alternative to `apiKey`) |
260
+ | `rpcUrl` | `string` | Conditional\* | Custom RPC URL for EVM chains (can be used alone or with `jwt`) |
261
+ | `solanaRpcUrl` | `string` | No | Custom RPC URL for Solana (separate from EVM `rpcUrl`) |
262
+ | `policyId` | `string \| string[]` | No | Gas Manager policy ID(s) for EVM sponsorship. If array is provided, backend uses first applicable policy |
263
+ | `solanaPolicyId` | `string \| string[]` | No | Gas Manager policy ID(s) for Solana sponsorship |
264
+ | `disableSponsorship` | `boolean` | No | Set to `true` to disable gas sponsorship by default (default: `false`) |
180
265
 
181
266
  \* **Required configuration (pick one):**
182
267
 
@@ -206,7 +291,7 @@ await sendTransaction(
206
291
 
207
292
  #### `useAlchemySendTransaction()`
208
293
 
209
- Send single or batch transactions with optional gas sponsorship.
294
+ Send single or batch EVM transactions with optional gas sponsorship.
210
295
 
211
296
  **Returns:**
212
297
 
@@ -242,6 +327,29 @@ Sign and submit prepared swap calls.
242
327
  - `data` - Swap result with `txnHash`
243
328
  - `reset()` - Reset hook state
244
329
 
330
+ #### `useAlchemySolanaTransaction(options?)`
331
+
332
+ Send Solana transactions with optional gas sponsorship via Alchemy.
333
+
334
+ **Parameters:**
335
+
336
+ - `options.rpcUrl` - Solana RPC URL (overrides provider config)
337
+ - `options.policyId` - Gas sponsorship policy ID (overrides provider config)
338
+ - `options.walletAddress` - Specific wallet address to use (defaults to first wallet)
339
+ - `options.confirmationOptions` - Transaction confirmation options
340
+
341
+ **Returns:**
342
+
343
+ - `sendTransactionAsync(params)` - Send transaction and await result (throws on error)
344
+ - `params.transfer` - Simple SOL transfer with `amount` (lamports) and `toAddress`
345
+ - `params.instructions` - Custom Solana transaction instructions array
346
+ - `sendTransaction(params)` - Send transaction (fire-and-forget, errors caught internally)
347
+ - `connection` - Active Solana connection instance
348
+ - `isPending` - Whether a transaction is currently being sent
349
+ - `error` - Error object if failed
350
+ - `data` - Transaction result with `hash` (base58 signature)
351
+ - `reset()` - Reset hook state
352
+
245
353
  #### `useAlchemyClient()`
246
354
 
247
355
  Get the underlying smart wallet client (advanced use cases).
@@ -19,8 +19,10 @@ interface ClientCache {
19
19
  * @param {React.ReactNode} props.children - React children to wrap with Alchemy configuration
20
20
  * @param {string} [props.apiKey] - Your Alchemy API key
21
21
  * @param {string} [props.jwt] - JWT token for authentication
22
- * @param {string} [props.rpcUrl] - Custom RPC URL
23
- * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s)
22
+ * @param {string} [props.rpcUrl] - Custom RPC URL for EVM chains
23
+ * @param {string} [props.solanaRpcUrl] - Custom RPC URL for Solana
24
+ * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s) for EVM chains
25
+ * @param {string | string[]} [props.solanaPolicyId] - Gas Manager policy ID(s) for Solana
24
26
  * @param {boolean} [props.disableSponsorship] - Set to true to disable sponsorship by default (default: false)
25
27
  * @returns {JSX.Element} Provider component
26
28
  *
@@ -12,8 +12,10 @@ const ClientCacheContext = createContext(null);
12
12
  * @param {React.ReactNode} props.children - React children to wrap with Alchemy configuration
13
13
  * @param {string} [props.apiKey] - Your Alchemy API key
14
14
  * @param {string} [props.jwt] - JWT token for authentication
15
- * @param {string} [props.rpcUrl] - Custom RPC URL
16
- * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s)
15
+ * @param {string} [props.rpcUrl] - Custom RPC URL for EVM chains
16
+ * @param {string} [props.solanaRpcUrl] - Custom RPC URL for Solana
17
+ * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s) for EVM chains
18
+ * @param {string | string[]} [props.solanaPolicyId] - Gas Manager policy ID(s) for Solana
17
19
  * @param {boolean} [props.disableSponsorship] - Set to true to disable sponsorship by default (default: false)
18
20
  * @returns {JSX.Element} Provider component
19
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Provider.js","sourceRoot":"","sources":["../../src/Provider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAEL,aAAa,EACb,UAAU,EACV,MAAM,EACN,SAAS,GACV,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIhD,MAAM,cAAc,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC;AAYzE,MAAM,kBAAkB,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,QAAQ,EACR,GAAG,MAAM,EACgC;IACzC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC;IAE3C,uFAAuF;IACvF,qFAAqF;IACrF,MAAM,KAAK,GAAG,MAAM,CAAc;QAChC,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,2DAA2D;IAC3D,MAAM,oBAAoB,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAE3D,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,OAAO,CAAC;QACtD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,CAAC;QACvD,MAAM,oBAAoB,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC;QAEnD,wBAAwB;QACxB,IAAI,gBAAgB,IAAI,CAAC,aAAa,EAAE,CAAC;YACvC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAC5B,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,2DAA2D;QAC3D,IACE,aAAa;YACb,iBAAiB;YACjB,oBAAoB;YACpB,iBAAiB,KAAK,oBAAoB,EAC1C,CAAC;YACD,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAC5B,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,8BAA8B;QAC9B,oBAAoB,CAAC,OAAO,GAAG,aAAa,CAAC;QAC7C,oBAAoB,CAAC,OAAO,GAAG,oBAAoB,CAAC;IACtD,CAAC,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAE3C,OAAO,CACL,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YACpC,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,YAC9C,QAAQ,GACmB,GACN,CAC3B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,mHAAmH,CACpH,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import {\n type PropsWithChildren,\n createContext,\n useContext,\n useRef,\n useEffect,\n} from \"react\";\nimport { usePrivy } from \"@privy-io/react-auth\";\nimport type { SmartWalletClient } from \"@account-kit/wallet-client\";\nimport type { AlchemyProviderConfig } from \"./types.js\";\n\nconst AlchemyContext = createContext<AlchemyProviderConfig | null>(null);\n\n/**\n * Client cache stored in React tree (similar to QueryClient in React Query)\n *\n * @internal\n */\ninterface ClientCache {\n client: SmartWalletClient | null;\n cacheKey: string | null;\n}\n\nconst ClientCacheContext = createContext<ClientCache | null>(null);\n\n/**\n * Provider component that configures Alchemy infrastructure for transaction handling\n * Must be nested INSIDE PrivyProvider to access authentication state\n * Automatically manages client cache lifecycle and resets on logout\n *\n * @param {PropsWithChildren<AlchemyProviderConfig>} props - Component props\n * @param {React.ReactNode} props.children - React children to wrap with Alchemy configuration\n * @param {string} [props.apiKey] - Your Alchemy API key\n * @param {string} [props.jwt] - JWT token for authentication\n * @param {string} [props.rpcUrl] - Custom RPC URL\n * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s)\n * @param {boolean} [props.disableSponsorship] - Set to true to disable sponsorship by default (default: false)\n * @returns {JSX.Element} Provider component\n *\n * @example\n * ```tsx\n * <PrivyProvider appId=\"...\">\n * <AlchemyProvider\n * apiKey=\"your-alchemy-api-key\"\n * policyId=\"your-gas-policy-id\"\n * >\n * <YourApp />\n * </AlchemyProvider>\n * </PrivyProvider>\n * ```\n */\nexport function AlchemyProvider({\n children,\n ...config\n}: PropsWithChildren<AlchemyProviderConfig>) {\n const { authenticated, user } = usePrivy();\n\n // Store cache in a ref - persists across renders but scoped to this component instance\n // This makes it SSR-safe (each request gets its own cache) and React StrictMode-safe\n const cache = useRef<ClientCache>({\n client: null,\n cacheKey: null,\n });\n\n // Track previous state to detect logout and wallet changes\n const prevAuthenticatedRef = useRef(authenticated);\n const prevWalletAddressRef = useRef(user?.wallet?.address);\n\n // Automatically reset cache when user logs out or switches wallets\n useEffect(() => {\n const wasAuthenticated = prevAuthenticatedRef.current;\n const prevWalletAddress = prevWalletAddressRef.current;\n const currentWalletAddress = user?.wallet?.address;\n\n // Reset cache on logout\n if (wasAuthenticated && !authenticated) {\n cache.current.client = null;\n cache.current.cacheKey = null;\n }\n\n // Reset cache on wallet address change (account switching)\n if (\n authenticated &&\n prevWalletAddress &&\n currentWalletAddress &&\n prevWalletAddress !== currentWalletAddress\n ) {\n cache.current.client = null;\n cache.current.cacheKey = null;\n }\n\n // Update refs for next render\n prevAuthenticatedRef.current = authenticated;\n prevWalletAddressRef.current = currentWalletAddress;\n }, [authenticated, user?.wallet?.address]);\n\n return (\n <AlchemyContext.Provider value={config}>\n <ClientCacheContext.Provider value={cache.current}>\n {children}\n </ClientCacheContext.Provider>\n </AlchemyContext.Provider>\n );\n}\n\n/**\n * Hook to access Alchemy provider configuration\n * Must be used within an <AlchemyProvider> component\n *\n * @returns {AlchemyProviderConfig} The current Alchemy configuration\n * @throws {Error} If used outside of AlchemyProvider\n *\n * @example\n * ```tsx\n * const config = useAlchemyConfig();\n * console.log('Policy ID:', config.policyId);\n * ```\n */\nexport function useAlchemyConfig(): AlchemyProviderConfig {\n const context = useContext(AlchemyContext);\n if (!context) {\n throw new Error(\"useAlchemyConfig must be used within <AlchemyProvider />\");\n }\n return context;\n}\n\n/**\n * Hook to access the client cache (internal use only)\n *\n * @internal\n * @returns {ClientCache} The client cache object\n */\nexport function useClientCache(): ClientCache {\n const context = useContext(ClientCacheContext);\n if (!context) {\n throw new Error(\n \"useClientCache must be used within <AlchemyProvider />. Make sure AlchemyProvider is nested inside PrivyProvider.\",\n );\n }\n return context;\n}\n"]}
1
+ {"version":3,"file":"Provider.js","sourceRoot":"","sources":["../../src/Provider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAEL,aAAa,EACb,UAAU,EACV,MAAM,EACN,SAAS,GACV,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIhD,MAAM,cAAc,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC;AAYzE,MAAM,kBAAkB,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,QAAQ,EACR,GAAG,MAAM,EACgC;IACzC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC;IAE3C,uFAAuF;IACvF,qFAAqF;IACrF,MAAM,KAAK,GAAG,MAAM,CAAc;QAChC,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,2DAA2D;IAC3D,MAAM,oBAAoB,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAE3D,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,OAAO,CAAC;QACtD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,CAAC;QACvD,MAAM,oBAAoB,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC;QAEnD,wBAAwB;QACxB,IAAI,gBAAgB,IAAI,CAAC,aAAa,EAAE,CAAC;YACvC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAC5B,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,2DAA2D;QAC3D,IACE,aAAa;YACb,iBAAiB;YACjB,oBAAoB;YACpB,iBAAiB,KAAK,oBAAoB,EAC1C,CAAC;YACD,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAC5B,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,8BAA8B;QAC9B,oBAAoB,CAAC,OAAO,GAAG,aAAa,CAAC;QAC7C,oBAAoB,CAAC,OAAO,GAAG,oBAAoB,CAAC;IACtD,CAAC,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAE3C,OAAO,CACL,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YACpC,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,YAC9C,QAAQ,GACmB,GACN,CAC3B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,mHAAmH,CACpH,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import {\n type PropsWithChildren,\n createContext,\n useContext,\n useRef,\n useEffect,\n} from \"react\";\nimport { usePrivy } from \"@privy-io/react-auth\";\nimport type { SmartWalletClient } from \"@account-kit/wallet-client\";\nimport type { AlchemyProviderConfig } from \"./types.js\";\n\nconst AlchemyContext = createContext<AlchemyProviderConfig | null>(null);\n\n/**\n * Client cache stored in React tree (similar to QueryClient in React Query)\n *\n * @internal\n */\ninterface ClientCache {\n client: SmartWalletClient | null;\n cacheKey: string | null;\n}\n\nconst ClientCacheContext = createContext<ClientCache | null>(null);\n\n/**\n * Provider component that configures Alchemy infrastructure for transaction handling\n * Must be nested INSIDE PrivyProvider to access authentication state\n * Automatically manages client cache lifecycle and resets on logout\n *\n * @param {PropsWithChildren<AlchemyProviderConfig>} props - Component props\n * @param {React.ReactNode} props.children - React children to wrap with Alchemy configuration\n * @param {string} [props.apiKey] - Your Alchemy API key\n * @param {string} [props.jwt] - JWT token for authentication\n * @param {string} [props.rpcUrl] - Custom RPC URL for EVM chains\n * @param {string} [props.solanaRpcUrl] - Custom RPC URL for Solana\n * @param {string | string[]} [props.policyId] - Gas Manager policy ID(s) for EVM chains\n * @param {string | string[]} [props.solanaPolicyId] - Gas Manager policy ID(s) for Solana\n * @param {boolean} [props.disableSponsorship] - Set to true to disable sponsorship by default (default: false)\n * @returns {JSX.Element} Provider component\n *\n * @example\n * ```tsx\n * <PrivyProvider appId=\"...\">\n * <AlchemyProvider\n * apiKey=\"your-alchemy-api-key\"\n * policyId=\"your-gas-policy-id\"\n * >\n * <YourApp />\n * </AlchemyProvider>\n * </PrivyProvider>\n * ```\n */\nexport function AlchemyProvider({\n children,\n ...config\n}: PropsWithChildren<AlchemyProviderConfig>) {\n const { authenticated, user } = usePrivy();\n\n // Store cache in a ref - persists across renders but scoped to this component instance\n // This makes it SSR-safe (each request gets its own cache) and React StrictMode-safe\n const cache = useRef<ClientCache>({\n client: null,\n cacheKey: null,\n });\n\n // Track previous state to detect logout and wallet changes\n const prevAuthenticatedRef = useRef(authenticated);\n const prevWalletAddressRef = useRef(user?.wallet?.address);\n\n // Automatically reset cache when user logs out or switches wallets\n useEffect(() => {\n const wasAuthenticated = prevAuthenticatedRef.current;\n const prevWalletAddress = prevWalletAddressRef.current;\n const currentWalletAddress = user?.wallet?.address;\n\n // Reset cache on logout\n if (wasAuthenticated && !authenticated) {\n cache.current.client = null;\n cache.current.cacheKey = null;\n }\n\n // Reset cache on wallet address change (account switching)\n if (\n authenticated &&\n prevWalletAddress &&\n currentWalletAddress &&\n prevWalletAddress !== currentWalletAddress\n ) {\n cache.current.client = null;\n cache.current.cacheKey = null;\n }\n\n // Update refs for next render\n prevAuthenticatedRef.current = authenticated;\n prevWalletAddressRef.current = currentWalletAddress;\n }, [authenticated, user?.wallet?.address]);\n\n return (\n <AlchemyContext.Provider value={config}>\n <ClientCacheContext.Provider value={cache.current}>\n {children}\n </ClientCacheContext.Provider>\n </AlchemyContext.Provider>\n );\n}\n\n/**\n * Hook to access Alchemy provider configuration\n * Must be used within an <AlchemyProvider> component\n *\n * @returns {AlchemyProviderConfig} The current Alchemy configuration\n * @throws {Error} If used outside of AlchemyProvider\n *\n * @example\n * ```tsx\n * const config = useAlchemyConfig();\n * console.log('Policy ID:', config.policyId);\n * ```\n */\nexport function useAlchemyConfig(): AlchemyProviderConfig {\n const context = useContext(AlchemyContext);\n if (!context) {\n throw new Error(\"useAlchemyConfig must be used within <AlchemyProvider />\");\n }\n return context;\n}\n\n/**\n * Hook to access the client cache (internal use only)\n *\n * @internal\n * @returns {ClientCache} The client cache object\n */\nexport function useClientCache(): ClientCache {\n const context = useContext(ClientCacheContext);\n if (!context) {\n throw new Error(\n \"useClientCache must be used within <AlchemyProvider />. Make sure AlchemyProvider is nested inside PrivyProvider.\",\n );\n }\n return context;\n}\n"]}
@@ -0,0 +1,163 @@
1
+ import { Connection, Transaction, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
2
+ /**
3
+ * Type helper for values that can be synchronous or asynchronous
4
+ *
5
+ * @template T - The value type
6
+ */
7
+ export type PromiseOrValue<T> = T | Promise<T>;
8
+ /**
9
+ * Callback to modify a transaction before it's signed
10
+ * Useful for adding additional signatures or metadata
11
+ *
12
+ * @param transaction - The unsigned transaction to modify
13
+ * @returns The modified transaction
14
+ */
15
+ export type PreSend = (this: void, transaction: VersionedTransaction | Transaction) => PromiseOrValue<VersionedTransaction | Transaction>;
16
+ /**
17
+ * Callback to transform instructions into a custom transaction
18
+ * Useful for advanced transaction construction (e.g., multi-sig, custom versioning)
19
+ *
20
+ * @param instructions - Array of Solana transaction instructions
21
+ * @returns Constructed transaction (legacy or versioned)
22
+ */
23
+ export type TransformInstruction = (this: void, instructions: TransactionInstruction[]) => PromiseOrValue<Transaction | VersionedTransaction>;
24
+ /**
25
+ * Optional transaction lifecycle hooks for advanced use cases
26
+ */
27
+ export type SolanaTransactionParamOptions = {
28
+ /** Hook called before signing the transaction */
29
+ preSend?: PreSend;
30
+ /** Custom transaction builder from instructions */
31
+ transformInstruction?: TransformInstruction;
32
+ };
33
+ /**
34
+ * Parameters for sending a Solana transaction
35
+ * Supports either a simple transfer or custom instructions
36
+ */
37
+ export type SolanaTransactionParams = {
38
+ /** Simple SOL transfer parameters */
39
+ transfer: {
40
+ /** Amount in lamports (accepts number or bigint) */
41
+ amount: number | bigint;
42
+ /** Recipient's base58-encoded address */
43
+ toAddress: string;
44
+ };
45
+ /** Optional transaction lifecycle hooks */
46
+ transactionComponents?: SolanaTransactionParamOptions;
47
+ /** Options for confirming the transaction on-chain */
48
+ confirmationOptions?: Parameters<Connection["confirmTransaction"]>[1];
49
+ } | {
50
+ /** Custom Solana transaction instructions */
51
+ instructions: TransactionInstruction[];
52
+ /** Optional transaction lifecycle hooks */
53
+ transactionComponents?: SolanaTransactionParamOptions;
54
+ /** Options for confirming the transaction on-chain */
55
+ confirmationOptions?: Parameters<Connection["confirmTransaction"]>[1];
56
+ };
57
+ /**
58
+ * Result of a successful Solana transaction
59
+ */
60
+ export interface SolanaTransactionResult {
61
+ /** Base58-encoded transaction signature (hash) */
62
+ hash: string;
63
+ }
64
+ /**
65
+ * Configuration options for useAlchemySolanaTransaction hook
66
+ */
67
+ export interface UseAlchemySolanaTransactionOptions {
68
+ /** Solana RPC URL (overrides provider config) */
69
+ rpcUrl?: string;
70
+ /** Gas sponsorship policy ID (overrides provider config) */
71
+ policyId?: string | void;
72
+ /** Transaction confirmation options */
73
+ confirmationOptions?: Parameters<Connection["confirmTransaction"]>[1];
74
+ /** Specific wallet address to use (defaults to first available wallet) */
75
+ walletAddress?: string;
76
+ }
77
+ /**
78
+ * Return type of useAlchemySolanaTransaction hook
79
+ */
80
+ export interface UseAlchemySolanaTransactionResult {
81
+ /** Active Solana connection instance */
82
+ readonly connection: Connection | null;
83
+ /** Transaction result if successful */
84
+ readonly data: void | SolanaTransactionResult;
85
+ /** Whether a transaction is currently being sent */
86
+ readonly isPending: boolean;
87
+ /** Error if transaction failed */
88
+ readonly error: Error | null;
89
+ /** Reset hook state (clears error, data, isPending) */
90
+ reset(): void;
91
+ /** Send transaction (fire-and-forget, errors caught internally) */
92
+ sendTransaction(params: SolanaTransactionParams): void;
93
+ /** Send transaction and await result (throws on error) */
94
+ sendTransactionAsync(params: SolanaTransactionParams): Promise<SolanaTransactionResult>;
95
+ }
96
+ /**
97
+ * Hook to send Solana transactions with optional gas sponsorship via Alchemy
98
+ * Works with Privy's Solana wallet integration for signing transactions
99
+ * Supports both simple transfers and custom instruction sets
100
+ *
101
+ * @param {UseAlchemySolanaTransactionOptions} [opts] - Configuration options
102
+ * @param {string} [opts.rpcUrl] - Solana RPC URL (overrides provider config)
103
+ * @param {string} [opts.policyId] - Gas sponsorship policy ID (overrides provider config)
104
+ * @param {string} [opts.walletAddress] - Specific wallet address to use (defaults to first wallet)
105
+ * @param {Parameters<Connection["confirmTransaction"]>[1]} [opts.confirmationOptions] - Transaction confirmation options
106
+ * @returns {UseAlchemySolanaTransactionResult} Hook result with transaction functions and state
107
+ *
108
+ * @example Simple SOL transfer
109
+ * ```tsx
110
+ * const { sendTransactionAsync, isPending, error, data } = useAlchemySolanaTransaction({
111
+ * rpcUrl: 'https://solana-mainnet.g.alchemy.com/v2/your-api-key',
112
+ * policyId: 'your-policy-id', // Optional: for gas sponsorship
113
+ * });
114
+ *
115
+ * const handleTransfer = async () => {
116
+ * try {
117
+ * const result = await sendTransactionAsync({
118
+ * transfer: {
119
+ * amount: 1_000_000_000, // 1 SOL in lamports
120
+ * toAddress: 'recipient-address',
121
+ * },
122
+ * });
123
+ * console.log('Transaction hash:', result.hash);
124
+ * } catch (err) {
125
+ * console.error('Transaction failed:', err);
126
+ * }
127
+ * };
128
+ * ```
129
+ *
130
+ * @example Custom instructions
131
+ * ```tsx
132
+ * import { SystemProgram, PublicKey } from '@solana/web3.js';
133
+ *
134
+ * const { sendTransactionAsync } = useAlchemySolanaTransaction();
135
+ *
136
+ * // Build your custom instructions
137
+ * const transferIx = SystemProgram.transfer({
138
+ * fromPubkey: new PublicKey(walletAddress),
139
+ * toPubkey: new PublicKey(recipientAddress),
140
+ * lamports: 1_000_000,
141
+ * });
142
+ *
143
+ * // Pass instructions array to the hook
144
+ * const result = await sendTransactionAsync({
145
+ * instructions: [transferIx],
146
+ * });
147
+ * ```
148
+ *
149
+ * @example With provider configuration
150
+ * ```tsx
151
+ * // In your provider setup
152
+ * <AlchemyProvider
153
+ * solanaRpcUrl="https://solana-mainnet.g.alchemy.com/v2/..."
154
+ * solanaPolicyId="your-solana-policy-id"
155
+ * >
156
+ * <YourApp />
157
+ * </AlchemyProvider>
158
+ *
159
+ * // In your component - uses provider config automatically
160
+ * const { sendTransactionAsync } = useAlchemySolanaTransaction();
161
+ * ```
162
+ */
163
+ export declare function useAlchemySolanaTransaction(opts?: UseAlchemySolanaTransactionOptions): UseAlchemySolanaTransactionResult;
@@ -0,0 +1,233 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import { Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction, VersionedTransaction, } from "@solana/web3.js";
3
+ import { useAlchemyConfig } from "../Provider.js";
4
+ import { createSolanaSponsoredTransaction } from "../util/createSolanaSponsoredTransaction.js";
5
+ import { useSignTransaction, useWallets } from "@privy-io/react-auth/solana";
6
+ import { createSolanaTransaction } from "../util/createSolanaTransaction.js";
7
+ /**
8
+ * Hook to send Solana transactions with optional gas sponsorship via Alchemy
9
+ * Works with Privy's Solana wallet integration for signing transactions
10
+ * Supports both simple transfers and custom instruction sets
11
+ *
12
+ * @param {UseAlchemySolanaTransactionOptions} [opts] - Configuration options
13
+ * @param {string} [opts.rpcUrl] - Solana RPC URL (overrides provider config)
14
+ * @param {string} [opts.policyId] - Gas sponsorship policy ID (overrides provider config)
15
+ * @param {string} [opts.walletAddress] - Specific wallet address to use (defaults to first wallet)
16
+ * @param {Parameters<Connection["confirmTransaction"]>[1]} [opts.confirmationOptions] - Transaction confirmation options
17
+ * @returns {UseAlchemySolanaTransactionResult} Hook result with transaction functions and state
18
+ *
19
+ * @example Simple SOL transfer
20
+ * ```tsx
21
+ * const { sendTransactionAsync, isPending, error, data } = useAlchemySolanaTransaction({
22
+ * rpcUrl: 'https://solana-mainnet.g.alchemy.com/v2/your-api-key',
23
+ * policyId: 'your-policy-id', // Optional: for gas sponsorship
24
+ * });
25
+ *
26
+ * const handleTransfer = async () => {
27
+ * try {
28
+ * const result = await sendTransactionAsync({
29
+ * transfer: {
30
+ * amount: 1_000_000_000, // 1 SOL in lamports
31
+ * toAddress: 'recipient-address',
32
+ * },
33
+ * });
34
+ * console.log('Transaction hash:', result.hash);
35
+ * } catch (err) {
36
+ * console.error('Transaction failed:', err);
37
+ * }
38
+ * };
39
+ * ```
40
+ *
41
+ * @example Custom instructions
42
+ * ```tsx
43
+ * import { SystemProgram, PublicKey } from '@solana/web3.js';
44
+ *
45
+ * const { sendTransactionAsync } = useAlchemySolanaTransaction();
46
+ *
47
+ * // Build your custom instructions
48
+ * const transferIx = SystemProgram.transfer({
49
+ * fromPubkey: new PublicKey(walletAddress),
50
+ * toPubkey: new PublicKey(recipientAddress),
51
+ * lamports: 1_000_000,
52
+ * });
53
+ *
54
+ * // Pass instructions array to the hook
55
+ * const result = await sendTransactionAsync({
56
+ * instructions: [transferIx],
57
+ * });
58
+ * ```
59
+ *
60
+ * @example With provider configuration
61
+ * ```tsx
62
+ * // In your provider setup
63
+ * <AlchemyProvider
64
+ * solanaRpcUrl="https://solana-mainnet.g.alchemy.com/v2/..."
65
+ * solanaPolicyId="your-solana-policy-id"
66
+ * >
67
+ * <YourApp />
68
+ * </AlchemyProvider>
69
+ *
70
+ * // In your component - uses provider config automatically
71
+ * const { sendTransactionAsync } = useAlchemySolanaTransaction();
72
+ * ```
73
+ */
74
+ export function useAlchemySolanaTransaction(opts = {}) {
75
+ const config = useAlchemyConfig();
76
+ const { wallets } = useWallets();
77
+ const { signTransaction } = useSignTransaction();
78
+ // Resolve the Privy Solana wallet to use
79
+ const embeddedWallet = useMemo(() => {
80
+ if (opts.walletAddress) {
81
+ const w = wallets.find((w) => w.address === opts.walletAddress);
82
+ if (!w)
83
+ throw new Error("Specified Solana wallet not found");
84
+ return w;
85
+ }
86
+ return wallets[0];
87
+ }, [wallets, opts.walletAddress]);
88
+ // Build Solana connection from rpcUrl (hook override or provider default)
89
+ const connection = useMemo(() => {
90
+ const url = opts.rpcUrl || config.solanaRpcUrl;
91
+ return url ? new Connection(url) : null;
92
+ }, [opts.rpcUrl, config.solanaRpcUrl]);
93
+ const [isPending, setIsPending] = useState(false);
94
+ const [error, setError] = useState(null);
95
+ const [data, setData] = useState(undefined);
96
+ const resolvedPolicyId = useMemo(() => {
97
+ if (opts.policyId != null)
98
+ return opts.policyId || undefined;
99
+ // Use solanaPolicyId from config, fallback to policyId for backwards compat
100
+ const configPolicy = config.solanaPolicyId || config.policyId;
101
+ return Array.isArray(configPolicy) ? configPolicy[0] : configPolicy;
102
+ }, [opts.policyId, config.solanaPolicyId, config.policyId]);
103
+ const mapTransformInstructions = useMemo(() => {
104
+ const addSponsorship = async (instructions) => {
105
+ const policyId = resolvedPolicyId;
106
+ if (!policyId)
107
+ throw new Error("Gas sponsorship requires a policyId (see provider or hook options).");
108
+ const localConnection = connection || missing("connection");
109
+ const fromAddress = embeddedWallet?.address;
110
+ if (!fromAddress)
111
+ throw new Error("No embedded Solana wallet connected");
112
+ return createSolanaSponsoredTransaction(instructions, localConnection, policyId, fromAddress);
113
+ };
114
+ const createTx = async (instructions) => {
115
+ const localConnection = connection || missing("connection");
116
+ const fromAddress = embeddedWallet?.address;
117
+ if (!fromAddress)
118
+ throw new Error("No embedded Solana wallet connected");
119
+ return createSolanaTransaction(instructions, localConnection, fromAddress);
120
+ };
121
+ const defaultTransform = !!resolvedPolicyId && !config.disableSponsorship
122
+ ? addSponsorship
123
+ : createTx;
124
+ return {
125
+ addSponsorship,
126
+ createTransaction: createTx,
127
+ default: defaultTransform,
128
+ };
129
+ }, [
130
+ resolvedPolicyId,
131
+ config.disableSponsorship,
132
+ connection,
133
+ embeddedWallet?.address,
134
+ ]);
135
+ const buildInstructions = useCallback((params, fromAddress) => {
136
+ if ("instructions" in params)
137
+ return params.instructions;
138
+ return [
139
+ SystemProgram.transfer({
140
+ fromPubkey: new PublicKey(fromAddress),
141
+ toPubkey: new PublicKey(params.transfer.toAddress),
142
+ lamports: typeof params.transfer.amount === "bigint"
143
+ ? Number(params.transfer.amount) // web3.js currently expects number; callers can pass bigint safely
144
+ : params.transfer.amount,
145
+ }),
146
+ ];
147
+ }, []);
148
+ function toUnsignedBytes(tx) {
149
+ // Serialize the full transaction structure (with placeholder signatures)
150
+ // Privy expects the complete transaction format, not just the message
151
+ if (tx instanceof VersionedTransaction) {
152
+ // VersionedTransaction.serialize() includes signature slots
153
+ return tx.serialize();
154
+ }
155
+ // Legacy Transaction: serialize without requiring all signatures
156
+ const buf = tx.serialize({
157
+ requireAllSignatures: false,
158
+ verifySignatures: false,
159
+ });
160
+ return buf instanceof Uint8Array ? buf : new Uint8Array(buf);
161
+ }
162
+ const sendTransactionAsync = useCallback(async (params) => {
163
+ setIsPending(true);
164
+ setError(null);
165
+ try {
166
+ const localConnection = connection || missing("connection");
167
+ if (!embeddedWallet?.address) {
168
+ throw new Error("No Solana wallet connected via Privy");
169
+ }
170
+ const fromAddress = embeddedWallet.address;
171
+ const { transactionComponents: { preSend, transformInstruction } = {}, confirmationOptions, } = params;
172
+ const instructions = buildInstructions(params, fromAddress);
173
+ let transaction;
174
+ if (transformInstruction) {
175
+ transaction = await transformInstruction(instructions);
176
+ }
177
+ else {
178
+ transaction = await mapTransformInstructions.default(instructions);
179
+ }
180
+ transaction = (await preSend?.(transaction)) || transaction;
181
+ // Sign the transaction using Privy's useSignTransaction hook
182
+ const unsignedBytes = toUnsignedBytes(transaction);
183
+ const { signedTransaction } = await signTransaction({
184
+ transaction: unsignedBytes,
185
+ wallet: embeddedWallet,
186
+ });
187
+ // Broadcast the signed transaction
188
+ const hash = await localConnection.sendRawTransaction(signedTransaction, { skipPreflight: false });
189
+ if (confirmationOptions || opts.confirmationOptions) {
190
+ await localConnection.confirmTransaction(hash, confirmationOptions || opts.confirmationOptions);
191
+ }
192
+ setData({ hash });
193
+ return { hash };
194
+ }
195
+ catch (err) {
196
+ const e = err instanceof Error ? err : new Error(String(err));
197
+ setError(e);
198
+ throw e;
199
+ }
200
+ finally {
201
+ setIsPending(false);
202
+ }
203
+ }, [
204
+ embeddedWallet,
205
+ connection,
206
+ buildInstructions,
207
+ mapTransformInstructions,
208
+ opts.confirmationOptions,
209
+ signTransaction,
210
+ ]);
211
+ const sendTransaction = useCallback((params) => {
212
+ // Prevent unhandled rejection warnings; error state is already set in sendTransactionAsync
213
+ sendTransactionAsync(params).catch(() => { });
214
+ }, [sendTransactionAsync]);
215
+ const reset = useCallback(() => {
216
+ setIsPending(false);
217
+ setError(null);
218
+ setData(undefined);
219
+ }, []);
220
+ return {
221
+ connection,
222
+ data,
223
+ isPending,
224
+ error,
225
+ reset,
226
+ sendTransaction,
227
+ sendTransactionAsync,
228
+ };
229
+ }
230
+ function missing(message) {
231
+ throw new Error(message);
232
+ }
233
+ //# sourceMappingURL=useAlchemySolanaTransaction.js.map