@unifold/headless-react 0.1.70-beta.1

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.
package/dist/index.mjs ADDED
@@ -0,0 +1,402 @@
1
+ // src/index.ts
2
+ import { UnifoldProvider, useUnifold as useUnifold8 } from "@unifold/react-provider";
3
+
4
+ // src/use-unifold-client.ts
5
+ import { useMemo } from "react";
6
+ import { createUnifoldClient } from "@unifold/core";
7
+ import { useUnifold } from "@unifold/react-provider";
8
+ function useUnifoldClient() {
9
+ const { publishableKey } = useUnifold();
10
+ return useMemo(
11
+ () => publishableKey ? createUnifoldClient({ publishableKey }) : null,
12
+ [publishableKey]
13
+ );
14
+ }
15
+
16
+ // src/use-deposit.ts
17
+ import { useCallback, useEffect, useMemo as useMemo2, useRef, useState, useSyncExternalStore } from "react";
18
+ import {
19
+ DepositSession,
20
+ DepositSessionEventType
21
+ } from "@unifold/core";
22
+ import { useUnifold as useUnifold2 } from "@unifold/react-provider";
23
+ var IDLE_SNAPSHOT = {
24
+ status: "idle",
25
+ addresses: [],
26
+ executions: [],
27
+ latestExecution: null,
28
+ isCheckingDeposit: false,
29
+ error: null
30
+ };
31
+ function useDeposit(options) {
32
+ const { publishableKey } = useUnifold2();
33
+ const {
34
+ externalUserId,
35
+ destination,
36
+ confirmationMode = "auto",
37
+ method,
38
+ autoStart = true
39
+ } = options;
40
+ const callbacksRef = useRef(options);
41
+ useEffect(() => {
42
+ callbacksRef.current = options;
43
+ });
44
+ const [session, setSession] = useState(null);
45
+ const sessionKey = [
46
+ publishableKey,
47
+ externalUserId ?? "",
48
+ destination.chainType,
49
+ destination.chainId,
50
+ destination.tokenAddress,
51
+ destination.recipientAddress,
52
+ JSON.stringify(destination.contractCalls ?? null),
53
+ confirmationMode,
54
+ method ?? ""
55
+ ].join("|");
56
+ useEffect(() => {
57
+ if (!publishableKey || !externalUserId) {
58
+ setSession(null);
59
+ return;
60
+ }
61
+ const nextSession = new DepositSession({
62
+ publishableKey,
63
+ externalUserId,
64
+ destination,
65
+ confirmationMode,
66
+ method
67
+ });
68
+ let previousStatus = nextSession.getSnapshot().status;
69
+ const offEvents = nextSession.on("*", (event) => {
70
+ const callbacks = callbacksRef.current;
71
+ callbacks.onEvent?.(event);
72
+ switch (event.type) {
73
+ case DepositSessionEventType.ADDRESSES_CREATED:
74
+ callbacks.onAddressesReady?.(event.data.object.addresses);
75
+ break;
76
+ case DepositSessionEventType.EXECUTION_DETECTED:
77
+ callbacks.onExecutionDetected?.(event.data.object);
78
+ break;
79
+ case DepositSessionEventType.EXECUTION_UPDATED:
80
+ callbacks.onExecutionUpdated?.(event.data.object);
81
+ break;
82
+ case DepositSessionEventType.EXECUTION_SUCCEEDED:
83
+ callbacks.onSuccess?.(event.data.object);
84
+ break;
85
+ case DepositSessionEventType.EXECUTION_FAILED:
86
+ callbacks.onError?.({
87
+ code: "DEPOSIT_FAILED",
88
+ message: "Deposit failed",
89
+ fatal: false,
90
+ cause: event.data.object
91
+ });
92
+ break;
93
+ case DepositSessionEventType.SESSION_ERRORED:
94
+ callbacks.onError?.({
95
+ code: event.data.object.code,
96
+ message: event.data.object.message,
97
+ fatal: event.data.object.fatal
98
+ });
99
+ break;
100
+ }
101
+ });
102
+ const offStatus = nextSession.subscribe(() => {
103
+ const status = nextSession.getSnapshot().status;
104
+ if (status !== previousStatus) {
105
+ previousStatus = status;
106
+ callbacksRef.current.onStatusChange?.(status);
107
+ }
108
+ });
109
+ setSession(nextSession);
110
+ if (autoStart) {
111
+ void nextSession.start();
112
+ }
113
+ return () => {
114
+ offEvents();
115
+ offStatus();
116
+ nextSession.destroy();
117
+ };
118
+ }, [sessionKey, autoStart]);
119
+ const subscribe = useCallback(
120
+ (listener) => session ? session.subscribe(listener) : () => {
121
+ },
122
+ [session]
123
+ );
124
+ const getSnapshot = useCallback(
125
+ () => session ? session.getSnapshot() : IDLE_SNAPSHOT,
126
+ [session]
127
+ );
128
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot, () => IDLE_SNAPSHOT);
129
+ const getAddress = useCallback(
130
+ (query) => snapshot.addresses.find((a) => a.chainType === query.chainType),
131
+ [snapshot.addresses]
132
+ );
133
+ const controls = useMemo2(
134
+ () => ({
135
+ start: () => session?.start() ?? Promise.resolve(),
136
+ confirmFundsSent: () => session?.confirmFundsSent(),
137
+ stop: () => session?.stop(),
138
+ restart: async () => {
139
+ if (!session) return;
140
+ session.stop();
141
+ await session.start();
142
+ }
143
+ }),
144
+ [session]
145
+ );
146
+ return {
147
+ status: snapshot.status,
148
+ addresses: snapshot.addresses,
149
+ getAddress,
150
+ executions: snapshot.executions,
151
+ latestExecution: snapshot.latestExecution,
152
+ isCheckingDeposit: snapshot.isCheckingDeposit,
153
+ error: snapshot.error,
154
+ ...controls,
155
+ session
156
+ };
157
+ }
158
+
159
+ // src/use-deposit-addresses.ts
160
+ import { useQuery } from "@tanstack/react-query";
161
+ import {
162
+ createDepositAddress,
163
+ mapWalletToDepositAddress
164
+ } from "@unifold/core";
165
+ import { useUnifold as useUnifold3 } from "@unifold/react-provider";
166
+ function useDepositAddresses(options) {
167
+ const { publishableKey } = useUnifold3();
168
+ const { externalUserId, destination, actionType, enabled = true } = options;
169
+ return useQuery({
170
+ // Key parity with @unifold/ui-react's useDepositAddress — do not reorder.
171
+ queryKey: [
172
+ "unifold",
173
+ "depositAddress",
174
+ externalUserId,
175
+ destination?.recipientAddress ?? null,
176
+ destination?.chainType ?? null,
177
+ destination?.chainId ?? null,
178
+ destination?.tokenAddress ?? null,
179
+ actionType ?? null,
180
+ destination?.contractCalls ?? null,
181
+ publishableKey
182
+ ],
183
+ queryFn: () => createDepositAddress(
184
+ {
185
+ external_user_id: externalUserId,
186
+ recipient_address: destination?.recipientAddress,
187
+ destination_chain_type: destination?.chainType,
188
+ destination_chain_id: destination?.chainId,
189
+ destination_token_address: destination?.tokenAddress,
190
+ action_type: actionType,
191
+ contract_calls: destination?.contractCalls
192
+ },
193
+ publishableKey
194
+ ),
195
+ select: (response) => response.data.map(mapWalletToDepositAddress),
196
+ enabled: enabled && !!externalUserId && !!publishableKey,
197
+ staleTime: 1e3 * 60 * 60,
198
+ // 1 hour — wallets don't change once created
199
+ gcTime: 1e3 * 60 * 60 * 24,
200
+ refetchOnMount: false,
201
+ refetchOnWindowFocus: false,
202
+ retry: 3,
203
+ retryDelay: (attempt) => Math.min(1e3 * 2 ** attempt, 1e4)
204
+ });
205
+ }
206
+
207
+ // src/use-supported-deposit-tokens.ts
208
+ import { useQuery as useQuery2 } from "@tanstack/react-query";
209
+ import {
210
+ getSupportedDepositTokens
211
+ } from "@unifold/core";
212
+ import { useUnifold as useUnifold4 } from "@unifold/react-provider";
213
+ function useSupportedDepositTokens(options = {}) {
214
+ const { publishableKey } = useUnifold4();
215
+ const { destination, productType, enabled = true } = options;
216
+ const apiOptions = destination || productType ? {
217
+ ...destination ? {
218
+ destination_token_address: destination.tokenAddress,
219
+ destination_chain_id: destination.chainId,
220
+ destination_chain_type: destination.chainType
221
+ } : {},
222
+ ...productType ? { product_type: productType } : {}
223
+ } : void 0;
224
+ return useQuery2({
225
+ // Key parity with @unifold/ui-react's useSupportedDepositTokens.
226
+ queryKey: [
227
+ "unifold",
228
+ "supportedDepositTokens",
229
+ publishableKey,
230
+ destination?.tokenAddress ?? null,
231
+ destination?.chainId ?? null,
232
+ destination?.chainType ?? null,
233
+ productType ?? null
234
+ ],
235
+ queryFn: () => getSupportedDepositTokens(publishableKey, apiOptions),
236
+ select: (response) => response.data,
237
+ enabled: enabled && !!publishableKey,
238
+ staleTime: 1e3 * 60 * 5,
239
+ gcTime: 1e3 * 60 * 30,
240
+ refetchOnMount: false,
241
+ refetchOnWindowFocus: false
242
+ });
243
+ }
244
+
245
+ // src/use-executions.ts
246
+ import { useQuery as useQuery3 } from "@tanstack/react-query";
247
+ import {
248
+ queryExecutions,
249
+ mapDirectExecution,
250
+ ActionType
251
+ } from "@unifold/core";
252
+ import { useUnifold as useUnifold5 } from "@unifold/react-provider";
253
+ function useExecutions(options) {
254
+ const { publishableKey } = useUnifold5();
255
+ const { externalUserId, refetchInterval = false, enabled = true, actionType = ActionType.Deposit } = options;
256
+ return useQuery3({
257
+ // Key parity with @unifold/ui-react's useExecutions.
258
+ queryKey: ["unifold", "executions", actionType, externalUserId, publishableKey],
259
+ queryFn: () => queryExecutions(externalUserId, publishableKey, actionType),
260
+ select: (response) => response.data.map(mapDirectExecution),
261
+ enabled: enabled && !!externalUserId && !!publishableKey,
262
+ refetchInterval,
263
+ staleTime: 0,
264
+ gcTime: 1e3 * 60 * 5,
265
+ refetchOnWindowFocus: false
266
+ });
267
+ }
268
+
269
+ // src/use-allowed-country.ts
270
+ import { useQuery as useQuery4, keepPreviousData } from "@tanstack/react-query";
271
+ import { getIpAddress, getProjectConfig } from "@unifold/core";
272
+ import { useUnifold as useUnifold6 } from "@unifold/react-provider";
273
+ function useAllowedCountry() {
274
+ const { publishableKey } = useUnifold6();
275
+ const ipQuery = useQuery4({
276
+ // Key parity with @unifold/core's useUserIp.
277
+ queryKey: ["unifold", "userIpInfo"],
278
+ queryFn: async () => {
279
+ const data = await getIpAddress();
280
+ const subdivision = data.subdivision_code || data.state || null;
281
+ return { alpha2: data.alpha2, country: data.country, subdivisionCode: subdivision };
282
+ },
283
+ refetchOnMount: false,
284
+ refetchOnReconnect: true,
285
+ refetchOnWindowFocus: false,
286
+ staleTime: 1e3 * 60 * 60,
287
+ gcTime: 1e3 * 60 * 60 * 24
288
+ });
289
+ const ip = ipQuery.data;
290
+ const configQuery = useQuery4({
291
+ // Key parity with @unifold/ui-react's useProjectConfig.
292
+ queryKey: ip?.alpha2 ? ["unifold", "projectConfig", publishableKey, ip.alpha2, ip.subdivisionCode ?? null] : ["unifold", "projectConfig", publishableKey],
293
+ queryFn: () => getProjectConfig(
294
+ publishableKey,
295
+ ip?.alpha2 ? { countryCode: ip.alpha2, subdivisionCode: ip.subdivisionCode ?? void 0 } : void 0
296
+ ),
297
+ enabled: !!publishableKey && !ipQuery.isLoading,
298
+ placeholderData: keepPreviousData,
299
+ staleTime: 1e3 * 60 * 30
300
+ });
301
+ const isLoading = ipQuery.isLoading || configQuery.isLoading;
302
+ const error = ipQuery.error || configQuery.error || null;
303
+ const projectConfig = configQuery.data;
304
+ let isAllowed = null;
305
+ if (ip && projectConfig) {
306
+ const blockedCodes = projectConfig.blocked_country_codes || [];
307
+ const blockedSubdivisions = projectConfig.blocked_country_subdivisions || [];
308
+ const countryUpper = ip.alpha2.toUpperCase();
309
+ const subdivisionUpper = (ip.subdivisionCode ?? "").toUpperCase();
310
+ const countryBlocked = blockedCodes.some((code) => code.toUpperCase() === countryUpper);
311
+ const subdivisionBlocked = blockedSubdivisions.some((entry) => {
312
+ if (entry.country_code.toUpperCase() !== countryUpper) return false;
313
+ return entry.subdivision_codes.some((code) => code.toUpperCase() === subdivisionUpper);
314
+ });
315
+ isAllowed = !countryBlocked && !subdivisionBlocked;
316
+ }
317
+ return {
318
+ isAllowed,
319
+ alpha2: ip?.alpha2 ?? null,
320
+ country: ip?.country ?? null,
321
+ subdivisionCode: ip?.subdivisionCode ?? null,
322
+ isLoading,
323
+ error
324
+ };
325
+ }
326
+
327
+ // src/use-address-validation.ts
328
+ import { useQuery as useQuery5 } from "@tanstack/react-query";
329
+ import {
330
+ verifyRecipientAddress
331
+ } from "@unifold/core";
332
+ import { useUnifold as useUnifold7 } from "@unifold/react-provider";
333
+ function useAddressValidation(options) {
334
+ const { publishableKey } = useUnifold7();
335
+ const { recipientAddress, destination, enabled = true } = options;
336
+ const shouldValidate = enabled && !!publishableKey && !!recipientAddress && !!destination;
337
+ const { data, isLoading, error } = useQuery5({
338
+ // Key parity with @unifold/ui-react's useAddressValidation.
339
+ queryKey: [
340
+ "unifold",
341
+ "addressValidation",
342
+ recipientAddress,
343
+ destination?.chainType,
344
+ destination?.chainId,
345
+ destination?.tokenAddress
346
+ ],
347
+ queryFn: () => verifyRecipientAddress(
348
+ {
349
+ chain_type: destination.chainType,
350
+ chain_id: destination.chainId,
351
+ token_address: destination.tokenAddress,
352
+ recipient_address: recipientAddress
353
+ },
354
+ publishableKey
355
+ ),
356
+ enabled: shouldValidate,
357
+ refetchOnMount: false,
358
+ refetchOnReconnect: false,
359
+ refetchOnWindowFocus: false,
360
+ staleTime: 1e3 * 60 * 5,
361
+ gcTime: 1e3 * 60 * 30
362
+ });
363
+ if (!shouldValidate) {
364
+ return { isValid: null, failureCode: null, metadata: null, isLoading: false, error: null };
365
+ }
366
+ return {
367
+ isValid: data?.valid ?? null,
368
+ failureCode: data?.failure_code ?? null,
369
+ metadata: data?.metadata ?? null,
370
+ isLoading,
371
+ error: error ?? null
372
+ };
373
+ }
374
+
375
+ // src/index.ts
376
+ import {
377
+ createUnifoldClient as createUnifoldClient2,
378
+ UnifoldClient,
379
+ DepositSession as DepositSession2,
380
+ DepositSessionEventType as DepositSessionEventType2,
381
+ DepositSessionWaitError,
382
+ ExecutionStatus as ExecutionStatus2,
383
+ ActionType as ActionType2
384
+ } from "@unifold/core";
385
+ export {
386
+ ActionType2 as ActionType,
387
+ DepositSession2 as DepositSession,
388
+ DepositSessionEventType2 as DepositSessionEventType,
389
+ DepositSessionWaitError,
390
+ ExecutionStatus2 as ExecutionStatus,
391
+ UnifoldClient,
392
+ UnifoldProvider,
393
+ createUnifoldClient2 as createUnifoldClient,
394
+ useAddressValidation,
395
+ useAllowedCountry,
396
+ useDeposit,
397
+ useDepositAddresses,
398
+ useExecutions,
399
+ useSupportedDepositTokens,
400
+ useUnifold8 as useUnifold,
401
+ useUnifoldClient
402
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@unifold/headless-react",
3
+ "version": "0.1.70-beta.1",
4
+ "description": "Unifold Headless React SDK - hooks-only (no UI) crypto deposit flows",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "peerDependencies": {
20
+ "react": "^18.2.0 || ^19.0.0"
21
+ },
22
+ "dependencies": {
23
+ "@tanstack/react-query": "^5.90.11",
24
+ "@unifold/core": "0.1.70-beta.1",
25
+ "@unifold/react-provider": "0.1.70-beta.1"
26
+ },
27
+ "devDependencies": {
28
+ "@types/react": "^19.0.0",
29
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
30
+ "@typescript-eslint/parser": "^6.21.0",
31
+ "eslint": "^8.57.0",
32
+ "eslint-config-prettier": "^9.1.0",
33
+ "eslint-plugin-prettier": "^5.1.3",
34
+ "eslint-plugin-react": "^7.34.1",
35
+ "eslint-plugin-react-hooks": "^4.6.0",
36
+ "prettier": "^3.2.5",
37
+ "react": "^19.0.0",
38
+ "tsup": "^8.0.0",
39
+ "typescript": "^5.0.0"
40
+ },
41
+ "keywords": [
42
+ "unifold",
43
+ "headless",
44
+ "hooks",
45
+ "react",
46
+ "sdk",
47
+ "crypto",
48
+ "deposit",
49
+ "web3"
50
+ ],
51
+ "author": "unifold.io",
52
+ "license": "Apache-2.0",
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "dev": "tsup --watch",
56
+ "clean": "rm -rf dist",
57
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
58
+ "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
59
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
60
+ "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
61
+ "type-check": "tsc --noEmit"
62
+ }
63
+ }