@frak-labs/react-sdk 0.1.0 → 0.1.1-beta.4dfea079

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 +332 -345
  4. package/dist/index.d.ts +332 -345
  5. package/dist/index.js +1 -1
  6. package/package.json +30 -15
  7. package/src/hook/helper/useReferralInteraction.test.ts +358 -0
  8. package/src/hook/helper/useReferralInteraction.ts +78 -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 +52 -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
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{Fragment as t,createContext as e,createElement as n,useCallback as r,useContext as o,useEffect as a,useMemo as i,useState as u}from"react";import{FrakContextManager as l,baseIframeProps as f,createIFrameFrakClient as s}from"@frak-labs/core-sdk";import{ClientNotFound as c,FrakRpcError as d,RpcErrorCodes as w}from"@frak-labs/frame-connector";import{displayModal as m,getProductInformation as k,openSso as y,processReferral as p,sendInteraction as h,sendTransaction as F,siweAuthenticate as v,watchWalletStatus as g}from"@frak-labs/core-sdk/actions";import{useMutation as C,useQuery as K,useQueryClient as q}from"@tanstack/react-query";let x=e(void 0);function b(t){let{children:e,config:r}=t;return n(x.Provider,{value:{...r,walletUrl:r.walletUrl??"https://wallet.frak.id",domain:r.domain??("undefined"!=typeof window?window?.location?.host:void 0)??"not-found"}},e)}function I(){let t=o(x);if(!t)throw new d(w.configError,"Frak config not found");return t}let P=e(void 0);function S({style:e,children:r}){let o=I(),[a,i]=u(void 0),l=n("iframe",{...f,src:`${o.walletUrl}/listener`,style:e??f.style,ref:t=>{t&&!a&&i(s({iframe:t,config:o}))}}),c=n(P.Provider,{value:a},r);return n(t,null,l,c)}function T(){return o(P)}function U(){let t=q(),e=T(),n=r(e=>{t.setQueryData(["frak-sdk","wallet-status-listener"],e)},[t]);return K({gcTime:0,staleTime:0,queryKey:["frak-sdk","wallet-status-listener"],queryFn:async()=>{if(!e)throw new c;return g(e,n)},enabled:!!e})}function E({mutations:t}={}){let e=T();return C({...t,mutationKey:["frak-sdk","send-transaction"],mutationFn:async t=>{if(!e)throw new c;return F(e,t)}})}function A({mutations:t}={}){let e=T();return C({...t,mutationKey:["frak-sdk","send-interaction"],mutationFn:async t=>{if(!e)throw new c;return h(e,t)}})}function D({mutations:t}={}){let e=T();return C({...t,mutationKey:["frak-sdk","siwe-authenticate",e?.config.domain??"no-domain"],mutationFn:async t=>{if(!e)throw new c;return v(e,t)}})}function L({productId:t,modalConfig:e,options:n}={}){let o=T(),{frakContext:f}=function(){let{location:t}=function(){let t=function(){let[t,e]=u(!1);return a(()=>{e(!0)},[]),t}(),[e,n]=u(t?window.location:void 0);a(()=>{if(t)return e||r(),window.addEventListener("popstate",r),()=>{window.removeEventListener("popstate",r)};function r(){n(window.location)}},[t,e]);let r=i(()=>e?.href,[e?.href]);return{location:e,href:r}}();return{frakContext:i(()=>t?.href?l.parse({url:t.href}):null,[t?.href]),updateContext:r(e=>{console.log("Updating context",{newContext:e}),l.replaceUrl({url:t?.href,context:e})},[t?.href])}}(),{data:s}=U(),{data:d,error:w,status:m}=K({gcTime:0,staleTime:0,queryKey:["frak-sdk","auto-referral-interaction",f?.r??"no-referrer",s?.key??"no-wallet-status",t??"no-product-id"],queryFn:()=>{if(!o)throw new c;return p(o,{walletStatus:s,frakContext:f,modalConfig:e,productId:t,options:n})},enabled:!!s});return i(()=>"pending"===m?"processing":"error"===m?w:d||"idle",[d,m,w])}function G({mutations:t}={}){let e=T();return C({...t,mutationKey:["frak-sdk","display-modal"],mutationFn:async t=>{if(!e)throw new c;return m(e,t)}})}function M({mutations:t}={}){let e=T();return C({...t,mutationKey:["frak-sdk","open-sso"],mutationFn:async t=>{if(!e)throw new c;return y(e,t)}})}function O({query:t}={}){let e=T();return K({...t,queryKey:["frak-sdk","get-product-information"],queryFn:async()=>{if(!e)throw new c;return k(e)}})}export{x as FrakConfigContext,b as FrakConfigProvider,P as FrakIFrameClientContext,S as FrakIFrameClientProvider,G as useDisplayModal,T as useFrakClient,I as useFrakConfig,O as useGetProductInformation,M as useOpenSso,L as useReferralInteraction,A as useSendInteraction,E as useSendTransactionAction,D as useSiweAuthenticate,U as useWalletStatus};
1
+ import{displayModal as e,getMerchantInformation as t,openSso as n,prepareSso as r,processReferral as i,sendTransaction as a,siweAuthenticate as o,watchWalletStatus as s}from"@frak-labs/core-sdk/actions";import{ClientNotFound as c,FrakRpcError as l,RpcErrorCodes as u}from"@frak-labs/frame-connector";import{useMutation as d,useQuery as f,useQueryClient as p}from"@tanstack/react-query";import{Fragment as m,createContext as h,createElement as g,useCallback as _,useContext as v,useEffect as y,useMemo as b,useState as x}from"react";import{FrakContextManager as S,baseIframeProps as C,createIFrameFrakClient as w}from"@frak-labs/core-sdk";const T=h(void 0);function E(e){let{children:t,config:n}=e;return g(T.Provider,{value:{...n,walletUrl:n.walletUrl??`https://wallet.frak.id`,domain:n.domain??(typeof window<`u`?window?.location?.host:void 0)??`not-found`}},t)}const D=h(void 0);function O({style:e,children:t}){let n=I(),[r,i]=x(void 0);return g(m,null,g(`iframe`,{...C,src:`${n.walletUrl}/listener`,style:e??C.style,ref:e=>{!e||r||i(w({iframe:e,config:n}))}}),g(D.Provider,{value:r},t))}function k(){return v(D)}function A(){let e=p(),t=k(),n=_(t=>{e.setQueryData([`frak-sdk`,`wallet-status-listener`],t)},[e]);return f({gcTime:0,staleTime:0,queryKey:[`frak-sdk`,`wallet-status-listener`],queryFn:async()=>{if(!t)throw new c;return s(t,n)},enabled:!!t})}function j(){let[e,t]=x(!1);return y(()=>{t(!0)},[]),e}function M(){let e=j(),[t,n]=x(e?window.location:void 0);return y(()=>{if(!e)return;function r(){n(window.location)}return t||r(),window.addEventListener(`popstate`,r),()=>{window.removeEventListener(`popstate`,r)}},[e,t]),{location:t,href:b(()=>t?.href,[t?.href])}}function N(){let{location:e}=M();return{frakContext:b(()=>e?.href?S.parse({url:e.href}):null,[e?.href]),updateContext:_(t=>{console.log(`Updating context`,{newContext:t}),S.replaceUrl({url:e?.href,context:t})},[e?.href])}}function P({modalConfig:e,options:t}={}){let n=k(),{frakContext:r}=N(),{data:a}=A(),{data:o,error:s,status:l}=f({gcTime:0,staleTime:0,queryKey:[`frak-sdk`,`auto-referral-interaction`,r?.r??`no-referrer`,a?.key??`no-wallet-status`],queryFn:()=>{if(!n)throw new c;return i(n,{walletStatus:a,frakContext:r,modalConfig:e,options:t})},enabled:!!a});return b(()=>l===`pending`?`processing`:l===`error`?s:o||`idle`,[o,l,s])}function F({mutations:t}={}){let n=k();return d({...t,mutationKey:[`frak-sdk`,`display-modal`],mutationFn:async t=>{if(!n)throw new c;return e(n,t)}})}function I(){let e=v(T);if(!e)throw new l(u.configError,`Frak config not found`);return e}function L({query:e}={}){let n=k();return f({...e,queryKey:[`frak-sdk`,`get-merchant-information`],queryFn:async()=>{if(!n)throw new c;return t(n)}})}function R({mutations:e}={}){let t=k();return d({...e,mutationKey:[`frak-sdk`,`open-sso`],mutationFn:async e=>{if(!t)throw new c;return n(t,e)}})}function z(e){let t=k();return f({queryKey:[`frak-sdk`,`prepare-sso`,e],queryFn:async()=>{if(!t)throw new c;return r(t,e)}})}function B({mutations:e}={}){let t=k();return d({...e,mutationKey:[`frak-sdk`,`send-transaction`],mutationFn:async e=>{if(!t)throw new c;return a(t,e)}})}function V({mutations:e}={}){let t=k();return d({...e,mutationKey:[`frak-sdk`,`siwe-authenticate`,t?.config.domain??`no-domain`],mutationFn:async e=>{if(!t)throw new c;return o(t,e)}})}export{T as FrakConfigContext,E as FrakConfigProvider,D as FrakIFrameClientContext,O as FrakIFrameClientProvider,F as useDisplayModal,k as useFrakClient,I as useFrakConfig,L as useGetMerchantInformation,R as useOpenSso,z as usePrepareSso,P as useReferralInteraction,B as useSendTransactionAction,V as useSiweAuthenticate,A as useWalletStatus};
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "0.1.0",
14
+ "version": "0.1.1-beta.4dfea079",
15
15
  "description": "React SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.",
16
16
  "repository": {
17
17
  "url": "https://github.com/frak-id/wallet",
@@ -33,12 +33,14 @@
33
33
  },
34
34
  "type": "module",
35
35
  "files": [
36
- "/dist"
36
+ "/dist",
37
+ "/src"
37
38
  ],
38
39
  "main": "./dist/index.cjs",
39
40
  "types": "./dist/index.d.cts",
40
41
  "exports": {
41
42
  ".": {
43
+ "development": "./src/index.ts",
42
44
  "import": {
43
45
  "types": "./dist/index.d.ts",
44
46
  "default": "./dist/index.js"
@@ -54,16 +56,19 @@
54
56
  "format:check": "biome check .",
55
57
  "format": "biome check --write .",
56
58
  "clean": "rimraf dist",
57
- "build": "rslib build",
58
- "build:watch": "rslib build --watch",
59
+ "build": "tsdown",
60
+ "build:watch": "tsdown --watch",
59
61
  "check-exports": "attw --pack .",
60
62
  "typecheck": "tsc --noEmit",
63
+ "test": "vitest",
64
+ "test:ui": "vitest --ui",
65
+ "test:coverage": "vitest --coverage",
61
66
  "prepublish": "bun run lint && bun run build",
62
67
  "publish": "echo 'Publishing react...'"
63
68
  },
64
69
  "dependencies": {
65
- "@frak-labs/frame-connector": "0.1.0",
66
- "@frak-labs/core-sdk": "0.1.0"
70
+ "@frak-labs/frame-connector": "0.1.0-beta.4dfea079",
71
+ "@frak-labs/core-sdk": "0.1.1-beta.4dfea079"
67
72
  },
68
73
  "peerDependencies": {
69
74
  "viem": "^2.x",
@@ -72,14 +77,24 @@
72
77
  },
73
78
  "devDependencies": {
74
79
  "@arethetypeswrong/cli": "^0.18.2",
75
- "@microsoft/api-extractor": "^7.52.8",
76
- "@rsbuild/plugin-react": "^1.3.2",
77
- "@rslib/core": "^0.9.2",
78
- "@types/node": "^24",
80
+ "@frak-labs/dev-tooling": "0.0.0",
81
+ "@frak-labs/test-foundation": "0.1.0",
82
+ "@frak-labs/wallet-shared": "0.0.1",
83
+ "@tanstack/react-query": "^5.90.20",
84
+ "@testing-library/jest-dom": "^6.9.1",
85
+ "@testing-library/react": "^16.3.2",
86
+ "@testing-library/react-hooks": "^8.0.1",
87
+ "@testing-library/user-event": "^14.6.1",
88
+ "@types/jsdom": "^27.0.0",
89
+ "@types/node": "^24.10.13",
90
+ "@types/react": "19.2.13",
91
+ "@vitest/coverage-v8": "^4.0.18",
92
+ "@vitest/ui": "^4.0.18",
93
+ "jsdom": "^28.0.0",
94
+ "react": "^19.2.4",
95
+ "tsdown": "^0.20.3",
79
96
  "typescript": "^5",
80
- "@frak-labs/dev-tooling": "0.0.0"
81
- },
82
- "browserslist": [
83
- "extends @frak-labs/browserslist-config"
84
- ]
97
+ "viem": "^2.39.0",
98
+ "vitest": "^4.0.18"
99
+ }
85
100
  }
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Tests for useReferralInteraction hook
3
+ * Tests automatic referral interaction submission
4
+ */
5
+
6
+ import { vi } from "vitest";
7
+
8
+ vi.mock("@frak-labs/core-sdk/actions");
9
+ vi.mock("../useFrakClient");
10
+ vi.mock("../useWalletStatus");
11
+ vi.mock("../utils/useFrakContext");
12
+
13
+ import type { FrakContext, WalletStatusReturnType } from "@frak-labs/core-sdk";
14
+ import { processReferral } from "@frak-labs/core-sdk/actions";
15
+ import { ClientNotFound } from "@frak-labs/frame-connector";
16
+ import { renderHook, waitFor } from "@testing-library/react";
17
+ import { describe, expect, test } from "../../../tests/vitest-fixtures";
18
+ import { useFrakClient } from "../useFrakClient";
19
+ import { useWalletStatus } from "../useWalletStatus";
20
+ import { useFrakContext } from "../utils/useFrakContext";
21
+ import { useReferralInteraction } from "./useReferralInteraction";
22
+
23
+ describe("useReferralInteraction", () => {
24
+ test("should return processing when wallet status is not available", ({
25
+ queryWrapper,
26
+ mockFrakClient,
27
+ }) => {
28
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
29
+ vi.mocked(useFrakContext).mockReturnValue({
30
+ frakContext: null,
31
+ updateContext: vi.fn(),
32
+ });
33
+ vi.mocked(useWalletStatus).mockReturnValue({
34
+ data: undefined,
35
+ isSuccess: false,
36
+ isPending: true,
37
+ } as ReturnType<typeof useWalletStatus>);
38
+
39
+ const { result } = renderHook(() => useReferralInteraction(), {
40
+ wrapper: queryWrapper.wrapper,
41
+ });
42
+
43
+ // Query is disabled when wallet status is not available, status remains pending
44
+ expect(result.current).toBe("processing");
45
+ });
46
+
47
+ test("should process referral successfully", async ({
48
+ queryWrapper,
49
+ mockFrakClient,
50
+ }) => {
51
+ const mockReferralState = "success";
52
+
53
+ const mockWalletStatus: WalletStatusReturnType = {
54
+ key: "connected",
55
+ wallet: "0x1234567890123456789012345678901234567890",
56
+ };
57
+
58
+ const mockFrakContext: FrakContext = {
59
+ r: "0x1234567890123456789012345678901234567890",
60
+ };
61
+
62
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
63
+ vi.mocked(useFrakContext).mockReturnValue({
64
+ frakContext: mockFrakContext,
65
+ updateContext: vi.fn(),
66
+ });
67
+ vi.mocked(useWalletStatus).mockReturnValue({
68
+ data: mockWalletStatus,
69
+ isSuccess: true,
70
+ isPending: false,
71
+ } as ReturnType<typeof useWalletStatus>);
72
+ vi.mocked(processReferral).mockResolvedValue(mockReferralState);
73
+
74
+ const { result } = renderHook(() => useReferralInteraction(), {
75
+ wrapper: queryWrapper.wrapper,
76
+ });
77
+
78
+ await waitFor(() => {
79
+ expect(result.current).toEqual(mockReferralState);
80
+ });
81
+
82
+ expect(processReferral).toHaveBeenCalledWith(mockFrakClient, {
83
+ walletStatus: mockWalletStatus,
84
+ frakContext: mockFrakContext,
85
+ modalConfig: undefined,
86
+ options: undefined,
87
+ });
88
+ });
89
+
90
+ test("should process referral with modalConfig", async ({
91
+ queryWrapper,
92
+ mockFrakClient,
93
+ }) => {
94
+ const mockReferralState = "success";
95
+
96
+ const modalConfig = {
97
+ metadata: {
98
+ logo: "https://example.com/logo.png",
99
+ },
100
+ };
101
+
102
+ const mockWalletStatus: WalletStatusReturnType = {
103
+ key: "connected",
104
+ wallet: "0x1234567890123456789012345678901234567890",
105
+ };
106
+
107
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
108
+ vi.mocked(useFrakContext).mockReturnValue({
109
+ frakContext: { r: "0x4567890123456789012345678901234567890123" },
110
+ updateContext: vi.fn(),
111
+ });
112
+ vi.mocked(useWalletStatus).mockReturnValue({
113
+ data: mockWalletStatus,
114
+ isSuccess: true,
115
+ isPending: false,
116
+ } as ReturnType<typeof useWalletStatus>);
117
+ vi.mocked(processReferral).mockResolvedValue(mockReferralState);
118
+
119
+ const { result } = renderHook(
120
+ () => useReferralInteraction({ modalConfig }),
121
+ {
122
+ wrapper: queryWrapper.wrapper,
123
+ }
124
+ );
125
+
126
+ await waitFor(() => {
127
+ expect(result.current).toEqual(mockReferralState);
128
+ });
129
+
130
+ expect(processReferral).toHaveBeenCalledWith(
131
+ mockFrakClient,
132
+ expect.objectContaining({
133
+ modalConfig,
134
+ })
135
+ );
136
+ });
137
+
138
+ test("should process referral with options", async ({
139
+ queryWrapper,
140
+ mockFrakClient,
141
+ }) => {
142
+ const mockReferralState = "success";
143
+
144
+ const options = {
145
+ alwaysAppendUrl: true,
146
+ };
147
+
148
+ const mockWalletStatus: WalletStatusReturnType = {
149
+ key: "connected",
150
+ wallet: "0x1234567890123456789012345678901234567890",
151
+ };
152
+
153
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
154
+ vi.mocked(useFrakContext).mockReturnValue({
155
+ frakContext: { r: "0x7890123456789012345678901234567890123456" },
156
+ updateContext: vi.fn(),
157
+ });
158
+ vi.mocked(useWalletStatus).mockReturnValue({
159
+ data: mockWalletStatus,
160
+ isSuccess: true,
161
+ isPending: false,
162
+ } as ReturnType<typeof useWalletStatus>);
163
+ vi.mocked(processReferral).mockResolvedValue(mockReferralState);
164
+
165
+ const { result } = renderHook(
166
+ () => useReferralInteraction({ options }),
167
+ {
168
+ wrapper: queryWrapper.wrapper,
169
+ }
170
+ );
171
+
172
+ await waitFor(() => {
173
+ expect(result.current).toEqual(mockReferralState);
174
+ });
175
+
176
+ expect(processReferral).toHaveBeenCalledWith(
177
+ mockFrakClient,
178
+ expect.objectContaining({
179
+ options,
180
+ })
181
+ );
182
+ });
183
+
184
+ test("should handle processing state", ({
185
+ queryWrapper,
186
+ mockFrakClient,
187
+ }) => {
188
+ const mockWalletStatus: WalletStatusReturnType = {
189
+ key: "connected",
190
+ wallet: "0x1234567890123456789012345678901234567890",
191
+ };
192
+
193
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
194
+ vi.mocked(useFrakContext).mockReturnValue({
195
+ frakContext: { r: "0x9999999999999999999999999999999999999999" },
196
+ updateContext: vi.fn(),
197
+ });
198
+ vi.mocked(useWalletStatus).mockReturnValue({
199
+ data: mockWalletStatus,
200
+ isSuccess: true,
201
+ isPending: false,
202
+ } as ReturnType<typeof useWalletStatus>);
203
+
204
+ // Mock processReferral to never resolve (simulate pending)
205
+ vi.mocked(processReferral).mockImplementation(
206
+ () => new Promise(() => {})
207
+ );
208
+
209
+ const { result } = renderHook(() => useReferralInteraction(), {
210
+ wrapper: queryWrapper.wrapper,
211
+ });
212
+
213
+ expect(result.current).toBe("processing");
214
+ });
215
+
216
+ test("should handle errors", async ({ queryWrapper, mockFrakClient }) => {
217
+ const error = new Error("Referral processing failed");
218
+
219
+ const mockWalletStatus: WalletStatusReturnType = {
220
+ key: "connected",
221
+ wallet: "0x1234567890123456789012345678901234567890",
222
+ };
223
+
224
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
225
+ vi.mocked(useFrakContext).mockReturnValue({
226
+ frakContext: { r: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" },
227
+ updateContext: vi.fn(),
228
+ });
229
+ vi.mocked(useWalletStatus).mockReturnValue({
230
+ data: mockWalletStatus,
231
+ isSuccess: true,
232
+ isPending: false,
233
+ } as ReturnType<typeof useWalletStatus>);
234
+ vi.mocked(processReferral).mockRejectedValue(error);
235
+
236
+ const { result } = renderHook(() => useReferralInteraction(), {
237
+ wrapper: queryWrapper.wrapper,
238
+ });
239
+
240
+ await waitFor(() => {
241
+ expect(result.current).toEqual(error);
242
+ });
243
+ });
244
+
245
+ test("should throw ClientNotFound when client is not available", async ({
246
+ queryWrapper,
247
+ }) => {
248
+ const mockWalletStatus: WalletStatusReturnType = {
249
+ key: "connected",
250
+ wallet: "0x1234567890123456789012345678901234567890",
251
+ };
252
+
253
+ vi.mocked(useFrakClient).mockReturnValue(undefined);
254
+ vi.mocked(useFrakContext).mockReturnValue({
255
+ frakContext: { r: "0xcccccccccccccccccccccccccccccccccccccccc" },
256
+ updateContext: vi.fn(),
257
+ });
258
+ vi.mocked(useWalletStatus).mockReturnValue({
259
+ data: mockWalletStatus,
260
+ isSuccess: true,
261
+ isPending: false,
262
+ } as ReturnType<typeof useWalletStatus>);
263
+
264
+ const { result } = renderHook(() => useReferralInteraction(), {
265
+ wrapper: queryWrapper.wrapper,
266
+ });
267
+
268
+ await waitFor(() => {
269
+ expect(result.current).toBeInstanceOf(ClientNotFound);
270
+ });
271
+ });
272
+
273
+ test("should update query key when referrer changes", async ({
274
+ queryWrapper,
275
+ mockFrakClient,
276
+ }) => {
277
+ const mockReferralState = "success";
278
+
279
+ const mockWalletStatus: WalletStatusReturnType = {
280
+ key: "connected",
281
+ wallet: "0x1234567890123456789012345678901234567890",
282
+ };
283
+
284
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
285
+ vi.mocked(useWalletStatus).mockReturnValue({
286
+ data: mockWalletStatus,
287
+ isSuccess: true,
288
+ isPending: false,
289
+ } as ReturnType<typeof useWalletStatus>);
290
+ vi.mocked(processReferral).mockResolvedValue(mockReferralState);
291
+
292
+ // Start with one referrer
293
+ vi.mocked(useFrakContext).mockReturnValue({
294
+ frakContext: { r: "0x1111111111111111111111111111111111111111" },
295
+ updateContext: vi.fn(),
296
+ });
297
+
298
+ const { rerender } = renderHook(() => useReferralInteraction(), {
299
+ wrapper: queryWrapper.wrapper,
300
+ });
301
+
302
+ await waitFor(() => {
303
+ expect(processReferral).toHaveBeenCalledTimes(1);
304
+ });
305
+
306
+ // Change referrer
307
+ vi.mocked(useFrakContext).mockReturnValue({
308
+ frakContext: { r: "0x2222222222222222222222222222222222222222" },
309
+ updateContext: vi.fn(),
310
+ });
311
+
312
+ rerender();
313
+
314
+ // Query should re-run with new referrer
315
+ await waitFor(() => {
316
+ expect(processReferral).toHaveBeenCalledTimes(2);
317
+ });
318
+ });
319
+
320
+ test("should handle no referrer in context", async ({
321
+ queryWrapper,
322
+ mockFrakClient,
323
+ }) => {
324
+ const mockReferralState = "success";
325
+
326
+ const mockWalletStatus: WalletStatusReturnType = {
327
+ key: "connected",
328
+ wallet: "0x1234567890123456789012345678901234567890",
329
+ };
330
+
331
+ vi.mocked(useFrakClient).mockReturnValue(mockFrakClient);
332
+ vi.mocked(useFrakContext).mockReturnValue({
333
+ frakContext: null, // No referrer
334
+ updateContext: vi.fn(),
335
+ });
336
+ vi.mocked(useWalletStatus).mockReturnValue({
337
+ data: mockWalletStatus,
338
+ isSuccess: true,
339
+ isPending: false,
340
+ } as ReturnType<typeof useWalletStatus>);
341
+ vi.mocked(processReferral).mockResolvedValue(mockReferralState);
342
+
343
+ const { result } = renderHook(() => useReferralInteraction(), {
344
+ wrapper: queryWrapper.wrapper,
345
+ });
346
+
347
+ await waitFor(() => {
348
+ expect(result.current).toEqual(mockReferralState);
349
+ });
350
+
351
+ expect(processReferral).toHaveBeenCalledWith(
352
+ mockFrakClient,
353
+ expect.objectContaining({
354
+ frakContext: null,
355
+ })
356
+ );
357
+ });
358
+ });
@@ -0,0 +1,78 @@
1
+ import type { DisplayEmbeddedWalletParamsType } from "@frak-labs/core-sdk";
2
+ import {
3
+ type ProcessReferralOptions,
4
+ processReferral,
5
+ } from "@frak-labs/core-sdk/actions";
6
+ import { ClientNotFound } from "@frak-labs/frame-connector";
7
+ import { useQuery } from "@tanstack/react-query";
8
+ import { useMemo } from "react";
9
+ import { useFrakClient } from "../useFrakClient";
10
+ import { useWalletStatus } from "../useWalletStatus";
11
+ import { useFrakContext } from "../utils/useFrakContext";
12
+
13
+ /**
14
+ * Helper hook to automatically submit a referral interaction when detected
15
+ *
16
+ * @group hooks
17
+ *
18
+ * @param args
19
+ * @param args.modalConfig - The modal configuration to display if the user is not logged in
20
+ * @param args.options - Some options for the referral interaction
21
+ *
22
+ * @returns The resulting referral state, or a potential error
23
+ *
24
+ * @description This function will automatically handle the referral interaction process
25
+ *
26
+ * @see {@link @frak-labs/core-sdk!actions.processReferral | `processReferral()`} for more details on the automatic referral handling process
27
+ */
28
+ export function useReferralInteraction({
29
+ modalConfig,
30
+ options,
31
+ }: {
32
+ modalConfig?: DisplayEmbeddedWalletParamsType;
33
+ options?: ProcessReferralOptions;
34
+ } = {}) {
35
+ // Get the frak client
36
+ const client = useFrakClient();
37
+
38
+ // Get the current frak context
39
+ const { frakContext } = useFrakContext();
40
+
41
+ // Get the wallet status
42
+ const { data: walletStatus } = useWalletStatus();
43
+
44
+ // Setup the query that will transmit the referral interaction
45
+ const {
46
+ data: referralState,
47
+ error,
48
+ status,
49
+ } = useQuery({
50
+ gcTime: 0,
51
+ staleTime: 0,
52
+ queryKey: [
53
+ "frak-sdk",
54
+ "auto-referral-interaction",
55
+ frakContext?.r ?? "no-referrer",
56
+ walletStatus?.key ?? "no-wallet-status",
57
+ ],
58
+ queryFn: () => {
59
+ if (!client) {
60
+ throw new ClientNotFound();
61
+ }
62
+
63
+ return processReferral(client, {
64
+ walletStatus,
65
+ frakContext,
66
+ modalConfig,
67
+ options,
68
+ });
69
+ },
70
+ enabled: !!walletStatus,
71
+ });
72
+
73
+ return useMemo(() => {
74
+ if (status === "pending") return "processing";
75
+ if (status === "error") return error;
76
+ return referralState || "idle";
77
+ }, [referralState, status, error]);
78
+ }
@@ -0,0 +1,10 @@
1
+ export { useReferralInteraction } from "./helper/useReferralInteraction";
2
+ export { useDisplayModal } from "./useDisplayModal";
3
+ export { useFrakClient } from "./useFrakClient";
4
+ export { useFrakConfig } from "./useFrakConfig";
5
+ export { useGetMerchantInformation } from "./useGetMerchantInformation";
6
+ export { useOpenSso } from "./useOpenSso";
7
+ export { usePrepareSso } from "./usePrepareSso";
8
+ export { useSendTransactionAction } from "./useSendTransaction";
9
+ export { useSiweAuthenticate } from "./useSiweAuthenticate";
10
+ export { useWalletStatus } from "./useWalletStatus";