@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 +1322 -973
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -45
- package/dist/index.d.ts +65 -45
- package/dist/index.js +1322 -973
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
517
|
-
return
|
|
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
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
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
|
|
1260
|
-
const {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
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:
|
|
1807
|
+
id: base64ToBytes2(payload.passkeyCredentialId)
|
|
1761
1808
|
}] : void 0;
|
|
1762
|
-
await
|
|
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:
|
|
1779
|
-
signature:
|
|
1780
|
-
authenticatorData:
|
|
1781
|
-
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
|
-
|
|
1981
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
2263
|
-
|
|
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
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
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,
|
|
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
|
|
6016
|
+
return phase.step === "one-tap-setup" ? "link" : "deposit";
|
|
5898
6017
|
}
|
|
5899
6018
|
return null;
|
|
5900
6019
|
}
|
|
5901
6020
|
function StepRenderer(props) {
|
|
5902
|
-
const
|
|
5903
|
-
|
|
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
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
6331
|
-
() =>
|
|
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
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
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
|
-
|
|
6352
|
-
|
|
6353
|
-
const
|
|
6354
|
-
|
|
6355
|
-
|
|
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
|
-
|
|
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
|
|
6667
|
+
authToken: token,
|
|
6566
6668
|
apiBaseUrl
|
|
6567
6669
|
});
|
|
6568
6670
|
if (matched) {
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
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: "
|
|
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
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
}, [
|
|
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
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
})
|
|
7618
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
8081
|
-
}, [state.mobileFlow, polling.transfer,
|
|
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 &&
|
|
8248
|
-
|
|
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,
|
|
8521
|
+
}, [pendingOneTapSetupAction, authExecutor, dispatch, oneTapLimitSavedDuringSetup]);
|
|
8252
8522
|
react.useEffect(() => {
|
|
8253
|
-
if (pendingOneTapSetupAction && !
|
|
8523
|
+
if (pendingOneTapSetupAction && !oneTapLimitSavedDuringSetup) {
|
|
8254
8524
|
reloadAccounts();
|
|
8255
8525
|
}
|
|
8256
|
-
}, [pendingOneTapSetupAction, reloadAccounts,
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
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
|
|
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,
|
|
8477
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
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.
|
|
9033
|
+
exports.screenForPhase = screenForPhase;
|
|
8685
9034
|
exports.useAuthorizationExecutor = useAuthorizationExecutor;
|
|
8686
9035
|
exports.useBlinkConfig = useBlinkConfig;
|
|
8687
9036
|
exports.useBlinkDepositAmount = useBlinkDepositAmount;
|