@swype-org/react-sdk 0.1.230 → 0.1.237

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.js CHANGED
@@ -155,6 +155,322 @@ function useBlinkDepositAmount() {
155
155
  };
156
156
  }
157
157
 
158
+ // src/passkey-delegation.ts
159
+ var PasskeyIframeBlockedError = class extends Error {
160
+ constructor(message = "Passkey creation is not supported in this browser context.") {
161
+ super(message);
162
+ this.name = "PasskeyIframeBlockedError";
163
+ }
164
+ };
165
+ function isInCrossOriginIframe() {
166
+ if (typeof window === "undefined") return false;
167
+ if (window.parent === window) return false;
168
+ try {
169
+ void window.parent.location.origin;
170
+ return false;
171
+ } catch {
172
+ return true;
173
+ }
174
+ }
175
+ function isSafari() {
176
+ if (typeof navigator === "undefined") return false;
177
+ const ua = navigator.userAgent;
178
+ return /Safari/i.test(ua) && !/Chrome|CriOS|Chromium|Edg|OPR|Firefox/i.test(ua);
179
+ }
180
+ var POPUP_RESULT_TIMEOUT_MS = 12e4;
181
+ var POPUP_CLOSED_POLL_MS = 500;
182
+ var POPUP_CLOSED_GRACE_MS = 1e3;
183
+ function createPasskeyViaPopup(options) {
184
+ return new Promise((resolve, reject) => {
185
+ const verificationToken = crypto.randomUUID();
186
+ const payload = { ...options, verificationToken };
187
+ const encoded = btoa(JSON.stringify(payload));
188
+ const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
189
+ const popup = window.open(popupUrl, "blink-passkey");
190
+ if (!popup) {
191
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
192
+ return;
193
+ }
194
+ let settled = false;
195
+ const timer = setTimeout(() => {
196
+ cleanup();
197
+ reject(new Error("Passkey creation timed out. Please try again."));
198
+ }, POPUP_RESULT_TIMEOUT_MS);
199
+ const closedPoll = setInterval(() => {
200
+ if (popup.closed) {
201
+ clearInterval(closedPoll);
202
+ setTimeout(() => {
203
+ if (!settled) {
204
+ settled = true;
205
+ cleanup();
206
+ checkServerForPasskeyByToken(
207
+ options.authToken,
208
+ options.apiBaseUrl,
209
+ verificationToken
210
+ ).then((result) => {
211
+ if (result) {
212
+ resolve(result);
213
+ } else {
214
+ reject(new Error("Passkey window was closed before completing."));
215
+ }
216
+ }).catch(() => {
217
+ reject(new Error("Passkey window was closed before completing."));
218
+ });
219
+ }
220
+ }, POPUP_CLOSED_GRACE_MS);
221
+ }
222
+ }, POPUP_CLOSED_POLL_MS);
223
+ function cleanup() {
224
+ clearTimeout(timer);
225
+ clearInterval(closedPoll);
226
+ }
227
+ });
228
+ }
229
+ var VERIFY_POPUP_TIMEOUT_MS = 6e4;
230
+ function findDevicePasskeyViaPopup(options) {
231
+ return new Promise((resolve, reject) => {
232
+ const verificationToken = crypto.randomUUID();
233
+ const payload = {
234
+ ...options,
235
+ verificationToken
236
+ };
237
+ const encoded = btoa(JSON.stringify(payload));
238
+ const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
239
+ const popup = window.open(popupUrl, "blink-passkey-verify");
240
+ if (!popup) {
241
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
242
+ return;
243
+ }
244
+ let settled = false;
245
+ const timer = setTimeout(() => {
246
+ cleanup();
247
+ resolve(null);
248
+ }, VERIFY_POPUP_TIMEOUT_MS);
249
+ const closedPoll = setInterval(() => {
250
+ if (popup.closed && !settled) {
251
+ clearInterval(closedPoll);
252
+ setTimeout(() => {
253
+ if (!settled) {
254
+ settled = true;
255
+ cleanup();
256
+ checkServerForPasskeyByToken(
257
+ options.authToken,
258
+ options.apiBaseUrl,
259
+ verificationToken
260
+ ).then((result) => {
261
+ resolve(result?.credentialId ?? null);
262
+ }).catch(() => {
263
+ resolve(null);
264
+ });
265
+ }
266
+ }, POPUP_CLOSED_GRACE_MS);
267
+ }
268
+ }, POPUP_CLOSED_POLL_MS);
269
+ function cleanup() {
270
+ clearTimeout(timer);
271
+ clearInterval(closedPoll);
272
+ }
273
+ });
274
+ }
275
+ async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
276
+ if (!authToken || !apiBaseUrl) return null;
277
+ const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
278
+ headers: { Authorization: `Bearer ${authToken}` }
279
+ });
280
+ if (!res.ok) return null;
281
+ const body = await res.json();
282
+ const passkeys = body.config.passkeys ?? [];
283
+ const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
284
+ return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
285
+ }
286
+
287
+ // src/passkeyRpId.ts
288
+ function normalizeConfiguredDomain(value) {
289
+ return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
290
+ }
291
+ function resolveRootDomainFromHostname(hostname) {
292
+ const trimmedHostname = hostname.trim().toLowerCase();
293
+ if (!trimmedHostname) {
294
+ return "localhost";
295
+ }
296
+ if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
297
+ return trimmedHostname;
298
+ }
299
+ const parts = trimmedHostname.split(".").filter(Boolean);
300
+ if (parts.length < 2) {
301
+ return trimmedHostname;
302
+ }
303
+ return parts.slice(-2).join(".");
304
+ }
305
+
306
+ // src/hooks/passkeyPublic.ts
307
+ function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
308
+ return new Promise((resolve, reject) => {
309
+ if (typeof document === "undefined") {
310
+ resolve();
311
+ return;
312
+ }
313
+ if (document.hasFocus()) {
314
+ resolve();
315
+ return;
316
+ }
317
+ const deadline = Date.now() + timeoutMs;
318
+ const timer = setInterval(() => {
319
+ if (document.hasFocus()) {
320
+ clearInterval(timer);
321
+ resolve();
322
+ } else if (Date.now() >= deadline) {
323
+ clearInterval(timer);
324
+ resolve();
325
+ }
326
+ }, intervalMs);
327
+ });
328
+ }
329
+ function toBase64(buffer) {
330
+ return btoa(String.fromCharCode(...new Uint8Array(buffer)));
331
+ }
332
+ function base64ToBytes(value) {
333
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
334
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
335
+ const raw = atob(padded);
336
+ const bytes = new Uint8Array(raw.length);
337
+ for (let i = 0; i < raw.length; i++) {
338
+ bytes[i] = raw.charCodeAt(i);
339
+ }
340
+ return bytes;
341
+ }
342
+ function readEnvValue(name) {
343
+ const meta = import.meta;
344
+ const metaValue = meta.env?.[name];
345
+ if (typeof metaValue === "string" && metaValue.trim().length > 0) {
346
+ return metaValue.trim();
347
+ }
348
+ const processValue = globalThis.process?.env?.[name];
349
+ if (typeof processValue === "string" && processValue.trim().length > 0) {
350
+ return processValue.trim();
351
+ }
352
+ return void 0;
353
+ }
354
+ function resolvePasskeyRpId() {
355
+ const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
356
+ if (configuredDomain) {
357
+ return normalizeConfiguredDomain(configuredDomain);
358
+ }
359
+ if (typeof window !== "undefined") {
360
+ return resolveRootDomainFromHostname(window.location.hostname);
361
+ }
362
+ return "localhost";
363
+ }
364
+ async function createPasskeyCredential(params) {
365
+ const challenge = new Uint8Array(32);
366
+ crypto.getRandomValues(challenge);
367
+ const rpId = resolvePasskeyRpId();
368
+ const publicKeyOptions = {
369
+ challenge,
370
+ rp: { name: "Blink", id: rpId },
371
+ user: {
372
+ id: new TextEncoder().encode(params.userId),
373
+ name: params.displayName,
374
+ displayName: params.displayName
375
+ },
376
+ pubKeyCredParams: [
377
+ { alg: -7, type: "public-key" },
378
+ { alg: -257, type: "public-key" }
379
+ ],
380
+ authenticatorSelection: {
381
+ authenticatorAttachment: "platform",
382
+ residentKey: "preferred",
383
+ userVerification: "required"
384
+ },
385
+ timeout: 6e4
386
+ };
387
+ if (isInCrossOriginIframe()) {
388
+ try {
389
+ await waitForDocumentFocus();
390
+ const credential2 = await navigator.credentials.create({
391
+ publicKey: publicKeyOptions
392
+ });
393
+ if (!credential2) {
394
+ throw new Error("Passkey creation was cancelled.");
395
+ }
396
+ return extractPasskeyResult(credential2);
397
+ } catch (err) {
398
+ if (err instanceof PasskeyIframeBlockedError) throw err;
399
+ if (err instanceof Error && err.message === "Passkey creation was cancelled.") throw err;
400
+ throw new PasskeyIframeBlockedError();
401
+ }
402
+ }
403
+ await waitForDocumentFocus();
404
+ const credential = await navigator.credentials.create({
405
+ publicKey: publicKeyOptions
406
+ });
407
+ if (!credential) {
408
+ throw new Error("Passkey creation was cancelled.");
409
+ }
410
+ return extractPasskeyResult(credential);
411
+ }
412
+ function extractPasskeyResult(credential) {
413
+ const response = credential.response;
414
+ const publicKeyBytes = response.getPublicKey?.();
415
+ return {
416
+ credentialId: toBase64(credential.rawId),
417
+ publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
418
+ };
419
+ }
420
+ function buildPasskeyPopupOptions(params) {
421
+ const challenge = new Uint8Array(32);
422
+ crypto.getRandomValues(challenge);
423
+ const rpId = resolvePasskeyRpId();
424
+ return {
425
+ challenge: toBase64(challenge),
426
+ rpId,
427
+ rpName: "Blink",
428
+ userId: toBase64(new TextEncoder().encode(params.userId)),
429
+ userName: params.displayName,
430
+ userDisplayName: params.displayName,
431
+ pubKeyCredParams: [
432
+ { alg: -7, type: "public-key" },
433
+ { alg: -257, type: "public-key" }
434
+ ],
435
+ authenticatorSelection: {
436
+ authenticatorAttachment: "platform",
437
+ residentKey: "preferred",
438
+ userVerification: "required"
439
+ },
440
+ timeout: 6e4,
441
+ authToken: params.authToken,
442
+ apiBaseUrl: params.apiBaseUrl
443
+ };
444
+ }
445
+ async function deviceHasPasskey(credentialId) {
446
+ const found = await findDevicePasskey([credentialId]);
447
+ return found != null;
448
+ }
449
+ async function findDevicePasskey(credentialIds) {
450
+ if (credentialIds.length === 0) return null;
451
+ try {
452
+ const challenge = new Uint8Array(32);
453
+ crypto.getRandomValues(challenge);
454
+ await waitForDocumentFocus();
455
+ const assertion = await navigator.credentials.get({
456
+ publicKey: {
457
+ challenge,
458
+ rpId: resolvePasskeyRpId(),
459
+ allowCredentials: credentialIds.map((id) => ({
460
+ type: "public-key",
461
+ id: base64ToBytes(id)
462
+ })),
463
+ userVerification: "discouraged",
464
+ timeout: 3e4
465
+ }
466
+ });
467
+ if (!assertion) return null;
468
+ return toBase64(assertion.rawId);
469
+ } catch {
470
+ return null;
471
+ }
472
+ }
473
+
158
474
  // src/api.ts
159
475
  var api_exports = {};
160
476
  __export(api_exports, {
@@ -166,6 +482,7 @@ __export(api_exports, {
166
482
  fetchAccount: () => fetchAccount,
167
483
  fetchAccounts: () => fetchAccounts,
168
484
  fetchAuthorizationSession: () => fetchAuthorizationSession,
485
+ fetchAuthorizationSessionByToken: () => fetchAuthorizationSessionByToken,
169
486
  fetchChains: () => fetchChains,
170
487
  fetchGuestAccount: () => fetchGuestAccount,
171
488
  fetchGuestTransferBalances: () => fetchGuestTransferBalances,
@@ -345,6 +662,13 @@ async function fetchAuthorizationSession(apiBaseUrl, sessionId) {
345
662
  if (!res.ok) await throwApiError(res);
346
663
  return await res.json();
347
664
  }
665
+ async function fetchAuthorizationSessionByToken(apiBaseUrl, token) {
666
+ const res = await fetch(
667
+ `${apiBaseUrl}/v1/authorization-sessions?token=${encodeURIComponent(token)}`
668
+ );
669
+ if (!res.ok) await throwApiError(res);
670
+ return await res.json();
671
+ }
348
672
  async function registerPasskey(apiBaseUrl, token, credentialId, publicKey) {
349
673
  const res = await fetch(`${apiBaseUrl}/v1/users/config/passkey`, {
350
674
  method: "POST",
@@ -501,17 +825,87 @@ async function setAccountOwner(apiBaseUrl, accessToken, accountId, guestSessionT
501
825
  if (!res.ok) await throwApiError(res);
502
826
  return await res.json();
503
827
  }
504
- async function reportActionCompletion(apiBaseUrl, actionId, result) {
505
- const res = await fetch(
506
- `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
507
- {
508
- method: "PATCH",
509
- headers: { "Content-Type": "application/json" },
510
- body: JSON.stringify({ status: "COMPLETED", result })
828
+ async function reportActionCompletion(apiBaseUrl, actionId, result) {
829
+ const res = await fetch(
830
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
831
+ {
832
+ method: "PATCH",
833
+ headers: { "Content-Type": "application/json" },
834
+ body: JSON.stringify({ status: "COMPLETED", result })
835
+ }
836
+ );
837
+ if (!res.ok) await throwApiError(res);
838
+ return await res.json();
839
+ }
840
+
841
+ // src/transferPolling.ts
842
+ async function pollTransferTick(params) {
843
+ const fetchTransfer2 = params.fetchTransfer ?? fetchTransfer;
844
+ const token = await params.getAccessToken();
845
+ if (!token) {
846
+ return { kind: "retry" };
847
+ }
848
+ try {
849
+ const transfer = await fetchTransfer2(params.apiBaseUrl, token, params.transferId);
850
+ return { kind: "success", transfer };
851
+ } catch (err) {
852
+ return {
853
+ kind: "error",
854
+ message: err instanceof Error ? err.message : "Polling error"
855
+ };
856
+ }
857
+ }
858
+
859
+ // src/hooks/useTransferPolling.ts
860
+ function useTransferPolling(intervalMs = 3e3) {
861
+ const { apiBaseUrl } = useBlinkConfig();
862
+ const { getAccessToken } = usePrivy();
863
+ const [transfer, setTransfer] = useState(null);
864
+ const [error, setError] = useState(null);
865
+ const [isPolling, setIsPolling] = useState(false);
866
+ const intervalRef = useRef(null);
867
+ const transferIdRef = useRef(null);
868
+ const stopPolling = useCallback(() => {
869
+ if (intervalRef.current) {
870
+ clearInterval(intervalRef.current);
871
+ intervalRef.current = null;
872
+ }
873
+ setIsPolling(false);
874
+ }, []);
875
+ const poll = useCallback(async () => {
876
+ if (!transferIdRef.current) return;
877
+ const result = await pollTransferTick({
878
+ apiBaseUrl,
879
+ transferId: transferIdRef.current,
880
+ getAccessToken
881
+ });
882
+ if (result.kind === "retry") {
883
+ return;
884
+ }
885
+ if (result.kind === "error") {
886
+ setError(result.message);
887
+ stopPolling();
888
+ return;
889
+ }
890
+ setError(null);
891
+ setTransfer(result.transfer);
892
+ if (result.transfer.status === "COMPLETED" || result.transfer.status === "FAILED") {
893
+ stopPolling();
511
894
  }
895
+ }, [apiBaseUrl, getAccessToken, stopPolling]);
896
+ const startPolling = useCallback(
897
+ (transferId) => {
898
+ stopPolling();
899
+ transferIdRef.current = transferId;
900
+ setIsPolling(true);
901
+ setError(null);
902
+ poll();
903
+ intervalRef.current = setInterval(poll, intervalMs);
904
+ },
905
+ [poll, intervalMs, stopPolling]
512
906
  );
513
- if (!res.ok) await throwApiError(res);
514
- return await res.json();
907
+ useEffect(() => () => stopPolling(), [stopPolling]);
908
+ return { transfer, error, isPolling, startPolling, stopPolling };
515
909
  }
516
910
 
517
911
  // node_modules/@wagmi/core/dist/esm/utils/getAction.js
@@ -811,250 +1205,61 @@ async function waitForTransactionReceipt(config, parameters) {
811
1205
  chainId: client.chain.id
812
1206
  };
813
1207
  }
814
-
815
- // src/passkeyRpId.ts
816
- function normalizeConfiguredDomain(value) {
817
- return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
818
- }
819
- function resolveRootDomainFromHostname(hostname) {
820
- const trimmedHostname = hostname.trim().toLowerCase();
821
- if (!trimmedHostname) {
822
- return "localhost";
823
- }
824
- if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
825
- return trimmedHostname;
826
- }
827
- const parts = trimmedHostname.split(".").filter(Boolean);
828
- if (parts.length < 2) {
829
- return trimmedHostname;
830
- }
831
- return parts.slice(-2).join(".");
832
- }
833
1208
  var ERC_6492_MAGIC_SUFFIX = "6492649264926492649264926492649264926492649264926492649264926492";
834
1209
  function normalizeSignature(sig) {
835
1210
  const hex = sig.startsWith("0x") ? sig.slice(2) : sig;
836
- if (hex.length === 130) {
837
- return `0x${hex}`;
838
- }
839
- if (hex.length === 128) {
840
- const r = hex.slice(0, 64);
841
- const yParityAndS = hex.slice(64, 128);
842
- const highByte = parseInt(yParityAndS.slice(0, 2), 16);
843
- const v = (highByte & 128) !== 0 ? 28 : 27;
844
- const sFirstByte = (highByte & 127).toString(16).padStart(2, "0");
845
- const s = sFirstByte + yParityAndS.slice(2);
846
- return `0x${r}${s}${v.toString(16)}`;
847
- }
848
- if (hex.length > 64 && hex.endsWith(ERC_6492_MAGIC_SUFFIX)) {
849
- const { signature: inner } = parseErc6492Signature(
850
- `0x${hex}`
851
- );
852
- return normalizeSignature(inner);
853
- }
854
- if (hex.length > 130) {
855
- try {
856
- const [, innerBytes] = decodeAbiParameters(
857
- [{ type: "uint256" }, { type: "bytes" }],
858
- `0x${hex}`
859
- );
860
- return normalizeSignature(innerBytes);
861
- } catch {
862
- try {
863
- const [wrapper] = decodeAbiParameters(
864
- [{
865
- type: "tuple",
866
- components: [{ type: "uint8" }, { type: "bytes" }]
867
- }],
868
- `0x${hex}`
869
- );
870
- return normalizeSignature(wrapper[1]);
871
- } catch {
872
- return `0x${hex}`;
873
- }
874
- }
875
- }
876
- throw new Error(
877
- `Invalid signature: unable to normalize. Length=${hex.length / 2} bytes. Expected 65, 64, ERC-6492 wrapped, or ABI-encoded SignatureWrapper.`
878
- );
879
- }
880
-
881
- // src/transferPolling.ts
882
- async function pollTransferTick(params) {
883
- const fetchTransfer2 = params.fetchTransfer ?? fetchTransfer;
884
- const token = await params.getAccessToken();
885
- if (!token) {
886
- return { kind: "retry" };
887
- }
888
- try {
889
- const transfer = await fetchTransfer2(params.apiBaseUrl, token, params.transferId);
890
- return { kind: "success", transfer };
891
- } catch (err) {
892
- return {
893
- kind: "error",
894
- message: err instanceof Error ? err.message : "Polling error"
895
- };
896
- }
897
- }
898
-
899
- // src/passkey-delegation.ts
900
- var PasskeyIframeBlockedError = class extends Error {
901
- constructor(message = "Passkey creation is not supported in this browser context.") {
902
- super(message);
903
- this.name = "PasskeyIframeBlockedError";
904
- }
905
- };
906
- function isInCrossOriginIframe() {
907
- if (typeof window === "undefined") return false;
908
- if (window.parent === window) return false;
909
- try {
910
- void window.parent.location.origin;
911
- return false;
912
- } catch {
913
- return true;
914
- }
915
- }
916
- function isSafari() {
917
- if (typeof navigator === "undefined") return false;
918
- const ua = navigator.userAgent;
919
- return /Safari/i.test(ua) && !/Chrome|CriOS|Chromium|Edg|OPR|Firefox/i.test(ua);
920
- }
921
- var POPUP_RESULT_TIMEOUT_MS = 12e4;
922
- var POPUP_CLOSED_POLL_MS = 500;
923
- var POPUP_CLOSED_GRACE_MS = 1e3;
924
- function createPasskeyViaPopup(options) {
925
- return new Promise((resolve, reject) => {
926
- const verificationToken = crypto.randomUUID();
927
- const payload = { ...options, verificationToken };
928
- const encoded = btoa(JSON.stringify(payload));
929
- const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
930
- const popup = window.open(popupUrl, "blink-passkey");
931
- if (!popup) {
932
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
933
- return;
934
- }
935
- let settled = false;
936
- const timer = setTimeout(() => {
937
- cleanup();
938
- reject(new Error("Passkey creation timed out. Please try again."));
939
- }, POPUP_RESULT_TIMEOUT_MS);
940
- const closedPoll = setInterval(() => {
941
- if (popup.closed) {
942
- clearInterval(closedPoll);
943
- setTimeout(() => {
944
- if (!settled) {
945
- settled = true;
946
- cleanup();
947
- checkServerForPasskeyByToken(
948
- options.authToken,
949
- options.apiBaseUrl,
950
- verificationToken
951
- ).then((result) => {
952
- if (result) {
953
- resolve(result);
954
- } else {
955
- reject(new Error("Passkey window was closed before completing."));
956
- }
957
- }).catch(() => {
958
- reject(new Error("Passkey window was closed before completing."));
959
- });
960
- }
961
- }, POPUP_CLOSED_GRACE_MS);
962
- }
963
- }, POPUP_CLOSED_POLL_MS);
964
- function cleanup() {
965
- clearTimeout(timer);
966
- clearInterval(closedPoll);
967
- }
968
- });
969
- }
970
- var VERIFY_POPUP_TIMEOUT_MS = 6e4;
971
- function findDevicePasskeyViaPopup(options) {
972
- return new Promise((resolve, reject) => {
973
- const verificationToken = crypto.randomUUID();
974
- const payload = {
975
- ...options,
976
- verificationToken
977
- };
978
- const encoded = btoa(JSON.stringify(payload));
979
- const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
980
- const popup = window.open(popupUrl, "blink-passkey-verify");
981
- if (!popup) {
982
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
983
- return;
984
- }
985
- let settled = false;
986
- const timer = setTimeout(() => {
987
- cleanup();
988
- resolve(null);
989
- }, VERIFY_POPUP_TIMEOUT_MS);
990
- const closedPoll = setInterval(() => {
991
- if (popup.closed && !settled) {
992
- clearInterval(closedPoll);
993
- setTimeout(() => {
994
- if (!settled) {
995
- settled = true;
996
- cleanup();
997
- checkServerForPasskeyByToken(
998
- options.authToken,
999
- options.apiBaseUrl,
1000
- verificationToken
1001
- ).then((result) => {
1002
- resolve(result?.credentialId ?? null);
1003
- }).catch(() => {
1004
- resolve(null);
1005
- });
1006
- }
1007
- }, POPUP_CLOSED_GRACE_MS);
1211
+ if (hex.length === 130) {
1212
+ return `0x${hex}`;
1213
+ }
1214
+ if (hex.length === 128) {
1215
+ const r = hex.slice(0, 64);
1216
+ const yParityAndS = hex.slice(64, 128);
1217
+ const highByte = parseInt(yParityAndS.slice(0, 2), 16);
1218
+ const v = (highByte & 128) !== 0 ? 28 : 27;
1219
+ const sFirstByte = (highByte & 127).toString(16).padStart(2, "0");
1220
+ const s = sFirstByte + yParityAndS.slice(2);
1221
+ return `0x${r}${s}${v.toString(16)}`;
1222
+ }
1223
+ if (hex.length > 64 && hex.endsWith(ERC_6492_MAGIC_SUFFIX)) {
1224
+ const { signature: inner } = parseErc6492Signature(
1225
+ `0x${hex}`
1226
+ );
1227
+ return normalizeSignature(inner);
1228
+ }
1229
+ if (hex.length > 130) {
1230
+ try {
1231
+ const [, innerBytes] = decodeAbiParameters(
1232
+ [{ type: "uint256" }, { type: "bytes" }],
1233
+ `0x${hex}`
1234
+ );
1235
+ return normalizeSignature(innerBytes);
1236
+ } catch {
1237
+ try {
1238
+ const [wrapper] = decodeAbiParameters(
1239
+ [{
1240
+ type: "tuple",
1241
+ components: [{ type: "uint8" }, { type: "bytes" }]
1242
+ }],
1243
+ `0x${hex}`
1244
+ );
1245
+ return normalizeSignature(wrapper[1]);
1246
+ } catch {
1247
+ return `0x${hex}`;
1008
1248
  }
1009
- }, POPUP_CLOSED_POLL_MS);
1010
- function cleanup() {
1011
- clearTimeout(timer);
1012
- clearInterval(closedPoll);
1013
1249
  }
1014
- });
1015
- }
1016
- async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
1017
- if (!authToken || !apiBaseUrl) return null;
1018
- const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
1019
- headers: { Authorization: `Bearer ${authToken}` }
1020
- });
1021
- if (!res.ok) return null;
1022
- const body = await res.json();
1023
- const passkeys = body.config.passkeys ?? [];
1024
- const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
1025
- return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
1250
+ }
1251
+ throw new Error(
1252
+ `Invalid signature: unable to normalize. Length=${hex.length / 2} bytes. Expected 65, 64, ERC-6492 wrapped, or ABI-encoded SignatureWrapper.`
1253
+ );
1026
1254
  }
1027
1255
 
1028
- // src/hooks.ts
1256
+ // src/hooks/authorizationExecutor.ts
1029
1257
  var WALLET_CLIENT_MAX_ATTEMPTS = 25;
1030
1258
  var WALLET_CLIENT_POLL_MS = 400;
1031
1259
  var ACTION_POLL_INTERVAL_MS = 500;
1032
1260
  var ACTION_POLL_MAX_RETRIES = 20;
1033
1261
  var SIGN_PERMIT2_POLL_MS = 1e3;
1034
1262
  var SIGN_PERMIT2_MAX_POLLS = 15;
1035
- var TRANSFER_SIGN_MAX_POLLS = 60;
1036
- function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
1037
- return new Promise((resolve, reject) => {
1038
- if (typeof document === "undefined") {
1039
- resolve();
1040
- return;
1041
- }
1042
- if (document.hasFocus()) {
1043
- resolve();
1044
- return;
1045
- }
1046
- const deadline = Date.now() + timeoutMs;
1047
- const timer = setInterval(() => {
1048
- if (document.hasFocus()) {
1049
- clearInterval(timer);
1050
- resolve();
1051
- } else if (Date.now() >= deadline) {
1052
- clearInterval(timer);
1053
- resolve();
1054
- }
1055
- }, intervalMs);
1056
- });
1057
- }
1058
1263
  function actionSuccess(action, message, data) {
1059
1264
  return { actionId: action.id, type: action.type, status: "success", message, data };
1060
1265
  }
@@ -1065,243 +1270,44 @@ function isUserRejection(msg) {
1065
1270
  const lower = msg.toLowerCase();
1066
1271
  return lower.includes("rejected") || lower.includes("denied");
1067
1272
  }
1068
- function hexToBytes(hex) {
1069
- const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1070
- const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
1071
- return new Uint8Array(bytes);
1072
- }
1073
- function toBase64(buffer) {
1074
- return btoa(String.fromCharCode(...new Uint8Array(buffer)));
1075
- }
1076
- function base64ToBytes(value) {
1077
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
1078
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
1079
- const raw = atob(padded);
1080
- const bytes = new Uint8Array(raw.length);
1081
- for (let i = 0; i < raw.length; i++) {
1082
- bytes[i] = raw.charCodeAt(i);
1083
- }
1084
- return bytes;
1085
- }
1086
- function readEnvValue(name) {
1087
- const meta = import.meta;
1088
- const metaValue = meta.env?.[name];
1089
- if (typeof metaValue === "string" && metaValue.trim().length > 0) {
1090
- return metaValue.trim();
1091
- }
1092
- const processValue = globalThis.process?.env?.[name];
1093
- if (typeof processValue === "string" && processValue.trim().length > 0) {
1094
- return processValue.trim();
1095
- }
1096
- return void 0;
1097
- }
1098
- function resolvePasskeyRpId() {
1099
- const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
1100
- if (configuredDomain) {
1101
- return normalizeConfiguredDomain(configuredDomain);
1102
- }
1103
- if (typeof window !== "undefined") {
1104
- return resolveRootDomainFromHostname(window.location.hostname);
1105
- }
1106
- return "localhost";
1107
- }
1108
1273
  async function waitForWalletClient(wagmiConfig2, params = {}) {
1109
1274
  for (let i = 0; i < WALLET_CLIENT_MAX_ATTEMPTS; i++) {
1110
1275
  try {
1111
1276
  const account = getAccount(wagmiConfig2);
1112
- const enrichedParams = account.connector ? { ...params, connector: account.connector } : params;
1113
- return await getWalletClient(wagmiConfig2, enrichedParams);
1114
- } catch {
1115
- if (i === WALLET_CLIENT_MAX_ATTEMPTS - 1) {
1116
- throw new Error("Wallet not ready. Please try again.");
1117
- }
1118
- await new Promise((r) => setTimeout(r, WALLET_CLIENT_POLL_MS));
1119
- }
1120
- }
1121
- throw new Error("Wallet not ready. Please try again.");
1122
- }
1123
- function parseSignTypedDataPayload(typedData) {
1124
- const { domain, types, primaryType, message } = typedData;
1125
- if (!domain || typeof domain !== "object" || Array.isArray(domain)) {
1126
- throw new Error("SIGN_PERMIT2 typedData is missing a valid domain object.");
1127
- }
1128
- if (!types || typeof types !== "object" || Array.isArray(types)) {
1129
- throw new Error("SIGN_PERMIT2 typedData is missing a valid types object.");
1130
- }
1131
- if (typeof primaryType !== "string") {
1132
- throw new Error("SIGN_PERMIT2 typedData is missing primaryType.");
1133
- }
1134
- if (!message || typeof message !== "object" || Array.isArray(message)) {
1135
- throw new Error("SIGN_PERMIT2 typedData is missing a valid message object.");
1136
- }
1137
- return {
1138
- domain,
1139
- types,
1140
- primaryType,
1141
- message
1142
- };
1143
- }
1144
- function getPendingActions(session, completedIds) {
1145
- return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
1146
- }
1147
- async function createPasskeyCredential(params) {
1148
- const challenge = new Uint8Array(32);
1149
- crypto.getRandomValues(challenge);
1150
- const rpId = resolvePasskeyRpId();
1151
- const publicKeyOptions = {
1152
- challenge,
1153
- rp: { name: "Blink", id: rpId },
1154
- user: {
1155
- id: new TextEncoder().encode(params.userId),
1156
- name: params.displayName,
1157
- displayName: params.displayName
1158
- },
1159
- pubKeyCredParams: [
1160
- { alg: -7, type: "public-key" },
1161
- { alg: -257, type: "public-key" }
1162
- ],
1163
- authenticatorSelection: {
1164
- authenticatorAttachment: "platform",
1165
- residentKey: "preferred",
1166
- userVerification: "required"
1167
- },
1168
- timeout: 6e4
1169
- };
1170
- if (isInCrossOriginIframe()) {
1171
- try {
1172
- await waitForDocumentFocus();
1173
- const credential2 = await navigator.credentials.create({
1174
- publicKey: publicKeyOptions
1175
- });
1176
- if (!credential2) {
1177
- throw new Error("Passkey creation was cancelled.");
1178
- }
1179
- return extractPasskeyResult(credential2);
1180
- } catch (err) {
1181
- if (err instanceof PasskeyIframeBlockedError) throw err;
1182
- if (err instanceof Error && err.message === "Passkey creation was cancelled.") throw err;
1183
- throw new PasskeyIframeBlockedError();
1184
- }
1185
- }
1186
- await waitForDocumentFocus();
1187
- const credential = await navigator.credentials.create({
1188
- publicKey: publicKeyOptions
1189
- });
1190
- if (!credential) {
1191
- throw new Error("Passkey creation was cancelled.");
1192
- }
1193
- return extractPasskeyResult(credential);
1194
- }
1195
- function extractPasskeyResult(credential) {
1196
- const response = credential.response;
1197
- const publicKeyBytes = response.getPublicKey?.();
1198
- return {
1199
- credentialId: toBase64(credential.rawId),
1200
- publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
1201
- };
1202
- }
1203
- function buildPasskeyPopupOptions(params) {
1204
- const challenge = new Uint8Array(32);
1205
- crypto.getRandomValues(challenge);
1206
- const rpId = resolvePasskeyRpId();
1207
- return {
1208
- challenge: toBase64(challenge),
1209
- rpId,
1210
- rpName: "Blink",
1211
- userId: toBase64(new TextEncoder().encode(params.userId)),
1212
- userName: params.displayName,
1213
- userDisplayName: params.displayName,
1214
- pubKeyCredParams: [
1215
- { alg: -7, type: "public-key" },
1216
- { alg: -257, type: "public-key" }
1217
- ],
1218
- authenticatorSelection: {
1219
- authenticatorAttachment: "platform",
1220
- residentKey: "preferred",
1221
- userVerification: "required"
1222
- },
1223
- timeout: 6e4,
1224
- authToken: params.authToken,
1225
- apiBaseUrl: params.apiBaseUrl
1226
- };
1227
- }
1228
- async function deviceHasPasskey(credentialId) {
1229
- const found = await findDevicePasskey([credentialId]);
1230
- return found != null;
1231
- }
1232
- async function findDevicePasskey(credentialIds) {
1233
- if (credentialIds.length === 0) return null;
1234
- try {
1235
- const challenge = new Uint8Array(32);
1236
- crypto.getRandomValues(challenge);
1237
- await waitForDocumentFocus();
1238
- const assertion = await navigator.credentials.get({
1239
- publicKey: {
1240
- challenge,
1241
- rpId: resolvePasskeyRpId(),
1242
- allowCredentials: credentialIds.map((id) => ({
1243
- type: "public-key",
1244
- id: base64ToBytes(id)
1245
- })),
1246
- userVerification: "discouraged",
1247
- timeout: 3e4
1277
+ const enrichedParams = account.connector ? { ...params, connector: account.connector } : params;
1278
+ return await getWalletClient(wagmiConfig2, enrichedParams);
1279
+ } catch {
1280
+ if (i === WALLET_CLIENT_MAX_ATTEMPTS - 1) {
1281
+ throw new Error("Wallet not ready. Please try again.");
1248
1282
  }
1249
- });
1250
- if (!assertion) return null;
1251
- return toBase64(assertion.rawId);
1252
- } catch {
1253
- return null;
1283
+ await new Promise((r) => setTimeout(r, WALLET_CLIENT_POLL_MS));
1284
+ }
1254
1285
  }
1286
+ throw new Error("Wallet not ready. Please try again.");
1255
1287
  }
1256
- function useTransferPolling(intervalMs = 3e3) {
1257
- const { apiBaseUrl } = useBlinkConfig();
1258
- const { getAccessToken } = usePrivy();
1259
- const [transfer, setTransfer] = useState(null);
1260
- const [error, setError] = useState(null);
1261
- const [isPolling, setIsPolling] = useState(false);
1262
- const intervalRef = useRef(null);
1263
- const transferIdRef = useRef(null);
1264
- const stopPolling = useCallback(() => {
1265
- if (intervalRef.current) {
1266
- clearInterval(intervalRef.current);
1267
- intervalRef.current = null;
1268
- }
1269
- setIsPolling(false);
1270
- }, []);
1271
- const poll = useCallback(async () => {
1272
- if (!transferIdRef.current) return;
1273
- const result = await pollTransferTick({
1274
- apiBaseUrl,
1275
- transferId: transferIdRef.current,
1276
- getAccessToken
1277
- });
1278
- if (result.kind === "retry") {
1279
- return;
1280
- }
1281
- if (result.kind === "error") {
1282
- setError(result.message);
1283
- stopPolling();
1284
- return;
1285
- }
1286
- setError(null);
1287
- setTransfer(result.transfer);
1288
- if (result.transfer.status === "COMPLETED" || result.transfer.status === "FAILED") {
1289
- stopPolling();
1290
- }
1291
- }, [apiBaseUrl, getAccessToken, stopPolling]);
1292
- const startPolling = useCallback(
1293
- (transferId) => {
1294
- stopPolling();
1295
- transferIdRef.current = transferId;
1296
- setIsPolling(true);
1297
- setError(null);
1298
- poll();
1299
- intervalRef.current = setInterval(poll, intervalMs);
1300
- },
1301
- [poll, intervalMs, stopPolling]
1302
- );
1303
- useEffect(() => () => stopPolling(), [stopPolling]);
1304
- return { transfer, error, isPolling, startPolling, stopPolling };
1288
+ function parseSignTypedDataPayload(typedData) {
1289
+ const { domain, types, primaryType, message } = typedData;
1290
+ if (!domain || typeof domain !== "object" || Array.isArray(domain)) {
1291
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid domain object.");
1292
+ }
1293
+ if (!types || typeof types !== "object" || Array.isArray(types)) {
1294
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid types object.");
1295
+ }
1296
+ if (typeof primaryType !== "string") {
1297
+ throw new Error("SIGN_PERMIT2 typedData is missing primaryType.");
1298
+ }
1299
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
1300
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid message object.");
1301
+ }
1302
+ return {
1303
+ domain,
1304
+ types,
1305
+ primaryType,
1306
+ message
1307
+ };
1308
+ }
1309
+ function getPendingActions(session, completedIds) {
1310
+ return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
1305
1311
  }
1306
1312
  async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsync) {
1307
1313
  try {
@@ -1699,6 +1705,47 @@ function useAuthorizationExecutor(options) {
1699
1705
  executeSessionById
1700
1706
  };
1701
1707
  }
1708
+ var TRANSFER_SIGN_MAX_POLLS = 60;
1709
+ function waitForDocumentFocus2(timeoutMs = 5e3, intervalMs = 100) {
1710
+ return new Promise((resolve, reject) => {
1711
+ if (typeof document === "undefined") {
1712
+ resolve();
1713
+ return;
1714
+ }
1715
+ if (document.hasFocus()) {
1716
+ resolve();
1717
+ return;
1718
+ }
1719
+ const deadline = Date.now() + timeoutMs;
1720
+ const timer = setInterval(() => {
1721
+ if (document.hasFocus()) {
1722
+ clearInterval(timer);
1723
+ resolve();
1724
+ } else if (Date.now() >= deadline) {
1725
+ clearInterval(timer);
1726
+ resolve();
1727
+ }
1728
+ }, intervalMs);
1729
+ });
1730
+ }
1731
+ function hexToBytes(hex) {
1732
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1733
+ const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
1734
+ return new Uint8Array(bytes);
1735
+ }
1736
+ function toBase642(buffer) {
1737
+ return btoa(String.fromCharCode(...new Uint8Array(buffer)));
1738
+ }
1739
+ function base64ToBytes2(value) {
1740
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
1741
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
1742
+ const raw = atob(padded);
1743
+ const bytes = new Uint8Array(raw.length);
1744
+ for (let i = 0; i < raw.length; i++) {
1745
+ bytes[i] = raw.charCodeAt(i);
1746
+ }
1747
+ return bytes;
1748
+ }
1702
1749
  function useTransferSigning(pollIntervalMs = 2e3, options) {
1703
1750
  const blinkConfig = useOptionalBlinkConfig();
1704
1751
  const apiBaseUrl = options?.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
@@ -1754,9 +1801,9 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
1754
1801
  let signedUserOp;
1755
1802
  const allowCredentials = payload.passkeyCredentialId ? [{
1756
1803
  type: "public-key",
1757
- id: base64ToBytes(payload.passkeyCredentialId)
1804
+ id: base64ToBytes2(payload.passkeyCredentialId)
1758
1805
  }] : void 0;
1759
- await waitForDocumentFocus();
1806
+ await waitForDocumentFocus2();
1760
1807
  const assertion = await navigator.credentials.get({
1761
1808
  publicKey: {
1762
1809
  challenge: hashBytes,
@@ -1772,10 +1819,10 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
1772
1819
  const response = assertion.response;
1773
1820
  signedUserOp = {
1774
1821
  ...payload.userOp,
1775
- credentialId: toBase64(assertion.rawId),
1776
- signature: toBase64(response.signature),
1777
- authenticatorData: toBase64(response.authenticatorData),
1778
- clientDataJSON: toBase64(response.clientDataJSON)
1822
+ credentialId: toBase642(assertion.rawId),
1823
+ signature: toBase642(response.signature),
1824
+ authenticatorData: toBase642(response.authenticatorData),
1825
+ clientDataJSON: toBase642(response.clientDataJSON)
1779
1826
  };
1780
1827
  return await signTransfer(
1781
1828
  apiBaseUrl,
@@ -1934,6 +1981,95 @@ function buildSelectSourceChoices(options) {
1934
1981
  })).filter((chain) => chain.tokens.length > 0).sort((a, b) => b.balance - a.balance);
1935
1982
  }
1936
1983
 
1984
+ // src/walletFlow.ts
1985
+ var MOBILE_USER_AGENT_PATTERN = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
1986
+ function isMobileUserAgent(userAgent) {
1987
+ if (!userAgent) {
1988
+ return false;
1989
+ }
1990
+ return MOBILE_USER_AGENT_PATTERN.test(userAgent);
1991
+ }
1992
+ function shouldUseWalletConnector(options) {
1993
+ return options.useWalletConnector ?? !isMobileUserAgent(options.userAgent);
1994
+ }
1995
+
1996
+ // src/paymentResolvePhase.ts
1997
+ function hasActiveWallet(accounts) {
1998
+ return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
1999
+ }
2000
+ function isTransferInFlight(transfer) {
2001
+ if (!transfer) return false;
2002
+ return ["CREATED", "SENDING", "SENT"].includes(transfer.status);
2003
+ }
2004
+ function resolvePhase(state) {
2005
+ const p = state.phase;
2006
+ if (p.step === "select-source" && state.transfer?.status === "COMPLETED" && state.guestPreauthorizing) {
2007
+ return p;
2008
+ }
2009
+ if (state.transfer?.status === "COMPLETED" && state.guestPreauthorizing) {
2010
+ return { step: "processing", transfer: state.transfer };
2011
+ }
2012
+ if (state.transfer?.status === "COMPLETED") {
2013
+ return { step: "completed", transfer: state.transfer };
2014
+ }
2015
+ if (state.transfer?.status === "FAILED") {
2016
+ return { step: "failed", transfer: state.transfer, error: state.error ?? "Transfer failed." };
2017
+ }
2018
+ if (state.creatingTransfer || isTransferInFlight(state.transfer)) {
2019
+ return { step: "processing", transfer: state.transfer };
2020
+ }
2021
+ if (p.step === "token-picker" || p.step === "one-tap-setup" || p.step === "select-source" || p.step === "confirm-sign" || p.step === "guest-token-picker") {
2022
+ return p;
2023
+ }
2024
+ if (state.mobileFlow && state.deeplinkUri) {
2025
+ return {
2026
+ step: "wallet-setup",
2027
+ mobile: { deeplinkUri: state.deeplinkUri, providerId: state.selectedProviderId },
2028
+ accountId: null
2029
+ };
2030
+ }
2031
+ if (p.step === "wallet-setup" && p.mobile == null) {
2032
+ return p;
2033
+ }
2034
+ if (state.isGuestFlow && state.selectedProviderId != null && !state.transfer) {
2035
+ return { step: "guest-token-picker" };
2036
+ }
2037
+ if (p.step === "wallet-picker" && !state.creatingTransfer && !(state.mobileFlow && state.deeplinkUri)) {
2038
+ return p;
2039
+ }
2040
+ if (!state.privyReady) {
2041
+ return { step: "initializing" };
2042
+ }
2043
+ if (state.privyAuthenticated && !state.activeCredentialId && !state.passkeyConfigLoaded) {
2044
+ return { step: "initializing" };
2045
+ }
2046
+ if (state.verificationTarget) {
2047
+ return { step: "otp-verify", target: state.verificationTarget };
2048
+ }
2049
+ if (state.loginRequested) {
2050
+ return { step: "login" };
2051
+ }
2052
+ if (state.passkeyConfigLoaded && !state.activeCredentialId) {
2053
+ if (state.knownCredentialIds.length > 0 && state.passkeyPopupNeeded) {
2054
+ return { step: "passkey-verify" };
2055
+ }
2056
+ return { step: "passkey-create", popupFallback: state.passkeyPopupNeeded };
2057
+ }
2058
+ if (state.loadingData && state.activeCredentialId && hasActiveWallet(state.accounts)) {
2059
+ return { step: "data-loading" };
2060
+ }
2061
+ if (state.activeCredentialId && !hasActiveWallet(state.accounts) && !state.mobileFlow) {
2062
+ return { step: "wallet-picker", reason: "link" };
2063
+ }
2064
+ if (state.activeCredentialId && hasActiveWallet(state.accounts) && !state.loadingData) {
2065
+ return { step: "deposit" };
2066
+ }
2067
+ if (state.isGuestFlow) {
2068
+ return { step: "wallet-picker", reason: "guest-entry" };
2069
+ }
2070
+ return { step: "wallet-picker", reason: "entry" };
2071
+ }
2072
+
1937
2073
  // src/paymentReducer.ts
1938
2074
  function deriveSourceTypeAndId(state) {
1939
2075
  if (state.selectedWalletId) {
@@ -1946,6 +2082,7 @@ function deriveSourceTypeAndId(state) {
1946
2082
  }
1947
2083
  function createInitialState(config) {
1948
2084
  return {
2085
+ phase: { step: "initializing" },
1949
2086
  error: null,
1950
2087
  providers: [],
1951
2088
  accounts: [],
@@ -1966,6 +2103,7 @@ function createInitialState(config) {
1966
2103
  knownCredentialIds: [],
1967
2104
  verificationTarget: null,
1968
2105
  oneTapLimit: 100,
2106
+ oneTapLimitSavedDuringSetup: false,
1969
2107
  mobileFlow: false,
1970
2108
  deeplinkUri: null,
1971
2109
  increasingLimit: false,
@@ -1973,12 +2111,19 @@ function createInitialState(config) {
1973
2111
  guestTransferId: null,
1974
2112
  guestSessionToken: null,
1975
2113
  guestPreauthAccountId: null,
2114
+ guestPreauthSessionId: null,
1976
2115
  activePublicKey: null,
1977
- userIntent: null,
1978
- loginRequested: false
2116
+ loginRequested: false,
2117
+ guestPreauthorizing: false,
2118
+ privyReady: false,
2119
+ privyAuthenticated: false
1979
2120
  };
1980
2121
  }
1981
2122
  function paymentReducer(state, action) {
2123
+ const next = applyAction(state, action);
2124
+ return { ...next, phase: resolvePhase(next) };
2125
+ }
2126
+ function applyAction(state, action) {
1982
2127
  switch (action.type) {
1983
2128
  // ── Auth ──────────────────────────────────────────────────────
1984
2129
  case "CODE_SENT":
@@ -2056,23 +2201,20 @@ function paymentReducer(state, action) {
2056
2201
  selectedProviderId: action.providerId,
2057
2202
  selectedAccountId: null,
2058
2203
  selectedWalletId: null,
2059
- selectedTokenSymbol: null,
2060
- userIntent: null
2204
+ selectedTokenSymbol: null
2061
2205
  };
2062
2206
  case "SELECT_ACCOUNT":
2063
2207
  return {
2064
2208
  ...state,
2065
2209
  selectedAccountId: action.accountId,
2066
2210
  selectedWalletId: action.walletId,
2067
- selectedTokenSymbol: null,
2068
- userIntent: null
2211
+ selectedTokenSymbol: null
2069
2212
  };
2070
2213
  case "SELECT_TOKEN":
2071
2214
  return {
2072
2215
  ...state,
2073
2216
  selectedWalletId: action.walletId,
2074
- selectedTokenSymbol: action.tokenSymbol,
2075
- userIntent: null
2217
+ selectedTokenSymbol: action.tokenSymbol
2076
2218
  };
2077
2219
  // ── Transfer lifecycle ───────────────────────────────────────
2078
2220
  case "PAY_STARTED":
@@ -2081,8 +2223,7 @@ function paymentReducer(state, action) {
2081
2223
  error: null,
2082
2224
  creatingTransfer: true,
2083
2225
  deeplinkUri: null,
2084
- mobileFlow: false,
2085
- userIntent: null
2226
+ mobileFlow: false
2086
2227
  };
2087
2228
  case "PAY_ENDED":
2088
2229
  return { ...state, creatingTransfer: false };
@@ -2146,7 +2287,8 @@ function paymentReducer(state, action) {
2146
2287
  transfer: action.transfer,
2147
2288
  error: null,
2148
2289
  mobileFlow: false,
2149
- deeplinkUri: null
2290
+ deeplinkUri: null,
2291
+ phase: { step: "confirm-sign", transfer: action.transfer }
2150
2292
  };
2151
2293
  case "CLEAR_MOBILE_STATE":
2152
2294
  return { ...state, mobileFlow: false, deeplinkUri: null };
@@ -2204,6 +2346,17 @@ function paymentReducer(state, action) {
2204
2346
  selectedWalletId: null,
2205
2347
  selectedTokenSymbol: null
2206
2348
  };
2349
+ case "GUEST_BACK_FROM_TOKEN_PICKER":
2350
+ return {
2351
+ ...state,
2352
+ selectedProviderId: null,
2353
+ guestTransferId: null,
2354
+ guestSessionToken: null,
2355
+ selectedAccountId: null,
2356
+ selectedWalletId: null,
2357
+ selectedTokenSymbol: null,
2358
+ error: null
2359
+ };
2207
2360
  case "GUEST_TRANSFER_COMPLETED":
2208
2361
  return {
2209
2362
  ...state,
@@ -2216,29 +2369,40 @@ function paymentReducer(state, action) {
2216
2369
  case "GUEST_PREAUTH_DETECTED":
2217
2370
  return {
2218
2371
  ...state,
2219
- guestPreauthAccountId: action.accountId
2372
+ guestPreauthAccountId: action.accountId,
2373
+ guestPreauthSessionId: action.sessionId ?? state.guestPreauthSessionId,
2374
+ selectedAccountId: action.accountId,
2375
+ selectedWalletId: null,
2376
+ selectedTokenSymbol: null
2220
2377
  };
2378
+ case "GUEST_PREAUTH_BEGIN":
2379
+ return { ...state, guestPreauthorizing: true, error: null };
2380
+ case "GUEST_PREAUTH_END":
2381
+ return { ...state, guestPreauthorizing: false };
2221
2382
  case "ACCOUNT_OWNER_SET":
2222
2383
  return {
2223
2384
  ...state,
2224
2385
  guestPreauthAccountId: null,
2386
+ guestPreauthSessionId: null,
2225
2387
  activePublicKey: null,
2226
2388
  error: null,
2227
- userIntent: "configure-one-tap"
2389
+ guestPreauthorizing: false,
2390
+ phase: { step: "one-tap-setup", action: null }
2228
2391
  };
2229
2392
  // ── User intent & error ──────────────────────────────────────
2230
2393
  case "SET_USER_INTENT":
2231
- return { ...state, userIntent: action.intent };
2394
+ return { ...state, phase: action.intent };
2232
2395
  case "REQUEST_LOGIN":
2233
2396
  return {
2234
2397
  ...state,
2235
2398
  loginRequested: true,
2236
2399
  transfer: null,
2237
- isGuestFlow: false,
2238
2400
  creatingTransfer: false
2239
2401
  };
2240
2402
  case "SET_ERROR":
2241
2403
  return { ...state, error: action.error };
2404
+ case "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP":
2405
+ return { ...state, oneTapLimitSavedDuringSetup: action.saved };
2242
2406
  // ── Lifecycle ────────────────────────────────────────────────
2243
2407
  case "NEW_PAYMENT":
2244
2408
  return {
@@ -2255,9 +2419,11 @@ function paymentReducer(state, action) {
2255
2419
  guestTransferId: null,
2256
2420
  guestSessionToken: null,
2257
2421
  guestPreauthAccountId: null,
2422
+ guestPreauthSessionId: null,
2258
2423
  activePublicKey: null,
2259
- userIntent: null,
2260
- loginRequested: false
2424
+ loginRequested: false,
2425
+ oneTapLimitSavedDuringSetup: false,
2426
+ guestPreauthorizing: false
2261
2427
  };
2262
2428
  case "LOGOUT":
2263
2429
  return {
@@ -2265,7 +2431,15 @@ function paymentReducer(state, action) {
2265
2431
  depositAmount: action.depositAmount,
2266
2432
  passkeyPopupNeeded: state.passkeyPopupNeeded,
2267
2433
  activeCredentialId: null
2268
- })
2434
+ }),
2435
+ privyReady: action.privyReady,
2436
+ privyAuthenticated: false
2437
+ };
2438
+ case "SYNC_PRIVY_SESSION":
2439
+ return {
2440
+ ...state,
2441
+ privyReady: action.ready,
2442
+ privyAuthenticated: action.authenticated
2269
2443
  };
2270
2444
  case "SYNC_AMOUNT":
2271
2445
  return { ...state, amount: action.amount };
@@ -2314,97 +2488,42 @@ function maskAuthIdentifier(identifier) {
2314
2488
  return `***-***-${visibleSuffix}`;
2315
2489
  }
2316
2490
 
2317
- // src/walletFlow.ts
2318
- var MOBILE_USER_AGENT_PATTERN = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
2319
- function isMobileUserAgent(userAgent) {
2320
- if (!userAgent) {
2321
- return false;
2322
- }
2323
- return MOBILE_USER_AGENT_PATTERN.test(userAgent);
2324
- }
2325
- function shouldUseWalletConnector(options) {
2326
- return options.useWalletConnector ?? !isMobileUserAgent(options.userAgent);
2327
- }
2328
-
2329
2491
  // src/resolveScreen.ts
2330
- function hasActiveWallet(accounts) {
2331
- return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
2332
- }
2333
- function isTransferTerminal(transfer) {
2334
- return transfer?.status === "COMPLETED" || transfer?.status === "FAILED";
2335
- }
2336
- function isTransferInFlight(transfer) {
2337
- if (!transfer) return false;
2338
- return ["CREATED", "SENDING", "SENT"].includes(transfer.status);
2339
- }
2340
- function isSetupTransfer(transfer) {
2341
- if (!transfer) return false;
2342
- return transfer.sources?.some(
2343
- (s) => s.wallets && Array.isArray(s.wallets) && s.wallets.length === 0
2344
- ) ?? false;
2345
- }
2346
- function resolveScreen(state) {
2347
- if (!state.privyReady) {
2348
- return "loading";
2349
- }
2350
- if (state.authenticated && !state.activeCredentialId && !state.passkeyConfigLoaded) {
2351
- return "loading";
2352
- }
2353
- if (!state.authenticated && !state.verificationTarget && !state.isGuestFlow && (state.isReturningUser || state.guestPreauthRedirect || state.loginRequested)) {
2354
- return "login";
2355
- }
2356
- if (!state.authenticated && state.verificationTarget != null) {
2357
- return "otp-verify";
2358
- }
2359
- if (state.authenticated && !state.activeCredentialId && state.passkeyConfigLoaded && (state.knownCredentialIds.length === 0 || !state.passkeyPopupNeeded)) {
2360
- return "create-passkey";
2361
- }
2362
- if (state.authenticated && !state.activeCredentialId && state.passkeyConfigLoaded && state.knownCredentialIds.length > 0 && state.passkeyPopupNeeded) {
2363
- return "verify-passkey";
2364
- }
2365
- if (isTransferTerminal(state.transfer)) {
2366
- return "success";
2367
- }
2368
- if (state.creatingTransfer || state.transfer != null && isTransferInFlight(state.transfer)) {
2369
- return "processing";
2370
- }
2371
- if (state.transfer?.status === "AUTHORIZED" && !state.isDesktop && !isSetupTransfer(state.transfer)) {
2372
- return "confirm-sign";
2373
- }
2374
- if (state.pendingSelectSource != null) {
2375
- return state.isDesktop ? "setup" : "select-source";
2376
- }
2377
- if (state.pendingOneTapSetup != null && !state.oneTapLimitAlreadySaved) {
2378
- return "setup";
2379
- }
2380
- if (state.mobileFlow || state.inlineAuthorizationExecuting) {
2381
- return state.isDesktop ? "setup-status" : "open-wallet";
2382
- }
2383
- if (state.isGuestFlow && state.selectedProviderId != null && !state.transfer && !state.guestSettingSender) {
2384
- return "guest-token-picker";
2385
- }
2386
- if (state.activeCredentialId && !hasActiveWallet(state.accounts) && !state.mobileFlow || !state.authenticated && !state.isReturningUser && !state.isGuestFlow) {
2387
- return "wallet-picker";
2388
- }
2389
- if (state.loadingData && state.activeCredentialId != null && hasActiveWallet(state.accounts)) {
2390
- return "loading";
2391
- }
2392
- if (state.userIntent === "pick-token" && state.selectedAccount != null) {
2393
- return "token-picker";
2394
- }
2395
- if (state.userIntent === "configure-one-tap") {
2396
- return "setup";
2397
- }
2398
- if (state.userIntent === "switch-wallet") {
2399
- return "wallet-picker";
2400
- }
2401
- if (state.activeCredentialId != null && hasActiveWallet(state.accounts) && !state.loadingData) {
2402
- return "deposit";
2403
- }
2404
- if (state.isGuestFlow) {
2405
- return "wallet-picker";
2492
+ function screenForPhase(phase) {
2493
+ switch (phase.step) {
2494
+ case "initializing":
2495
+ case "data-loading":
2496
+ return "loading";
2497
+ case "login":
2498
+ return "login";
2499
+ case "otp-verify":
2500
+ return "otp-verify";
2501
+ case "passkey-create":
2502
+ return "create-passkey";
2503
+ case "passkey-verify":
2504
+ return "verify-passkey";
2505
+ case "wallet-picker":
2506
+ return "wallet-picker";
2507
+ case "wallet-setup":
2508
+ return phase.mobile ? "open-wallet" : "setup-status";
2509
+ case "select-source":
2510
+ return phase.isDesktop ? "setup" : "select-source";
2511
+ case "one-tap-setup":
2512
+ return "setup";
2513
+ case "guest-token-picker":
2514
+ return "guest-token-picker";
2515
+ case "token-picker":
2516
+ return "token-picker";
2517
+ case "deposit":
2518
+ return "deposit";
2519
+ case "processing":
2520
+ return "processing";
2521
+ case "confirm-sign":
2522
+ return "confirm-sign";
2523
+ case "completed":
2524
+ case "failed":
2525
+ return "success";
2406
2526
  }
2407
- return "wallet-picker";
2408
2527
  }
2409
2528
  var MUTED = "#7fa4b0";
2410
2529
  var LOGO_SIZE = 48;
@@ -5887,85 +6006,66 @@ var DEPOSIT_SCREENS = /* @__PURE__ */ new Set([
5887
6006
  "processing",
5888
6007
  "success"
5889
6008
  ]);
5890
- function getFlowPhase(screen, userIntent) {
6009
+ function getFlowPhase(screen, phase) {
5891
6010
  if (LINK_SCREENS.has(screen)) return "link";
5892
6011
  if (DEPOSIT_SCREENS.has(screen)) return "deposit";
5893
6012
  if (screen === "token-picker" || screen === "select-source" || screen === "guest-token-picker") {
5894
- return userIntent === "configure-one-tap" ? "link" : "deposit";
6013
+ return phase.step === "one-tap-setup" ? "link" : "deposit";
5895
6014
  }
5896
6015
  return null;
5897
6016
  }
5898
6017
  function StepRenderer(props) {
5899
- const isDesktop = shouldUseWalletConnector({
5900
- userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
5901
- });
5902
- const isReturningUser = props.state.activeCredentialId != null || typeof window !== "undefined" && window.localStorage.getItem("blink_active_credential") != null;
5903
- const screenState = {
5904
- privyReady: props.ready,
5905
- authenticated: props.authenticated,
5906
- verificationTarget: props.state.verificationTarget,
5907
- activeCredentialId: props.state.activeCredentialId,
5908
- knownCredentialIds: props.state.knownCredentialIds,
5909
- passkeyConfigLoaded: props.state.passkeyConfigLoaded,
5910
- passkeyPopupNeeded: props.state.passkeyPopupNeeded,
5911
- accounts: props.state.accounts,
5912
- isGuestFlow: props.state.isGuestFlow,
5913
- selectedProviderId: props.state.selectedProviderId,
5914
- mobileFlow: props.state.mobileFlow,
5915
- inlineAuthorizationExecuting: props.inlineAuthorizationExecuting,
5916
- creatingTransfer: props.state.creatingTransfer,
5917
- transfer: props.state.transfer,
5918
- pendingSelectSource: props.pendingSelectSource,
5919
- pendingOneTapSetup: props.pendingOneTapSetup,
5920
- oneTapLimitAlreadySaved: props.oneTapLimitAlreadySaved,
5921
- loadingData: props.state.loadingData,
5922
- isDesktop,
5923
- isReturningUser,
5924
- guestPreauthRedirect: props.state.guestPreauthAccountId != null,
5925
- loginRequested: props.state.loginRequested,
5926
- userIntent: props.state.userIntent,
5927
- selectedAccount: props.selectedAccount,
5928
- guestSettingSender: props.guestSettingSender
5929
- };
5930
- const screen = resolveScreen(screenState);
5931
- const phase = getFlowPhase(screen, props.state.userIntent);
5932
- return /* @__PURE__ */ jsx(FlowPhaseProvider, { phase, children: /* @__PURE__ */ jsx(StepRendererContent, { ...props, screen, isDesktop }) });
6018
+ const screen = screenForPhase(props.flow.state.phase);
6019
+ const flowPhase = getFlowPhase(screen, props.flow.state.phase);
6020
+ return /* @__PURE__ */ jsx(FlowPhaseProvider, { phase: flowPhase, children: /* @__PURE__ */ jsx(StepRendererContent, { ...props, screen }) });
5933
6021
  }
5934
6022
  function StepRendererContent({
5935
- state,
5936
- authenticated,
5937
- activeOtpStatus,
5938
- pollingTransfer,
5939
- pollingError,
5940
- authExecutorError,
5941
- transferSigningSigning,
5942
- transferSigningError,
5943
- pendingConnections,
5944
- depositEligibleAccounts,
5945
- sourceName,
5946
- maxSourceBalance,
5947
- tokenCount,
5948
- selectedAccount,
5949
- selectedSource,
5950
- selectSourceChoices,
5951
- selectSourceRecommended,
5952
- selectSourceAvailableBalance,
5953
- guestTokenEntries,
5954
- guestLoadingBalances,
5955
- guestSettingSender,
5956
- authInput,
5957
- otpCode,
5958
- selectSourceChainName,
5959
- selectSourceTokenSymbol,
5960
- savingOneTapLimit,
5961
- merchantName,
5962
- onBack,
5963
- onDismiss,
5964
- depositAmount,
6023
+ flow,
6024
+ remote,
6025
+ derived,
6026
+ forms,
5965
6027
  handlers,
5966
- screen,
5967
- isDesktop
6028
+ screen
5968
6029
  }) {
6030
+ const {
6031
+ state,
6032
+ authenticated,
6033
+ activeOtpStatus,
6034
+ isDesktop,
6035
+ merchantName,
6036
+ onBack,
6037
+ onDismiss,
6038
+ depositAmount
6039
+ } = flow;
6040
+ const {
6041
+ pollingTransfer,
6042
+ pollingError,
6043
+ authExecutorError,
6044
+ transferSigningSigning,
6045
+ transferSigningError
6046
+ } = remote;
6047
+ const {
6048
+ pendingConnections,
6049
+ depositEligibleAccounts,
6050
+ sourceName,
6051
+ maxSourceBalance,
6052
+ tokenCount,
6053
+ selectedAccount,
6054
+ selectedSource,
6055
+ selectSourceChoices,
6056
+ selectSourceRecommended,
6057
+ selectSourceAvailableBalance
6058
+ } = derived;
6059
+ const {
6060
+ guestTokenEntries,
6061
+ guestLoadingBalances,
6062
+ guestSettingSender,
6063
+ authInput,
6064
+ otpCode,
6065
+ selectSourceChainName,
6066
+ selectSourceTokenSymbol,
6067
+ savingOneTapLimit
6068
+ } = forms;
5969
6069
  const selectedWallet = selectedAccount?.wallets.find((w) => w.id === state.selectedWalletId);
5970
6070
  const selectedSourceLabel = selectedSource && selectedWallet ? `${selectedSource.token.symbol} on ${selectedWallet.chain.name}` : void 0;
5971
6071
  switch (screen) {
@@ -6038,7 +6138,7 @@ function StepRendererContent({
6038
6138
  onPrepareProvider: handlers.onPrepareProvider,
6039
6139
  onSelectProvider: handlers.onSelectProvider,
6040
6140
  onContinueConnection: handlers.onContinueConnection,
6041
- onBack: isEntryPoint ? onBack : () => handlers.onSetUserIntent(null),
6141
+ onBack: isEntryPoint ? onBack : () => handlers.onSetPhase({ step: "deposit" }),
6042
6142
  onLogout: authenticated ? handlers.onLogout : void 0,
6043
6143
  onLogin: handlers.onLogin,
6044
6144
  showLoginOption: isEntryPoint
@@ -6069,7 +6169,7 @@ function StepRendererContent({
6069
6169
  limit: state.oneTapLimit,
6070
6170
  tokensApproved: 0,
6071
6171
  merchantName,
6072
- onContinue: () => handlers.onSetUserIntent("configure-one-tap"),
6172
+ onContinue: () => handlers.onSetPhase({ step: "one-tap-setup", action: null }),
6073
6173
  onLogout: handlers.onLogout,
6074
6174
  error: state.error || authExecutorError
6075
6175
  }
@@ -6088,7 +6188,7 @@ function StepRendererContent({
6088
6188
  tokenCount: effectiveTokenCount,
6089
6189
  sourceName,
6090
6190
  onSetupOneTap: handlers.onSetupOneTap,
6091
- onBack: () => handlers.onSetUserIntent(null),
6191
+ onBack: () => handlers.onSetPhase({ step: "deposit" }),
6092
6192
  onLogout: handlers.onLogout,
6093
6193
  onAdvanced: handlers.onSelectToken,
6094
6194
  selectedSourceLabel: effectiveSourceLabel,
@@ -6124,7 +6224,7 @@ function StepRendererContent({
6124
6224
  processing: state.creatingTransfer,
6125
6225
  error: state.error,
6126
6226
  onDeposit: handlers.onPay,
6127
- onSwitchWallet: () => handlers.onSetUserIntent("switch-wallet"),
6227
+ onSwitchWallet: () => handlers.onSetPhase({ step: "wallet-picker", reason: "switch" }),
6128
6228
  onBack: onBack ?? (() => handlers.onLogout()),
6129
6229
  onLogout: handlers.onLogout,
6130
6230
  onIncreaseLimit: handlers.onIncreaseLimit,
@@ -6133,7 +6233,7 @@ function StepRendererContent({
6133
6233
  selectedAccountId: state.selectedAccountId,
6134
6234
  onSelectAccount: handlers.onSelectAccount,
6135
6235
  onAuthorizeAccount: handlers.onContinueConnection,
6136
- onAddProvider: () => handlers.onSetUserIntent("switch-wallet"),
6236
+ onAddProvider: () => handlers.onSetPhase({ step: "wallet-picker", reason: "switch" }),
6137
6237
  onSelectToken: handlers.onSelectToken,
6138
6238
  selectedSourceLabel,
6139
6239
  selectedTokenSymbol: selectedSource?.token.symbol
@@ -6151,7 +6251,7 @@ function StepRendererContent({
6151
6251
  chains: state.chains,
6152
6252
  onSelectAuthorized: handlers.onSelectAuthorizedToken,
6153
6253
  onAuthorizeToken: handlers.onAuthorizeToken,
6154
- onBack: () => handlers.onSetUserIntent(null),
6254
+ onBack: () => handlers.onSetPhase({ step: "deposit" }),
6155
6255
  onLogout: handlers.onLogout,
6156
6256
  depositAmount: depositAmount ?? void 0,
6157
6257
  selectedTokenSymbol: selectedSource?.token.symbol,
@@ -6169,7 +6269,7 @@ function StepRendererContent({
6169
6269
  depositAmount: depositAmount ?? void 0,
6170
6270
  error: state.error,
6171
6271
  onSelect: handlers.onSelectGuestToken,
6172
- onBack: () => handlers.onSetUserIntent(null)
6272
+ onBack: handlers.onGuestBackFromTokenPicker
6173
6273
  }
6174
6274
  );
6175
6275
  case "processing": {
@@ -6308,56 +6408,41 @@ var buttonStyle3 = {
6308
6408
  fontFamily: "inherit",
6309
6409
  cursor: "pointer"
6310
6410
  };
6311
- function useDerivedState(state) {
6411
+ function selectedSourceForWallet(selectedWallet, selectedTokenSymbol) {
6412
+ if (!selectedWallet) return null;
6413
+ if (selectedTokenSymbol) {
6414
+ return selectedWallet.sources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
6415
+ }
6416
+ return selectedWallet.sources.find((s) => s.token.status === "AUTHORIZED") ?? selectedWallet.sources[0] ?? null;
6417
+ }
6418
+ function computeDerivedState(state) {
6312
6419
  const { sourceType, sourceId } = deriveSourceTypeAndId(state);
6313
6420
  const selectedAccount = state.accounts.find((a) => a.id === state.selectedAccountId);
6314
6421
  const selectedWallet = selectedAccount?.wallets.find(
6315
6422
  (w) => w.id === state.selectedWalletId
6316
6423
  );
6317
- const selectedSource = useMemo(() => {
6318
- if (!selectedWallet) return null;
6319
- if (state.selectedTokenSymbol) {
6320
- return selectedWallet.sources.find(
6321
- (s) => s.token.symbol === state.selectedTokenSymbol
6322
- ) ?? null;
6323
- }
6324
- return selectedWallet.sources.find((s) => s.token.status === "AUTHORIZED") ?? selectedWallet.sources[0] ?? null;
6325
- }, [selectedWallet, state.selectedTokenSymbol]);
6424
+ const selectedSource = selectedSourceForWallet(selectedWallet, state.selectedTokenSymbol);
6326
6425
  const sourceName = selectedAccount?.name ?? selectedWallet?.chain.name ?? "Wallet";
6327
- const pendingConnections = useMemo(
6328
- () => state.accounts.filter(
6329
- (a) => a.wallets.length > 0 && !a.wallets.some((w) => w.status === "ACTIVE")
6330
- ),
6331
- [state.accounts]
6332
- );
6333
- const depositEligibleAccounts = useMemo(
6334
- () => getDepositEligibleAccounts(state.accounts),
6335
- [state.accounts]
6426
+ const pendingConnections = state.accounts.filter(
6427
+ (a) => a.wallets.length > 0 && !a.wallets.some((w) => w.status === "ACTIVE")
6336
6428
  );
6337
- const maxSourceBalance = useMemo(() => {
6338
- let max = 0;
6339
- for (const acct of state.accounts) {
6340
- for (const wallet of acct.wallets) {
6341
- for (const source of wallet.sources) {
6342
- if (source.balance.available.amount > max) {
6343
- max = source.balance.available.amount;
6344
- }
6429
+ const depositEligibleAccounts = getDepositEligibleAccounts(state.accounts);
6430
+ let maxSourceBalance = 0;
6431
+ for (const acct of state.accounts) {
6432
+ for (const wallet of acct.wallets) {
6433
+ for (const source of wallet.sources) {
6434
+ if (source.balance.available.amount > maxSourceBalance) {
6435
+ maxSourceBalance = source.balance.available.amount;
6345
6436
  }
6346
6437
  }
6347
6438
  }
6348
- return max;
6349
- }, [state.accounts]);
6350
- const tokenCount = useMemo(() => {
6351
- let count = 0;
6352
- for (const acct of state.accounts) {
6353
- for (const wallet of acct.wallets) {
6354
- count += wallet.sources.filter(
6355
- (s) => s.balance.available.amount > 0
6356
- ).length;
6357
- }
6439
+ }
6440
+ let tokenCount = 0;
6441
+ for (const acct of state.accounts) {
6442
+ for (const wallet of acct.wallets) {
6443
+ tokenCount += wallet.sources.filter((s) => s.balance.available.amount > 0).length;
6358
6444
  }
6359
- return count;
6360
- }, [state.accounts]);
6445
+ }
6361
6446
  return {
6362
6447
  sourceType,
6363
6448
  sourceId,
@@ -6371,6 +6456,9 @@ function useDerivedState(state) {
6371
6456
  tokenCount
6372
6457
  };
6373
6458
  }
6459
+ function useDerivedState(state) {
6460
+ return useMemo(() => computeDerivedState(state), [state]);
6461
+ }
6374
6462
  function useAuthHandlers(dispatch, verificationTarget) {
6375
6463
  const {
6376
6464
  sendCode: sendEmailCode,
@@ -6463,9 +6551,18 @@ function usePasskeyHandlers(dispatch, apiBaseUrl, accounts, knownCredentialIds,
6463
6551
  const { user, getAccessToken } = usePrivy();
6464
6552
  const checkingPasskeyRef = useRef(false);
6465
6553
  const activateExistingCredential = useCallback(async (credentialId) => {
6466
- dispatch({ type: "PASSKEY_ACTIVATED", credentialId });
6467
- window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
6468
6554
  const token = await getAccessToken();
6555
+ let publicKey;
6556
+ if (token) {
6557
+ try {
6558
+ const { config } = await fetchUserConfig(apiBaseUrl, token);
6559
+ const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
6560
+ publicKey = allPasskeys.find((p) => p.credentialId === credentialId)?.publicKey;
6561
+ } catch {
6562
+ }
6563
+ }
6564
+ dispatch({ type: "PASSKEY_ACTIVATED", credentialId, publicKey });
6565
+ window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
6469
6566
  if (token) {
6470
6567
  reportPasskeyActivity(apiBaseUrl, token, credentialId).catch(() => {
6471
6568
  });
@@ -6538,9 +6635,13 @@ function usePasskeyHandlers(dispatch, apiBaseUrl, accounts, knownCredentialIds,
6538
6635
  authToken: token ?? void 0,
6539
6636
  apiBaseUrl
6540
6637
  });
6541
- const { credentialId } = await createPasskeyViaPopup(popupOptions);
6542
- dispatch({ type: "PASSKEY_ACTIVATED", credentialId });
6638
+ const { credentialId, publicKey } = await createPasskeyViaPopup(popupOptions);
6639
+ dispatch({ type: "PASSKEY_ACTIVATED", credentialId, publicKey });
6543
6640
  localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
6641
+ if (token) {
6642
+ reportPasskeyActivity(apiBaseUrl, token, credentialId).catch(() => {
6643
+ });
6644
+ }
6544
6645
  } catch (err) {
6545
6646
  captureException(err);
6546
6647
  dispatch({
@@ -6556,19 +6657,25 @@ function usePasskeyHandlers(dispatch, apiBaseUrl, accounts, knownCredentialIds,
6556
6657
  dispatch({ type: "SET_ERROR", error: null });
6557
6658
  try {
6558
6659
  const token = await getAccessToken();
6660
+ if (!token) throw new Error("Not authenticated");
6559
6661
  const matched = await findDevicePasskeyViaPopup({
6560
6662
  credentialIds: knownCredentialIds,
6561
6663
  rpId: resolvePasskeyRpId(),
6562
- authToken: token ?? void 0,
6664
+ authToken: token,
6563
6665
  apiBaseUrl
6564
6666
  });
6565
6667
  if (matched) {
6566
- dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched });
6567
- window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
6568
- if (token) {
6569
- reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
6570
- });
6668
+ let publicKey;
6669
+ try {
6670
+ const { config } = await fetchUserConfig(apiBaseUrl, token);
6671
+ const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
6672
+ publicKey = allPasskeys.find((p) => p.credentialId === matched)?.publicKey;
6673
+ } catch {
6571
6674
  }
6675
+ dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched, publicKey });
6676
+ window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
6677
+ reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
6678
+ });
6572
6679
  } else {
6573
6680
  dispatch({
6574
6681
  type: "SET_ERROR",
@@ -6936,7 +7043,9 @@ function useProviderHandlers(deps) {
6936
7043
  reauthTokenRef,
6937
7044
  authenticated,
6938
7045
  merchantAuthorization,
6939
- destination
7046
+ destination,
7047
+ guestSessionToken,
7048
+ selectedProviderId
6940
7049
  } = deps;
6941
7050
  const wagmiConfig2 = useConfig();
6942
7051
  const { connectAsync, connectors } = useConnect();
@@ -7249,7 +7358,7 @@ function useProviderHandlers(deps) {
7249
7358
  reauthTokenRef
7250
7359
  ]);
7251
7360
  const handleNavigateToTokenPicker = useCallback(() => {
7252
- dispatch({ type: "SET_USER_INTENT", intent: "pick-token" });
7361
+ dispatch({ type: "SET_USER_INTENT", intent: { step: "token-picker" } });
7253
7362
  }, [dispatch]);
7254
7363
  const handleSelectAuthorizedToken = useCallback((walletId, tokenSymbol) => {
7255
7364
  dispatch({ type: "SELECT_TOKEN", walletId, tokenSymbol });
@@ -7332,6 +7441,76 @@ function useProviderHandlers(deps) {
7332
7441
  reauthSessionIdRef,
7333
7442
  reauthTokenRef
7334
7443
  ]);
7444
+ const handlePreauthorize = useCallback(async () => {
7445
+ if (!guestSessionToken || !selectedProviderId) {
7446
+ dispatch({
7447
+ type: "SET_ERROR",
7448
+ error: "Missing guest session or wallet provider. Try again from the payment screen."
7449
+ });
7450
+ return;
7451
+ }
7452
+ const isMobile = !shouldUseWalletConnector({
7453
+ useWalletConnector: useWalletConnectorProp,
7454
+ userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
7455
+ });
7456
+ const providerName = providers.find((p) => p.id === selectedProviderId)?.name ?? "Wallet";
7457
+ if (!isMobile) {
7458
+ dispatch({ type: "GUEST_PREAUTH_BEGIN" });
7459
+ }
7460
+ try {
7461
+ const created = await createGuestAccount(
7462
+ apiBaseUrl,
7463
+ guestSessionToken,
7464
+ selectedProviderId,
7465
+ providerName
7466
+ );
7467
+ const session = await fetchAuthorizationSessionByToken(
7468
+ apiBaseUrl,
7469
+ created.sessionToken
7470
+ );
7471
+ if (isMobile) {
7472
+ handlingMobileReturnRef.current = false;
7473
+ mobileSetupFlowRef.current = true;
7474
+ setupAccountIdRef.current = created.accountId;
7475
+ persistMobileFlowState({
7476
+ accountId: created.accountId,
7477
+ sessionId: session.id,
7478
+ deeplinkUri: created.sessionUri,
7479
+ providerId: selectedProviderId,
7480
+ isSetup: true,
7481
+ guestSessionToken
7482
+ });
7483
+ triggerDeeplink(created.sessionUri);
7484
+ dispatch({ type: "MOBILE_DEEPLINK_READY", deeplinkUri: created.sessionUri });
7485
+ }
7486
+ dispatch({
7487
+ type: "GUEST_PREAUTH_DETECTED",
7488
+ accountId: created.accountId,
7489
+ sessionId: session.id
7490
+ });
7491
+ } catch (err) {
7492
+ captureException(err);
7493
+ if (!isMobile) {
7494
+ dispatch({ type: "GUEST_PREAUTH_END" });
7495
+ }
7496
+ dispatch({
7497
+ type: "SET_ERROR",
7498
+ error: err instanceof Error ? err.message : "Failed to start preauthorization"
7499
+ });
7500
+ onError?.(err instanceof Error ? err.message : "Failed to start preauthorization");
7501
+ }
7502
+ }, [
7503
+ guestSessionToken,
7504
+ selectedProviderId,
7505
+ providers,
7506
+ apiBaseUrl,
7507
+ dispatch,
7508
+ onError,
7509
+ useWalletConnectorProp,
7510
+ mobileSetupFlowRef,
7511
+ handlingMobileReturnRef,
7512
+ setupAccountIdRef
7513
+ ]);
7335
7514
  return {
7336
7515
  handlePrepareProvider,
7337
7516
  handleSelectProvider,
@@ -7340,7 +7519,8 @@ function useProviderHandlers(deps) {
7340
7519
  handleIncreaseLimit,
7341
7520
  handleNavigateToTokenPicker,
7342
7521
  handleSelectAuthorizedToken,
7343
- handleAuthorizeToken
7522
+ handleAuthorizeToken,
7523
+ handlePreauthorize
7344
7524
  };
7345
7525
  }
7346
7526
 
@@ -7544,11 +7724,15 @@ function useGuestTransferHandlers(deps) {
7544
7724
  };
7545
7725
  execute();
7546
7726
  }, [isGuestFlow, guestTransferId, guestSessionToken, settingSender, apiBaseUrl, wagmiConfig2, switchChainAsync, dispatch, onComplete, onError]);
7727
+ const handleGuestBackFromTokenPicker = useCallback(() => {
7728
+ dispatch({ type: "GUEST_BACK_FROM_TOKEN_PICKER" });
7729
+ }, [dispatch]);
7547
7730
  return {
7548
7731
  guestTokenEntries,
7549
7732
  loadingBalances,
7550
7733
  settingSender,
7551
- handleSelectGuestToken
7734
+ handleSelectGuestToken,
7735
+ handleGuestBackFromTokenPicker
7552
7736
  };
7553
7737
  }
7554
7738
  function useOneTapSetupHandlers(deps) {
@@ -7558,16 +7742,24 @@ function useOneTapSetupHandlers(deps) {
7558
7742
  apiBaseUrl,
7559
7743
  authExecutor,
7560
7744
  selectSourceChainName,
7561
- selectSourceTokenSymbol
7745
+ selectSourceTokenSymbol,
7746
+ authorizationSessionIdForGuest
7562
7747
  } = deps;
7563
7748
  const [savingOneTapLimit, setSavingOneTapLimit] = useState(false);
7564
- const oneTapLimitSavedDuringSetupRef = useRef(false);
7565
7749
  const handleSetupOneTap = useCallback(async (limit) => {
7566
7750
  setSavingOneTapLimit(true);
7567
7751
  try {
7568
- const token = await getAccessToken();
7569
- if (!token) throw new Error("Not authenticated");
7570
- await updateUserConfig(apiBaseUrl, token, { defaultAllowance: limit });
7752
+ if (authorizationSessionIdForGuest) {
7753
+ await updateUserConfigBySession(
7754
+ apiBaseUrl,
7755
+ authorizationSessionIdForGuest,
7756
+ { defaultAllowance: limit }
7757
+ );
7758
+ } else {
7759
+ const token = await getAccessToken();
7760
+ if (!token) throw new Error("Not authenticated");
7761
+ await updateUserConfig(apiBaseUrl, token, { defaultAllowance: limit });
7762
+ }
7571
7763
  if (authExecutor.pendingSelectSource) {
7572
7764
  const action = authExecutor.pendingSelectSource;
7573
7765
  const recommended = action.metadata?.recommended;
@@ -7582,12 +7774,12 @@ function useOneTapSetupHandlers(deps) {
7582
7774
  chainName = recommended?.chainName ?? choices[0]?.chainName ?? "Base";
7583
7775
  tokenSymbol = recommended?.tokenSymbol ?? choices[0]?.tokens[0]?.tokenSymbol ?? "USDC";
7584
7776
  }
7585
- oneTapLimitSavedDuringSetupRef.current = true;
7777
+ dispatch({ type: "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP", saved: true });
7586
7778
  authExecutor.resolveSelectSource({ chainName, tokenSymbol });
7587
7779
  } else if (authExecutor.pendingOneTapSetup) {
7588
7780
  authExecutor.resolveOneTapSetup();
7589
7781
  }
7590
- dispatch({ type: "SET_USER_INTENT", intent: null });
7782
+ dispatch({ type: "SET_USER_INTENT", intent: { step: "deposit" } });
7591
7783
  } catch (err) {
7592
7784
  captureException(err);
7593
7785
  dispatch({
@@ -7597,127 +7789,84 @@ function useOneTapSetupHandlers(deps) {
7597
7789
  } finally {
7598
7790
  setSavingOneTapLimit(false);
7599
7791
  }
7600
- }, [getAccessToken, apiBaseUrl, authExecutor, dispatch, selectSourceChainName, selectSourceTokenSymbol]);
7792
+ }, [
7793
+ getAccessToken,
7794
+ apiBaseUrl,
7795
+ authExecutor,
7796
+ dispatch,
7797
+ selectSourceChainName,
7798
+ selectSourceTokenSymbol,
7799
+ authorizationSessionIdForGuest
7800
+ ]);
7601
7801
  return {
7602
7802
  handleSetupOneTap,
7603
- savingOneTapLimit,
7604
- oneTapLimitSavedDuringSetupRef
7803
+ savingOneTapLimit
7605
7804
  };
7606
7805
  }
7607
-
7608
- // src/dataLoading.ts
7609
- function resolveDataLoadAction({
7610
- authenticated,
7611
- accountsCount,
7612
- hasActiveCredential,
7613
- loading
7614
- }) {
7615
- if (!authenticated || accountsCount > 0 || !hasActiveCredential) {
7616
- return "reset";
7617
- }
7618
- if (loading) {
7619
- return "wait";
7620
- }
7621
- return "load";
7622
- }
7623
-
7624
- // src/processingStatus.ts
7625
- var PROCESSING_TIMEOUT_MS = 18e4;
7626
- function resolvePreferredTransfer(polledTransfer, localTransfer) {
7627
- return polledTransfer ?? localTransfer;
7628
- }
7629
- function getTransferStatus(polledTransfer, localTransfer) {
7630
- const transfer = resolvePreferredTransfer(polledTransfer, localTransfer);
7631
- return transfer?.status ?? "UNKNOWN";
7632
- }
7633
- function hasProcessingTimedOut(processingStartedAtMs, nowMs) {
7634
- if (!processingStartedAtMs) return false;
7635
- return nowMs - processingStartedAtMs >= PROCESSING_TIMEOUT_MS;
7636
- }
7637
- var STATUS_DISPLAY_LABELS = {
7638
- CREATED: "created",
7639
- AUTHORIZED: "authorized",
7640
- SENDING: "sending",
7641
- SENT: "confirming delivery",
7642
- COMPLETED: "completed",
7643
- FAILED: "failed"
7644
- };
7645
- function getStatusDisplayLabel(status) {
7646
- return STATUS_DISPLAY_LABELS[status] ?? status;
7647
- }
7648
- function buildProcessingTimeoutMessage(status) {
7649
- const label = getStatusDisplayLabel(status);
7650
- return `Payment is taking longer than expected (status: ${label}). Please try again.`;
7806
+ function usePrivySessionSyncEffect(deps) {
7807
+ const { dispatch, ready, authenticated } = deps;
7808
+ useEffect(() => {
7809
+ dispatch({
7810
+ type: "SYNC_PRIVY_SESSION",
7811
+ ready,
7812
+ authenticated
7813
+ });
7814
+ }, [dispatch, ready, authenticated]);
7651
7815
  }
7652
-
7653
- // src/hooks/usePaymentEffects.ts
7654
- function usePaymentEffects(deps) {
7816
+ function useOtpEffects(deps) {
7655
7817
  const {
7656
7818
  state,
7657
7819
  dispatch,
7658
- ready,
7659
7820
  authenticated,
7660
- apiBaseUrl,
7661
- depositAmount,
7662
- onComplete,
7663
- onError,
7664
- polling,
7665
- authExecutor,
7666
- reloadAccounts,
7667
7821
  activeOtpStatus,
7668
7822
  activeOtpErrorMessage,
7669
7823
  otpCode,
7670
- handleVerifyLoginCode,
7824
+ handleVerifyLoginCode
7825
+ } = deps;
7826
+ useEffect(() => {
7827
+ if (authenticated || !state.verificationTarget) return;
7828
+ if (activeOtpErrorMessage) dispatch({ type: "SET_ERROR", error: activeOtpErrorMessage });
7829
+ }, [activeOtpErrorMessage, authenticated, state.verificationTarget, dispatch]);
7830
+ useEffect(() => {
7831
+ if (state.verificationTarget && !authenticated && /^\d{6}$/.test(otpCode.trim()) && activeOtpStatus === "awaiting-code-input") {
7832
+ handleVerifyLoginCode();
7833
+ }
7834
+ }, [otpCode, state.verificationTarget, authenticated, activeOtpStatus, handleVerifyLoginCode]);
7835
+ }
7836
+ function usePasskeyCheckEffect(deps) {
7837
+ const {
7838
+ dispatch,
7839
+ ready,
7840
+ authenticated,
7841
+ apiBaseUrl,
7842
+ activeCredentialId,
7843
+ passkeyConfigLoaded,
7844
+ checkingPasskeyRef,
7671
7845
  setAuthInput,
7672
7846
  setOtpCode,
7847
+ polling,
7673
7848
  mobileSetupFlowRef,
7674
7849
  handlingMobileReturnRef,
7675
7850
  setupAccountIdRef,
7676
7851
  reauthSessionIdRef,
7677
7852
  reauthTokenRef,
7678
- loadingDataRef,
7679
- pollingTransferIdRef,
7680
- processingStartedAtRef,
7681
- checkingPasskeyRef,
7682
- pendingSelectSourceAction,
7683
- selectSourceChoices,
7684
- selectSourceRecommended,
7685
- setSelectSourceChainName,
7686
- setSelectSourceTokenSymbol,
7687
- initializedSelectSourceActionRef,
7688
- oneTapLimitSavedDuringSetupRef,
7689
- handleAuthorizedMobileReturn
7853
+ pollingTransferIdRef
7690
7854
  } = deps;
7691
7855
  const { getAccessToken } = usePrivy();
7692
- const onCompleteRef = useRef(onComplete);
7693
- onCompleteRef.current = onComplete;
7856
+ const onCompleteRef = useRef(deps.onComplete);
7857
+ onCompleteRef.current = deps.onComplete;
7694
7858
  const getAccessTokenRef = useRef(getAccessToken);
7695
7859
  getAccessTokenRef.current = getAccessToken;
7696
7860
  const pollingRef = useRef(polling);
7697
7861
  pollingRef.current = polling;
7698
- const handleAuthorizedMobileReturnRef = useRef(handleAuthorizedMobileReturn);
7699
- handleAuthorizedMobileReturnRef.current = handleAuthorizedMobileReturn;
7700
- const lastAccountFetchRef = useRef(0);
7701
- useEffect(() => {
7702
- if (depositAmount != null) {
7703
- dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
7704
- }
7705
- }, [depositAmount, dispatch]);
7706
- useEffect(() => {
7707
- if (authenticated || !state.verificationTarget) return;
7708
- if (activeOtpErrorMessage) dispatch({ type: "SET_ERROR", error: activeOtpErrorMessage });
7709
- }, [activeOtpErrorMessage, authenticated, state.verificationTarget, dispatch]);
7710
- useEffect(() => {
7711
- if (state.verificationTarget && !authenticated && /^\d{6}$/.test(otpCode.trim()) && activeOtpStatus === "awaiting-code-input") {
7712
- handleVerifyLoginCode();
7713
- }
7714
- }, [otpCode, state.verificationTarget, authenticated, activeOtpStatus, handleVerifyLoginCode]);
7862
+ const handleAuthorizedMobileReturnRef = useRef(deps.handleAuthorizedMobileReturn);
7863
+ handleAuthorizedMobileReturnRef.current = deps.handleAuthorizedMobileReturn;
7715
7864
  useEffect(() => {
7716
7865
  if (!ready || !authenticated) {
7717
7866
  checkingPasskeyRef.current = false;
7718
7867
  return;
7719
7868
  }
7720
- if (state.passkeyConfigLoaded || state.activeCredentialId) return;
7869
+ if (passkeyConfigLoaded || activeCredentialId) return;
7721
7870
  if (checkingPasskeyRef.current) return;
7722
7871
  checkingPasskeyRef.current = true;
7723
7872
  let cancelled = false;
@@ -7815,11 +7964,7 @@ function usePaymentEffects(deps) {
7815
7964
  return;
7816
7965
  }
7817
7966
  if (existingTransfer.status === "AUTHORIZED") {
7818
- if (persisted.isSetup) {
7819
- await handleAuthorizedMobileReturnRef.current(existingTransfer, true);
7820
- } else {
7821
- await handleAuthorizedMobileReturnRef.current(existingTransfer, false);
7822
- }
7967
+ await handleAuthorizedMobileReturnRef.current(existingTransfer, !!persisted.isSetup);
7823
7968
  return;
7824
7969
  }
7825
7970
  if (persisted.isSetup) {
@@ -7861,7 +8006,18 @@ function usePaymentEffects(deps) {
7861
8006
  if (token || cancelled) break;
7862
8007
  await new Promise((r) => setTimeout(r, 1e3));
7863
8008
  }
7864
- if (!token || cancelled) return;
8009
+ if (cancelled) return;
8010
+ if (!token) {
8011
+ dispatch({
8012
+ type: "PASSKEY_CONFIG_LOADED",
8013
+ knownIds: []
8014
+ });
8015
+ dispatch({
8016
+ type: "SET_ERROR",
8017
+ error: "Could not refresh your session. Try signing in again."
8018
+ });
8019
+ return;
8020
+ }
7865
8021
  const { config } = await fetchUserConfig(apiBaseUrl, token);
7866
8022
  if (cancelled) return;
7867
8023
  const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
@@ -7870,11 +8026,13 @@ function usePaymentEffects(deps) {
7870
8026
  knownIds: allPasskeys.map((p) => p.credentialId),
7871
8027
  oneTapLimit: config.defaultAllowance ?? void 0
7872
8028
  });
7873
- if (allPasskeys.length === 0) {
7874
- return;
7875
- }
7876
- if (state.activeCredentialId && allPasskeys.some((p) => p.credentialId === state.activeCredentialId)) {
7877
- await restoreState(state.activeCredentialId, token);
8029
+ if (allPasskeys.length === 0) return;
8030
+ if (activeCredentialId && allPasskeys.some((p) => p.credentialId === activeCredentialId)) {
8031
+ const pk = allPasskeys.find((p) => p.credentialId === activeCredentialId)?.publicKey;
8032
+ if (pk) {
8033
+ dispatch({ type: "PASSKEY_ACTIVATED", credentialId: activeCredentialId, publicKey: pk });
8034
+ }
8035
+ await restoreState(activeCredentialId, token);
7878
8036
  return;
7879
8037
  }
7880
8038
  if (cancelled) return;
@@ -7892,13 +8050,22 @@ function usePaymentEffects(deps) {
7892
8050
  }
7893
8051
  if (cancelled) return;
7894
8052
  if (matched) {
7895
- dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched });
8053
+ const publicKey = allPasskeys.find((p) => p.credentialId === matched)?.publicKey;
8054
+ dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched, publicKey });
7896
8055
  window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
7897
8056
  reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
7898
8057
  });
7899
8058
  await restoreState(matched, token);
7900
8059
  }
7901
- } catch {
8060
+ } catch (err) {
8061
+ dispatch({
8062
+ type: "PASSKEY_CONFIG_LOADED",
8063
+ knownIds: []
8064
+ });
8065
+ dispatch({
8066
+ type: "SET_ERROR",
8067
+ error: err instanceof Error ? err.message : "Unable to load passkey settings."
8068
+ });
7902
8069
  }
7903
8070
  };
7904
8071
  checkPasskey();
@@ -7906,7 +8073,39 @@ function usePaymentEffects(deps) {
7906
8073
  cancelled = true;
7907
8074
  checkingPasskeyRef.current = false;
7908
8075
  };
7909
- }, [ready, authenticated, apiBaseUrl, state.activeCredentialId, state.passkeyConfigLoaded]);
8076
+ }, [ready, authenticated, apiBaseUrl, activeCredentialId, passkeyConfigLoaded]);
8077
+ }
8078
+
8079
+ // src/dataLoading.ts
8080
+ function resolveDataLoadAction({
8081
+ authenticated,
8082
+ accountsCount,
8083
+ hasActiveCredential,
8084
+ loading
8085
+ }) {
8086
+ if (!authenticated || accountsCount > 0 || !hasActiveCredential) {
8087
+ return "reset";
8088
+ }
8089
+ if (loading) {
8090
+ return "wait";
8091
+ }
8092
+ return "load";
8093
+ }
8094
+
8095
+ // src/hooks/useDataLoadEffect.ts
8096
+ function useDataLoadEffect(deps) {
8097
+ const {
8098
+ state,
8099
+ dispatch,
8100
+ authenticated,
8101
+ apiBaseUrl,
8102
+ depositAmount,
8103
+ loadingDataRef
8104
+ } = deps;
8105
+ const { getAccessToken } = usePrivy();
8106
+ const getAccessTokenRef = useRef(getAccessToken);
8107
+ getAccessTokenRef.current = getAccessToken;
8108
+ const lastAccountFetchRef = useRef(0);
7910
8109
  useEffect(() => {
7911
8110
  const loadAction = resolveDataLoadAction({
7912
8111
  authenticated,
@@ -8006,6 +8205,61 @@ function usePaymentEffects(deps) {
8006
8205
  cancelled = true;
8007
8206
  };
8008
8207
  }, [authenticated, state.providers.length, state.activeCredentialId, apiBaseUrl]);
8208
+ useEffect(() => {
8209
+ if (state.accounts.length > 0 && state.activeCredentialId && !state.loadingData && !state.transfer && authenticated && Date.now() - lastAccountFetchRef.current > 15e3) {
8210
+ lastAccountFetchRef.current = Date.now();
8211
+ deps.reloadAccounts();
8212
+ }
8213
+ }, [
8214
+ state.accounts.length,
8215
+ state.activeCredentialId,
8216
+ state.loadingData,
8217
+ state.transfer,
8218
+ authenticated,
8219
+ deps
8220
+ ]);
8221
+ }
8222
+
8223
+ // src/processingStatus.ts
8224
+ var PROCESSING_TIMEOUT_MS = 18e4;
8225
+ function resolvePreferredTransfer(polledTransfer, localTransfer) {
8226
+ return polledTransfer ?? localTransfer;
8227
+ }
8228
+ function getTransferStatus(polledTransfer, localTransfer) {
8229
+ const transfer = resolvePreferredTransfer(polledTransfer, localTransfer);
8230
+ return transfer?.status ?? "UNKNOWN";
8231
+ }
8232
+ function hasProcessingTimedOut(processingStartedAtMs, nowMs) {
8233
+ if (!processingStartedAtMs) return false;
8234
+ return nowMs - processingStartedAtMs >= PROCESSING_TIMEOUT_MS;
8235
+ }
8236
+ var STATUS_DISPLAY_LABELS = {
8237
+ CREATED: "created",
8238
+ AUTHORIZED: "authorized",
8239
+ SENDING: "sending",
8240
+ SENT: "confirming delivery",
8241
+ COMPLETED: "completed",
8242
+ FAILED: "failed"
8243
+ };
8244
+ function getStatusDisplayLabel(status) {
8245
+ return STATUS_DISPLAY_LABELS[status] ?? status;
8246
+ }
8247
+ function buildProcessingTimeoutMessage(status) {
8248
+ const label = getStatusDisplayLabel(status);
8249
+ return `Payment is taking longer than expected (status: ${label}). Please try again.`;
8250
+ }
8251
+
8252
+ // src/hooks/useProcessingEffect.ts
8253
+ function useProcessingEffect(deps) {
8254
+ const {
8255
+ state,
8256
+ dispatch,
8257
+ polling,
8258
+ processingStartedAtRef,
8259
+ onComplete,
8260
+ onError,
8261
+ reloadAccounts
8262
+ } = deps;
8009
8263
  useEffect(() => {
8010
8264
  if (!polling.transfer) return;
8011
8265
  if (polling.transfer.status === "COMPLETED") {
@@ -8018,19 +8272,6 @@ function usePaymentEffects(deps) {
8018
8272
  dispatch({ type: "TRANSFER_FAILED", transfer: polling.transfer, error: "Transfer failed." });
8019
8273
  }
8020
8274
  }, [polling.transfer, onComplete, dispatch, reloadAccounts]);
8021
- useEffect(() => {
8022
- if (state.accounts.length > 0 && state.activeCredentialId && !state.loadingData && !state.transfer && authenticated && Date.now() - lastAccountFetchRef.current > 15e3) {
8023
- lastAccountFetchRef.current = Date.now();
8024
- reloadAccounts();
8025
- }
8026
- }, [
8027
- state.accounts.length,
8028
- state.activeCredentialId,
8029
- state.loadingData,
8030
- state.transfer,
8031
- authenticated,
8032
- reloadAccounts
8033
- ]);
8034
8275
  useEffect(() => {
8035
8276
  const isProcessing = state.creatingTransfer || state.transfer != null && ["CREATED", "SENDING", "SENT"].includes(state.transfer.status);
8036
8277
  if (!isProcessing) {
@@ -8066,6 +8307,26 @@ function usePaymentEffects(deps) {
8066
8307
  dispatch,
8067
8308
  processingStartedAtRef
8068
8309
  ]);
8310
+ }
8311
+ function useMobilePollingEffect(deps) {
8312
+ const {
8313
+ state,
8314
+ dispatch,
8315
+ polling,
8316
+ mobileSetupFlowRef,
8317
+ handlingMobileReturnRef,
8318
+ setupAccountIdRef,
8319
+ reauthSessionIdRef,
8320
+ reauthTokenRef,
8321
+ pollingTransferIdRef,
8322
+ reloadAccounts,
8323
+ apiBaseUrl
8324
+ } = deps;
8325
+ const { getAccessToken } = usePrivy();
8326
+ const getAccessTokenRef = useRef(getAccessToken);
8327
+ getAccessTokenRef.current = getAccessToken;
8328
+ const handleAuthorizedMobileReturnRef = useRef(deps.handleAuthorizedMobileReturn);
8329
+ handleAuthorizedMobileReturnRef.current = deps.handleAuthorizedMobileReturn;
8069
8330
  useEffect(() => {
8070
8331
  if (!state.mobileFlow) {
8071
8332
  handlingMobileReturnRef.current = false;
@@ -8074,8 +8335,8 @@ function usePaymentEffects(deps) {
8074
8335
  if (handlingMobileReturnRef.current) return;
8075
8336
  const polledTransfer = polling.transfer;
8076
8337
  if (!polledTransfer || polledTransfer.status !== "AUTHORIZED") return;
8077
- void handleAuthorizedMobileReturn(polledTransfer, mobileSetupFlowRef.current);
8078
- }, [state.mobileFlow, polling.transfer, handleAuthorizedMobileReturn, handlingMobileReturnRef, mobileSetupFlowRef]);
8338
+ void handleAuthorizedMobileReturnRef.current(polledTransfer, mobileSetupFlowRef.current);
8339
+ }, [state.mobileFlow, polling.transfer, handlingMobileReturnRef, mobileSetupFlowRef]);
8079
8340
  useEffect(() => {
8080
8341
  if (!state.mobileFlow || !mobileSetupFlowRef.current) return;
8081
8342
  if (!state.activeCredentialId || !setupAccountIdRef.current) return;
@@ -8151,14 +8412,10 @@ function usePaymentEffects(deps) {
8151
8412
  poll();
8152
8413
  const intervalId = window.setInterval(poll, POLL_INTERVAL_MS);
8153
8414
  const handleVisibility = () => {
8154
- if (document.visibilityState === "visible" && !cancelled) {
8155
- poll();
8156
- }
8415
+ if (document.visibilityState === "visible" && !cancelled) poll();
8157
8416
  };
8158
8417
  const handlePageShow = (e) => {
8159
- if (e.persisted && !cancelled) {
8160
- poll();
8161
- }
8418
+ if (e.persisted && !cancelled) poll();
8162
8419
  };
8163
8420
  document.addEventListener("visibilitychange", handleVisibility);
8164
8421
  window.addEventListener("pageshow", handlePageShow);
@@ -8209,6 +8466,16 @@ function usePaymentEffects(deps) {
8209
8466
  handlingMobileReturnRef,
8210
8467
  pollingTransferIdRef
8211
8468
  ]);
8469
+ }
8470
+ function useSelectSourceEffect(deps) {
8471
+ const {
8472
+ pendingSelectSourceAction,
8473
+ selectSourceChoices,
8474
+ selectSourceRecommended,
8475
+ setSelectSourceChainName,
8476
+ setSelectSourceTokenSymbol,
8477
+ initializedSelectSourceActionRef
8478
+ } = deps;
8212
8479
  useEffect(() => {
8213
8480
  if (!pendingSelectSourceAction) {
8214
8481
  initializedSelectSourceActionRef.current = null;
@@ -8239,38 +8506,27 @@ function usePaymentEffects(deps) {
8239
8506
  setSelectSourceTokenSymbol,
8240
8507
  initializedSelectSourceActionRef
8241
8508
  ]);
8509
+ }
8510
+ function useOneTapAutoResolveEffect(deps) {
8511
+ const { authExecutor, dispatch, oneTapLimitSavedDuringSetup, reloadAccounts } = deps;
8242
8512
  const pendingOneTapSetupAction = authExecutor.pendingOneTapSetup;
8243
8513
  useEffect(() => {
8244
- if (pendingOneTapSetupAction && oneTapLimitSavedDuringSetupRef.current) {
8245
- oneTapLimitSavedDuringSetupRef.current = false;
8514
+ if (pendingOneTapSetupAction && oneTapLimitSavedDuringSetup) {
8515
+ dispatch({ type: "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP", saved: false });
8246
8516
  authExecutor.resolveOneTapSetup();
8247
8517
  }
8248
- }, [pendingOneTapSetupAction, authExecutor, oneTapLimitSavedDuringSetupRef]);
8518
+ }, [pendingOneTapSetupAction, authExecutor, dispatch, oneTapLimitSavedDuringSetup]);
8249
8519
  useEffect(() => {
8250
- if (pendingOneTapSetupAction && !oneTapLimitSavedDuringSetupRef.current) {
8520
+ if (pendingOneTapSetupAction && !oneTapLimitSavedDuringSetup) {
8251
8521
  reloadAccounts();
8252
8522
  }
8253
- }, [pendingOneTapSetupAction, reloadAccounts, oneTapLimitSavedDuringSetupRef]);
8254
- useEffect(() => {
8255
- if (!state.guestSessionToken) return;
8256
- if (state.guestPreauthAccountId) return;
8257
- if (!state.transfer || state.transfer.status !== "COMPLETED") return;
8258
- let cancelled = false;
8259
- const checkGuestAccount = async () => {
8260
- try {
8261
- const result = await fetchGuestAccount(apiBaseUrl, state.guestSessionToken);
8262
- if (cancelled) return;
8263
- if (result && !result.hasPasskey) {
8264
- dispatch({ type: "GUEST_PREAUTH_DETECTED", accountId: result.accountId });
8265
- }
8266
- } catch {
8267
- }
8268
- };
8269
- checkGuestAccount();
8270
- return () => {
8271
- cancelled = true;
8272
- };
8273
- }, [state.transfer, state.guestSessionToken, state.guestPreauthAccountId, apiBaseUrl, dispatch]);
8523
+ }, [pendingOneTapSetupAction, reloadAccounts, oneTapLimitSavedDuringSetup]);
8524
+ }
8525
+ function useGuestPreauthEffect(deps) {
8526
+ const { state, dispatch, authenticated, apiBaseUrl, reloadAccounts } = deps;
8527
+ const { getAccessToken } = usePrivy();
8528
+ const getAccessTokenRef = useRef(getAccessToken);
8529
+ getAccessTokenRef.current = getAccessToken;
8274
8530
  const settingOwnerRef = useRef(false);
8275
8531
  useEffect(() => {
8276
8532
  if (!state.guestPreauthAccountId) return;
@@ -8279,6 +8535,8 @@ function usePaymentEffects(deps) {
8279
8535
  if (!authenticated) return;
8280
8536
  if (!state.guestSessionToken) return;
8281
8537
  if (settingOwnerRef.current) return;
8538
+ const hasActive = state.accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
8539
+ if (!hasActive) return;
8282
8540
  settingOwnerRef.current = true;
8283
8541
  let cancelled = false;
8284
8542
  const setOwner = async () => {
@@ -8316,12 +8574,69 @@ function usePaymentEffects(deps) {
8316
8574
  state.activeCredentialId,
8317
8575
  state.activePublicKey,
8318
8576
  state.guestSessionToken,
8577
+ state.accounts,
8319
8578
  authenticated,
8320
8579
  apiBaseUrl,
8321
8580
  dispatch,
8322
8581
  reloadAccounts
8323
8582
  ]);
8324
8583
  }
8584
+ function useGuestDesktopPreauthSessionEffect(deps) {
8585
+ const { state, authExecutor, reloadAccounts, dispatch, desktopGuestPreauth } = deps;
8586
+ const preauthExecutingRef = useRef(false);
8587
+ useEffect(() => {
8588
+ if (!desktopGuestPreauth) return;
8589
+ if (!state.guestPreauthorizing) return;
8590
+ if (!state.guestPreauthSessionId || preauthExecutingRef.current) return;
8591
+ preauthExecutingRef.current = true;
8592
+ const runPreauthSession = async () => {
8593
+ try {
8594
+ await authExecutor.executeSessionById(state.guestPreauthSessionId);
8595
+ await reloadAccounts();
8596
+ } catch {
8597
+ } finally {
8598
+ preauthExecutingRef.current = false;
8599
+ dispatch({ type: "GUEST_PREAUTH_END" });
8600
+ }
8601
+ };
8602
+ void runPreauthSession();
8603
+ }, [
8604
+ desktopGuestPreauth,
8605
+ state.guestPreauthorizing,
8606
+ state.guestPreauthSessionId,
8607
+ authExecutor,
8608
+ reloadAccounts,
8609
+ dispatch
8610
+ ]);
8611
+ }
8612
+ function useGuestPreauthPhaseSyncEffect(deps) {
8613
+ const { state, dispatch, authExecutor, isDesktop } = deps;
8614
+ useEffect(() => {
8615
+ if (!state.guestPreauthorizing || !isDesktop) return;
8616
+ const pending = authExecutor.pendingSelectSource;
8617
+ if (pending) {
8618
+ const intent = {
8619
+ step: "select-source",
8620
+ action: pending,
8621
+ isDesktop
8622
+ };
8623
+ if (state.phase.step === "select-source" && state.phase.action.id === pending.id) {
8624
+ return;
8625
+ }
8626
+ dispatch({ type: "SET_USER_INTENT", intent });
8627
+ return;
8628
+ }
8629
+ if (state.phase.step === "select-source") {
8630
+ dispatch({ type: "SET_USER_INTENT", intent: { step: "one-tap-setup", action: null } });
8631
+ }
8632
+ }, [
8633
+ state.guestPreauthorizing,
8634
+ state.phase,
8635
+ isDesktop,
8636
+ authExecutor.pendingSelectSource,
8637
+ dispatch
8638
+ ]);
8639
+ }
8325
8640
  function BlinkPayment(props) {
8326
8641
  const resetKey = useRef(0);
8327
8642
  const handleBoundaryReset = useCallback(() => {
@@ -8343,6 +8658,10 @@ function BlinkPaymentInner({
8343
8658
  const { apiBaseUrl, depositAmount } = useBlinkConfig();
8344
8659
  const { ready, authenticated, logout, getAccessToken } = usePrivy();
8345
8660
  useLoginWithOAuth();
8661
+ const isDesktop = shouldUseWalletConnector({
8662
+ useWalletConnector: useWalletConnectorProp,
8663
+ userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
8664
+ });
8346
8665
  const [state, dispatch] = useReducer(
8347
8666
  paymentReducer,
8348
8667
  {
@@ -8426,7 +8745,9 @@ function BlinkPaymentInner({
8426
8745
  reauthTokenRef: mobileFlowRefs.reauthTokenRef,
8427
8746
  authenticated,
8428
8747
  merchantAuthorization,
8429
- destination
8748
+ destination,
8749
+ guestSessionToken: state.guestSessionToken,
8750
+ selectedProviderId: state.selectedProviderId
8430
8751
  });
8431
8752
  const oneTapSetup = useOneTapSetupHandlers({
8432
8753
  dispatch,
@@ -8434,7 +8755,8 @@ function BlinkPaymentInner({
8434
8755
  apiBaseUrl,
8435
8756
  authExecutor,
8436
8757
  selectSourceChainName: sourceSelection.selectSourceChainName,
8437
- selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol
8758
+ selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
8759
+ authorizationSessionIdForGuest: state.guestPreauthSessionId
8438
8760
  });
8439
8761
  const guestTransfer = useGuestTransferHandlers({
8440
8762
  dispatch,
@@ -8448,13 +8770,12 @@ function BlinkPaymentInner({
8448
8770
  clearMobileFlowState();
8449
8771
  transfer.processingStartedAtRef.current = null;
8450
8772
  transfer.pollingTransferIdRef.current = null;
8451
- oneTapSetup.oneTapLimitSavedDuringSetupRef.current = false;
8452
8773
  dispatch({
8453
8774
  type: "NEW_PAYMENT",
8454
8775
  depositAmount,
8455
8776
  firstAccountId: state.accounts.length > 0 ? state.accounts[0].id : null
8456
8777
  });
8457
- }, [depositAmount, state.accounts, transfer, oneTapSetup]);
8778
+ }, [depositAmount, state.accounts, transfer]);
8458
8779
  const handleLogout = useCallback(async () => {
8459
8780
  try {
8460
8781
  await logout();
@@ -8466,65 +8787,113 @@ function BlinkPaymentInner({
8466
8787
  }
8467
8788
  polling.stopPolling();
8468
8789
  passkey.checkingPasskeyRef.current = false;
8469
- oneTapSetup.oneTapLimitSavedDuringSetupRef.current = false;
8470
8790
  auth.setAuthInput("");
8471
8791
  auth.setOtpCode("");
8472
- dispatch({ type: "LOGOUT", depositAmount });
8473
- }, [logout, polling, depositAmount, auth, passkey, oneTapSetup]);
8474
- usePaymentEffects({
8792
+ dispatch({ type: "LOGOUT", depositAmount, privyReady: ready });
8793
+ }, [logout, polling, depositAmount, auth, passkey, ready]);
8794
+ useEffect(() => {
8795
+ if (depositAmount != null) {
8796
+ dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
8797
+ }
8798
+ }, [depositAmount, dispatch]);
8799
+ usePrivySessionSyncEffect({ dispatch, ready, authenticated });
8800
+ useOtpEffects({
8475
8801
  state,
8476
8802
  dispatch,
8477
- ready,
8478
8803
  authenticated,
8479
- apiBaseUrl,
8480
- depositAmount,
8481
- onComplete,
8482
- onError,
8483
- polling,
8484
- authExecutor,
8485
- reloadAccounts: transfer.reloadAccounts,
8486
8804
  activeOtpStatus: auth.activeOtpStatus,
8487
8805
  activeOtpErrorMessage: auth.activeOtpErrorMessage,
8488
8806
  otpCode: auth.otpCode,
8489
8807
  handleVerifyLoginCode: auth.handleVerifyLoginCode,
8490
8808
  setAuthInput: auth.setAuthInput,
8809
+ setOtpCode: auth.setOtpCode
8810
+ });
8811
+ usePasskeyCheckEffect({
8812
+ dispatch,
8813
+ ready,
8814
+ authenticated,
8815
+ apiBaseUrl,
8816
+ activeCredentialId: state.activeCredentialId,
8817
+ passkeyConfigLoaded: state.passkeyConfigLoaded,
8818
+ checkingPasskeyRef: passkey.checkingPasskeyRef,
8819
+ setAuthInput: auth.setAuthInput,
8491
8820
  setOtpCode: auth.setOtpCode,
8821
+ polling,
8492
8822
  mobileSetupFlowRef: mobileFlowRefs.mobileSetupFlowRef,
8493
8823
  handlingMobileReturnRef: mobileFlowRefs.handlingMobileReturnRef,
8494
8824
  setupAccountIdRef: mobileFlowRefs.setupAccountIdRef,
8495
8825
  reauthSessionIdRef: mobileFlowRefs.reauthSessionIdRef,
8496
8826
  reauthTokenRef: mobileFlowRefs.reauthTokenRef,
8497
- loadingDataRef: mobileFlowRefs.loadingDataRef,
8498
8827
  pollingTransferIdRef: transfer.pollingTransferIdRef,
8828
+ handleAuthorizedMobileReturn: mobileFlow.handleAuthorizedMobileReturn,
8829
+ onComplete
8830
+ });
8831
+ useDataLoadEffect({
8832
+ state,
8833
+ dispatch,
8834
+ authenticated,
8835
+ apiBaseUrl,
8836
+ depositAmount,
8837
+ loadingDataRef: mobileFlowRefs.loadingDataRef,
8838
+ reloadAccounts: transfer.reloadAccounts
8839
+ });
8840
+ useProcessingEffect({
8841
+ state,
8842
+ dispatch,
8843
+ polling,
8499
8844
  processingStartedAtRef: transfer.processingStartedAtRef,
8500
- checkingPasskeyRef: passkey.checkingPasskeyRef,
8845
+ onComplete,
8846
+ onError,
8847
+ reloadAccounts: transfer.reloadAccounts
8848
+ });
8849
+ useMobilePollingEffect({
8850
+ state,
8851
+ dispatch,
8852
+ polling,
8853
+ mobileSetupFlowRef: mobileFlowRefs.mobileSetupFlowRef,
8854
+ handlingMobileReturnRef: mobileFlowRefs.handlingMobileReturnRef,
8855
+ setupAccountIdRef: mobileFlowRefs.setupAccountIdRef,
8856
+ reauthSessionIdRef: mobileFlowRefs.reauthSessionIdRef,
8857
+ reauthTokenRef: mobileFlowRefs.reauthTokenRef,
8858
+ pollingTransferIdRef: transfer.pollingTransferIdRef,
8859
+ reloadAccounts: transfer.reloadAccounts,
8860
+ handleAuthorizedMobileReturn: mobileFlow.handleAuthorizedMobileReturn,
8861
+ apiBaseUrl
8862
+ });
8863
+ useSelectSourceEffect({
8501
8864
  pendingSelectSourceAction: sourceSelection.pendingSelectSourceAction,
8502
8865
  selectSourceChoices: sourceSelection.selectSourceChoices,
8503
8866
  selectSourceRecommended: sourceSelection.selectSourceRecommended,
8504
8867
  setSelectSourceChainName: sourceSelection.setSelectSourceChainName,
8505
8868
  setSelectSourceTokenSymbol: sourceSelection.setSelectSourceTokenSymbol,
8506
- initializedSelectSourceActionRef: sourceSelection.initializedSelectSourceActionRef,
8507
- oneTapLimitSavedDuringSetupRef: oneTapSetup.oneTapLimitSavedDuringSetupRef,
8508
- handleAuthorizedMobileReturn: mobileFlow.handleAuthorizedMobileReturn
8869
+ initializedSelectSourceActionRef: sourceSelection.initializedSelectSourceActionRef
8509
8870
  });
8510
- const autoSelectingRef = useRef(false);
8511
- useEffect(() => {
8512
- if (!state.isGuestFlow || !state.selectedProviderId || !state.activeCredentialId || !authenticated || autoSelectingRef.current || state.transfer != null) return;
8513
- const hasActive = state.accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
8514
- if (hasActive) return;
8515
- autoSelectingRef.current = true;
8516
- provider.handleSelectProvider(state.selectedProviderId).finally(() => {
8517
- autoSelectingRef.current = false;
8518
- });
8519
- }, [
8520
- state.isGuestFlow,
8521
- state.selectedProviderId,
8522
- state.activeCredentialId,
8523
- state.transfer,
8524
- state.accounts,
8871
+ useOneTapAutoResolveEffect({
8872
+ authExecutor,
8873
+ dispatch,
8874
+ oneTapLimitSavedDuringSetup: state.oneTapLimitSavedDuringSetup,
8875
+ reloadAccounts: transfer.reloadAccounts
8876
+ });
8877
+ useGuestPreauthEffect({
8878
+ state,
8879
+ dispatch,
8525
8880
  authenticated,
8526
- provider
8527
- ]);
8881
+ apiBaseUrl,
8882
+ reloadAccounts: transfer.reloadAccounts
8883
+ });
8884
+ useGuestPreauthPhaseSyncEffect({
8885
+ state,
8886
+ dispatch,
8887
+ authExecutor,
8888
+ isDesktop
8889
+ });
8890
+ useGuestDesktopPreauthSessionEffect({
8891
+ state,
8892
+ authExecutor,
8893
+ reloadAccounts: transfer.reloadAccounts,
8894
+ dispatch,
8895
+ desktopGuestPreauth: isDesktop
8896
+ });
8528
8897
  const handlers = useMemo(() => ({
8529
8898
  onSendLoginCode: auth.handleSendLoginCode,
8530
8899
  onVerifyLoginCode: auth.handleVerifyLoginCode,
@@ -8547,7 +8916,7 @@ function BlinkPaymentInner({
8547
8916
  onBackFromOpenWallet: () => dispatch({ type: "CLEAR_MOBILE_STATE" }),
8548
8917
  onLogout: handleLogout,
8549
8918
  onNewPayment: handleNewPayment,
8550
- onSetUserIntent: (intent) => dispatch({ type: "SET_USER_INTENT", intent }),
8919
+ onSetPhase: (phase) => dispatch({ type: "SET_USER_INTENT", intent: phase }),
8551
8920
  onSetAuthInput: auth.setAuthInput,
8552
8921
  onSetOtpCode: (code) => {
8553
8922
  auth.setOtpCode(code);
@@ -8561,30 +8930,9 @@ function BlinkPaymentInner({
8561
8930
  onSelectAuthorizedToken: provider.handleSelectAuthorizedToken,
8562
8931
  onAuthorizeToken: provider.handleAuthorizeToken,
8563
8932
  onSelectGuestToken: guestTransfer.handleSelectGuestToken,
8933
+ onGuestBackFromTokenPicker: guestTransfer.handleGuestBackFromTokenPicker,
8564
8934
  onLogin: () => dispatch({ type: "REQUEST_LOGIN" }),
8565
- onPreauthorize: async () => {
8566
- if (state.guestPreauthAccountId) {
8567
- dispatch({ type: "REQUEST_LOGIN" });
8568
- return;
8569
- }
8570
- if (!state.guestSessionToken || !state.selectedProviderId) {
8571
- dispatch({ type: "REQUEST_LOGIN" });
8572
- return;
8573
- }
8574
- try {
8575
- const providerName = state.providers.find((p) => p.id === state.selectedProviderId)?.name ?? "Wallet";
8576
- const created = await createGuestAccount(
8577
- apiBaseUrl,
8578
- state.guestSessionToken,
8579
- state.selectedProviderId,
8580
- providerName
8581
- );
8582
- dispatch({ type: "GUEST_PREAUTH_DETECTED", accountId: created.accountId });
8583
- dispatch({ type: "REQUEST_LOGIN" });
8584
- } catch {
8585
- dispatch({ type: "REQUEST_LOGIN" });
8586
- }
8587
- }
8935
+ onPreauthorize: provider.handlePreauthorize
8588
8936
  }), [
8589
8937
  auth,
8590
8938
  passkey,
@@ -8595,56 +8943,57 @@ function BlinkPaymentInner({
8595
8943
  oneTapSetup,
8596
8944
  guestTransfer,
8597
8945
  handleLogout,
8598
- handleNewPayment,
8599
- state.guestPreauthAccountId,
8600
- state.guestSessionToken,
8601
- state.selectedProviderId,
8602
- state.providers,
8603
- apiBaseUrl
8946
+ handleNewPayment
8604
8947
  ]);
8605
8948
  return /* @__PURE__ */ jsx(
8606
8949
  StepRenderer,
8607
8950
  {
8608
- state,
8609
- ready,
8610
- authenticated,
8611
- activeOtpStatus: auth.activeOtpStatus,
8612
- pollingTransfer: polling.transfer,
8613
- pollingError: polling.error,
8614
- authExecutorError: authExecutor.error,
8615
- inlineAuthorizationExecuting: authExecutor.executing,
8616
- transferSigningSigning: transferSigning.signing,
8617
- transferSigningError: transferSigning.error,
8618
- pendingSelectSource: authExecutor.pendingSelectSource,
8619
- pendingOneTapSetup: authExecutor.pendingOneTapSetup,
8620
- oneTapLimitAlreadySaved: oneTapSetup.oneTapLimitSavedDuringSetupRef.current,
8621
- pendingConnections: derived.pendingConnections,
8622
- depositEligibleAccounts: derived.depositEligibleAccounts,
8623
- sourceName: derived.sourceName,
8624
- maxSourceBalance: derived.maxSourceBalance,
8625
- tokenCount: derived.tokenCount,
8626
- selectedAccount: derived.selectedAccount,
8627
- selectedSource: derived.selectedSource,
8628
- selectSourceChoices: sourceSelection.selectSourceChoices,
8629
- selectSourceRecommended: sourceSelection.selectSourceRecommended,
8630
- selectSourceAvailableBalance: sourceSelection.selectSourceAvailableBalance,
8631
- guestTokenEntries: guestTransfer.guestTokenEntries,
8632
- guestLoadingBalances: guestTransfer.loadingBalances,
8633
- guestSettingSender: guestTransfer.settingSender,
8634
- authInput: auth.authInput,
8635
- otpCode: auth.otpCode,
8636
- selectSourceChainName: sourceSelection.selectSourceChainName,
8637
- selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
8638
- savingOneTapLimit: oneTapSetup.savingOneTapLimit,
8639
- merchantName,
8640
- onBack,
8641
- onDismiss,
8642
- depositAmount,
8951
+ flow: {
8952
+ state,
8953
+ authenticated,
8954
+ activeOtpStatus: auth.activeOtpStatus,
8955
+ isDesktop,
8956
+ merchantName,
8957
+ onBack,
8958
+ onDismiss,
8959
+ depositAmount
8960
+ },
8961
+ remote: {
8962
+ pollingTransfer: polling.transfer,
8963
+ pollingError: polling.error,
8964
+ authExecutorError: authExecutor.error,
8965
+ transferSigningSigning: transferSigning.signing,
8966
+ transferSigningError: transferSigning.error,
8967
+ pendingSelectSource: authExecutor.pendingSelectSource,
8968
+ pendingOneTapSetup: authExecutor.pendingOneTapSetup
8969
+ },
8970
+ derived: {
8971
+ pendingConnections: derived.pendingConnections,
8972
+ depositEligibleAccounts: derived.depositEligibleAccounts,
8973
+ sourceName: derived.sourceName,
8974
+ maxSourceBalance: derived.maxSourceBalance,
8975
+ tokenCount: derived.tokenCount,
8976
+ selectedAccount: derived.selectedAccount,
8977
+ selectedSource: derived.selectedSource,
8978
+ selectSourceChoices: sourceSelection.selectSourceChoices,
8979
+ selectSourceRecommended: sourceSelection.selectSourceRecommended,
8980
+ selectSourceAvailableBalance: sourceSelection.selectSourceAvailableBalance
8981
+ },
8982
+ forms: {
8983
+ authInput: auth.authInput,
8984
+ otpCode: auth.otpCode,
8985
+ selectSourceChainName: sourceSelection.selectSourceChainName,
8986
+ selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
8987
+ savingOneTapLimit: oneTapSetup.savingOneTapLimit,
8988
+ guestTokenEntries: guestTransfer.guestTokenEntries,
8989
+ guestLoadingBalances: guestTransfer.loadingBalances,
8990
+ guestSettingSender: guestTransfer.settingSender
8991
+ },
8643
8992
  handlers
8644
8993
  }
8645
8994
  );
8646
8995
  }
8647
8996
 
8648
- export { AdvancedSourceScreen, BLINK_LOGO, BLINK_MASCOT, BlinkLoadingScreen, BlinkPayment, BlinkProvider, FlowPhaseProvider, IconCircle, InfoBanner, OutlineButton, PasskeyIframeBlockedError, PasskeyScreen, PoweredByFooter, PrimaryButton, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, Spinner, StepList, TokenPickerScreen, api_exports as blinkApi, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, findDevicePasskeyViaPopup, getTheme, lightTheme, resolvePasskeyRpId, resolveScreen, useAuthorizationExecutor, useBlinkConfig, useBlinkDepositAmount, useTransferPolling, useTransferSigning };
8997
+ export { AdvancedSourceScreen, BLINK_LOGO, BLINK_MASCOT, BlinkLoadingScreen, BlinkPayment, BlinkProvider, FlowPhaseProvider, IconCircle, InfoBanner, OutlineButton, PasskeyIframeBlockedError, PasskeyScreen, PoweredByFooter, PrimaryButton, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, Spinner, StepList, TokenPickerScreen, api_exports as blinkApi, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, findDevicePasskeyViaPopup, getTheme, lightTheme, resolvePasskeyRpId, screenForPhase, useAuthorizationExecutor, useBlinkConfig, useBlinkDepositAmount, useTransferPolling, useTransferSigning };
8649
8998
  //# sourceMappingURL=index.js.map
8650
8999
  //# sourceMappingURL=index.js.map