@frak-labs/react-sdk 0.1.1 → 0.2.0-beta.7898df5b

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 (38) hide show
  1. package/README.md +25 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +29 -68
  4. package/dist/index.d.ts +28 -67
  5. package/dist/index.js +1 -1
  6. package/package.json +17 -16
  7. package/src/hook/helper/useReferralInteraction.test.ts +309 -0
  8. package/src/hook/helper/useReferralInteraction.ts +73 -0
  9. package/src/hook/index.ts +10 -0
  10. package/src/hook/useDisplayModal.test.ts +275 -0
  11. package/src/hook/useDisplayModal.ts +68 -0
  12. package/src/hook/useFrakClient.test.ts +119 -0
  13. package/src/hook/useFrakClient.ts +11 -0
  14. package/src/hook/useFrakConfig.test.ts +184 -0
  15. package/src/hook/useFrakConfig.ts +22 -0
  16. package/src/hook/useGetMerchantInformation.ts +56 -0
  17. package/src/hook/useOpenSso.test.ts +202 -0
  18. package/src/hook/useOpenSso.ts +51 -0
  19. package/src/hook/usePrepareSso.test.ts +197 -0
  20. package/src/hook/usePrepareSso.ts +55 -0
  21. package/src/hook/useSendTransaction.test.ts +218 -0
  22. package/src/hook/useSendTransaction.ts +62 -0
  23. package/src/hook/useSiweAuthenticate.test.ts +258 -0
  24. package/src/hook/useSiweAuthenticate.ts +66 -0
  25. package/src/hook/useWalletStatus.test.ts +112 -0
  26. package/src/hook/useWalletStatus.ts +55 -0
  27. package/src/hook/utils/useFrakContext.test.ts +157 -0
  28. package/src/hook/utils/useFrakContext.ts +42 -0
  29. package/src/hook/utils/useMounted.test.ts +70 -0
  30. package/src/hook/utils/useMounted.ts +12 -0
  31. package/src/hook/utils/useWindowLocation.test.ts +54 -0
  32. package/src/hook/utils/useWindowLocation.ts +40 -0
  33. package/src/index.ts +25 -0
  34. package/src/provider/FrakConfigProvider.test.ts +246 -0
  35. package/src/provider/FrakConfigProvider.ts +54 -0
  36. package/src/provider/FrakIFrameClientProvider.test.tsx +209 -0
  37. package/src/provider/FrakIFrameClientProvider.ts +86 -0
  38. package/src/provider/index.ts +7 -0
@@ -0,0 +1,51 @@
1
+ import type { OpenSsoParamsType, OpenSsoReturnType } from "@frak-labs/core-sdk";
2
+ import { openSso } from "@frak-labs/core-sdk/actions";
3
+ import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
4
+ import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
5
+ import { useFrakClient } from "./useFrakClient";
6
+
7
+ /** @ignore */
8
+ type MutationOptions = Omit<
9
+ UseMutationOptions<OpenSsoReturnType, FrakRpcError, OpenSsoParamsType>,
10
+ "mutationFn" | "mutationKey"
11
+ >;
12
+
13
+ /** @inline */
14
+ interface UseOpenSsoParams {
15
+ /**
16
+ * Optional mutation options, see {@link @tanstack/react-query!useMutation | `useMutation()`} for more infos
17
+ */
18
+ mutations?: MutationOptions;
19
+ }
20
+
21
+ /**
22
+ * Hook that return a mutation helping to open the SSO page
23
+ *
24
+ * It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.openSso | `openSso()`} action
25
+ *
26
+ * @param args - Optional config object with `mutations` for customizing the underlying {@link @tanstack/react-query!useMutation | `useMutation()`}
27
+ *
28
+ * @group hooks
29
+ *
30
+ * @returns
31
+ * The mutation hook wrapping the `openSso()` action
32
+ * The `mutate` and `mutateAsync` argument is of type {@link @frak-labs/core-sdk!index.OpenSsoParamsType | `OpenSsoParamsType`}
33
+ * The mutation doesn't output any value
34
+ *
35
+ * @see {@link @frak-labs/core-sdk!actions.openSso | `openSso()`} for more info about the underlying action
36
+ * @see {@link @tanstack/react-query!useMutation | `useMutation()`} for more info about the mutation options and response
37
+ */
38
+ export function useOpenSso({ mutations }: UseOpenSsoParams = {}) {
39
+ const client = useFrakClient();
40
+
41
+ return useMutation({
42
+ ...mutations,
43
+ mutationKey: ["frak-sdk", "open-sso"],
44
+ mutationFn: async (params: OpenSsoParamsType) => {
45
+ if (!client) {
46
+ throw new ClientNotFound();
47
+ }
48
+ return openSso(client, params);
49
+ },
50
+ });
51
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Tests for usePrepareSso hook
3
+ * Tests TanStack Query wrapper for preparing SSO URLs
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ vi.mock("@frak-labs/core-sdk/actions");
9
+
10
+ import type { PrepareSsoReturnType } from "@frak-labs/core-sdk";
11
+ import { prepareSso } from "@frak-labs/core-sdk/actions";
12
+ import { ClientNotFound } from "@frak-labs/frame-connector";
13
+ import { renderHook, waitFor } from "@testing-library/react";
14
+ import { describe, expect, test } from "../../tests/vitest-fixtures";
15
+ import { usePrepareSso } from "./usePrepareSso";
16
+
17
+ describe("usePrepareSso", () => {
18
+ test("should throw ClientNotFound when client is not available", async ({
19
+ queryWrapper,
20
+ }) => {
21
+ const { result } = renderHook(
22
+ () => usePrepareSso({ directExit: true }),
23
+ {
24
+ wrapper: queryWrapper.wrapper,
25
+ }
26
+ );
27
+
28
+ await waitFor(() => {
29
+ expect(result.current.isError).toBe(true);
30
+ });
31
+
32
+ expect(result.current.error).toBeInstanceOf(ClientNotFound);
33
+ });
34
+
35
+ test("should prepare SSO URL successfully", async ({
36
+ mockFrakProviders,
37
+ }) => {
38
+ const mockSsoResult: PrepareSsoReturnType = {
39
+ ssoUrl: "https://wallet-test.frak.id/sso?params=xyz",
40
+ };
41
+
42
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
43
+
44
+ const { result } = renderHook(
45
+ () => usePrepareSso({ directExit: true }),
46
+ {
47
+ wrapper: mockFrakProviders,
48
+ }
49
+ );
50
+
51
+ await waitFor(() => {
52
+ expect(result.current.isSuccess).toBe(true);
53
+ });
54
+
55
+ expect(result.current.data).toEqual(mockSsoResult);
56
+ expect(result.current.data?.ssoUrl).toContain("sso");
57
+ });
58
+
59
+ test("should handle SSO params with redirectUrl", async ({
60
+ mockFrakProviders,
61
+ }) => {
62
+ const mockSsoResult: PrepareSsoReturnType = {
63
+ ssoUrl: "https://wallet-test.frak.id/sso?redirect=example.com",
64
+ };
65
+
66
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
67
+
68
+ const { result } = renderHook(
69
+ () =>
70
+ usePrepareSso({
71
+ redirectUrl: "https://example.com/callback",
72
+ directExit: false,
73
+ }),
74
+ {
75
+ wrapper: mockFrakProviders,
76
+ }
77
+ );
78
+
79
+ await waitFor(() => {
80
+ expect(result.current.isSuccess).toBe(true);
81
+ });
82
+
83
+ expect(result.current.data).toEqual(mockSsoResult);
84
+ expect(prepareSso).toHaveBeenCalledWith(
85
+ expect.anything(),
86
+ expect.objectContaining({
87
+ redirectUrl: "https://example.com/callback",
88
+ directExit: false,
89
+ })
90
+ );
91
+ });
92
+
93
+ test("should handle SSO params with metadata", async ({
94
+ mockFrakProviders,
95
+ }) => {
96
+ const mockSsoResult: PrepareSsoReturnType = {
97
+ ssoUrl: "https://wallet-test.frak.id/sso?metadata=xyz",
98
+ };
99
+
100
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
101
+
102
+ const { result } = renderHook(
103
+ () =>
104
+ usePrepareSso({
105
+ metadata: {
106
+ logoUrl: "https://example.com/logo.png",
107
+ homepageLink: "https://example.com",
108
+ },
109
+ directExit: true,
110
+ }),
111
+ {
112
+ wrapper: mockFrakProviders,
113
+ }
114
+ );
115
+
116
+ await waitFor(() => {
117
+ expect(result.current.isSuccess).toBe(true);
118
+ });
119
+
120
+ expect(result.current.data).toEqual(mockSsoResult);
121
+ });
122
+
123
+ test("should handle RPC errors", async ({ mockFrakProviders }) => {
124
+ const error = new Error("SSO preparation failed");
125
+ vi.mocked(prepareSso).mockRejectedValue(error);
126
+
127
+ const { result } = renderHook(
128
+ () => usePrepareSso({ directExit: true }),
129
+ {
130
+ wrapper: mockFrakProviders,
131
+ }
132
+ );
133
+
134
+ await waitFor(() => {
135
+ expect(result.current.isError).toBe(true);
136
+ });
137
+
138
+ expect(result.current.error).toEqual(error);
139
+ });
140
+
141
+ test("should update query key with params", async ({
142
+ mockFrakProviders,
143
+ }) => {
144
+ const mockSsoResult: PrepareSsoReturnType = {
145
+ ssoUrl: "https://wallet-test.frak.id/sso?params=abc",
146
+ };
147
+
148
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
149
+
150
+ const params = {
151
+ directExit: false,
152
+ redirectUrl: "https://example.com",
153
+ };
154
+
155
+ const { result, rerender } = renderHook(() => usePrepareSso(params), {
156
+ wrapper: mockFrakProviders,
157
+ });
158
+
159
+ await waitFor(() => {
160
+ expect(result.current.isSuccess).toBe(true);
161
+ });
162
+
163
+ expect(result.current.data).toEqual(mockSsoResult);
164
+
165
+ // Query key includes params, so changes should trigger re-fetch
166
+ rerender();
167
+ expect(result.current.data).toEqual(mockSsoResult);
168
+ });
169
+
170
+ test("should call prepareSso with client and params", async ({
171
+ mockFrakProviders,
172
+ mockFrakClient,
173
+ }) => {
174
+ const mockSsoResult: PrepareSsoReturnType = {
175
+ ssoUrl: "https://wallet-test.frak.id/sso?test=123",
176
+ };
177
+
178
+ vi.mocked(prepareSso).mockResolvedValue(mockSsoResult);
179
+
180
+ const params = {
181
+ directExit: true,
182
+ metadata: {
183
+ logoUrl: "https://example.com/logo.png",
184
+ },
185
+ };
186
+
187
+ const { result } = renderHook(() => usePrepareSso(params), {
188
+ wrapper: mockFrakProviders,
189
+ });
190
+
191
+ await waitFor(() => {
192
+ expect(result.current.isSuccess).toBe(true);
193
+ });
194
+
195
+ expect(prepareSso).toHaveBeenCalledWith(mockFrakClient, params);
196
+ });
197
+ });
@@ -0,0 +1,55 @@
1
+ import type { PrepareSsoParamsType } from "@frak-labs/core-sdk";
2
+ import { prepareSso } from "@frak-labs/core-sdk/actions";
3
+ import { ClientNotFound } from "@frak-labs/frame-connector";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { useFrakClient } from "./useFrakClient";
6
+
7
+ /**
8
+ * Hook that generates SSO URL for popup flow
9
+ *
10
+ * This is a **synchronous** hook (no async calls) that generates the SSO URL
11
+ * client-side without communicating with the wallet iframe.
12
+ *
13
+ * @param params - SSO parameters for URL generation
14
+ *
15
+ * @group hooks
16
+ *
17
+ * @returns
18
+ * Object containing:
19
+ * - `ssoUrl`: Generated SSO URL (or undefined if client not ready)
20
+ * - `isReady`: Boolean indicating if URL is available
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * function MyComponent() {
25
+ * const { data } = usePrepareSso({
26
+ * metadata: { logoUrl: "..." },
27
+ * directExit: true
28
+ * });
29
+ *
30
+ * const handleClick = () => {
31
+ * if (ssoUrl) {
32
+ * window.open(data?.ssoUrl, "_blank");
33
+ * }
34
+ * };
35
+ *
36
+ * return <button onClick={handleClick} disabled={!isReady}>Login</button>;
37
+ * }
38
+ * ```
39
+ *
40
+ * @see {@link @frak-labs/core-sdk!actions.prepareSso | `prepareSso()`} for the underlying action
41
+ * @see {@link @frak-labs/core-sdk!actions.openSso | `openSso()`} for the recommended high-level API
42
+ */
43
+ export function usePrepareSso(params: PrepareSsoParamsType) {
44
+ const client = useFrakClient();
45
+
46
+ return useQuery({
47
+ queryKey: ["frak-sdk", "prepare-sso", params],
48
+ queryFn: async () => {
49
+ if (!client) {
50
+ throw new ClientNotFound();
51
+ }
52
+ return prepareSso(client, params);
53
+ },
54
+ });
55
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Tests for useSendTransactionAction hook
3
+ * Tests TanStack Mutation wrapper for sending transactions
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ vi.mock("@frak-labs/core-sdk/actions");
9
+
10
+ import type { SendTransactionReturnType } from "@frak-labs/core-sdk";
11
+ import { sendTransaction } from "@frak-labs/core-sdk/actions";
12
+ import { ClientNotFound } from "@frak-labs/frame-connector";
13
+ import { renderHook, waitFor } from "@testing-library/react";
14
+ import type { Hex } from "viem";
15
+ import { describe, expect, test } from "../../tests/vitest-fixtures";
16
+ import { useSendTransactionAction } from "./useSendTransaction";
17
+
18
+ describe("useSendTransactionAction", () => {
19
+ test("should throw ClientNotFound when client is not available", async ({
20
+ queryWrapper,
21
+ }) => {
22
+ const { result } = renderHook(() => useSendTransactionAction(), {
23
+ wrapper: queryWrapper.wrapper,
24
+ });
25
+
26
+ await waitFor(() => {
27
+ expect(result.current.mutate).toBeDefined();
28
+ });
29
+
30
+ result.current.mutate({
31
+ tx: {
32
+ to: "0x1234567890123456789012345678901234567890",
33
+ data: "0x",
34
+ },
35
+ });
36
+
37
+ await waitFor(() => {
38
+ expect(result.current.isError).toBe(true);
39
+ });
40
+
41
+ expect(result.current.error).toBeInstanceOf(ClientNotFound);
42
+ });
43
+
44
+ test("should send transaction successfully", async ({
45
+ mockFrakProviders,
46
+ }) => {
47
+ const mockResult: SendTransactionReturnType = {
48
+ hash: "0xabcdef1234567890" as Hex,
49
+ };
50
+
51
+ vi.mocked(sendTransaction).mockResolvedValue(mockResult);
52
+
53
+ const { result } = renderHook(() => useSendTransactionAction(), {
54
+ wrapper: mockFrakProviders,
55
+ });
56
+
57
+ result.current.mutate({
58
+ tx: {
59
+ to: "0x1234567890123456789012345678901234567890",
60
+ data: "0x",
61
+ },
62
+ });
63
+
64
+ await waitFor(() => {
65
+ expect(result.current.isSuccess).toBe(true);
66
+ });
67
+
68
+ expect(result.current.data).toEqual(mockResult);
69
+ expect(sendTransaction).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ test("should send transaction with metadata", async ({
73
+ mockFrakProviders,
74
+ }) => {
75
+ const mockResult: SendTransactionReturnType = {
76
+ hash: "0xhash123" as Hex,
77
+ };
78
+
79
+ vi.mocked(sendTransaction).mockResolvedValue(mockResult);
80
+
81
+ const { result } = renderHook(() => useSendTransactionAction(), {
82
+ wrapper: mockFrakProviders,
83
+ });
84
+
85
+ result.current.mutate({
86
+ tx: {
87
+ to: "0x1234567890123456789012345678901234567890",
88
+ data: "0x",
89
+ },
90
+ metadata: {
91
+ header: {
92
+ title: "Send Transaction",
93
+ },
94
+ },
95
+ });
96
+
97
+ await waitFor(() => {
98
+ expect(result.current.isSuccess).toBe(true);
99
+ });
100
+
101
+ expect(result.current.data).toEqual(mockResult);
102
+ });
103
+
104
+ test("should handle mutateAsync", async ({ mockFrakProviders }) => {
105
+ const mockResult: SendTransactionReturnType = {
106
+ hash: "0xasync123" as Hex,
107
+ };
108
+
109
+ vi.mocked(sendTransaction).mockResolvedValue(mockResult);
110
+
111
+ const { result } = renderHook(() => useSendTransactionAction(), {
112
+ wrapper: mockFrakProviders,
113
+ });
114
+
115
+ const response = await result.current.mutateAsync({
116
+ tx: {
117
+ to: "0x1234567890123456789012345678901234567890",
118
+ data: "0x",
119
+ },
120
+ });
121
+
122
+ expect(response).toEqual(mockResult);
123
+
124
+ await waitFor(() => {
125
+ expect(result.current.isSuccess).toBe(true);
126
+ });
127
+ });
128
+
129
+ test("should handle RPC errors", async ({ mockFrakProviders }) => {
130
+ const error = new Error("Transaction send failed");
131
+ vi.mocked(sendTransaction).mockRejectedValue(error);
132
+
133
+ const { result } = renderHook(() => useSendTransactionAction(), {
134
+ wrapper: mockFrakProviders,
135
+ });
136
+
137
+ result.current.mutate({
138
+ tx: {
139
+ to: "0x1234567890123456789012345678901234567890",
140
+ data: "0x",
141
+ },
142
+ });
143
+
144
+ await waitFor(() => {
145
+ expect(result.current.isError).toBe(true);
146
+ });
147
+
148
+ expect(result.current.error).toEqual(error);
149
+ });
150
+
151
+ test("should handle mutation options", async ({ mockFrakProviders }) => {
152
+ const mockResult: SendTransactionReturnType = {
153
+ hash: "0xoptions123" as Hex,
154
+ };
155
+
156
+ vi.mocked(sendTransaction).mockResolvedValue(mockResult);
157
+
158
+ const onSuccess = vi.fn();
159
+ const onError = vi.fn();
160
+
161
+ const { result } = renderHook(
162
+ () =>
163
+ useSendTransactionAction({
164
+ mutations: {
165
+ onSuccess,
166
+ onError,
167
+ },
168
+ }),
169
+ {
170
+ wrapper: mockFrakProviders,
171
+ }
172
+ );
173
+
174
+ result.current.mutate({
175
+ tx: {
176
+ to: "0x1234567890123456789012345678901234567890",
177
+ data: "0x",
178
+ },
179
+ });
180
+
181
+ await waitFor(() => {
182
+ expect(result.current.isSuccess).toBe(true);
183
+ });
184
+
185
+ expect(onSuccess).toHaveBeenCalled();
186
+ expect(onError).not.toHaveBeenCalled();
187
+ });
188
+
189
+ test("should reset mutation state", async ({ mockFrakProviders }) => {
190
+ const mockResult: SendTransactionReturnType = {
191
+ hash: "0xreset123" as Hex,
192
+ };
193
+
194
+ vi.mocked(sendTransaction).mockResolvedValue(mockResult);
195
+
196
+ const { result } = renderHook(() => useSendTransactionAction(), {
197
+ wrapper: mockFrakProviders,
198
+ });
199
+
200
+ result.current.mutate({
201
+ tx: {
202
+ to: "0x1234567890123456789012345678901234567890",
203
+ data: "0x",
204
+ },
205
+ });
206
+
207
+ await waitFor(() => {
208
+ expect(result.current.isSuccess).toBe(true);
209
+ });
210
+
211
+ result.current.reset();
212
+
213
+ await waitFor(() => {
214
+ expect(result.current.data).toBeUndefined();
215
+ expect(result.current.isIdle).toBe(true);
216
+ });
217
+ });
218
+ });
@@ -0,0 +1,62 @@
1
+ import type { SendTransactionReturnType } from "@frak-labs/core-sdk";
2
+ import {
3
+ type SendTransactionParams,
4
+ sendTransaction,
5
+ } from "@frak-labs/core-sdk/actions";
6
+ import { ClientNotFound, type FrakRpcError } from "@frak-labs/frame-connector";
7
+ import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
8
+ import { useFrakClient } from "./useFrakClient";
9
+
10
+ /** @ignore */
11
+ type MutationOptions = Omit<
12
+ UseMutationOptions<
13
+ SendTransactionReturnType,
14
+ FrakRpcError,
15
+ SendTransactionParams
16
+ >,
17
+ "mutationFn" | "mutationKey"
18
+ >;
19
+
20
+ /** @inline */
21
+ interface UseSendTransactionParams {
22
+ /**
23
+ * Optional mutation options, see {@link @tanstack/react-query!useMutation | `useMutation()`} for more infos
24
+ */
25
+ mutations?: MutationOptions;
26
+ }
27
+
28
+ /**
29
+ * Hook that return a mutation helping to send a transaction
30
+ *
31
+ * It's a {@link @tanstack/react-query!home | `tanstack`} wrapper around the {@link @frak-labs/core-sdk!actions.sendTransaction | `sendTransaction()`} action
32
+ *
33
+ * @param args - Optional config object with `mutations` for customizing the underlying {@link @tanstack/react-query!useMutation | `useMutation()`}
34
+ *
35
+ * @group hooks
36
+ *
37
+ * @returns
38
+ * The mutation hook wrapping the `sendTransaction()` action
39
+ * The `mutate` and `mutateAsync` argument is of type {@link @frak-labs/core-sdk!actions.SendTransactionParams | `SendTransactionParams`}
40
+ * The `data` result is a {@link @frak-labs/core-sdk!index.SendTransactionReturnType | `SendTransactionReturnType`}
41
+ *
42
+ * @see {@link @frak-labs/core-sdk!actions.sendTransaction | `sendTransaction()`} for more info about the underlying action
43
+ * @see {@link @tanstack/react-query!useMutation | `useMutation()`} for more info about the mutation options and response
44
+ */
45
+ export function useSendTransactionAction({
46
+ mutations,
47
+ }: UseSendTransactionParams = {}) {
48
+ const client = useFrakClient();
49
+
50
+ return useMutation({
51
+ ...mutations,
52
+ mutationKey: ["frak-sdk", "send-transaction"],
53
+ mutationFn: async (params: SendTransactionParams) => {
54
+ if (!client) {
55
+ throw new ClientNotFound();
56
+ }
57
+
58
+ // Send the transaction
59
+ return sendTransaction(client, params);
60
+ },
61
+ });
62
+ }