@account-kit/privy-integration 4.68.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +372 -0
  3. package/dist/esm/Provider.d.ts +61 -0
  4. package/dist/esm/Provider.js +100 -0
  5. package/dist/esm/Provider.js.map +1 -0
  6. package/dist/esm/hooks/internal/useEmbeddedWallet.d.ts +10 -0
  7. package/dist/esm/hooks/internal/useEmbeddedWallet.js +22 -0
  8. package/dist/esm/hooks/internal/useEmbeddedWallet.js.map +1 -0
  9. package/dist/esm/hooks/useAlchemyClient.d.ts +17 -0
  10. package/dist/esm/hooks/useAlchemyClient.js +119 -0
  11. package/dist/esm/hooks/useAlchemyClient.js.map +1 -0
  12. package/dist/esm/hooks/useAlchemyPrepareSwap.d.ts +42 -0
  13. package/dist/esm/hooks/useAlchemyPrepareSwap.js +95 -0
  14. package/dist/esm/hooks/useAlchemyPrepareSwap.js.map +1 -0
  15. package/dist/esm/hooks/useAlchemySendTransaction.d.ts +26 -0
  16. package/dist/esm/hooks/useAlchemySendTransaction.js +127 -0
  17. package/dist/esm/hooks/useAlchemySendTransaction.js.map +1 -0
  18. package/dist/esm/hooks/useAlchemySubmitSwap.d.ts +31 -0
  19. package/dist/esm/hooks/useAlchemySubmitSwap.js +93 -0
  20. package/dist/esm/hooks/useAlchemySubmitSwap.js.map +1 -0
  21. package/dist/esm/index.d.ts +6 -0
  22. package/dist/esm/index.js +8 -0
  23. package/dist/esm/index.js.map +1 -0
  24. package/dist/esm/types.d.ts +124 -0
  25. package/dist/esm/types.js +2 -0
  26. package/dist/esm/types.js.map +1 -0
  27. package/dist/esm/util/getChain.d.ts +1 -0
  28. package/dist/esm/util/getChain.js +98 -0
  29. package/dist/esm/util/getChain.js.map +1 -0
  30. package/dist/esm/version.d.ts +1 -0
  31. package/dist/esm/version.js +4 -0
  32. package/dist/esm/version.js.map +1 -0
  33. package/dist/types/Provider.d.ts +62 -0
  34. package/dist/types/Provider.d.ts.map +1 -0
  35. package/dist/types/hooks/internal/useEmbeddedWallet.d.ts +11 -0
  36. package/dist/types/hooks/internal/useEmbeddedWallet.d.ts.map +1 -0
  37. package/dist/types/hooks/useAlchemyClient.d.ts +18 -0
  38. package/dist/types/hooks/useAlchemyClient.d.ts.map +1 -0
  39. package/dist/types/hooks/useAlchemyPrepareSwap.d.ts +43 -0
  40. package/dist/types/hooks/useAlchemyPrepareSwap.d.ts.map +1 -0
  41. package/dist/types/hooks/useAlchemySendTransaction.d.ts +27 -0
  42. package/dist/types/hooks/useAlchemySendTransaction.d.ts.map +1 -0
  43. package/dist/types/hooks/useAlchemySubmitSwap.d.ts +32 -0
  44. package/dist/types/hooks/useAlchemySubmitSwap.d.ts.map +1 -0
  45. package/dist/types/index.d.ts +7 -0
  46. package/dist/types/index.d.ts.map +1 -0
  47. package/dist/types/types.d.ts +125 -0
  48. package/dist/types/types.d.ts.map +1 -0
  49. package/dist/types/util/getChain.d.ts +2 -0
  50. package/dist/types/util/getChain.d.ts.map +1 -0
  51. package/dist/types/version.d.ts +2 -0
  52. package/dist/types/version.d.ts.map +1 -0
  53. package/package.json +66 -0
  54. package/src/hooks/internal/useEmbeddedWallet.ts +29 -0
  55. package/src/hooks/useAlchemyClient.ts +160 -0
  56. package/src/hooks/useAlchemyPrepareSwap.ts +113 -0
  57. package/src/hooks/useAlchemySendTransaction.ts +160 -0
  58. package/src/hooks/useAlchemySubmitSwap.ts +120 -0
  59. package/src/index.ts +23 -0
  60. package/src/types.ts +159 -0
  61. package/src/util/getChain.ts +145 -0
  62. package/src/version.ts +3 -0
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@account-kit/privy-integration",
3
+ "version": "4.68.0",
4
+ "description": "Use Alchemy gas sponsorship, swaps and more with Privy",
5
+ "author": "Alchemy",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "type": "module",
9
+ "main": "./dist/esm/index.js",
10
+ "module": "./dist/esm/index.js",
11
+ "types": "./dist/types/index.d.ts",
12
+ "typings": "./dist/types/index.d.ts",
13
+ "sideEffects": false,
14
+ "files": [
15
+ "dist",
16
+ "src/**/*.ts",
17
+ "!dist/**/*.tsbuildinfo",
18
+ "!vitest.config.ts",
19
+ "!.env",
20
+ "!src/**/*.test.ts",
21
+ "!src/**/*.test-d.ts",
22
+ "!src/__tests__/**/*"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/types/index.d.ts",
27
+ "import": "./dist/esm/index.js",
28
+ "default": "./dist/esm/index.js"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "scripts": {
33
+ "prebuild": "tsx ./inject-version.ts",
34
+ "build": "yarn clean && yarn build:esm && yarn build:types",
35
+ "build:esm": "tsc --project tsconfig.build.json --outDir ./dist/esm",
36
+ "build:types": "tsc --project tsconfig.build.json --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
37
+ "clean": "rm -rf ./dist",
38
+ "test": "vitest --passWithNoTests",
39
+ "test:run": "vitest run --passWithNoTests"
40
+ },
41
+ "devDependencies": {
42
+ "@privy-io/react-auth": "^2.3.1",
43
+ "typescript-template": "*"
44
+ },
45
+ "dependencies": {
46
+ "@account-kit/infra": "^4.68.0",
47
+ "@account-kit/wallet-client": "^4.68.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@privy-io/react-auth": "^2.3.1 || ^3.0.0",
51
+ "viem": "^2.29.2"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "registry": "https://registry.npmjs.org/"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/alchemyplatform/aa-sdk.git"
60
+ },
61
+ "bugs": {
62
+ "url": "https://github.com/alchemyplatform/aa-sdk/issues"
63
+ },
64
+ "homepage": "https://github.com/alchemyplatform/aa-sdk#readme",
65
+ "gitHead": "f95d7f917b6d5b6ef9fa53a2bcaa658bd6aa6673"
66
+ }
@@ -0,0 +1,29 @@
1
+ import { useCallback } from "react";
2
+ import {
3
+ useWallets,
4
+ type ConnectedWallet as PrivyWallet,
5
+ } from "@privy-io/react-auth";
6
+
7
+ /**
8
+ * Internal hook to get the Privy embedded wallet
9
+ * Shared across multiple hooks to avoid duplication
10
+ *
11
+ * @internal
12
+ * @returns {() => PrivyWallet} Function that returns the embedded wallet
13
+ * @throws {Error} If embedded wallet is not found
14
+ */
15
+ 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;
29
+ }
@@ -0,0 +1,160 @@
1
+ import { useCallback } from "react";
2
+ import {
3
+ WalletClientSigner,
4
+ type AuthorizationRequest,
5
+ ConnectionConfigSchema,
6
+ } from "@aa-sdk/core";
7
+ import {
8
+ createWalletClient,
9
+ custom,
10
+ type Address,
11
+ type Authorization,
12
+ } from "viem";
13
+ import { useSign7702Authorization } from "@privy-io/react-auth";
14
+ import {
15
+ createSmartWalletClient,
16
+ type SmartWalletClient,
17
+ } from "@account-kit/wallet-client";
18
+ import { alchemy } from "@account-kit/infra";
19
+ import { useAlchemyConfig, useClientCache } from "../Provider.js";
20
+ import { getChain } from "../util/getChain.js";
21
+ import { useEmbeddedWallet } from "./internal/useEmbeddedWallet.js";
22
+
23
+ /**
24
+ * Hook to get and memoize a SmartWalletClient instance
25
+ * The client is cached in the AlchemyProvider context (React tree scoped)
26
+ * Automatically clears cache on logout via the provider
27
+ *
28
+ * @returns {{ getClient: () => Promise<SmartWalletClient> }} Object containing the smart wallet client getter
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const { getClient } = useAlchemyClient();
33
+ * const smartWalletClient = await getClient();
34
+ * ```
35
+ */
36
+ export function useAlchemyClient() {
37
+ const { signAuthorization } = useSign7702Authorization();
38
+ const config = useAlchemyConfig();
39
+ const cache = useClientCache();
40
+ const getEmbeddedWallet = useEmbeddedWallet();
41
+
42
+ const getEmbeddedWalletChain = useCallback(() => {
43
+ const embedded = getEmbeddedWallet();
44
+ // Handle CAIP-2 format like "eip155:1"
45
+ const chainIdStr = embedded.chainId?.toString();
46
+
47
+ if (!chainIdStr) {
48
+ throw new Error(
49
+ "Embedded wallet chainId is not set. Please ensure the wallet is connected to a network.",
50
+ );
51
+ }
52
+
53
+ const numericChainId = chainIdStr.includes(":")
54
+ ? chainIdStr.split(":")[1]
55
+ : chainIdStr;
56
+
57
+ const parsedChainId = Number(numericChainId);
58
+
59
+ if (isNaN(parsedChainId)) {
60
+ throw new Error(
61
+ `Failed to parse chainId from embedded wallet. Received: ${chainIdStr}`,
62
+ );
63
+ }
64
+
65
+ return getChain(parsedChainId);
66
+ }, [getEmbeddedWallet]);
67
+
68
+ const getClient = useCallback(async (): Promise<SmartWalletClient> => {
69
+ const embeddedWallet = getEmbeddedWallet();
70
+ const chain = getEmbeddedWalletChain();
71
+
72
+ // Generate a cache key based on configuration and wallet address
73
+ const currentCacheKey = JSON.stringify({
74
+ address: embeddedWallet.address,
75
+ chainId: chain.id,
76
+ apiKey: config.apiKey,
77
+ jwt: config.jwt,
78
+ rpcUrl: config.rpcUrl,
79
+ policyId: config.policyId,
80
+ });
81
+
82
+ // Return cached client if configuration hasn't changed
83
+ if (cache.client && cache.cacheKey === currentCacheKey) {
84
+ return cache.client;
85
+ }
86
+
87
+ // Configuration changed or no cache exists, create new client
88
+ const provider = await embeddedWallet.getEthereumProvider();
89
+
90
+ // Create base signer from Privy wallet
91
+ const baseSigner = new WalletClientSigner(
92
+ createWalletClient({
93
+ account: embeddedWallet.address as Address,
94
+ chain,
95
+ transport: custom(provider),
96
+ }),
97
+ "privy",
98
+ );
99
+
100
+ // Extend signer with EIP-7702 authorization support
101
+ const signer = {
102
+ ...baseSigner,
103
+ signAuthorization: async (
104
+ unsignedAuth: AuthorizationRequest<number>,
105
+ ): Promise<Authorization<number, true>> => {
106
+ const signature = await signAuthorization({
107
+ ...unsignedAuth,
108
+ contractAddress: unsignedAuth.address ?? unsignedAuth.contractAddress,
109
+ });
110
+
111
+ return {
112
+ ...unsignedAuth,
113
+ ...signature,
114
+ };
115
+ },
116
+ };
117
+
118
+ // Determine transport configuration using schema validation
119
+ // This properly handles combinations like rpcUrl + jwt together
120
+ const transportConfig = ConnectionConfigSchema.parse({
121
+ rpcUrl: config.rpcUrl,
122
+ apiKey: config.apiKey,
123
+ jwt: config.jwt,
124
+ });
125
+
126
+ const transport = alchemy(transportConfig);
127
+
128
+ transport.updateHeaders({
129
+ "X-Alchemy-Client-Breadcrumb": "privyIntegrationSdk",
130
+ });
131
+
132
+ // Create and cache the smart wallet client in provider context
133
+ cache.client = createSmartWalletClient({
134
+ chain,
135
+ transport,
136
+ signer,
137
+ policyIds: config.policyId
138
+ ? Array.isArray(config.policyId)
139
+ ? config.policyId
140
+ : [config.policyId]
141
+ : undefined,
142
+ });
143
+
144
+ // Store the cache key
145
+ cache.cacheKey = currentCacheKey;
146
+
147
+ return cache.client;
148
+ }, [
149
+ getEmbeddedWallet,
150
+ getEmbeddedWalletChain,
151
+ signAuthorization,
152
+ config.apiKey,
153
+ config.jwt,
154
+ config.rpcUrl,
155
+ config.policyId,
156
+ cache,
157
+ ]);
158
+
159
+ return { getClient };
160
+ }
@@ -0,0 +1,113 @@
1
+ import { useCallback, useState } from "react";
2
+ import { type Address } from "viem";
3
+ import { swapActions } from "@account-kit/wallet-client/experimental";
4
+ import { useAlchemyClient } from "./useAlchemyClient.js";
5
+ import { useEmbeddedWallet } from "./internal/useEmbeddedWallet.js";
6
+ import type {
7
+ PrepareSwapRequest,
8
+ PrepareSwapResult,
9
+ UsePrepareSwapResult,
10
+ } from "../types";
11
+
12
+ /**
13
+ * Hook to request swap quotes and prepare swap calls
14
+ * Part of the two-step swap process: prepare → submit
15
+ * Use with `useAlchemySubmitSwap()` to execute the prepared swap
16
+ *
17
+ * Supports two modes:
18
+ * 1. Specify exact amount to swap FROM (`fromAmount`)
19
+ * 2. Specify minimum amount to receive TO (`minimumToAmount`)
20
+ *
21
+ * @returns {UsePrepareSwapResult} Hook result with prepareSwap function and state
22
+ *
23
+ * @example Complete swap flow
24
+ * ```tsx
25
+ * const { prepareSwap } = useAlchemyPrepareSwap();
26
+ * const { submitSwap } = useAlchemySubmitSwap();
27
+ *
28
+ * // Step 1: Prepare the swap (get quote)
29
+ * const preparedSwap = await prepareSwap({
30
+ * fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
31
+ * toToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
32
+ * fromAmount: '0xde0b6b3a7640000', // 1 ETH in hex
33
+ * });
34
+ *
35
+ * // Step 2: Execute the swap
36
+ * const result = await submitSwap(preparedSwap);
37
+ * ```
38
+ *
39
+ * @example Swap for minimum amount TO
40
+ * ```tsx
41
+ * const { prepareSwap } = useAlchemyPrepareSwap();
42
+ *
43
+ * // Swap ETH to get at least 100 USDC
44
+ * const result = await prepareSwap({
45
+ * fromToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
46
+ * toToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
47
+ * minimumToAmount: '0x5f5e100', // 100 USDC (6 decimals) in hex
48
+ * });
49
+ * console.log('Quote expiry:', new Date(parseInt(result.quote.expiry, 16) * 1000));
50
+ * ```
51
+ */
52
+ export function useAlchemyPrepareSwap(): UsePrepareSwapResult {
53
+ const { getClient } = useAlchemyClient();
54
+ const getEmbeddedWallet = useEmbeddedWallet();
55
+
56
+ const [isLoading, setIsLoading] = useState(false);
57
+ const [error, setError] = useState<Error | null>(null);
58
+ const [data, setData] = useState<PrepareSwapResult | null>(null);
59
+
60
+ const prepareSwap = useCallback(
61
+ async (request: PrepareSwapRequest): Promise<PrepareSwapResult> => {
62
+ setIsLoading(true);
63
+ setError(null);
64
+
65
+ try {
66
+ const client = await getClient();
67
+ const embeddedWallet = getEmbeddedWallet();
68
+
69
+ // Extend client with swap actions
70
+ const swapClient = client.extend(swapActions);
71
+
72
+ // Request the swap quote
73
+ // Note: Gas sponsorship capabilities are configured on the client itself
74
+ const response = await swapClient.requestQuoteV0({
75
+ ...request,
76
+ from: request.from || (embeddedWallet.address as Address),
77
+ });
78
+
79
+ // Validate that we got prepared calls, not raw calls
80
+ if (response.rawCalls) {
81
+ throw new Error(
82
+ "Received raw calls instead of prepared calls. Ensure returnRawCalls is not set to true.",
83
+ );
84
+ }
85
+
86
+ setData(response);
87
+ return response;
88
+ } catch (err) {
89
+ const errorObj =
90
+ err instanceof Error ? err : new Error("Failed to prepare swap");
91
+ setError(errorObj);
92
+ throw errorObj;
93
+ } finally {
94
+ setIsLoading(false);
95
+ }
96
+ },
97
+ [getClient, getEmbeddedWallet],
98
+ );
99
+
100
+ const reset = useCallback(() => {
101
+ setError(null);
102
+ setData(null);
103
+ setIsLoading(false);
104
+ }, []);
105
+
106
+ return {
107
+ prepareSwap,
108
+ isLoading,
109
+ error,
110
+ data,
111
+ reset,
112
+ };
113
+ }
@@ -0,0 +1,160 @@
1
+ import { useCallback, useState } from "react";
2
+ import { type Address, type Hex, isHex } from "viem";
3
+ import { useAlchemyClient } from "./useAlchemyClient.js";
4
+ import { useAlchemyConfig } from "../Provider.js";
5
+ import { useEmbeddedWallet } from "./internal/useEmbeddedWallet.js";
6
+ import type {
7
+ UnsignedTransactionRequest,
8
+ SendTransactionOptions,
9
+ SendTransactionResult,
10
+ UseSendTransactionResult,
11
+ } from "../types";
12
+
13
+ /**
14
+ * Normalize value to hex format
15
+ * Accepts bigint, number, decimal string, or hex string
16
+ *
17
+ * @param {string | number | bigint} value - Value to normalize
18
+ * @returns {Hex} Hex string representation of the value
19
+ */
20
+ function normalizeValue(value: string | number | bigint): Hex {
21
+ if (typeof value === "bigint") {
22
+ return `0x${value.toString(16)}`;
23
+ }
24
+ if (typeof value === "number") {
25
+ return `0x${BigInt(value).toString(16)}`;
26
+ }
27
+ if (isHex(value)) {
28
+ return value;
29
+ }
30
+ // Assume decimal string
31
+ return `0x${BigInt(value).toString(16)}`;
32
+ }
33
+
34
+ /**
35
+ * Hook to send transactions with optional gas sponsorship via Alchemy
36
+ * Drop-in alternative to Privy's useSendTransaction hook
37
+ *
38
+ * @returns {UseSendTransactionResult} Hook result with sendTransaction function and state
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * const { sendTransaction, isLoading, error, data } = useAlchemySendTransaction();
43
+ *
44
+ * const handleSend = async () => {
45
+ * try {
46
+ * const result = await sendTransaction({
47
+ * to: '0x...',
48
+ * data: '0x...',
49
+ * value: '1000000000000000000', // 1 ETH
50
+ * });
51
+ * console.log('Transaction hash:', result.txnHash);
52
+ * } catch (err) {
53
+ * console.error('Transaction failed:', err);
54
+ * }
55
+ * };
56
+ * ```
57
+ */
58
+ export function useAlchemySendTransaction(): UseSendTransactionResult {
59
+ const { getClient } = useAlchemyClient();
60
+ const config = useAlchemyConfig();
61
+ const getEmbeddedWallet = useEmbeddedWallet();
62
+
63
+ const [isLoading, setIsLoading] = useState(false);
64
+ const [error, setError] = useState<Error | null>(null);
65
+ const [data, setData] = useState<SendTransactionResult | null>(null);
66
+
67
+ const sendTransaction = useCallback(
68
+ async (
69
+ input: UnsignedTransactionRequest,
70
+ options?: SendTransactionOptions,
71
+ ): Promise<SendTransactionResult> => {
72
+ setIsLoading(true);
73
+ setError(null);
74
+
75
+ try {
76
+ const client = await getClient();
77
+ const embeddedWallet = getEmbeddedWallet();
78
+
79
+ // Determine if transaction should be sponsored
80
+ const hasPolicyId = !!config.policyId;
81
+ const enableSponsorship = !config.disableSponsorship;
82
+ const shouldSponsor =
83
+ options?.disableSponsorship !== undefined
84
+ ? !options.disableSponsorship
85
+ : hasPolicyId && enableSponsorship;
86
+
87
+ // Format the transaction call
88
+ const formattedCall = {
89
+ to: input.to,
90
+ data: input.data,
91
+ value: input.value ? normalizeValue(input.value) : undefined,
92
+ };
93
+
94
+ // Build capabilities based on sponsorship
95
+ const policyId = Array.isArray(config.policyId)
96
+ ? config.policyId[0]
97
+ : config.policyId;
98
+
99
+ const capabilities: {
100
+ eip7702Auth: true;
101
+ paymasterService?: { policyId: string };
102
+ } = { eip7702Auth: true };
103
+
104
+ if (shouldSponsor && policyId) {
105
+ capabilities.paymasterService = { policyId };
106
+ }
107
+
108
+ // Send the transaction
109
+ const result = await client.sendCalls({
110
+ from: embeddedWallet.address as Address,
111
+ calls: [formattedCall],
112
+ capabilities,
113
+ });
114
+
115
+ if (!result.preparedCallIds || result.preparedCallIds.length === 0) {
116
+ throw new Error(
117
+ "No prepared call IDs returned from transaction submission",
118
+ );
119
+ }
120
+
121
+ // Wait for the transaction to be confirmed
122
+ const txStatus = await client.waitForCallsStatus({
123
+ id: result.preparedCallIds[0],
124
+ timeout: 60_000,
125
+ });
126
+
127
+ const txnHash = txStatus.receipts?.[0]?.transactionHash;
128
+ if (!txnHash) {
129
+ throw new Error("Transaction hash not found in receipt");
130
+ }
131
+
132
+ const txResult: SendTransactionResult = { txnHash };
133
+ setData(txResult);
134
+ return txResult;
135
+ } catch (err) {
136
+ const errorObj =
137
+ err instanceof Error ? err : new Error("Transaction failed");
138
+ setError(errorObj);
139
+ throw errorObj;
140
+ } finally {
141
+ setIsLoading(false);
142
+ }
143
+ },
144
+ [getClient, getEmbeddedWallet, config.policyId, config.disableSponsorship],
145
+ );
146
+
147
+ const reset = useCallback(() => {
148
+ setError(null);
149
+ setData(null);
150
+ setIsLoading(false);
151
+ }, []);
152
+
153
+ return {
154
+ sendTransaction,
155
+ isLoading,
156
+ error,
157
+ data,
158
+ reset,
159
+ };
160
+ }
@@ -0,0 +1,120 @@
1
+ import { useCallback, useState } from "react";
2
+ import { swapActions } from "@account-kit/wallet-client/experimental";
3
+ import { useAlchemyClient } from "./useAlchemyClient.js";
4
+ import type {
5
+ PrepareSwapResult,
6
+ SubmitSwapResult,
7
+ UseSubmitSwapResult,
8
+ } from "../types.js";
9
+
10
+ /**
11
+ * Hook to sign and submit prepared swap calls
12
+ * Part of the two-step swap process: prepare → submit
13
+ *
14
+ * @returns {UseSubmitSwapResult} Hook result with submitSwap function and state
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const { prepareSwap } = useAlchemyPrepareSwap();
19
+ * const { submitSwap, isLoading, error, data } = useAlchemySubmitSwap();
20
+ *
21
+ * const handleSwap = async () => {
22
+ * try {
23
+ * // Step 1: Prepare the swap
24
+ * const preparedSwap = await prepareSwap({
25
+ * fromToken: '0x...',
26
+ * toToken: '0x...',
27
+ * fromAmount: '0x...',
28
+ * });
29
+ *
30
+ * // Step 2: Submit the swap
31
+ * const result = await submitSwap(preparedSwap);
32
+ * console.log('Swap confirmed:', result.txnHash);
33
+ * } catch (err) {
34
+ * console.error('Swap failed:', err);
35
+ * }
36
+ * };
37
+ * ```
38
+ */
39
+ export function useAlchemySubmitSwap(): UseSubmitSwapResult {
40
+ const { getClient } = useAlchemyClient();
41
+
42
+ const [isLoading, setIsLoading] = useState(false);
43
+ const [error, setError] = useState<Error | null>(null);
44
+ const [data, setData] = useState<SubmitSwapResult | null>(null);
45
+
46
+ const submitSwap = useCallback(
47
+ async (preparedSwap: PrepareSwapResult): Promise<SubmitSwapResult> => {
48
+ setIsLoading(true);
49
+ setError(null);
50
+
51
+ try {
52
+ const client = await getClient();
53
+
54
+ // Extend client with swap actions
55
+ const swapClient = client.extend(swapActions);
56
+
57
+ // Sign the prepared calls
58
+ const signedCalls = await swapClient.signPreparedCalls(preparedSwap);
59
+
60
+ // Send the signed calls
61
+ const { preparedCallIds } =
62
+ await swapClient.sendPreparedCalls(signedCalls);
63
+
64
+ if (!preparedCallIds || preparedCallIds.length === 0) {
65
+ throw new Error("No prepared call IDs returned from swap submission");
66
+ }
67
+
68
+ // Wait for the swap to be confirmed
69
+ const callStatusResult = await swapClient.waitForCallsStatus({
70
+ id: preparedCallIds[0],
71
+ timeout: 60_000,
72
+ });
73
+
74
+ // Validate the transaction was successful
75
+ if (
76
+ callStatusResult.status !== "success" ||
77
+ !callStatusResult.receipts ||
78
+ !callStatusResult.receipts[0]
79
+ ) {
80
+ throw new Error(
81
+ `Swap failed with status ${
82
+ callStatusResult.status
83
+ }. Full receipt:\n${JSON.stringify(callStatusResult, null, 2)}`,
84
+ );
85
+ }
86
+
87
+ const txnHash = callStatusResult.receipts[0].transactionHash;
88
+ if (!txnHash) {
89
+ throw new Error("Transaction hash not found in receipt");
90
+ }
91
+
92
+ const result: SubmitSwapResult = { txnHash };
93
+ setData(result);
94
+ return result;
95
+ } catch (err) {
96
+ const errorObj =
97
+ err instanceof Error ? err : new Error("Failed to submit swap");
98
+ setError(errorObj);
99
+ throw errorObj;
100
+ } finally {
101
+ setIsLoading(false);
102
+ }
103
+ },
104
+ [getClient],
105
+ );
106
+
107
+ const reset = useCallback(() => {
108
+ setError(null);
109
+ setData(null);
110
+ setIsLoading(false);
111
+ }, []);
112
+
113
+ return {
114
+ submitSwap,
115
+ isLoading,
116
+ error,
117
+ data,
118
+ reset,
119
+ };
120
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ // Provider
2
+ export { AlchemyProvider, useAlchemyConfig } from "./Provider.js";
3
+
4
+ // Hooks
5
+ export { useAlchemyClient } from "./hooks/useAlchemyClient.js";
6
+ export { useAlchemySendTransaction } from "./hooks/useAlchemySendTransaction.js";
7
+ export { useAlchemyPrepareSwap } from "./hooks/useAlchemyPrepareSwap.js";
8
+ export { useAlchemySubmitSwap } from "./hooks/useAlchemySubmitSwap.js";
9
+
10
+ // Types
11
+ export type {
12
+ AlchemyProviderConfig,
13
+ UnsignedTransactionRequest,
14
+ SendTransactionOptions,
15
+ SendTransactionResult,
16
+ UseSendTransactionResult,
17
+ PrepareSwapRequest,
18
+ PrepareSwapResult,
19
+ UsePrepareSwapResult,
20
+ SubmitSwapResult,
21
+ UseSubmitSwapResult,
22
+ SwapQuote,
23
+ } from "./types.js";