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