@unifold/headless-react 0.1.70-beta.1 → 0.1.70

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/README.md CHANGED
@@ -71,13 +71,21 @@ function DepositUSDC({ externalUserId }: { externalUserId: string }) {
71
71
  }
72
72
  ```
73
73
 
74
- `useDeposit` drives an explicit status state machine:
74
+ `useDeposit` drives an explicit **lifecycle** state machine:
75
75
 
76
76
  ```
77
- idle → creating_addresses → awaiting_funds processing → succeeded | failed
77
+ idle → creating_addresses → ready processing
78
78
  ↘ error (fatal: address creation / invalid recipient)
79
79
  ```
80
80
 
81
+ `processing` is a live hint — “an execution is currently in flight” — and
82
+ toggles back to `ready` when everything settles. **Outcomes never
83
+ appear on the session status**: a session is an ongoing watcher that can
84
+ observe many executions, so a session-level "succeeded"/"failed" would
85
+ mislead the moment a second deposit arrives. Read outcomes from
86
+ `executions`/`latestExecution` statuses, the `direct_execution.succeeded` /
87
+ `.failed` events, or `waitForSuccess()`.
88
+
81
89
  The headless SDK is deliberately **not IP-aware** — `useDeposit` never geo-gates
82
90
  address creation. If you want the modal's region gate, opt in with
83
91
  `useAllowedCountry` and gate your own UI on `isAllowed`.
@@ -108,9 +116,9 @@ For imperative flows, the session exposes `await`-style sugar over the event
108
116
  stream (they only listen — neither starts nor stops the session):
109
117
 
110
118
  ```ts
111
- // Generic primitive over the status state machine:
112
- await session.waitForStatus('processing'); // deposit detected
113
- await session.waitForStatus(['succeeded', 'failed']); // terminal outcome
119
+ // Generic primitive over the lifecycle state machine:
120
+ await session.waitForStatus('processing'); // live activity detected
121
+ await session.waitForStatus('ready'); // addresses ready / all settled
114
122
 
115
123
  // The 90% case — mirrors beginDeposit()'s promise contract
116
124
  // (resolve on success, reject on failure):
package/dist/index.js CHANGED
@@ -77,6 +77,8 @@ function useDeposit(options) {
77
77
  (0, import_react2.useEffect)(() => {
78
78
  callbacksRef.current = options;
79
79
  });
80
+ const autoStartRef = (0, import_react2.useRef)(autoStart);
81
+ autoStartRef.current = autoStart;
80
82
  const [session, setSession] = (0, import_react2.useState)(null);
81
83
  const sessionKey = [
82
84
  publishableKey,
@@ -143,7 +145,7 @@ function useDeposit(options) {
143
145
  }
144
146
  });
145
147
  setSession(nextSession);
146
- if (autoStart) {
148
+ if (autoStartRef.current) {
147
149
  void nextSession.start();
148
150
  }
149
151
  return () => {
@@ -151,7 +153,7 @@ function useDeposit(options) {
151
153
  offStatus();
152
154
  nextSession.destroy();
153
155
  };
154
- }, [sessionKey, autoStart]);
156
+ }, [sessionKey]);
155
157
  const subscribe = (0, import_react2.useCallback)(
156
158
  (listener) => session ? session.subscribe(listener) : () => {
157
159
  },
@@ -226,7 +228,9 @@ function useDepositAddresses(options) {
226
228
  publishableKey
227
229
  ),
228
230
  select: (response) => response.data.map(import_core3.mapWalletToDepositAddress),
229
- enabled: enabled && !!externalUserId && !!publishableKey,
231
+ // When a destination is supplied it must be complete — never issue a
232
+ // create call with a partially-filled destination.
233
+ enabled: enabled && !!externalUserId && !!publishableKey && (!destination || !!destination.chainType && !!destination.chainId && !!destination.tokenAddress && !!destination.recipientAddress),
230
234
  staleTime: 1e3 * 60 * 60,
231
235
  // 1 hour — wallets don't change once created
232
236
  gcTime: 1e3 * 60 * 60 * 24,
@@ -244,11 +248,12 @@ var import_react_provider4 = require("@unifold/react-provider");
244
248
  function useSupportedDepositTokens(options = {}) {
245
249
  const { publishableKey } = (0, import_react_provider4.useUnifold)();
246
250
  const { destination, productType, enabled = true } = options;
247
- const apiOptions = destination || productType ? {
248
- ...destination ? {
249
- destination_token_address: destination.tokenAddress,
250
- destination_chain_id: destination.chainId,
251
- destination_chain_type: destination.chainType
251
+ const completeDestination = destination?.chainType && destination?.chainId && destination?.tokenAddress ? destination : void 0;
252
+ const apiOptions = completeDestination || productType ? {
253
+ ...completeDestination ? {
254
+ destination_token_address: completeDestination.tokenAddress,
255
+ destination_chain_id: completeDestination.chainId,
256
+ destination_chain_type: completeDestination.chainType
252
257
  } : {},
253
258
  ...productType ? { product_type: productType } : {}
254
259
  } : void 0;
@@ -279,7 +284,12 @@ var import_core5 = require("@unifold/core");
279
284
  var import_react_provider5 = require("@unifold/react-provider");
280
285
  function useExecutions(options) {
281
286
  const { publishableKey } = (0, import_react_provider5.useUnifold)();
282
- const { externalUserId, refetchInterval = false, enabled = true, actionType = import_core5.ActionType.Deposit } = options;
287
+ const {
288
+ externalUserId,
289
+ refetchInterval = false,
290
+ enabled = true,
291
+ actionType = import_core5.ActionType.Deposit
292
+ } = options;
283
293
  return (0, import_react_query3.useQuery)({
284
294
  // Key parity with @unifold/ui-react's useExecutions.
285
295
  queryKey: ["unifold", "executions", actionType, externalUserId, publishableKey],
@@ -358,7 +368,8 @@ var import_react_provider7 = require("@unifold/react-provider");
358
368
  function useAddressValidation(options) {
359
369
  const { publishableKey } = (0, import_react_provider7.useUnifold)();
360
370
  const { recipientAddress, destination, enabled = true } = options;
361
- const shouldValidate = enabled && !!publishableKey && !!recipientAddress && !!destination;
371
+ const hasCompleteDestination = !!destination?.chainType && !!destination?.chainId && !!destination?.tokenAddress;
372
+ const shouldValidate = enabled && !!publishableKey && !!recipientAddress && hasCompleteDestination;
362
373
  const { data, isLoading, error } = (0, import_react_query5.useQuery)({
363
374
  // Key parity with @unifold/ui-react's useAddressValidation.
364
375
  queryKey: [
@@ -379,11 +390,15 @@ function useAddressValidation(options) {
379
390
  publishableKey
380
391
  ),
381
392
  enabled: shouldValidate,
382
- refetchOnMount: false,
383
- refetchOnReconnect: false,
393
+ // Address validity is EXTERNAL state that can flip without the query key
394
+ // changing (e.g. an Algorand recipient opts in to the asset). Always
395
+ // revalidate on mount and never serve a stale verdict — otherwise a user
396
+ // who fixes the problem still sees "invalid" until a cache window expires.
397
+ refetchOnMount: "always",
398
+ refetchOnReconnect: true,
384
399
  refetchOnWindowFocus: false,
385
- staleTime: 1e3 * 60 * 5,
386
- gcTime: 1e3 * 60 * 30
400
+ staleTime: 0,
401
+ gcTime: 1e3 * 60 * 5
387
402
  });
388
403
  if (!shouldValidate) {
389
404
  return { isValid: null, failureCode: null, metadata: null, isLoading: false, error: null };
package/dist/index.mjs CHANGED
@@ -41,6 +41,8 @@ function useDeposit(options) {
41
41
  useEffect(() => {
42
42
  callbacksRef.current = options;
43
43
  });
44
+ const autoStartRef = useRef(autoStart);
45
+ autoStartRef.current = autoStart;
44
46
  const [session, setSession] = useState(null);
45
47
  const sessionKey = [
46
48
  publishableKey,
@@ -107,7 +109,7 @@ function useDeposit(options) {
107
109
  }
108
110
  });
109
111
  setSession(nextSession);
110
- if (autoStart) {
112
+ if (autoStartRef.current) {
111
113
  void nextSession.start();
112
114
  }
113
115
  return () => {
@@ -115,7 +117,7 @@ function useDeposit(options) {
115
117
  offStatus();
116
118
  nextSession.destroy();
117
119
  };
118
- }, [sessionKey, autoStart]);
120
+ }, [sessionKey]);
119
121
  const subscribe = useCallback(
120
122
  (listener) => session ? session.subscribe(listener) : () => {
121
123
  },
@@ -193,7 +195,9 @@ function useDepositAddresses(options) {
193
195
  publishableKey
194
196
  ),
195
197
  select: (response) => response.data.map(mapWalletToDepositAddress),
196
- enabled: enabled && !!externalUserId && !!publishableKey,
198
+ // When a destination is supplied it must be complete — never issue a
199
+ // create call with a partially-filled destination.
200
+ enabled: enabled && !!externalUserId && !!publishableKey && (!destination || !!destination.chainType && !!destination.chainId && !!destination.tokenAddress && !!destination.recipientAddress),
197
201
  staleTime: 1e3 * 60 * 60,
198
202
  // 1 hour — wallets don't change once created
199
203
  gcTime: 1e3 * 60 * 60 * 24,
@@ -213,11 +217,12 @@ import { useUnifold as useUnifold4 } from "@unifold/react-provider";
213
217
  function useSupportedDepositTokens(options = {}) {
214
218
  const { publishableKey } = useUnifold4();
215
219
  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
220
+ const completeDestination = destination?.chainType && destination?.chainId && destination?.tokenAddress ? destination : void 0;
221
+ const apiOptions = completeDestination || productType ? {
222
+ ...completeDestination ? {
223
+ destination_token_address: completeDestination.tokenAddress,
224
+ destination_chain_id: completeDestination.chainId,
225
+ destination_chain_type: completeDestination.chainType
221
226
  } : {},
222
227
  ...productType ? { product_type: productType } : {}
223
228
  } : void 0;
@@ -252,7 +257,12 @@ import {
252
257
  import { useUnifold as useUnifold5 } from "@unifold/react-provider";
253
258
  function useExecutions(options) {
254
259
  const { publishableKey } = useUnifold5();
255
- const { externalUserId, refetchInterval = false, enabled = true, actionType = ActionType.Deposit } = options;
260
+ const {
261
+ externalUserId,
262
+ refetchInterval = false,
263
+ enabled = true,
264
+ actionType = ActionType.Deposit
265
+ } = options;
256
266
  return useQuery3({
257
267
  // Key parity with @unifold/ui-react's useExecutions.
258
268
  queryKey: ["unifold", "executions", actionType, externalUserId, publishableKey],
@@ -333,7 +343,8 @@ import { useUnifold as useUnifold7 } from "@unifold/react-provider";
333
343
  function useAddressValidation(options) {
334
344
  const { publishableKey } = useUnifold7();
335
345
  const { recipientAddress, destination, enabled = true } = options;
336
- const shouldValidate = enabled && !!publishableKey && !!recipientAddress && !!destination;
346
+ const hasCompleteDestination = !!destination?.chainType && !!destination?.chainId && !!destination?.tokenAddress;
347
+ const shouldValidate = enabled && !!publishableKey && !!recipientAddress && hasCompleteDestination;
337
348
  const { data, isLoading, error } = useQuery5({
338
349
  // Key parity with @unifold/ui-react's useAddressValidation.
339
350
  queryKey: [
@@ -354,11 +365,15 @@ function useAddressValidation(options) {
354
365
  publishableKey
355
366
  ),
356
367
  enabled: shouldValidate,
357
- refetchOnMount: false,
358
- refetchOnReconnect: false,
368
+ // Address validity is EXTERNAL state that can flip without the query key
369
+ // changing (e.g. an Algorand recipient opts in to the asset). Always
370
+ // revalidate on mount and never serve a stale verdict — otherwise a user
371
+ // who fixes the problem still sees "invalid" until a cache window expires.
372
+ refetchOnMount: "always",
373
+ refetchOnReconnect: true,
359
374
  refetchOnWindowFocus: false,
360
- staleTime: 1e3 * 60 * 5,
361
- gcTime: 1e3 * 60 * 30
375
+ staleTime: 0,
376
+ gcTime: 1e3 * 60 * 5
362
377
  });
363
378
  if (!shouldValidate) {
364
379
  return { isValid: null, failureCode: null, metadata: null, isLoading: false, error: null };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifold/headless-react",
3
- "version": "0.1.70-beta.1",
3
+ "version": "0.1.70",
4
4
  "description": "Unifold Headless React SDK - hooks-only (no UI) crypto deposit flows",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@tanstack/react-query": "^5.90.11",
24
- "@unifold/core": "0.1.70-beta.1",
25
- "@unifold/react-provider": "0.1.70-beta.1"
24
+ "@unifold/core": "0.1.70",
25
+ "@unifold/react-provider": "0.1.70"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/react": "^19.0.0",