@rhinestone/1auth 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  buildTransactionReview,
3
+ createOneAuthProvider,
3
4
  createPasskeyProvider,
4
5
  encodeWebAuthnSignature,
5
6
  getAllSupportedChainsAndTokens,
@@ -18,7 +19,7 @@ import {
18
19
  isTestnet,
19
20
  isTokenAddressSupported,
20
21
  resolveTokenAddress
21
- } from "./chunk-U7KZ4XMQ.mjs";
22
+ } from "./chunk-X73ALCGW.mjs";
22
23
 
23
24
  // src/client.ts
24
25
  import { parseUnits, hashTypedData } from "viem";
@@ -26,7 +27,7 @@ var POPUP_WIDTH = 450;
26
27
  var POPUP_HEIGHT = 600;
27
28
  var DEFAULT_EMBED_WIDTH = "400px";
28
29
  var DEFAULT_EMBED_HEIGHT = "500px";
29
- var MODAL_WIDTH = 360;
30
+ var MODAL_WIDTH = 340;
30
31
  var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
31
32
  var OneAuthClient = class {
32
33
  constructor(config) {
@@ -34,6 +35,12 @@ var OneAuthClient = class {
34
35
  const dialogUrl = config.dialogUrl || providerUrl;
35
36
  this.config = { ...config, providerUrl, dialogUrl };
36
37
  this.theme = this.config.theme || {};
38
+ if (typeof document !== "undefined") {
39
+ this.injectPreconnect(providerUrl);
40
+ if (dialogUrl !== providerUrl) {
41
+ this.injectPreconnect(dialogUrl);
42
+ }
43
+ }
37
44
  }
38
45
  /**
39
46
  * Update the theme configuration at runtime
@@ -95,9 +102,7 @@ var OneAuthClient = class {
95
102
  const response = await fetch(
96
103
  `${this.config.providerUrl}/api/intent/status/${intentId}`,
97
104
  {
98
- headers: {
99
- "x-client-id": this.config.clientId
100
- }
105
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
101
106
  }
102
107
  );
103
108
  if (response.ok) {
@@ -121,9 +126,11 @@ var OneAuthClient = class {
121
126
  async authWithModal(options) {
122
127
  const dialogUrl = this.getDialogUrl();
123
128
  const params = new URLSearchParams({
124
- clientId: this.config.clientId,
125
129
  mode: "iframe"
126
130
  });
131
+ if (this.config.clientId) {
132
+ params.set("clientId", this.config.clientId);
133
+ }
127
134
  if (options?.username) {
128
135
  params.set("username", options.username);
129
136
  }
@@ -139,6 +146,182 @@ var OneAuthClient = class {
139
146
  const { dialog, iframe, cleanup } = this.createModalDialog(url);
140
147
  return this.waitForModalAuthResponse(dialog, iframe, cleanup);
141
148
  }
149
+ /**
150
+ * Open the connect dialog (lightweight connection without passkey auth).
151
+ *
152
+ * This method shows a simple connection confirmation dialog that doesn't
153
+ * require a passkey signature. Users can optionally enable "auto-connect"
154
+ * to skip this dialog in the future.
155
+ *
156
+ * If the user has never connected before, this will return action: "switch"
157
+ * to indicate that the full auth modal should be opened instead.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const result = await client.connectWithModal();
162
+ *
163
+ * if (result.success) {
164
+ * console.log('Connected as:', result.username);
165
+ * } else if (result.action === 'switch') {
166
+ * // User needs to sign in first
167
+ * const authResult = await client.authWithModal();
168
+ * }
169
+ * ```
170
+ */
171
+ async connectWithModal(options) {
172
+ const dialogUrl = this.getDialogUrl();
173
+ const params = new URLSearchParams({
174
+ mode: "iframe"
175
+ });
176
+ if (this.config.clientId) {
177
+ params.set("clientId", this.config.clientId);
178
+ }
179
+ const themeParams = this.getThemeParams(options?.theme);
180
+ if (themeParams) {
181
+ const themeParsed = new URLSearchParams(themeParams);
182
+ themeParsed.forEach((value, key) => params.set(key, value));
183
+ }
184
+ const url = `${dialogUrl}/dialog/connect?${params.toString()}`;
185
+ const { dialog, iframe, cleanup } = this.createModalDialog(url);
186
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
187
+ mode: "iframe"
188
+ });
189
+ if (!ready) {
190
+ return {
191
+ success: false,
192
+ action: "cancel",
193
+ error: { code: "USER_CANCELLED", message: "Connection was cancelled" }
194
+ };
195
+ }
196
+ return this.waitForConnectResponse(dialog, iframe, cleanup);
197
+ }
198
+ /**
199
+ * Check if a user has already granted consent for the requested fields.
200
+ * This is a read-only check — no dialog is shown.
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const result = await client.checkConsent({
205
+ * username: "alice",
206
+ * fields: ["email"],
207
+ * });
208
+ * if (result.hasConsent) {
209
+ * console.log(result.data?.email);
210
+ * }
211
+ * ```
212
+ */
213
+ async checkConsent(options) {
214
+ const clientId = options.clientId ?? this.config.clientId;
215
+ if (!clientId) {
216
+ return {
217
+ hasConsent: false
218
+ };
219
+ }
220
+ const username = options.username ?? options.accountAddress;
221
+ if (!username) {
222
+ return {
223
+ hasConsent: false
224
+ };
225
+ }
226
+ try {
227
+ const res = await fetch(`${this.config.providerUrl || "https://passkey.1auth.box"}/api/consent`, {
228
+ method: "POST",
229
+ headers: {
230
+ "Content-Type": "application/json",
231
+ ...clientId ? { "x-client-id": clientId } : {}
232
+ },
233
+ body: JSON.stringify({
234
+ username,
235
+ requestedFields: options.fields,
236
+ clientId
237
+ })
238
+ });
239
+ if (!res.ok) {
240
+ return { hasConsent: false };
241
+ }
242
+ const data = await res.json();
243
+ return {
244
+ hasConsent: data.hasConsent ?? false,
245
+ data: data.data,
246
+ grantedAt: data.grantedAt
247
+ };
248
+ } catch {
249
+ return { hasConsent: false };
250
+ }
251
+ }
252
+ /**
253
+ * Request consent from the user to share their data.
254
+ *
255
+ * First checks if consent was already granted (returns cached data immediately).
256
+ * If not, opens the consent dialog where the user can review and approve sharing.
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const result = await client.requestConsent({
261
+ * username: "alice",
262
+ * fields: ["email", "deviceNames"],
263
+ * });
264
+ * if (result.success) {
265
+ * console.log(result.data?.email);
266
+ * console.log(result.cached); // true if no dialog was shown
267
+ * }
268
+ * ```
269
+ */
270
+ async requestConsent(options) {
271
+ const clientId = options.clientId ?? this.config.clientId;
272
+ if (!clientId) {
273
+ return {
274
+ success: false,
275
+ error: { code: "INVALID_REQUEST", message: "clientId is required (set in config or options)" }
276
+ };
277
+ }
278
+ const username = options.username ?? options.accountAddress;
279
+ if (!username) {
280
+ return {
281
+ success: false,
282
+ error: { code: "INVALID_REQUEST", message: "username or accountAddress is required" }
283
+ };
284
+ }
285
+ if (!options.fields || options.fields.length === 0) {
286
+ return {
287
+ success: false,
288
+ error: { code: "INVALID_REQUEST", message: "At least one field is required" }
289
+ };
290
+ }
291
+ const existing = await this.checkConsent({ ...options, clientId });
292
+ if (existing.hasConsent && existing.data) {
293
+ return {
294
+ success: true,
295
+ data: existing.data,
296
+ grantedAt: existing.grantedAt,
297
+ cached: true
298
+ };
299
+ }
300
+ const dialogUrl = this.getDialogUrl();
301
+ const params = new URLSearchParams({
302
+ mode: "iframe",
303
+ username,
304
+ clientId,
305
+ fields: options.fields.join(",")
306
+ });
307
+ const themeParams = this.getThemeParams(options.theme);
308
+ if (themeParams) {
309
+ const themeParsed = new URLSearchParams(themeParams);
310
+ themeParsed.forEach((value, key) => params.set(key, value));
311
+ }
312
+ const url = `${dialogUrl}/dialog/consent?${params.toString()}`;
313
+ const { dialog, iframe, cleanup } = this.createModalDialog(url);
314
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
315
+ mode: "iframe"
316
+ });
317
+ if (!ready) {
318
+ return {
319
+ success: false,
320
+ error: { code: "USER_CANCELLED", message: "User closed the dialog" }
321
+ };
322
+ }
323
+ return this.waitForConsentResponse(dialog, iframe, cleanup);
324
+ }
142
325
  /**
143
326
  * Authenticate a user with an optional challenge to sign.
144
327
  *
@@ -174,9 +357,11 @@ var OneAuthClient = class {
174
357
  async authenticate(options) {
175
358
  const dialogUrl = this.getDialogUrl();
176
359
  const params = new URLSearchParams({
177
- clientId: this.config.clientId,
178
360
  mode: "iframe"
179
361
  });
362
+ if (this.config.clientId) {
363
+ params.set("clientId", this.config.clientId);
364
+ }
180
365
  if (options?.challenge) {
181
366
  params.set("challenge", options.challenge);
182
367
  }
@@ -190,28 +375,30 @@ var OneAuthClient = class {
190
375
  return this.waitForAuthenticateResponse(dialog, iframe, cleanup);
191
376
  }
192
377
  /**
193
- * Show signing in a modal overlay (Porto-style iframe dialog)
378
+ * Show signing in a modal overlay (iframe dialog)
194
379
  */
195
380
  async signWithModal(options) {
196
381
  const dialogUrl = this.getDialogUrl();
197
382
  const themeParams = this.getThemeParams(options?.theme);
198
383
  const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
199
384
  const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
200
- const dialogOrigin = this.getDialogOrigin();
201
- await new Promise((resolve) => {
202
- iframe.onload = () => {
203
- iframe.contentWindow?.postMessage({
204
- type: "PASSKEY_INIT",
205
- mode: "iframe",
206
- challenge: options.challenge,
207
- username: options.username,
208
- description: options.description,
209
- transaction: options.transaction,
210
- metadata: options.metadata
211
- }, dialogOrigin);
212
- resolve();
213
- };
385
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
386
+ mode: "iframe",
387
+ challenge: options.challenge,
388
+ username: options.username,
389
+ description: options.description,
390
+ transaction: options.transaction,
391
+ metadata: options.metadata
214
392
  });
393
+ if (!ready) {
394
+ return {
395
+ success: false,
396
+ error: {
397
+ code: "USER_REJECTED",
398
+ message: "User closed the dialog"
399
+ }
400
+ };
401
+ }
215
402
  return this.waitForSigningResponse(dialog, iframe, cleanup);
216
403
  }
217
404
  /**
@@ -262,7 +449,8 @@ var OneAuthClient = class {
262
449
  }
263
450
  };
264
451
  }
265
- if (!username && !signedIntent?.accountAddress) {
452
+ const accountAddress = signedIntent?.accountAddress || options.accountAddress;
453
+ if (!username && !accountAddress) {
266
454
  return {
267
455
  success: false,
268
456
  intentId: "",
@@ -284,158 +472,264 @@ var OneAuthClient = class {
284
472
  }
285
473
  };
286
474
  }
475
+ const serializedTokenRequests = options.tokenRequests?.map((r) => ({
476
+ token: r.token,
477
+ amount: r.amount.toString()
478
+ }));
287
479
  let prepareResponse;
288
- try {
289
- const requestBody = signedIntent || {
290
- username: options.username,
291
- targetChain: options.targetChain,
292
- calls: options.calls,
293
- tokenRequests: options.tokenRequests,
294
- sourceAssets: options.sourceAssets,
295
- clientId: this.config.clientId
480
+ const requestBody = signedIntent || {
481
+ username: options.username,
482
+ accountAddress: options.accountAddress,
483
+ targetChain: options.targetChain,
484
+ calls: options.calls,
485
+ tokenRequests: serializedTokenRequests,
486
+ sourceAssets: options.sourceAssets,
487
+ sourceChainId: options.sourceChainId,
488
+ ...this.config.clientId && { clientId: this.config.clientId }
489
+ };
490
+ const dialogUrl = this.getDialogUrl();
491
+ const themeParams = this.getThemeParams();
492
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
493
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
494
+ const [prepareResult, dialogResult] = await Promise.all([
495
+ this.prepareIntent(requestBody),
496
+ this.waitForDialogReadyDeferred(dialog, iframe, cleanup)
497
+ ]);
498
+ if (!dialogResult.ready) {
499
+ return {
500
+ success: false,
501
+ intentId: "",
502
+ status: "failed",
503
+ error: { code: "USER_CANCELLED", message: "User closed the dialog" }
296
504
  };
297
- const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
298
- method: "POST",
299
- headers: {
300
- "Content-Type": "application/json"
301
- },
302
- body: JSON.stringify(requestBody)
303
- });
304
- if (!response.ok) {
305
- const errorData = await response.json().catch(() => ({}));
306
- const errorMessage = errorData.error || "Failed to prepare intent";
307
- if (errorMessage.includes("User not found")) {
308
- localStorage.removeItem("1auth-user");
309
- }
310
- return {
311
- success: false,
312
- intentId: "",
313
- status: "failed",
314
- error: {
315
- code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
316
- message: errorMessage
317
- }
318
- };
319
- }
320
- prepareResponse = await response.json();
321
- } catch (error) {
505
+ }
506
+ if (!prepareResult.success) {
507
+ this.sendPrepareError(iframe, prepareResult.error.message);
508
+ await this.waitForDialogClose(dialog, cleanup);
322
509
  return {
323
510
  success: false,
324
511
  intentId: "",
325
512
  status: "failed",
326
- error: {
327
- code: "NETWORK_ERROR",
328
- message: error instanceof Error ? error.message : "Network error"
329
- }
513
+ error: prepareResult.error
330
514
  };
331
515
  }
332
- const dialogUrl = this.getDialogUrl();
333
- const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe`;
334
- const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
516
+ prepareResponse = prepareResult.data;
335
517
  const dialogOrigin = this.getDialogOrigin();
336
- await new Promise((resolve) => {
337
- const handleReady = (event) => {
338
- if (event.origin !== dialogOrigin) return;
339
- if (event.data?.type === "PASSKEY_READY") {
340
- window.removeEventListener("message", handleReady);
341
- iframe.contentWindow?.postMessage({
342
- type: "PASSKEY_INIT",
343
- mode: "iframe",
344
- calls,
345
- chainId: targetChain,
346
- transaction: prepareResponse.transaction,
347
- challenge: prepareResponse.challenge,
348
- username,
349
- accountAddress: prepareResponse.accountAddress,
350
- intentId: prepareResponse.intentId
351
- }, dialogOrigin);
352
- resolve();
518
+ const initPayload = {
519
+ mode: "iframe",
520
+ calls,
521
+ chainId: targetChain,
522
+ transaction: prepareResponse.transaction,
523
+ challenge: prepareResponse.challenge,
524
+ username,
525
+ accountAddress: prepareResponse.accountAddress,
526
+ originMessages: prepareResponse.originMessages,
527
+ tokenRequests: serializedTokenRequests,
528
+ expiresAt: prepareResponse.expiresAt,
529
+ userId: prepareResponse.userId,
530
+ intentOp: prepareResponse.intentOp,
531
+ digestResult: prepareResponse.digestResult,
532
+ tier: prepareResult.tier
533
+ };
534
+ dialogResult.sendInit(initPayload);
535
+ const handleReReady = (event) => {
536
+ if (event.origin !== dialogOrigin) return;
537
+ if (event.data?.type === "PASSKEY_READY") {
538
+ iframe.contentWindow?.postMessage(
539
+ { type: "PASSKEY_INIT", ...initPayload },
540
+ dialogOrigin
541
+ );
542
+ }
543
+ };
544
+ window.addEventListener("message", handleReReady);
545
+ const signingResult = await this.waitForSigningWithRefresh(
546
+ dialog,
547
+ iframe,
548
+ cleanup,
549
+ dialogOrigin,
550
+ // Refresh callback - called when dialog requests a quote refresh
551
+ async () => {
552
+ console.log("[SDK] Dialog requested quote refresh, re-preparing intent");
553
+ try {
554
+ const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
555
+ method: "POST",
556
+ headers: { "Content-Type": "application/json" },
557
+ body: JSON.stringify(requestBody),
558
+ credentials: "include"
559
+ });
560
+ if (!refreshResponse.ok) {
561
+ console.error("[SDK] Quote refresh failed:", await refreshResponse.text());
562
+ return null;
563
+ }
564
+ const refreshedData = await refreshResponse.json();
565
+ prepareResponse = refreshedData;
566
+ return {
567
+ intentOp: refreshedData.intentOp,
568
+ expiresAt: refreshedData.expiresAt,
569
+ challenge: refreshedData.challenge,
570
+ originMessages: refreshedData.originMessages,
571
+ transaction: refreshedData.transaction,
572
+ digestResult: refreshedData.digestResult
573
+ };
574
+ } catch (error) {
575
+ console.error("[SDK] Quote refresh error:", error);
576
+ return null;
353
577
  }
354
- };
355
- window.addEventListener("message", handleReady);
356
- });
357
- const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
578
+ }
579
+ );
580
+ window.removeEventListener("message", handleReReady);
358
581
  if (!signingResult.success) {
359
582
  return {
360
583
  success: false,
361
- intentId: prepareResponse.intentId,
584
+ intentId: "",
585
+ // No intentId yet - signing was cancelled before execute
362
586
  status: "failed",
363
587
  error: signingResult.error
364
588
  };
365
589
  }
590
+ const dialogExecutedIntent = "intentId" in signingResult && signingResult.intentId;
366
591
  let executeResponse;
367
- try {
368
- const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
369
- method: "POST",
370
- headers: {
371
- "Content-Type": "application/json"
372
- },
373
- body: JSON.stringify({
374
- intentId: prepareResponse.intentId,
375
- signature: signingResult.signature,
376
- passkey: signingResult.passkey
377
- // Include passkey info for signature encoding
378
- })
379
- });
380
- if (!response.ok) {
381
- const errorData = await response.json().catch(() => ({}));
592
+ if (dialogExecutedIntent) {
593
+ executeResponse = {
594
+ success: true,
595
+ intentId: signingResult.intentId,
596
+ status: "pending"
597
+ };
598
+ } else {
599
+ try {
600
+ const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
601
+ method: "POST",
602
+ headers: {
603
+ "Content-Type": "application/json"
604
+ },
605
+ body: JSON.stringify({
606
+ // Data from prepare response (no intentId yet - created on execute)
607
+ intentOp: prepareResponse.intentOp,
608
+ userId: prepareResponse.userId,
609
+ targetChain: prepareResponse.targetChain,
610
+ calls: prepareResponse.calls,
611
+ expiresAt: prepareResponse.expiresAt,
612
+ digestResult: prepareResponse.digestResult,
613
+ // Signature from dialog
614
+ signature: signingResult.signature,
615
+ passkey: signingResult.passkey
616
+ // Include passkey info for signature encoding
617
+ })
618
+ });
619
+ if (!response.ok) {
620
+ const errorData = await response.json().catch(() => ({}));
621
+ this.sendTransactionStatus(iframe, "failed");
622
+ await this.waitForDialogClose(dialog, cleanup);
623
+ return {
624
+ success: false,
625
+ intentId: "",
626
+ // No intentId - execute failed before creation
627
+ status: "failed",
628
+ error: {
629
+ code: "EXECUTE_FAILED",
630
+ message: errorData.error || "Failed to execute intent"
631
+ }
632
+ };
633
+ }
634
+ executeResponse = await response.json();
635
+ } catch (error) {
382
636
  this.sendTransactionStatus(iframe, "failed");
383
637
  await this.waitForDialogClose(dialog, cleanup);
384
638
  return {
385
639
  success: false,
386
- intentId: prepareResponse.intentId,
640
+ intentId: "",
641
+ // No intentId - network error before creation
387
642
  status: "failed",
388
643
  error: {
389
- code: "EXECUTE_FAILED",
390
- message: errorData.error || "Failed to execute intent"
644
+ code: "NETWORK_ERROR",
645
+ message: error instanceof Error ? error.message : "Network error"
391
646
  }
392
647
  };
393
648
  }
394
- executeResponse = await response.json();
395
- } catch (error) {
396
- this.sendTransactionStatus(iframe, "failed");
397
- await this.waitForDialogClose(dialog, cleanup);
398
- return {
399
- success: false,
400
- intentId: prepareResponse.intentId,
401
- status: "failed",
402
- error: {
403
- code: "NETWORK_ERROR",
404
- message: error instanceof Error ? error.message : "Network error"
405
- }
406
- };
407
649
  }
408
- const closeOn = options.closeOn || "preconfirmed";
409
- const acceptPreconfirmations = closeOn !== "completed";
410
650
  let finalStatus = executeResponse.status;
411
651
  let finalTxHash = executeResponse.transactionHash;
412
652
  if (finalStatus === "pending") {
413
- this.sendTransactionStatus(iframe, "processing");
414
- try {
415
- const waitResponse = await fetch(
416
- `${this.config.providerUrl}/api/intent/wait/${prepareResponse.intentId}?preconfirm=${acceptPreconfirmations}`,
417
- {
418
- headers: {
419
- "x-client-id": this.config.clientId
653
+ this.sendTransactionStatus(iframe, "pending");
654
+ let userClosedEarly = false;
655
+ const dialogOrigin2 = this.getDialogOrigin();
656
+ const earlyCloseHandler = (event) => {
657
+ if (event.origin !== dialogOrigin2) return;
658
+ if (event.data?.type === "PASSKEY_CLOSE") {
659
+ userClosedEarly = true;
660
+ cleanup();
661
+ }
662
+ };
663
+ window.addEventListener("message", earlyCloseHandler);
664
+ const maxAttempts = 120;
665
+ const pollIntervalMs = 1500;
666
+ let lastStatus = "pending";
667
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
668
+ if (userClosedEarly) break;
669
+ try {
670
+ const statusResponse = await fetch(
671
+ `${this.config.providerUrl}/api/intent/status/${executeResponse.intentId}`,
672
+ {
673
+ method: "GET",
674
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
675
+ }
676
+ );
677
+ if (statusResponse.ok) {
678
+ const statusResult = await statusResponse.json();
679
+ finalStatus = statusResult.status;
680
+ finalTxHash = statusResult.transactionHash;
681
+ if (finalStatus !== lastStatus) {
682
+ this.sendTransactionStatus(iframe, finalStatus, finalTxHash);
683
+ lastStatus = finalStatus;
684
+ }
685
+ const closeOn2 = options.closeOn || "preconfirmed";
686
+ const successStatuses2 = {
687
+ claimed: ["claimed", "preconfirmed", "filled", "completed"],
688
+ preconfirmed: ["preconfirmed", "filled", "completed"],
689
+ filled: ["filled", "completed"],
690
+ completed: ["completed"]
691
+ };
692
+ const isTerminal = finalStatus === "failed" || finalStatus === "expired";
693
+ const isSuccess = successStatuses2[closeOn2]?.includes(finalStatus) ?? false;
694
+ if (isTerminal || isSuccess) {
695
+ break;
420
696
  }
421
697
  }
422
- );
423
- if (waitResponse.ok) {
424
- const waitResult = await waitResponse.json();
425
- finalStatus = waitResult.status === "preconfirmed" || waitResult.status === "completed" ? "completed" : waitResult.status;
426
- finalTxHash = waitResult.transactionHash;
427
- } else {
428
- console.error("Wait endpoint failed:", await waitResponse.text());
698
+ } catch (pollError) {
699
+ console.error("Failed to poll intent status:", pollError);
429
700
  }
430
- } catch (waitError) {
431
- console.error("Failed to wait for intent:", waitError);
701
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
702
+ }
703
+ window.removeEventListener("message", earlyCloseHandler);
704
+ if (userClosedEarly) {
705
+ cleanup();
706
+ return {
707
+ success: false,
708
+ intentId: executeResponse.intentId,
709
+ status: finalStatus,
710
+ transactionHash: finalTxHash,
711
+ operationId: executeResponse.operationId,
712
+ error: {
713
+ code: "USER_CANCELLED",
714
+ message: "User closed the dialog"
715
+ }
716
+ };
432
717
  }
433
718
  }
434
- const displayStatus = finalStatus === "completed" ? "confirmed" : finalStatus;
719
+ const closeOn = options.closeOn || "preconfirmed";
720
+ const successStatuses = {
721
+ claimed: ["claimed", "preconfirmed", "filled", "completed"],
722
+ preconfirmed: ["preconfirmed", "filled", "completed"],
723
+ filled: ["filled", "completed"],
724
+ completed: ["completed"]
725
+ };
726
+ const isSuccessStatus = successStatuses[closeOn]?.includes(finalStatus) ?? false;
727
+ const displayStatus = isSuccessStatus ? "confirmed" : finalStatus;
728
+ const closePromise = this.waitForDialogClose(dialog, cleanup);
435
729
  this.sendTransactionStatus(iframe, displayStatus, finalTxHash);
436
- await this.waitForDialogClose(dialog, cleanup);
730
+ await closePromise;
437
731
  if (options.waitForHash && !finalTxHash) {
438
- const hash = await this.waitForTransactionHash(prepareResponse.intentId, {
732
+ const hash = await this.waitForTransactionHash(executeResponse.intentId, {
439
733
  timeoutMs: options.hashTimeoutMs,
440
734
  intervalMs: options.hashIntervalMs
441
735
  });
@@ -446,7 +740,7 @@ var OneAuthClient = class {
446
740
  finalStatus = "failed";
447
741
  return {
448
742
  success: false,
449
- intentId: prepareResponse.intentId,
743
+ intentId: executeResponse.intentId,
450
744
  status: finalStatus,
451
745
  transactionHash: finalTxHash,
452
746
  operationId: executeResponse.operationId,
@@ -458,14 +752,223 @@ var OneAuthClient = class {
458
752
  }
459
753
  }
460
754
  return {
461
- success: finalStatus === "completed",
462
- intentId: prepareResponse.intentId,
755
+ success: isSuccessStatus,
756
+ intentId: executeResponse.intentId,
463
757
  status: finalStatus,
464
758
  transactionHash: finalTxHash,
465
759
  operationId: executeResponse.operationId,
466
760
  error: executeResponse.error
467
761
  };
468
762
  }
763
+ /**
764
+ * Send a batch of intents for multi-chain execution with a single passkey tap.
765
+ *
766
+ * This method prepares multiple intents, shows a paginated review,
767
+ * and signs all intents with a single passkey tap via a shared merkle tree.
768
+ *
769
+ * @example
770
+ * ```typescript
771
+ * const result = await client.sendBatchIntent({
772
+ * username: 'alice',
773
+ * intents: [
774
+ * {
775
+ * targetChain: 8453, // Base
776
+ * calls: [{ to: '0x...', data: '0x...', label: 'Swap on Base' }],
777
+ * },
778
+ * {
779
+ * targetChain: 42161, // Arbitrum
780
+ * calls: [{ to: '0x...', data: '0x...', label: 'Mint on Arbitrum' }],
781
+ * },
782
+ * ],
783
+ * });
784
+ *
785
+ * if (result.success) {
786
+ * console.log(`${result.successCount} intents submitted`);
787
+ * }
788
+ * ```
789
+ */
790
+ async sendBatchIntent(options) {
791
+ if (!options.username && !options.accountAddress) {
792
+ return {
793
+ success: false,
794
+ results: [],
795
+ successCount: 0,
796
+ failureCount: 0
797
+ };
798
+ }
799
+ if (!options.intents?.length) {
800
+ return {
801
+ success: false,
802
+ results: [],
803
+ successCount: 0,
804
+ failureCount: 0
805
+ };
806
+ }
807
+ const serializedIntents = options.intents.map((intent) => ({
808
+ targetChain: intent.targetChain,
809
+ calls: intent.calls,
810
+ tokenRequests: intent.tokenRequests?.map((r) => ({
811
+ token: r.token,
812
+ amount: r.amount.toString()
813
+ })),
814
+ sourceAssets: intent.sourceAssets,
815
+ sourceChainId: intent.sourceChainId,
816
+ moduleInstall: intent.moduleInstall
817
+ }));
818
+ const requestBody = {
819
+ ...options.username && { username: options.username },
820
+ ...options.accountAddress && { accountAddress: options.accountAddress },
821
+ intents: serializedIntents,
822
+ ...this.config.clientId && { clientId: this.config.clientId }
823
+ };
824
+ const dialogUrl = this.getDialogUrl();
825
+ const themeParams = this.getThemeParams();
826
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
827
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
828
+ const [prepareResult, dialogResult] = await Promise.all([
829
+ this.prepareBatchIntent(requestBody),
830
+ this.waitForDialogReadyDeferred(dialog, iframe, cleanup)
831
+ ]);
832
+ if (!dialogResult.ready) {
833
+ return {
834
+ success: false,
835
+ results: [],
836
+ successCount: 0,
837
+ failureCount: 0
838
+ };
839
+ }
840
+ if (!prepareResult.success) {
841
+ const failedIntents = prepareResult.failedIntents;
842
+ const failureResults = failedIntents?.map((f) => ({
843
+ index: f.index,
844
+ success: false,
845
+ intentId: "",
846
+ status: "failed",
847
+ error: { message: f.error, code: "PREPARE_FAILED" }
848
+ })) ?? [];
849
+ this.sendPrepareError(iframe, prepareResult.error);
850
+ await this.waitForDialogClose(dialog, cleanup);
851
+ return {
852
+ success: false,
853
+ results: failureResults,
854
+ successCount: 0,
855
+ failureCount: failureResults.length,
856
+ error: prepareResult.error
857
+ };
858
+ }
859
+ let prepareResponse = prepareResult.data;
860
+ const dialogOrigin = this.getDialogOrigin();
861
+ const batchInitPayload = {
862
+ mode: "iframe",
863
+ batchMode: true,
864
+ batchIntents: prepareResponse.intents,
865
+ batchFailedIntents: prepareResponse.failedIntents,
866
+ challenge: prepareResponse.challenge,
867
+ username: options.username,
868
+ accountAddress: prepareResponse.accountAddress,
869
+ userId: prepareResponse.userId,
870
+ expiresAt: prepareResponse.expiresAt,
871
+ tier: prepareResult.tier
872
+ };
873
+ dialogResult.sendInit(batchInitPayload);
874
+ const handleBatchReReady = (event) => {
875
+ if (event.origin !== dialogOrigin) return;
876
+ if (event.data?.type === "PASSKEY_READY") {
877
+ iframe.contentWindow?.postMessage(
878
+ { type: "PASSKEY_INIT", ...batchInitPayload },
879
+ dialogOrigin
880
+ );
881
+ }
882
+ };
883
+ window.addEventListener("message", handleBatchReReady);
884
+ const batchResult = await new Promise((resolve) => {
885
+ const handleMessage = async (event) => {
886
+ if (event.origin !== dialogOrigin) return;
887
+ const message = event.data;
888
+ if (message?.type === "PASSKEY_REFRESH_QUOTE") {
889
+ console.log("[SDK] Batch dialog requested quote refresh, re-preparing all intents");
890
+ try {
891
+ const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
892
+ method: "POST",
893
+ headers: { "Content-Type": "application/json" },
894
+ body: JSON.stringify(requestBody)
895
+ });
896
+ if (refreshResponse.ok) {
897
+ const refreshed = await refreshResponse.json();
898
+ prepareResponse = refreshed;
899
+ iframe.contentWindow?.postMessage({
900
+ type: "PASSKEY_REFRESH_COMPLETE",
901
+ batchIntents: refreshed.intents,
902
+ challenge: refreshed.challenge,
903
+ expiresAt: refreshed.expiresAt
904
+ }, dialogOrigin);
905
+ } else {
906
+ iframe.contentWindow?.postMessage({
907
+ type: "PASSKEY_REFRESH_ERROR",
908
+ error: "Failed to refresh batch quotes"
909
+ }, dialogOrigin);
910
+ }
911
+ } catch {
912
+ iframe.contentWindow?.postMessage({
913
+ type: "PASSKEY_REFRESH_ERROR",
914
+ error: "Failed to refresh batch quotes"
915
+ }, dialogOrigin);
916
+ }
917
+ return;
918
+ }
919
+ if (message?.type === "PASSKEY_SIGNING_RESULT") {
920
+ window.removeEventListener("message", handleMessage);
921
+ if (message.success && message.data?.batchResults) {
922
+ const rawResults = message.data.batchResults;
923
+ const results = rawResults.map((r) => ({
924
+ index: r.index,
925
+ success: r.success ?? r.status !== "FAILED",
926
+ intentId: r.intentId || r.operationId || "",
927
+ status: r.status === "FAILED" ? "failed" : "pending",
928
+ error: r.error ? { code: "EXECUTE_FAILED", message: r.error } : void 0
929
+ }));
930
+ const prepareFailures = (prepareResponse.failedIntents ?? []).map((f) => ({
931
+ index: f.index,
932
+ success: false,
933
+ intentId: "",
934
+ status: "failed",
935
+ error: { code: "PREPARE_FAILED", message: f.error }
936
+ }));
937
+ const allResults = [...results, ...prepareFailures].sort((a, b) => a.index - b.index);
938
+ const successCount = allResults.filter((r) => r.success).length;
939
+ await this.waitForDialogClose(dialog, cleanup);
940
+ resolve({
941
+ success: successCount === allResults.length,
942
+ results: allResults,
943
+ successCount,
944
+ failureCount: allResults.length - successCount
945
+ });
946
+ } else {
947
+ cleanup();
948
+ resolve({
949
+ success: false,
950
+ results: [],
951
+ successCount: 0,
952
+ failureCount: 0
953
+ });
954
+ }
955
+ }
956
+ if (message?.type === "PASSKEY_CLOSE") {
957
+ window.removeEventListener("message", handleMessage);
958
+ cleanup();
959
+ resolve({
960
+ success: false,
961
+ results: [],
962
+ successCount: 0,
963
+ failureCount: 0
964
+ });
965
+ }
966
+ };
967
+ window.addEventListener("message", handleMessage);
968
+ });
969
+ window.removeEventListener("message", handleBatchReReady);
970
+ return batchResult;
971
+ }
469
972
  /**
470
973
  * Send transaction status to the dialog iframe
471
974
  */
@@ -542,7 +1045,77 @@ var OneAuthClient = class {
542
1045
  const payload = message?.data;
543
1046
  if (message?.type === "PASSKEY_SIGNING_RESULT") {
544
1047
  window.removeEventListener("message", handleMessage);
545
- if (message.success && payload?.signature) {
1048
+ if (message.success && payload?.intentId) {
1049
+ resolve({
1050
+ success: true,
1051
+ intentId: payload.intentId
1052
+ });
1053
+ } else if (message.success && payload?.signature) {
1054
+ resolve({
1055
+ success: true,
1056
+ signature: payload.signature,
1057
+ passkey: payload.passkey,
1058
+ signedHash: payload.signedHash
1059
+ });
1060
+ } else {
1061
+ resolve({
1062
+ success: false,
1063
+ error: message.error || {
1064
+ code: "SIGNING_FAILED",
1065
+ message: "Signing failed"
1066
+ }
1067
+ });
1068
+ }
1069
+ } else if (message?.type === "PASSKEY_CLOSE") {
1070
+ window.removeEventListener("message", handleMessage);
1071
+ cleanup();
1072
+ resolve({
1073
+ success: false,
1074
+ error: {
1075
+ code: "USER_REJECTED",
1076
+ message: "User closed the dialog"
1077
+ }
1078
+ });
1079
+ }
1080
+ };
1081
+ window.addEventListener("message", handleMessage);
1082
+ });
1083
+ }
1084
+ /**
1085
+ * Wait for signing result with auto-refresh support
1086
+ * This method handles both signing results and quote refresh requests from the dialog
1087
+ */
1088
+ waitForSigningWithRefresh(dialog, iframe, cleanup, dialogOrigin, onRefresh) {
1089
+ console.log("[SDK] waitForSigningWithRefresh, expecting origin:", dialogOrigin);
1090
+ return new Promise((resolve) => {
1091
+ const handleMessage = async (event) => {
1092
+ if (event.origin !== dialogOrigin) return;
1093
+ const message = event.data;
1094
+ if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1095
+ console.log("[SDK] Received quote refresh request from dialog");
1096
+ const refreshedData = await onRefresh();
1097
+ if (refreshedData) {
1098
+ iframe.contentWindow?.postMessage({
1099
+ type: "PASSKEY_REFRESH_COMPLETE",
1100
+ ...refreshedData
1101
+ }, dialogOrigin);
1102
+ } else {
1103
+ iframe.contentWindow?.postMessage({
1104
+ type: "PASSKEY_REFRESH_ERROR",
1105
+ error: "Failed to refresh quote"
1106
+ }, dialogOrigin);
1107
+ }
1108
+ return;
1109
+ }
1110
+ const payload = message?.data;
1111
+ if (message?.type === "PASSKEY_SIGNING_RESULT") {
1112
+ window.removeEventListener("message", handleMessage);
1113
+ if (message.success && payload?.intentId) {
1114
+ resolve({
1115
+ success: true,
1116
+ intentId: payload.intentId
1117
+ });
1118
+ } else if (message.success && payload?.signature) {
546
1119
  resolve({
547
1120
  success: true,
548
1121
  signature: payload.signature,
@@ -574,28 +1147,165 @@ var OneAuthClient = class {
574
1147
  });
575
1148
  }
576
1149
  /**
577
- * Wait for the dialog to be closed
1150
+ * Wait for the dialog to be closed
1151
+ */
1152
+ waitForDialogClose(dialog, cleanup) {
1153
+ const dialogOrigin = this.getDialogOrigin();
1154
+ return new Promise((resolve) => {
1155
+ const handleMessage = (event) => {
1156
+ if (event.origin !== dialogOrigin) return;
1157
+ if (event.data?.type === "PASSKEY_CLOSE") {
1158
+ window.removeEventListener("message", handleMessage);
1159
+ cleanup();
1160
+ resolve();
1161
+ }
1162
+ };
1163
+ const handleClose = () => {
1164
+ window.removeEventListener("message", handleMessage);
1165
+ dialog.removeEventListener("close", handleClose);
1166
+ cleanup();
1167
+ resolve();
1168
+ };
1169
+ window.addEventListener("message", handleMessage);
1170
+ dialog.addEventListener("close", handleClose);
1171
+ });
1172
+ }
1173
+ /**
1174
+ * Inject a preconnect link tag to pre-warm DNS + TLS for a given URL.
1175
+ */
1176
+ injectPreconnect(url) {
1177
+ try {
1178
+ const origin = new URL(url).origin;
1179
+ if (document.querySelector(`link[rel="preconnect"][href="${origin}"]`)) return;
1180
+ const link = document.createElement("link");
1181
+ link.rel = "preconnect";
1182
+ link.href = origin;
1183
+ link.crossOrigin = "anonymous";
1184
+ document.head.appendChild(link);
1185
+ } catch {
1186
+ }
1187
+ }
1188
+ /**
1189
+ * Wait for the dialog iframe to signal ready without sending init data.
1190
+ * Returns a sendInit function the caller uses once prepare data is available.
1191
+ */
1192
+ waitForDialogReadyDeferred(dialog, iframe, cleanup) {
1193
+ const dialogOrigin = this.getDialogOrigin();
1194
+ return new Promise((resolve) => {
1195
+ let settled = false;
1196
+ const teardown = () => {
1197
+ if (settled) return;
1198
+ settled = true;
1199
+ clearTimeout(readyTimeout);
1200
+ window.removeEventListener("message", handleMessage);
1201
+ dialog.removeEventListener("close", handleClose);
1202
+ };
1203
+ const handleMessage = (event) => {
1204
+ if (event.origin !== dialogOrigin) return;
1205
+ if (event.data?.type === "PASSKEY_READY") {
1206
+ teardown();
1207
+ resolve({
1208
+ ready: true,
1209
+ sendInit: (initMessage) => {
1210
+ iframe.contentWindow?.postMessage(
1211
+ { type: "PASSKEY_INIT", ...initMessage },
1212
+ dialogOrigin
1213
+ );
1214
+ }
1215
+ });
1216
+ } else if (event.data?.type === "PASSKEY_CLOSE") {
1217
+ teardown();
1218
+ cleanup();
1219
+ resolve({ ready: false });
1220
+ }
1221
+ };
1222
+ const handleClose = () => {
1223
+ teardown();
1224
+ resolve({ ready: false });
1225
+ };
1226
+ const readyTimeout = setTimeout(() => {
1227
+ teardown();
1228
+ cleanup();
1229
+ resolve({ ready: false });
1230
+ }, 1e4);
1231
+ window.addEventListener("message", handleMessage);
1232
+ dialog.addEventListener("close", handleClose);
1233
+ });
1234
+ }
1235
+ /**
1236
+ * Prepare an intent by calling the orchestrator for a quote.
1237
+ * Returns the prepare response and the origin tier from the response header.
1238
+ */
1239
+ async prepareIntent(requestBody) {
1240
+ try {
1241
+ const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
1242
+ method: "POST",
1243
+ headers: { "Content-Type": "application/json" },
1244
+ body: JSON.stringify(requestBody)
1245
+ });
1246
+ if (!response.ok) {
1247
+ const errorData = await response.json().catch(() => ({}));
1248
+ const errorMessage = errorData.error || "Failed to prepare intent";
1249
+ if (errorMessage.includes("User not found")) {
1250
+ localStorage.removeItem("1auth-user");
1251
+ }
1252
+ return {
1253
+ success: false,
1254
+ error: {
1255
+ code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
1256
+ message: errorMessage
1257
+ }
1258
+ };
1259
+ }
1260
+ const tier = response.headers.get("X-Origin-Tier");
1261
+ return { success: true, data: await response.json(), tier };
1262
+ } catch (error) {
1263
+ return {
1264
+ success: false,
1265
+ error: {
1266
+ code: "NETWORK_ERROR",
1267
+ message: error instanceof Error ? error.message : "Network error"
1268
+ }
1269
+ };
1270
+ }
1271
+ }
1272
+ /**
1273
+ * Prepare a batch intent by calling the orchestrator for quotes on all intents.
1274
+ */
1275
+ async prepareBatchIntent(requestBody) {
1276
+ try {
1277
+ const response = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
1278
+ method: "POST",
1279
+ headers: { "Content-Type": "application/json" },
1280
+ body: JSON.stringify(requestBody)
1281
+ });
1282
+ if (!response.ok) {
1283
+ const errorData = await response.json().catch(() => ({}));
1284
+ const errorMessage = errorData.error || "Failed to prepare batch intent";
1285
+ if (errorMessage.includes("User not found")) {
1286
+ localStorage.removeItem("1auth-user");
1287
+ }
1288
+ return {
1289
+ success: false,
1290
+ error: errorMessage,
1291
+ failedIntents: errorData.failedIntents
1292
+ };
1293
+ }
1294
+ const tier = response.headers.get("X-Origin-Tier");
1295
+ return { success: true, data: await response.json(), tier };
1296
+ } catch {
1297
+ return { success: false, error: "Network error" };
1298
+ }
1299
+ }
1300
+ /**
1301
+ * Send a prepare error message to the dialog iframe.
578
1302
  */
579
- waitForDialogClose(dialog, cleanup) {
1303
+ sendPrepareError(iframe, error) {
580
1304
  const dialogOrigin = this.getDialogOrigin();
581
- return new Promise((resolve) => {
582
- const handleMessage = (event) => {
583
- if (event.origin !== dialogOrigin) return;
584
- if (event.data?.type === "PASSKEY_CLOSE") {
585
- window.removeEventListener("message", handleMessage);
586
- cleanup();
587
- resolve();
588
- }
589
- };
590
- const handleClose = () => {
591
- window.removeEventListener("message", handleMessage);
592
- dialog.removeEventListener("close", handleClose);
593
- cleanup();
594
- resolve();
595
- };
596
- window.addEventListener("message", handleMessage);
597
- dialog.addEventListener("close", handleClose);
598
- });
1305
+ iframe.contentWindow?.postMessage(
1306
+ { type: "PASSKEY_PREPARE_ERROR", error },
1307
+ dialogOrigin
1308
+ );
599
1309
  }
600
1310
  /**
601
1311
  * Poll for intent status
@@ -608,9 +1318,7 @@ var OneAuthClient = class {
608
1318
  const response = await fetch(
609
1319
  `${this.config.providerUrl}/api/intent/status/${intentId}`,
610
1320
  {
611
- headers: {
612
- "x-client-id": this.config.clientId
613
- }
1321
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
614
1322
  }
615
1323
  );
616
1324
  if (!response.ok) {
@@ -645,6 +1353,43 @@ var OneAuthClient = class {
645
1353
  };
646
1354
  }
647
1355
  }
1356
+ /**
1357
+ * Get the history of intents for the authenticated user.
1358
+ *
1359
+ * Requires an active session (user must be logged in).
1360
+ *
1361
+ * @example
1362
+ * ```typescript
1363
+ * // Get recent intents
1364
+ * const history = await client.getIntentHistory({ limit: 10 });
1365
+ *
1366
+ * // Filter by status
1367
+ * const pending = await client.getIntentHistory({ status: 'pending' });
1368
+ *
1369
+ * // Filter by date range
1370
+ * const lastWeek = await client.getIntentHistory({
1371
+ * from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
1372
+ * });
1373
+ * ```
1374
+ */
1375
+ async getIntentHistory(options) {
1376
+ const queryParams = new URLSearchParams();
1377
+ if (options?.limit) queryParams.set("limit", String(options.limit));
1378
+ if (options?.offset) queryParams.set("offset", String(options.offset));
1379
+ if (options?.status) queryParams.set("status", options.status);
1380
+ if (options?.from) queryParams.set("from", options.from);
1381
+ if (options?.to) queryParams.set("to", options.to);
1382
+ const url = `${this.config.providerUrl}/api/intent/history${queryParams.toString() ? `?${queryParams}` : ""}`;
1383
+ const response = await fetch(url, {
1384
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {},
1385
+ credentials: "include"
1386
+ });
1387
+ if (!response.ok) {
1388
+ const errorData = await response.json().catch(() => ({}));
1389
+ throw new Error(errorData.error || "Failed to get intent history");
1390
+ }
1391
+ return response.json();
1392
+ }
648
1393
  /**
649
1394
  * Send a swap intent through the Rhinestone orchestrator
650
1395
  *
@@ -707,17 +1452,21 @@ var OneAuthClient = class {
707
1452
  };
708
1453
  }
709
1454
  };
710
- const fromTokenResult = resolveToken(options.fromToken, "fromToken");
711
- if (!fromTokenResult.address) {
712
- return {
713
- success: false,
714
- intentId: "",
715
- status: "failed",
716
- error: {
717
- code: "INVALID_TOKEN",
718
- message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
719
- }
720
- };
1455
+ let fromTokenAddress;
1456
+ if (options.fromToken) {
1457
+ const fromTokenResult = resolveToken(options.fromToken, "fromToken");
1458
+ if (!fromTokenResult.address) {
1459
+ return {
1460
+ success: false,
1461
+ intentId: "",
1462
+ status: "failed",
1463
+ error: {
1464
+ code: "INVALID_TOKEN",
1465
+ message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
1466
+ }
1467
+ };
1468
+ }
1469
+ fromTokenAddress = fromTokenResult.address;
721
1470
  }
722
1471
  const toTokenResult = resolveToken(options.toToken, "toToken");
723
1472
  if (!toTokenResult.address) {
@@ -731,18 +1480,17 @@ var OneAuthClient = class {
731
1480
  }
732
1481
  };
733
1482
  }
734
- const fromTokenAddress = fromTokenResult.address;
735
1483
  const toTokenAddress = toTokenResult.address;
736
1484
  console.log("[SDK sendSwap] Token resolution:", {
737
- fromToken: options.fromToken,
738
- fromTokenAddress,
1485
+ fromToken: options.fromToken ?? "Any",
1486
+ fromTokenAddress: fromTokenAddress ?? "orchestrator picks",
739
1487
  toToken: options.toToken,
740
1488
  toTokenAddress,
741
1489
  targetChain: options.targetChain
742
1490
  });
743
1491
  const formatTokenLabel = (token, fallback) => {
744
1492
  if (!token.startsWith("0x")) {
745
- return token.toUpperCase();
1493
+ return token;
746
1494
  }
747
1495
  try {
748
1496
  return getTokenSymbol(token, options.targetChain);
@@ -750,15 +1498,11 @@ var OneAuthClient = class {
750
1498
  return fallback;
751
1499
  }
752
1500
  };
753
- const fromSymbol = formatTokenLabel(
754
- options.fromToken,
755
- `${options.fromToken.slice(0, 6)}...${options.fromToken.slice(-4)}`
756
- );
757
1501
  const toSymbol = formatTokenLabel(
758
1502
  options.toToken,
759
1503
  `${options.toToken.slice(0, 6)}...${options.toToken.slice(-4)}`
760
1504
  );
761
- const isFromNativeEth = fromTokenAddress === "0x0000000000000000000000000000000000000000";
1505
+ const isFromNativeEth = fromTokenAddress ? fromTokenAddress === "0x0000000000000000000000000000000000000000" : false;
762
1506
  const isToNativeEth = toTokenAddress === "0x0000000000000000000000000000000000000000";
763
1507
  const KNOWN_DECIMALS = {
764
1508
  ETH: 18,
@@ -768,31 +1512,33 @@ var OneAuthClient = class {
768
1512
  USDT0: 6
769
1513
  };
770
1514
  const getDecimals = (symbol, chainId) => {
771
- const upperSymbol = symbol.toUpperCase();
772
1515
  try {
773
- const decimals = getTokenDecimals(upperSymbol, chainId);
774
- console.log(`[SDK] getTokenDecimals(${upperSymbol}, ${chainId}) = ${decimals}`);
1516
+ const match = getSupportedTokens(chainId).find(
1517
+ (t) => t.symbol.toUpperCase() === symbol.toUpperCase()
1518
+ );
1519
+ if (match) {
1520
+ console.log(`[SDK] getTokenDecimals(${match.symbol}, ${chainId}) = ${match.decimals}`);
1521
+ return match.decimals;
1522
+ }
1523
+ const decimals = getTokenDecimals(symbol, chainId);
1524
+ console.log(`[SDK] getTokenDecimals(${symbol}, ${chainId}) = ${decimals}`);
775
1525
  return decimals;
776
1526
  } catch (e) {
777
- console.warn(`[SDK] getTokenDecimals failed for ${upperSymbol}, using fallback`, e);
1527
+ const upperSymbol = symbol.toUpperCase();
1528
+ console.warn(`[SDK] getTokenDecimals failed for ${symbol}, using fallback`, e);
778
1529
  return KNOWN_DECIMALS[upperSymbol] ?? 18;
779
1530
  }
780
1531
  };
781
- const fromDecimals = getDecimals(options.fromToken, options.targetChain);
782
1532
  const toDecimals = getDecimals(options.toToken, options.targetChain);
783
- const isBridge = options.fromToken.toUpperCase() === options.toToken.toUpperCase();
784
- let tokenRequests;
785
- if (!isToNativeEth) {
786
- tokenRequests = [{
787
- token: toTokenAddress,
788
- amount: parseUnits(options.amount, toDecimals).toString()
789
- }];
790
- }
1533
+ const isBridge = options.fromToken ? options.fromToken.toUpperCase() === options.toToken.toUpperCase() : false;
1534
+ const tokenRequests = [{
1535
+ token: toTokenAddress,
1536
+ amount: parseUnits(options.amount, toDecimals)
1537
+ }];
791
1538
  console.log("[SDK sendSwap] Building intent:", {
792
1539
  isBridge,
793
1540
  isFromNativeEth,
794
1541
  isToNativeEth,
795
- fromDecimals,
796
1542
  toDecimals,
797
1543
  tokenRequests
798
1544
  });
@@ -803,15 +1549,20 @@ var OneAuthClient = class {
803
1549
  {
804
1550
  // Minimal call - just signals to orchestrator we want the tokenRequests delivered
805
1551
  to: toTokenAddress,
806
- value: "0"
1552
+ value: "0",
1553
+ // SDK provides labels so dialog shows "Buy ETH" not "Send ETH / To: 0x000..."
1554
+ label: `Buy ${toSymbol}`,
1555
+ sublabel: `${options.amount} ${toSymbol}`
807
1556
  }
808
1557
  ],
809
1558
  // Request specific output tokens - this is what actually matters for swaps
810
1559
  tokenRequests,
811
1560
  // Constrain orchestrator to use only the fromToken as input
812
1561
  // This ensures the swap uses the correct source token
813
- // Pass the symbol (not address) so orchestrator can resolve per-chain
814
- sourceAssets: options.sourceAssets || [options.fromToken.toUpperCase()],
1562
+ // Use canonical symbol casing from registry (e.g. "MockUSD" not "MOCKUSD")
1563
+ sourceAssets: options.sourceAssets || (options.fromToken ? [options.fromToken] : void 0),
1564
+ // Pass source chain ID so orchestrator knows which chain to look for tokens on
1565
+ sourceChainId: options.sourceChainId,
815
1566
  closeOn: options.closeOn || "preconfirmed",
816
1567
  waitForHash: options.waitForHash,
817
1568
  hashTimeoutMs: options.hashTimeoutMs,
@@ -820,7 +1571,7 @@ var OneAuthClient = class {
820
1571
  return {
821
1572
  ...result,
822
1573
  quote: result.success ? {
823
- fromToken: fromTokenAddress,
1574
+ fromToken: fromTokenAddress ?? options.fromToken,
824
1575
  toToken: toTokenAddress,
825
1576
  amountIn: options.amount,
826
1577
  amountOut: "",
@@ -862,26 +1613,24 @@ var OneAuthClient = class {
862
1613
  const themeParams = this.getThemeParams(options?.theme);
863
1614
  const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
864
1615
  const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
865
- const dialogOrigin = this.getDialogOrigin();
866
- await new Promise((resolve) => {
867
- const handleReady = (event) => {
868
- if (event.origin !== dialogOrigin) return;
869
- if (event.data?.type === "PASSKEY_READY") {
870
- window.removeEventListener("message", handleReady);
871
- iframe.contentWindow?.postMessage({
872
- type: "PASSKEY_INIT",
873
- mode: "iframe",
874
- message: options.message,
875
- challenge: options.challenge || options.message,
876
- username: options.username,
877
- description: options.description,
878
- metadata: options.metadata
879
- }, dialogOrigin);
880
- resolve();
1616
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
1617
+ mode: "iframe",
1618
+ message: options.message,
1619
+ challenge: options.challenge || options.message,
1620
+ username: options.username,
1621
+ accountAddress: options.accountAddress,
1622
+ description: options.description,
1623
+ metadata: options.metadata
1624
+ });
1625
+ if (!ready) {
1626
+ return {
1627
+ success: false,
1628
+ error: {
1629
+ code: "USER_REJECTED",
1630
+ message: "User closed the dialog"
881
1631
  }
882
1632
  };
883
- window.addEventListener("message", handleReady);
884
- });
1633
+ }
885
1634
  const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
886
1635
  cleanup();
887
1636
  if (signingResult.success) {
@@ -950,31 +1699,29 @@ var OneAuthClient = class {
950
1699
  const themeParams = this.getThemeParams(options?.theme);
951
1700
  const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
952
1701
  const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
953
- const dialogOrigin = this.getDialogOrigin();
954
- await new Promise((resolve) => {
955
- const handleReady = (event) => {
956
- if (event.origin !== dialogOrigin) return;
957
- if (event.data?.type === "PASSKEY_READY") {
958
- window.removeEventListener("message", handleReady);
959
- iframe.contentWindow?.postMessage({
960
- type: "PASSKEY_INIT",
961
- mode: "iframe",
962
- signingMode: "typedData",
963
- typedData: {
964
- domain: options.domain,
965
- types: options.types,
966
- primaryType: options.primaryType,
967
- message: options.message
968
- },
969
- challenge: signedHash,
970
- username: options.username,
971
- description: options.description
972
- }, dialogOrigin);
973
- resolve();
1702
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
1703
+ mode: "iframe",
1704
+ signingMode: "typedData",
1705
+ typedData: {
1706
+ domain: options.domain,
1707
+ types: options.types,
1708
+ primaryType: options.primaryType,
1709
+ message: options.message
1710
+ },
1711
+ challenge: signedHash,
1712
+ username: options.username,
1713
+ accountAddress: options.accountAddress,
1714
+ description: options.description
1715
+ });
1716
+ if (!ready) {
1717
+ return {
1718
+ success: false,
1719
+ error: {
1720
+ code: "USER_REJECTED",
1721
+ message: "User closed the dialog"
974
1722
  }
975
1723
  };
976
- window.addEventListener("message", handleReady);
977
- });
1724
+ }
978
1725
  const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
979
1726
  cleanup();
980
1727
  if (signingResult.success) {
@@ -1037,7 +1784,7 @@ var OneAuthClient = class {
1037
1784
  iframe.style.borderRadius = "12px";
1038
1785
  iframe.style.boxShadow = "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)";
1039
1786
  iframe.id = `passkey-embed-${requestId}`;
1040
- iframe.allow = "publickey-credentials-get *; publickey-credentials-create *";
1787
+ iframe.allow = "publickey-credentials-get *; publickey-credentials-create *; identity-credentials-get";
1041
1788
  iframe.onload = () => {
1042
1789
  options.onReady?.();
1043
1790
  };
@@ -1128,9 +1875,7 @@ var OneAuthClient = class {
1128
1875
  const response = await fetch(
1129
1876
  `${this.config.providerUrl}/api/users/${encodeURIComponent(username)}/passkeys`,
1130
1877
  {
1131
- headers: {
1132
- "x-client-id": this.config.clientId
1133
- }
1878
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
1134
1879
  }
1135
1880
  );
1136
1881
  if (!response.ok) {
@@ -1149,7 +1894,7 @@ var OneAuthClient = class {
1149
1894
  "Content-Type": "application/json"
1150
1895
  },
1151
1896
  body: JSON.stringify({
1152
- clientId: this.config.clientId,
1897
+ ...this.config.clientId && { clientId: this.config.clientId },
1153
1898
  username: options.username,
1154
1899
  challenge: options.challenge,
1155
1900
  description: options.description,
@@ -1175,6 +1920,50 @@ var OneAuthClient = class {
1175
1920
  `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
1176
1921
  );
1177
1922
  }
1923
+ /**
1924
+ * Wait for the dialog iframe to signal ready, then send init data.
1925
+ * Also handles early close (X button, escape, backdrop) during the ready phase.
1926
+ * Returns true if dialog is ready, false if it was closed before becoming ready.
1927
+ */
1928
+ waitForDialogReady(dialog, iframe, cleanup, initMessage) {
1929
+ const dialogOrigin = this.getDialogOrigin();
1930
+ return new Promise((resolve) => {
1931
+ let settled = false;
1932
+ const teardown = () => {
1933
+ if (settled) return;
1934
+ settled = true;
1935
+ clearTimeout(readyTimeout);
1936
+ window.removeEventListener("message", handleMessage);
1937
+ dialog.removeEventListener("close", handleClose);
1938
+ };
1939
+ const handleMessage = (event) => {
1940
+ if (event.origin !== dialogOrigin) return;
1941
+ if (event.data?.type === "PASSKEY_READY") {
1942
+ teardown();
1943
+ iframe.contentWindow?.postMessage({
1944
+ type: "PASSKEY_INIT",
1945
+ ...initMessage
1946
+ }, dialogOrigin);
1947
+ resolve(true);
1948
+ } else if (event.data?.type === "PASSKEY_CLOSE") {
1949
+ teardown();
1950
+ cleanup();
1951
+ resolve(false);
1952
+ }
1953
+ };
1954
+ const handleClose = () => {
1955
+ teardown();
1956
+ resolve(false);
1957
+ };
1958
+ const readyTimeout = setTimeout(() => {
1959
+ teardown();
1960
+ cleanup();
1961
+ resolve(false);
1962
+ }, 1e4);
1963
+ window.addEventListener("message", handleMessage);
1964
+ dialog.addEventListener("close", handleClose);
1965
+ });
1966
+ }
1178
1967
  /**
1179
1968
  * Create a modal dialog with an iframe inside.
1180
1969
  */
@@ -1211,7 +2000,9 @@ var OneAuthClient = class {
1211
2000
  border-radius: 14px;
1212
2001
  border: none;
1213
2002
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
1214
- transition: width 0.2s ease-out, height 0.15s ease-out;
2003
+ transition: height 0.15s ease-out;
2004
+ max-height: calc(100vh - 100px);
2005
+ max-height: calc(100dvh - 100px);
1215
2006
  }
1216
2007
 
1217
2008
  @media (min-width: 769px) {
@@ -1273,14 +2064,10 @@ var OneAuthClient = class {
1273
2064
  const iframe = document.createElement("iframe");
1274
2065
  iframe.setAttribute(
1275
2066
  "allow",
1276
- "publickey-credentials-get *; publickey-credentials-create *; clipboard-write"
2067
+ "publickey-credentials-get *; publickey-credentials-create *; clipboard-write; identity-credentials-get"
1277
2068
  );
1278
2069
  iframe.setAttribute("aria-label", "Passkey Authentication");
1279
2070
  iframe.setAttribute("tabindex", "0");
1280
- iframe.setAttribute(
1281
- "sandbox",
1282
- "allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
1283
- );
1284
2071
  iframe.setAttribute("src", url);
1285
2072
  iframe.setAttribute("title", "Passkey");
1286
2073
  iframe.style.border = "none";
@@ -1290,10 +2077,8 @@ var OneAuthClient = class {
1290
2077
  const handleMessage = (event) => {
1291
2078
  if (event.origin !== hostUrl.origin) return;
1292
2079
  if (event.data?.type === "PASSKEY_RESIZE") {
1293
- iframe.style.height = `${event.data.height}px`;
1294
- if (event.data.width) {
1295
- iframe.style.width = `${event.data.width}px`;
1296
- }
2080
+ const maxHeight = window.innerHeight - 100;
2081
+ iframe.style.height = `${Math.min(event.data.height, maxHeight)}px`;
1297
2082
  } else if (event.data?.type === "PASSKEY_DISCONNECT") {
1298
2083
  localStorage.removeItem("1auth-user");
1299
2084
  }
@@ -1311,7 +2096,10 @@ var OneAuthClient = class {
1311
2096
  }
1312
2097
  });
1313
2098
  dialog.showModal();
2099
+ let cleanedUp = false;
1314
2100
  const cleanup = () => {
2101
+ if (cleanedUp) return;
2102
+ cleanedUp = true;
1315
2103
  window.removeEventListener("message", handleMessage);
1316
2104
  document.removeEventListener("keydown", handleEscape);
1317
2105
  dialog.close();
@@ -1319,12 +2107,24 @@ var OneAuthClient = class {
1319
2107
  };
1320
2108
  return { dialog, iframe, cleanup };
1321
2109
  }
1322
- waitForModalAuthResponse(_dialog, _iframe, cleanup) {
2110
+ waitForModalAuthResponse(_dialog, iframe, cleanup) {
1323
2111
  const dialogOrigin = this.getDialogOrigin();
1324
2112
  return new Promise((resolve) => {
2113
+ let dialogReady = false;
1325
2114
  const handleMessage = (event) => {
1326
2115
  if (event.origin !== dialogOrigin) return;
1327
2116
  const data = event.data;
2117
+ if (data?.type === "PASSKEY_READY") {
2118
+ dialogReady = true;
2119
+ iframe.contentWindow?.postMessage({
2120
+ type: "PASSKEY_INIT",
2121
+ mode: "iframe"
2122
+ }, dialogOrigin);
2123
+ return;
2124
+ }
2125
+ if (!dialogReady && data?.type === "PASSKEY_CLOSE") {
2126
+ return;
2127
+ }
1328
2128
  if (data?.type === "PASSKEY_LOGIN_RESULT") {
1329
2129
  window.removeEventListener("message", handleMessage);
1330
2130
  cleanup();
@@ -1332,6 +2132,7 @@ var OneAuthClient = class {
1332
2132
  resolve({
1333
2133
  success: true,
1334
2134
  username: data.data?.username,
2135
+ address: data.data?.address,
1335
2136
  user: data.data?.user
1336
2137
  });
1337
2138
  } else {
@@ -1346,7 +2147,8 @@ var OneAuthClient = class {
1346
2147
  if (data.success) {
1347
2148
  resolve({
1348
2149
  success: true,
1349
- username: data.data?.username
2150
+ username: data.data?.username,
2151
+ address: data.data?.address
1350
2152
  });
1351
2153
  } else {
1352
2154
  resolve({
@@ -1354,6 +2156,11 @@ var OneAuthClient = class {
1354
2156
  error: data.error
1355
2157
  });
1356
2158
  }
2159
+ } else if (data?.type === "PASSKEY_RETRY_POPUP") {
2160
+ window.removeEventListener("message", handleMessage);
2161
+ cleanup();
2162
+ const popupUrl = data.data?.url?.replace("mode=iframe", "mode=popup") || `${this.getDialogUrl()}/dialog/auth?mode=popup${this.config.clientId ? `&clientId=${this.config.clientId}` : ""}`;
2163
+ this.waitForPopupAuthResponse(popupUrl).then(resolve);
1357
2164
  } else if (data?.type === "PASSKEY_CLOSE") {
1358
2165
  window.removeEventListener("message", handleMessage);
1359
2166
  cleanup();
@@ -1369,6 +2176,79 @@ var OneAuthClient = class {
1369
2176
  window.addEventListener("message", handleMessage);
1370
2177
  });
1371
2178
  }
2179
+ /**
2180
+ * Open a popup for auth and wait for the result.
2181
+ * Used when iframe mode fails (e.g., due to password manager interference).
2182
+ */
2183
+ waitForPopupAuthResponse(url) {
2184
+ const dialogOrigin = this.getDialogOrigin();
2185
+ const popup = this.openPopup(url);
2186
+ return new Promise((resolve) => {
2187
+ const pollTimer = setInterval(() => {
2188
+ if (popup?.closed) {
2189
+ clearInterval(pollTimer);
2190
+ window.removeEventListener("message", handleMessage);
2191
+ resolve({
2192
+ success: false,
2193
+ error: {
2194
+ code: "USER_CANCELLED",
2195
+ message: "Authentication was cancelled"
2196
+ }
2197
+ });
2198
+ }
2199
+ }, 500);
2200
+ const handleMessage = (event) => {
2201
+ if (event.origin !== dialogOrigin) return;
2202
+ const data = event.data;
2203
+ if (data?.type === "PASSKEY_LOGIN_RESULT") {
2204
+ clearInterval(pollTimer);
2205
+ window.removeEventListener("message", handleMessage);
2206
+ popup?.close();
2207
+ if (data.success) {
2208
+ resolve({
2209
+ success: true,
2210
+ username: data.data?.username,
2211
+ address: data.data?.address,
2212
+ user: data.data?.user
2213
+ });
2214
+ } else {
2215
+ resolve({
2216
+ success: false,
2217
+ error: data.error
2218
+ });
2219
+ }
2220
+ } else if (data?.type === "PASSKEY_REGISTER_RESULT") {
2221
+ clearInterval(pollTimer);
2222
+ window.removeEventListener("message", handleMessage);
2223
+ popup?.close();
2224
+ if (data.success) {
2225
+ resolve({
2226
+ success: true,
2227
+ username: data.data?.username,
2228
+ address: data.data?.address
2229
+ });
2230
+ } else {
2231
+ resolve({
2232
+ success: false,
2233
+ error: data.error
2234
+ });
2235
+ }
2236
+ } else if (data?.type === "PASSKEY_CLOSE") {
2237
+ clearInterval(pollTimer);
2238
+ window.removeEventListener("message", handleMessage);
2239
+ popup?.close();
2240
+ resolve({
2241
+ success: false,
2242
+ error: {
2243
+ code: "USER_CANCELLED",
2244
+ message: "Authentication was cancelled"
2245
+ }
2246
+ });
2247
+ }
2248
+ };
2249
+ window.addEventListener("message", handleMessage);
2250
+ });
2251
+ }
1372
2252
  waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
1373
2253
  const dialogOrigin = this.getDialogOrigin();
1374
2254
  return new Promise((resolve) => {
@@ -1408,6 +2288,84 @@ var OneAuthClient = class {
1408
2288
  window.addEventListener("message", handleMessage);
1409
2289
  });
1410
2290
  }
2291
+ waitForConnectResponse(_dialog, _iframe, cleanup) {
2292
+ const dialogOrigin = this.getDialogOrigin();
2293
+ return new Promise((resolve) => {
2294
+ const handleMessage = (event) => {
2295
+ if (event.origin !== dialogOrigin) return;
2296
+ const data = event.data;
2297
+ if (data?.type === "PASSKEY_CONNECT_RESULT") {
2298
+ window.removeEventListener("message", handleMessage);
2299
+ cleanup();
2300
+ if (data.success) {
2301
+ resolve({
2302
+ success: true,
2303
+ username: data.data?.username,
2304
+ address: data.data?.address,
2305
+ autoConnected: data.data?.autoConnected
2306
+ });
2307
+ } else {
2308
+ resolve({
2309
+ success: false,
2310
+ action: data.data?.action,
2311
+ error: data.error
2312
+ });
2313
+ }
2314
+ } else if (data?.type === "PASSKEY_CLOSE") {
2315
+ window.removeEventListener("message", handleMessage);
2316
+ cleanup();
2317
+ resolve({
2318
+ success: false,
2319
+ action: "cancel",
2320
+ error: {
2321
+ code: "USER_CANCELLED",
2322
+ message: "Connection was cancelled"
2323
+ }
2324
+ });
2325
+ }
2326
+ };
2327
+ window.addEventListener("message", handleMessage);
2328
+ });
2329
+ }
2330
+ waitForConsentResponse(_dialog, _iframe, cleanup) {
2331
+ const dialogOrigin = this.getDialogOrigin();
2332
+ return new Promise((resolve) => {
2333
+ const handleMessage = (event) => {
2334
+ if (event.origin !== dialogOrigin) return;
2335
+ const data = event.data;
2336
+ if (data?.type === "PASSKEY_CONSENT_RESULT") {
2337
+ window.removeEventListener("message", handleMessage);
2338
+ cleanup();
2339
+ if (data.success) {
2340
+ resolve({
2341
+ success: true,
2342
+ data: data.data,
2343
+ grantedAt: data.data?.grantedAt
2344
+ });
2345
+ } else {
2346
+ resolve({
2347
+ success: false,
2348
+ error: data.error ?? {
2349
+ code: "USER_REJECTED",
2350
+ message: "User denied the consent request"
2351
+ }
2352
+ });
2353
+ }
2354
+ } else if (data?.type === "PASSKEY_CLOSE") {
2355
+ window.removeEventListener("message", handleMessage);
2356
+ cleanup();
2357
+ resolve({
2358
+ success: false,
2359
+ error: {
2360
+ code: "USER_CANCELLED",
2361
+ message: "User closed the dialog"
2362
+ }
2363
+ });
2364
+ }
2365
+ };
2366
+ window.addEventListener("message", handleMessage);
2367
+ });
2368
+ }
1411
2369
  waitForModalSigningResponse(requestId, _dialog, _iframe, cleanup) {
1412
2370
  const dialogOrigin = this.getDialogOrigin();
1413
2371
  return new Promise((resolve) => {
@@ -1496,9 +2454,7 @@ var OneAuthClient = class {
1496
2454
  const response = await fetch(
1497
2455
  `${this.config.providerUrl}/api/sign/request/${requestId}`,
1498
2456
  {
1499
- headers: {
1500
- "x-client-id": this.config.clientId
1501
- }
2457
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
1502
2458
  }
1503
2459
  );
1504
2460
  if (!response.ok) {
@@ -1640,6 +2596,9 @@ function createPasskeyWalletClient(config) {
1640
2596
  if (!result.success) {
1641
2597
  throw new Error(result.error?.message || "Signing failed");
1642
2598
  }
2599
+ if (!result.signature) {
2600
+ throw new Error("No signature received");
2601
+ }
1643
2602
  return encodeWebAuthnSignature(result.signature);
1644
2603
  };
1645
2604
  const signTransactionImpl = async (transaction) => {
@@ -1660,6 +2619,9 @@ function createPasskeyWalletClient(config) {
1660
2619
  if (!result.success) {
1661
2620
  throw new Error(result.error?.message || "Signing failed");
1662
2621
  }
2622
+ if (!result.signature) {
2623
+ throw new Error("No signature received");
2624
+ }
1663
2625
  return encodeWebAuthnSignature(result.signature);
1664
2626
  };
1665
2627
  const signTypedDataImpl = async (typedData) => {
@@ -1681,6 +2643,9 @@ function createPasskeyWalletClient(config) {
1681
2643
  if (!result.success) {
1682
2644
  throw new Error(result.error?.message || "Signing failed");
1683
2645
  }
2646
+ if (!result.signature) {
2647
+ throw new Error("No signature received");
2648
+ }
1684
2649
  return encodeWebAuthnSignature(result.signature);
1685
2650
  };
1686
2651
  const buildIntentPayload = async (calls, targetChainOverride) => {
@@ -1749,11 +2714,12 @@ function createPasskeyWalletClient(config) {
1749
2714
  * Send multiple calls as a single batched transaction
1750
2715
  */
1751
2716
  async sendCalls(params) {
1752
- const { calls, chainId: targetChain } = params;
2717
+ const { calls, chainId: targetChain, tokenRequests } = params;
1753
2718
  const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
1754
2719
  const intentPayload = await buildIntentPayload(calls, targetChain);
1755
2720
  const result = await provider.sendIntent({
1756
2721
  ...intentPayload,
2722
+ tokenRequests,
1757
2723
  closeOn,
1758
2724
  waitForHash: config.waitForHash ?? true,
1759
2725
  hashTimeoutMs: config.hashTimeoutMs,
@@ -2197,9 +3163,10 @@ function BatchQueueWidget({ onSignAll }) {
2197
3163
 
2198
3164
  // src/verify.ts
2199
3165
  import { keccak256, toBytes } from "viem";
2200
- var PASSKEY_MESSAGE_PREFIX = "Passkey Signed Message:\n";
3166
+ var ETHEREUM_MESSAGE_PREFIX = "Ethereum Signed Message:\n";
3167
+ var PASSKEY_MESSAGE_PREFIX = ETHEREUM_MESSAGE_PREFIX;
2201
3168
  function hashMessage2(message) {
2202
- const prefixed = PASSKEY_MESSAGE_PREFIX + message.length.toString() + message;
3169
+ const prefixed = ETHEREUM_MESSAGE_PREFIX + message.length.toString() + message;
2203
3170
  return keccak256(toBytes(prefixed));
2204
3171
  }
2205
3172
  function verifyMessageHash(message, signedHash) {
@@ -2210,9 +3177,11 @@ function verifyMessageHash(message, signedHash) {
2210
3177
  export {
2211
3178
  BatchQueueProvider,
2212
3179
  BatchQueueWidget,
3180
+ ETHEREUM_MESSAGE_PREFIX,
2213
3181
  OneAuthClient,
2214
3182
  PASSKEY_MESSAGE_PREFIX,
2215
3183
  OneAuthClient as PasskeyProviderClient,
3184
+ createOneAuthProvider,
2216
3185
  createPasskeyAccount,
2217
3186
  createPasskeyProvider,
2218
3187
  createPasskeyWalletClient,