@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.js CHANGED
@@ -32,9 +32,11 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BatchQueueProvider: () => BatchQueueProvider,
34
34
  BatchQueueWidget: () => BatchQueueWidget,
35
+ ETHEREUM_MESSAGE_PREFIX: () => ETHEREUM_MESSAGE_PREFIX,
35
36
  OneAuthClient: () => OneAuthClient,
36
37
  PASSKEY_MESSAGE_PREFIX: () => PASSKEY_MESSAGE_PREFIX,
37
38
  PasskeyProviderClient: () => OneAuthClient,
39
+ createOneAuthProvider: () => createOneAuthProvider,
38
40
  createPasskeyAccount: () => createPasskeyAccount,
39
41
  createPasskeyProvider: () => createPasskeyProvider,
40
42
  createPasskeyWalletClient: () => createPasskeyWalletClient,
@@ -69,12 +71,15 @@ var import_viem = require("viem");
69
71
  var viemChains = __toESM(require("viem/chains"));
70
72
  var import_sdk = require("@rhinestone/sdk");
71
73
  var env = typeof process !== "undefined" ? process.env : {};
72
- var ALL_VIEM_CHAINS = Object.values(viemChains).filter(
73
- (value) => typeof value === "object" && value !== null && "id" in value && "name" in value
74
- );
75
- var VIEM_CHAIN_BY_ID = new Map(
76
- ALL_VIEM_CHAINS.map((chain) => [chain.id, chain])
77
- );
74
+ var VIEM_CHAIN_BY_ID = /* @__PURE__ */ new Map();
75
+ for (const value of Object.values(viemChains)) {
76
+ if (typeof value !== "object" || value === null || !("id" in value) || !("name" in value)) continue;
77
+ const chain = value;
78
+ const existing = VIEM_CHAIN_BY_ID.get(chain.id);
79
+ if (!existing || existing.testnet && !chain.testnet) {
80
+ VIEM_CHAIN_BY_ID.set(chain.id, chain);
81
+ }
82
+ }
78
83
  var SUPPORTED_CHAIN_IDS = new Set(
79
84
  (0, import_sdk.getAllSupportedChainsAndTokens)().map((entry) => entry.chainId)
80
85
  );
@@ -156,7 +161,13 @@ function resolveTokenAddress(token, chainId) {
156
161
  if ((0, import_viem.isAddress)(token)) {
157
162
  return token;
158
163
  }
159
- return (0, import_sdk.getTokenAddress)(token.toUpperCase(), chainId);
164
+ const match = getSupportedTokens(chainId).find(
165
+ (t) => t.symbol.toUpperCase() === token.toUpperCase()
166
+ );
167
+ if (!match) {
168
+ return (0, import_sdk.getTokenAddress)(token, chainId);
169
+ }
170
+ return match.address;
160
171
  }
161
172
  function isTestnet(chainId) {
162
173
  try {
@@ -189,7 +200,7 @@ var POPUP_WIDTH = 450;
189
200
  var POPUP_HEIGHT = 600;
190
201
  var DEFAULT_EMBED_WIDTH = "400px";
191
202
  var DEFAULT_EMBED_HEIGHT = "500px";
192
- var MODAL_WIDTH = 360;
203
+ var MODAL_WIDTH = 340;
193
204
  var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
194
205
  var OneAuthClient = class {
195
206
  constructor(config) {
@@ -197,6 +208,12 @@ var OneAuthClient = class {
197
208
  const dialogUrl = config.dialogUrl || providerUrl;
198
209
  this.config = { ...config, providerUrl, dialogUrl };
199
210
  this.theme = this.config.theme || {};
211
+ if (typeof document !== "undefined") {
212
+ this.injectPreconnect(providerUrl);
213
+ if (dialogUrl !== providerUrl) {
214
+ this.injectPreconnect(dialogUrl);
215
+ }
216
+ }
200
217
  }
201
218
  /**
202
219
  * Update the theme configuration at runtime
@@ -258,9 +275,7 @@ var OneAuthClient = class {
258
275
  const response = await fetch(
259
276
  `${this.config.providerUrl}/api/intent/status/${intentId}`,
260
277
  {
261
- headers: {
262
- "x-client-id": this.config.clientId
263
- }
278
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
264
279
  }
265
280
  );
266
281
  if (response.ok) {
@@ -284,9 +299,11 @@ var OneAuthClient = class {
284
299
  async authWithModal(options) {
285
300
  const dialogUrl = this.getDialogUrl();
286
301
  const params = new URLSearchParams({
287
- clientId: this.config.clientId,
288
302
  mode: "iframe"
289
303
  });
304
+ if (this.config.clientId) {
305
+ params.set("clientId", this.config.clientId);
306
+ }
290
307
  if (options?.username) {
291
308
  params.set("username", options.username);
292
309
  }
@@ -302,6 +319,182 @@ var OneAuthClient = class {
302
319
  const { dialog, iframe, cleanup } = this.createModalDialog(url);
303
320
  return this.waitForModalAuthResponse(dialog, iframe, cleanup);
304
321
  }
322
+ /**
323
+ * Open the connect dialog (lightweight connection without passkey auth).
324
+ *
325
+ * This method shows a simple connection confirmation dialog that doesn't
326
+ * require a passkey signature. Users can optionally enable "auto-connect"
327
+ * to skip this dialog in the future.
328
+ *
329
+ * If the user has never connected before, this will return action: "switch"
330
+ * to indicate that the full auth modal should be opened instead.
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * const result = await client.connectWithModal();
335
+ *
336
+ * if (result.success) {
337
+ * console.log('Connected as:', result.username);
338
+ * } else if (result.action === 'switch') {
339
+ * // User needs to sign in first
340
+ * const authResult = await client.authWithModal();
341
+ * }
342
+ * ```
343
+ */
344
+ async connectWithModal(options) {
345
+ const dialogUrl = this.getDialogUrl();
346
+ const params = new URLSearchParams({
347
+ mode: "iframe"
348
+ });
349
+ if (this.config.clientId) {
350
+ params.set("clientId", this.config.clientId);
351
+ }
352
+ const themeParams = this.getThemeParams(options?.theme);
353
+ if (themeParams) {
354
+ const themeParsed = new URLSearchParams(themeParams);
355
+ themeParsed.forEach((value, key) => params.set(key, value));
356
+ }
357
+ const url = `${dialogUrl}/dialog/connect?${params.toString()}`;
358
+ const { dialog, iframe, cleanup } = this.createModalDialog(url);
359
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
360
+ mode: "iframe"
361
+ });
362
+ if (!ready) {
363
+ return {
364
+ success: false,
365
+ action: "cancel",
366
+ error: { code: "USER_CANCELLED", message: "Connection was cancelled" }
367
+ };
368
+ }
369
+ return this.waitForConnectResponse(dialog, iframe, cleanup);
370
+ }
371
+ /**
372
+ * Check if a user has already granted consent for the requested fields.
373
+ * This is a read-only check — no dialog is shown.
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * const result = await client.checkConsent({
378
+ * username: "alice",
379
+ * fields: ["email"],
380
+ * });
381
+ * if (result.hasConsent) {
382
+ * console.log(result.data?.email);
383
+ * }
384
+ * ```
385
+ */
386
+ async checkConsent(options) {
387
+ const clientId = options.clientId ?? this.config.clientId;
388
+ if (!clientId) {
389
+ return {
390
+ hasConsent: false
391
+ };
392
+ }
393
+ const username = options.username ?? options.accountAddress;
394
+ if (!username) {
395
+ return {
396
+ hasConsent: false
397
+ };
398
+ }
399
+ try {
400
+ const res = await fetch(`${this.config.providerUrl || "https://passkey.1auth.box"}/api/consent`, {
401
+ method: "POST",
402
+ headers: {
403
+ "Content-Type": "application/json",
404
+ ...clientId ? { "x-client-id": clientId } : {}
405
+ },
406
+ body: JSON.stringify({
407
+ username,
408
+ requestedFields: options.fields,
409
+ clientId
410
+ })
411
+ });
412
+ if (!res.ok) {
413
+ return { hasConsent: false };
414
+ }
415
+ const data = await res.json();
416
+ return {
417
+ hasConsent: data.hasConsent ?? false,
418
+ data: data.data,
419
+ grantedAt: data.grantedAt
420
+ };
421
+ } catch {
422
+ return { hasConsent: false };
423
+ }
424
+ }
425
+ /**
426
+ * Request consent from the user to share their data.
427
+ *
428
+ * First checks if consent was already granted (returns cached data immediately).
429
+ * If not, opens the consent dialog where the user can review and approve sharing.
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * const result = await client.requestConsent({
434
+ * username: "alice",
435
+ * fields: ["email", "deviceNames"],
436
+ * });
437
+ * if (result.success) {
438
+ * console.log(result.data?.email);
439
+ * console.log(result.cached); // true if no dialog was shown
440
+ * }
441
+ * ```
442
+ */
443
+ async requestConsent(options) {
444
+ const clientId = options.clientId ?? this.config.clientId;
445
+ if (!clientId) {
446
+ return {
447
+ success: false,
448
+ error: { code: "INVALID_REQUEST", message: "clientId is required (set in config or options)" }
449
+ };
450
+ }
451
+ const username = options.username ?? options.accountAddress;
452
+ if (!username) {
453
+ return {
454
+ success: false,
455
+ error: { code: "INVALID_REQUEST", message: "username or accountAddress is required" }
456
+ };
457
+ }
458
+ if (!options.fields || options.fields.length === 0) {
459
+ return {
460
+ success: false,
461
+ error: { code: "INVALID_REQUEST", message: "At least one field is required" }
462
+ };
463
+ }
464
+ const existing = await this.checkConsent({ ...options, clientId });
465
+ if (existing.hasConsent && existing.data) {
466
+ return {
467
+ success: true,
468
+ data: existing.data,
469
+ grantedAt: existing.grantedAt,
470
+ cached: true
471
+ };
472
+ }
473
+ const dialogUrl = this.getDialogUrl();
474
+ const params = new URLSearchParams({
475
+ mode: "iframe",
476
+ username,
477
+ clientId,
478
+ fields: options.fields.join(",")
479
+ });
480
+ const themeParams = this.getThemeParams(options.theme);
481
+ if (themeParams) {
482
+ const themeParsed = new URLSearchParams(themeParams);
483
+ themeParsed.forEach((value, key) => params.set(key, value));
484
+ }
485
+ const url = `${dialogUrl}/dialog/consent?${params.toString()}`;
486
+ const { dialog, iframe, cleanup } = this.createModalDialog(url);
487
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
488
+ mode: "iframe"
489
+ });
490
+ if (!ready) {
491
+ return {
492
+ success: false,
493
+ error: { code: "USER_CANCELLED", message: "User closed the dialog" }
494
+ };
495
+ }
496
+ return this.waitForConsentResponse(dialog, iframe, cleanup);
497
+ }
305
498
  /**
306
499
  * Authenticate a user with an optional challenge to sign.
307
500
  *
@@ -337,9 +530,11 @@ var OneAuthClient = class {
337
530
  async authenticate(options) {
338
531
  const dialogUrl = this.getDialogUrl();
339
532
  const params = new URLSearchParams({
340
- clientId: this.config.clientId,
341
533
  mode: "iframe"
342
534
  });
535
+ if (this.config.clientId) {
536
+ params.set("clientId", this.config.clientId);
537
+ }
343
538
  if (options?.challenge) {
344
539
  params.set("challenge", options.challenge);
345
540
  }
@@ -353,28 +548,30 @@ var OneAuthClient = class {
353
548
  return this.waitForAuthenticateResponse(dialog, iframe, cleanup);
354
549
  }
355
550
  /**
356
- * Show signing in a modal overlay (Porto-style iframe dialog)
551
+ * Show signing in a modal overlay (iframe dialog)
357
552
  */
358
553
  async signWithModal(options) {
359
554
  const dialogUrl = this.getDialogUrl();
360
555
  const themeParams = this.getThemeParams(options?.theme);
361
556
  const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
362
557
  const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
363
- const dialogOrigin = this.getDialogOrigin();
364
- await new Promise((resolve) => {
365
- iframe.onload = () => {
366
- iframe.contentWindow?.postMessage({
367
- type: "PASSKEY_INIT",
368
- mode: "iframe",
369
- challenge: options.challenge,
370
- username: options.username,
371
- description: options.description,
372
- transaction: options.transaction,
373
- metadata: options.metadata
374
- }, dialogOrigin);
375
- resolve();
376
- };
558
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
559
+ mode: "iframe",
560
+ challenge: options.challenge,
561
+ username: options.username,
562
+ description: options.description,
563
+ transaction: options.transaction,
564
+ metadata: options.metadata
377
565
  });
566
+ if (!ready) {
567
+ return {
568
+ success: false,
569
+ error: {
570
+ code: "USER_REJECTED",
571
+ message: "User closed the dialog"
572
+ }
573
+ };
574
+ }
378
575
  return this.waitForSigningResponse(dialog, iframe, cleanup);
379
576
  }
380
577
  /**
@@ -425,7 +622,8 @@ var OneAuthClient = class {
425
622
  }
426
623
  };
427
624
  }
428
- if (!username && !signedIntent?.accountAddress) {
625
+ const accountAddress = signedIntent?.accountAddress || options.accountAddress;
626
+ if (!username && !accountAddress) {
429
627
  return {
430
628
  success: false,
431
629
  intentId: "",
@@ -447,158 +645,264 @@ var OneAuthClient = class {
447
645
  }
448
646
  };
449
647
  }
648
+ const serializedTokenRequests = options.tokenRequests?.map((r) => ({
649
+ token: r.token,
650
+ amount: r.amount.toString()
651
+ }));
450
652
  let prepareResponse;
451
- try {
452
- const requestBody = signedIntent || {
453
- username: options.username,
454
- targetChain: options.targetChain,
455
- calls: options.calls,
456
- tokenRequests: options.tokenRequests,
457
- sourceAssets: options.sourceAssets,
458
- clientId: this.config.clientId
653
+ const requestBody = signedIntent || {
654
+ username: options.username,
655
+ accountAddress: options.accountAddress,
656
+ targetChain: options.targetChain,
657
+ calls: options.calls,
658
+ tokenRequests: serializedTokenRequests,
659
+ sourceAssets: options.sourceAssets,
660
+ sourceChainId: options.sourceChainId,
661
+ ...this.config.clientId && { clientId: this.config.clientId }
662
+ };
663
+ const dialogUrl = this.getDialogUrl();
664
+ const themeParams = this.getThemeParams();
665
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
666
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
667
+ const [prepareResult, dialogResult] = await Promise.all([
668
+ this.prepareIntent(requestBody),
669
+ this.waitForDialogReadyDeferred(dialog, iframe, cleanup)
670
+ ]);
671
+ if (!dialogResult.ready) {
672
+ return {
673
+ success: false,
674
+ intentId: "",
675
+ status: "failed",
676
+ error: { code: "USER_CANCELLED", message: "User closed the dialog" }
459
677
  };
460
- const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
461
- method: "POST",
462
- headers: {
463
- "Content-Type": "application/json"
464
- },
465
- body: JSON.stringify(requestBody)
466
- });
467
- if (!response.ok) {
468
- const errorData = await response.json().catch(() => ({}));
469
- const errorMessage = errorData.error || "Failed to prepare intent";
470
- if (errorMessage.includes("User not found")) {
471
- localStorage.removeItem("1auth-user");
472
- }
473
- return {
474
- success: false,
475
- intentId: "",
476
- status: "failed",
477
- error: {
478
- code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
479
- message: errorMessage
480
- }
481
- };
482
- }
483
- prepareResponse = await response.json();
484
- } catch (error) {
678
+ }
679
+ if (!prepareResult.success) {
680
+ this.sendPrepareError(iframe, prepareResult.error.message);
681
+ await this.waitForDialogClose(dialog, cleanup);
485
682
  return {
486
683
  success: false,
487
684
  intentId: "",
488
685
  status: "failed",
489
- error: {
490
- code: "NETWORK_ERROR",
491
- message: error instanceof Error ? error.message : "Network error"
492
- }
686
+ error: prepareResult.error
493
687
  };
494
688
  }
495
- const dialogUrl = this.getDialogUrl();
496
- const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe`;
497
- const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
689
+ prepareResponse = prepareResult.data;
498
690
  const dialogOrigin = this.getDialogOrigin();
499
- await new Promise((resolve) => {
500
- const handleReady = (event) => {
501
- if (event.origin !== dialogOrigin) return;
502
- if (event.data?.type === "PASSKEY_READY") {
503
- window.removeEventListener("message", handleReady);
504
- iframe.contentWindow?.postMessage({
505
- type: "PASSKEY_INIT",
506
- mode: "iframe",
507
- calls,
508
- chainId: targetChain,
509
- transaction: prepareResponse.transaction,
510
- challenge: prepareResponse.challenge,
511
- username,
512
- accountAddress: prepareResponse.accountAddress,
513
- intentId: prepareResponse.intentId
514
- }, dialogOrigin);
515
- resolve();
691
+ const initPayload = {
692
+ mode: "iframe",
693
+ calls,
694
+ chainId: targetChain,
695
+ transaction: prepareResponse.transaction,
696
+ challenge: prepareResponse.challenge,
697
+ username,
698
+ accountAddress: prepareResponse.accountAddress,
699
+ originMessages: prepareResponse.originMessages,
700
+ tokenRequests: serializedTokenRequests,
701
+ expiresAt: prepareResponse.expiresAt,
702
+ userId: prepareResponse.userId,
703
+ intentOp: prepareResponse.intentOp,
704
+ digestResult: prepareResponse.digestResult,
705
+ tier: prepareResult.tier
706
+ };
707
+ dialogResult.sendInit(initPayload);
708
+ const handleReReady = (event) => {
709
+ if (event.origin !== dialogOrigin) return;
710
+ if (event.data?.type === "PASSKEY_READY") {
711
+ iframe.contentWindow?.postMessage(
712
+ { type: "PASSKEY_INIT", ...initPayload },
713
+ dialogOrigin
714
+ );
715
+ }
716
+ };
717
+ window.addEventListener("message", handleReReady);
718
+ const signingResult = await this.waitForSigningWithRefresh(
719
+ dialog,
720
+ iframe,
721
+ cleanup,
722
+ dialogOrigin,
723
+ // Refresh callback - called when dialog requests a quote refresh
724
+ async () => {
725
+ console.log("[SDK] Dialog requested quote refresh, re-preparing intent");
726
+ try {
727
+ const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
728
+ method: "POST",
729
+ headers: { "Content-Type": "application/json" },
730
+ body: JSON.stringify(requestBody),
731
+ credentials: "include"
732
+ });
733
+ if (!refreshResponse.ok) {
734
+ console.error("[SDK] Quote refresh failed:", await refreshResponse.text());
735
+ return null;
736
+ }
737
+ const refreshedData = await refreshResponse.json();
738
+ prepareResponse = refreshedData;
739
+ return {
740
+ intentOp: refreshedData.intentOp,
741
+ expiresAt: refreshedData.expiresAt,
742
+ challenge: refreshedData.challenge,
743
+ originMessages: refreshedData.originMessages,
744
+ transaction: refreshedData.transaction,
745
+ digestResult: refreshedData.digestResult
746
+ };
747
+ } catch (error) {
748
+ console.error("[SDK] Quote refresh error:", error);
749
+ return null;
516
750
  }
517
- };
518
- window.addEventListener("message", handleReady);
519
- });
520
- const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
751
+ }
752
+ );
753
+ window.removeEventListener("message", handleReReady);
521
754
  if (!signingResult.success) {
522
755
  return {
523
756
  success: false,
524
- intentId: prepareResponse.intentId,
757
+ intentId: "",
758
+ // No intentId yet - signing was cancelled before execute
525
759
  status: "failed",
526
760
  error: signingResult.error
527
761
  };
528
762
  }
763
+ const dialogExecutedIntent = "intentId" in signingResult && signingResult.intentId;
529
764
  let executeResponse;
530
- try {
531
- const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
532
- method: "POST",
533
- headers: {
534
- "Content-Type": "application/json"
535
- },
536
- body: JSON.stringify({
537
- intentId: prepareResponse.intentId,
538
- signature: signingResult.signature,
539
- passkey: signingResult.passkey
540
- // Include passkey info for signature encoding
541
- })
542
- });
543
- if (!response.ok) {
544
- const errorData = await response.json().catch(() => ({}));
765
+ if (dialogExecutedIntent) {
766
+ executeResponse = {
767
+ success: true,
768
+ intentId: signingResult.intentId,
769
+ status: "pending"
770
+ };
771
+ } else {
772
+ try {
773
+ const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
774
+ method: "POST",
775
+ headers: {
776
+ "Content-Type": "application/json"
777
+ },
778
+ body: JSON.stringify({
779
+ // Data from prepare response (no intentId yet - created on execute)
780
+ intentOp: prepareResponse.intentOp,
781
+ userId: prepareResponse.userId,
782
+ targetChain: prepareResponse.targetChain,
783
+ calls: prepareResponse.calls,
784
+ expiresAt: prepareResponse.expiresAt,
785
+ digestResult: prepareResponse.digestResult,
786
+ // Signature from dialog
787
+ signature: signingResult.signature,
788
+ passkey: signingResult.passkey
789
+ // Include passkey info for signature encoding
790
+ })
791
+ });
792
+ if (!response.ok) {
793
+ const errorData = await response.json().catch(() => ({}));
794
+ this.sendTransactionStatus(iframe, "failed");
795
+ await this.waitForDialogClose(dialog, cleanup);
796
+ return {
797
+ success: false,
798
+ intentId: "",
799
+ // No intentId - execute failed before creation
800
+ status: "failed",
801
+ error: {
802
+ code: "EXECUTE_FAILED",
803
+ message: errorData.error || "Failed to execute intent"
804
+ }
805
+ };
806
+ }
807
+ executeResponse = await response.json();
808
+ } catch (error) {
545
809
  this.sendTransactionStatus(iframe, "failed");
546
810
  await this.waitForDialogClose(dialog, cleanup);
547
811
  return {
548
812
  success: false,
549
- intentId: prepareResponse.intentId,
813
+ intentId: "",
814
+ // No intentId - network error before creation
550
815
  status: "failed",
551
816
  error: {
552
- code: "EXECUTE_FAILED",
553
- message: errorData.error || "Failed to execute intent"
817
+ code: "NETWORK_ERROR",
818
+ message: error instanceof Error ? error.message : "Network error"
554
819
  }
555
820
  };
556
821
  }
557
- executeResponse = await response.json();
558
- } catch (error) {
559
- this.sendTransactionStatus(iframe, "failed");
560
- await this.waitForDialogClose(dialog, cleanup);
561
- return {
562
- success: false,
563
- intentId: prepareResponse.intentId,
564
- status: "failed",
565
- error: {
566
- code: "NETWORK_ERROR",
567
- message: error instanceof Error ? error.message : "Network error"
568
- }
569
- };
570
822
  }
571
- const closeOn = options.closeOn || "preconfirmed";
572
- const acceptPreconfirmations = closeOn !== "completed";
573
823
  let finalStatus = executeResponse.status;
574
824
  let finalTxHash = executeResponse.transactionHash;
575
825
  if (finalStatus === "pending") {
576
- this.sendTransactionStatus(iframe, "processing");
577
- try {
578
- const waitResponse = await fetch(
579
- `${this.config.providerUrl}/api/intent/wait/${prepareResponse.intentId}?preconfirm=${acceptPreconfirmations}`,
580
- {
581
- headers: {
582
- "x-client-id": this.config.clientId
826
+ this.sendTransactionStatus(iframe, "pending");
827
+ let userClosedEarly = false;
828
+ const dialogOrigin2 = this.getDialogOrigin();
829
+ const earlyCloseHandler = (event) => {
830
+ if (event.origin !== dialogOrigin2) return;
831
+ if (event.data?.type === "PASSKEY_CLOSE") {
832
+ userClosedEarly = true;
833
+ cleanup();
834
+ }
835
+ };
836
+ window.addEventListener("message", earlyCloseHandler);
837
+ const maxAttempts = 120;
838
+ const pollIntervalMs = 1500;
839
+ let lastStatus = "pending";
840
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
841
+ if (userClosedEarly) break;
842
+ try {
843
+ const statusResponse = await fetch(
844
+ `${this.config.providerUrl}/api/intent/status/${executeResponse.intentId}`,
845
+ {
846
+ method: "GET",
847
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
848
+ }
849
+ );
850
+ if (statusResponse.ok) {
851
+ const statusResult = await statusResponse.json();
852
+ finalStatus = statusResult.status;
853
+ finalTxHash = statusResult.transactionHash;
854
+ if (finalStatus !== lastStatus) {
855
+ this.sendTransactionStatus(iframe, finalStatus, finalTxHash);
856
+ lastStatus = finalStatus;
857
+ }
858
+ const closeOn2 = options.closeOn || "preconfirmed";
859
+ const successStatuses2 = {
860
+ claimed: ["claimed", "preconfirmed", "filled", "completed"],
861
+ preconfirmed: ["preconfirmed", "filled", "completed"],
862
+ filled: ["filled", "completed"],
863
+ completed: ["completed"]
864
+ };
865
+ const isTerminal = finalStatus === "failed" || finalStatus === "expired";
866
+ const isSuccess = successStatuses2[closeOn2]?.includes(finalStatus) ?? false;
867
+ if (isTerminal || isSuccess) {
868
+ break;
583
869
  }
584
870
  }
585
- );
586
- if (waitResponse.ok) {
587
- const waitResult = await waitResponse.json();
588
- finalStatus = waitResult.status === "preconfirmed" || waitResult.status === "completed" ? "completed" : waitResult.status;
589
- finalTxHash = waitResult.transactionHash;
590
- } else {
591
- console.error("Wait endpoint failed:", await waitResponse.text());
592
- }
593
- } catch (waitError) {
594
- console.error("Failed to wait for intent:", waitError);
871
+ } catch (pollError) {
872
+ console.error("Failed to poll intent status:", pollError);
873
+ }
874
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
875
+ }
876
+ window.removeEventListener("message", earlyCloseHandler);
877
+ if (userClosedEarly) {
878
+ cleanup();
879
+ return {
880
+ success: false,
881
+ intentId: executeResponse.intentId,
882
+ status: finalStatus,
883
+ transactionHash: finalTxHash,
884
+ operationId: executeResponse.operationId,
885
+ error: {
886
+ code: "USER_CANCELLED",
887
+ message: "User closed the dialog"
888
+ }
889
+ };
595
890
  }
596
891
  }
597
- const displayStatus = finalStatus === "completed" ? "confirmed" : finalStatus;
892
+ const closeOn = options.closeOn || "preconfirmed";
893
+ const successStatuses = {
894
+ claimed: ["claimed", "preconfirmed", "filled", "completed"],
895
+ preconfirmed: ["preconfirmed", "filled", "completed"],
896
+ filled: ["filled", "completed"],
897
+ completed: ["completed"]
898
+ };
899
+ const isSuccessStatus = successStatuses[closeOn]?.includes(finalStatus) ?? false;
900
+ const displayStatus = isSuccessStatus ? "confirmed" : finalStatus;
901
+ const closePromise = this.waitForDialogClose(dialog, cleanup);
598
902
  this.sendTransactionStatus(iframe, displayStatus, finalTxHash);
599
- await this.waitForDialogClose(dialog, cleanup);
903
+ await closePromise;
600
904
  if (options.waitForHash && !finalTxHash) {
601
- const hash = await this.waitForTransactionHash(prepareResponse.intentId, {
905
+ const hash = await this.waitForTransactionHash(executeResponse.intentId, {
602
906
  timeoutMs: options.hashTimeoutMs,
603
907
  intervalMs: options.hashIntervalMs
604
908
  });
@@ -609,7 +913,7 @@ var OneAuthClient = class {
609
913
  finalStatus = "failed";
610
914
  return {
611
915
  success: false,
612
- intentId: prepareResponse.intentId,
916
+ intentId: executeResponse.intentId,
613
917
  status: finalStatus,
614
918
  transactionHash: finalTxHash,
615
919
  operationId: executeResponse.operationId,
@@ -621,14 +925,223 @@ var OneAuthClient = class {
621
925
  }
622
926
  }
623
927
  return {
624
- success: finalStatus === "completed",
625
- intentId: prepareResponse.intentId,
928
+ success: isSuccessStatus,
929
+ intentId: executeResponse.intentId,
626
930
  status: finalStatus,
627
931
  transactionHash: finalTxHash,
628
932
  operationId: executeResponse.operationId,
629
933
  error: executeResponse.error
630
934
  };
631
935
  }
936
+ /**
937
+ * Send a batch of intents for multi-chain execution with a single passkey tap.
938
+ *
939
+ * This method prepares multiple intents, shows a paginated review,
940
+ * and signs all intents with a single passkey tap via a shared merkle tree.
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * const result = await client.sendBatchIntent({
945
+ * username: 'alice',
946
+ * intents: [
947
+ * {
948
+ * targetChain: 8453, // Base
949
+ * calls: [{ to: '0x...', data: '0x...', label: 'Swap on Base' }],
950
+ * },
951
+ * {
952
+ * targetChain: 42161, // Arbitrum
953
+ * calls: [{ to: '0x...', data: '0x...', label: 'Mint on Arbitrum' }],
954
+ * },
955
+ * ],
956
+ * });
957
+ *
958
+ * if (result.success) {
959
+ * console.log(`${result.successCount} intents submitted`);
960
+ * }
961
+ * ```
962
+ */
963
+ async sendBatchIntent(options) {
964
+ if (!options.username && !options.accountAddress) {
965
+ return {
966
+ success: false,
967
+ results: [],
968
+ successCount: 0,
969
+ failureCount: 0
970
+ };
971
+ }
972
+ if (!options.intents?.length) {
973
+ return {
974
+ success: false,
975
+ results: [],
976
+ successCount: 0,
977
+ failureCount: 0
978
+ };
979
+ }
980
+ const serializedIntents = options.intents.map((intent) => ({
981
+ targetChain: intent.targetChain,
982
+ calls: intent.calls,
983
+ tokenRequests: intent.tokenRequests?.map((r) => ({
984
+ token: r.token,
985
+ amount: r.amount.toString()
986
+ })),
987
+ sourceAssets: intent.sourceAssets,
988
+ sourceChainId: intent.sourceChainId,
989
+ moduleInstall: intent.moduleInstall
990
+ }));
991
+ const requestBody = {
992
+ ...options.username && { username: options.username },
993
+ ...options.accountAddress && { accountAddress: options.accountAddress },
994
+ intents: serializedIntents,
995
+ ...this.config.clientId && { clientId: this.config.clientId }
996
+ };
997
+ const dialogUrl = this.getDialogUrl();
998
+ const themeParams = this.getThemeParams();
999
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
1000
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
1001
+ const [prepareResult, dialogResult] = await Promise.all([
1002
+ this.prepareBatchIntent(requestBody),
1003
+ this.waitForDialogReadyDeferred(dialog, iframe, cleanup)
1004
+ ]);
1005
+ if (!dialogResult.ready) {
1006
+ return {
1007
+ success: false,
1008
+ results: [],
1009
+ successCount: 0,
1010
+ failureCount: 0
1011
+ };
1012
+ }
1013
+ if (!prepareResult.success) {
1014
+ const failedIntents = prepareResult.failedIntents;
1015
+ const failureResults = failedIntents?.map((f) => ({
1016
+ index: f.index,
1017
+ success: false,
1018
+ intentId: "",
1019
+ status: "failed",
1020
+ error: { message: f.error, code: "PREPARE_FAILED" }
1021
+ })) ?? [];
1022
+ this.sendPrepareError(iframe, prepareResult.error);
1023
+ await this.waitForDialogClose(dialog, cleanup);
1024
+ return {
1025
+ success: false,
1026
+ results: failureResults,
1027
+ successCount: 0,
1028
+ failureCount: failureResults.length,
1029
+ error: prepareResult.error
1030
+ };
1031
+ }
1032
+ let prepareResponse = prepareResult.data;
1033
+ const dialogOrigin = this.getDialogOrigin();
1034
+ const batchInitPayload = {
1035
+ mode: "iframe",
1036
+ batchMode: true,
1037
+ batchIntents: prepareResponse.intents,
1038
+ batchFailedIntents: prepareResponse.failedIntents,
1039
+ challenge: prepareResponse.challenge,
1040
+ username: options.username,
1041
+ accountAddress: prepareResponse.accountAddress,
1042
+ userId: prepareResponse.userId,
1043
+ expiresAt: prepareResponse.expiresAt,
1044
+ tier: prepareResult.tier
1045
+ };
1046
+ dialogResult.sendInit(batchInitPayload);
1047
+ const handleBatchReReady = (event) => {
1048
+ if (event.origin !== dialogOrigin) return;
1049
+ if (event.data?.type === "PASSKEY_READY") {
1050
+ iframe.contentWindow?.postMessage(
1051
+ { type: "PASSKEY_INIT", ...batchInitPayload },
1052
+ dialogOrigin
1053
+ );
1054
+ }
1055
+ };
1056
+ window.addEventListener("message", handleBatchReReady);
1057
+ const batchResult = await new Promise((resolve) => {
1058
+ const handleMessage = async (event) => {
1059
+ if (event.origin !== dialogOrigin) return;
1060
+ const message = event.data;
1061
+ if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1062
+ console.log("[SDK] Batch dialog requested quote refresh, re-preparing all intents");
1063
+ try {
1064
+ const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
1065
+ method: "POST",
1066
+ headers: { "Content-Type": "application/json" },
1067
+ body: JSON.stringify(requestBody)
1068
+ });
1069
+ if (refreshResponse.ok) {
1070
+ const refreshed = await refreshResponse.json();
1071
+ prepareResponse = refreshed;
1072
+ iframe.contentWindow?.postMessage({
1073
+ type: "PASSKEY_REFRESH_COMPLETE",
1074
+ batchIntents: refreshed.intents,
1075
+ challenge: refreshed.challenge,
1076
+ expiresAt: refreshed.expiresAt
1077
+ }, dialogOrigin);
1078
+ } else {
1079
+ iframe.contentWindow?.postMessage({
1080
+ type: "PASSKEY_REFRESH_ERROR",
1081
+ error: "Failed to refresh batch quotes"
1082
+ }, dialogOrigin);
1083
+ }
1084
+ } catch {
1085
+ iframe.contentWindow?.postMessage({
1086
+ type: "PASSKEY_REFRESH_ERROR",
1087
+ error: "Failed to refresh batch quotes"
1088
+ }, dialogOrigin);
1089
+ }
1090
+ return;
1091
+ }
1092
+ if (message?.type === "PASSKEY_SIGNING_RESULT") {
1093
+ window.removeEventListener("message", handleMessage);
1094
+ if (message.success && message.data?.batchResults) {
1095
+ const rawResults = message.data.batchResults;
1096
+ const results = rawResults.map((r) => ({
1097
+ index: r.index,
1098
+ success: r.success ?? r.status !== "FAILED",
1099
+ intentId: r.intentId || r.operationId || "",
1100
+ status: r.status === "FAILED" ? "failed" : "pending",
1101
+ error: r.error ? { code: "EXECUTE_FAILED", message: r.error } : void 0
1102
+ }));
1103
+ const prepareFailures = (prepareResponse.failedIntents ?? []).map((f) => ({
1104
+ index: f.index,
1105
+ success: false,
1106
+ intentId: "",
1107
+ status: "failed",
1108
+ error: { code: "PREPARE_FAILED", message: f.error }
1109
+ }));
1110
+ const allResults = [...results, ...prepareFailures].sort((a, b) => a.index - b.index);
1111
+ const successCount = allResults.filter((r) => r.success).length;
1112
+ await this.waitForDialogClose(dialog, cleanup);
1113
+ resolve({
1114
+ success: successCount === allResults.length,
1115
+ results: allResults,
1116
+ successCount,
1117
+ failureCount: allResults.length - successCount
1118
+ });
1119
+ } else {
1120
+ cleanup();
1121
+ resolve({
1122
+ success: false,
1123
+ results: [],
1124
+ successCount: 0,
1125
+ failureCount: 0
1126
+ });
1127
+ }
1128
+ }
1129
+ if (message?.type === "PASSKEY_CLOSE") {
1130
+ window.removeEventListener("message", handleMessage);
1131
+ cleanup();
1132
+ resolve({
1133
+ success: false,
1134
+ results: [],
1135
+ successCount: 0,
1136
+ failureCount: 0
1137
+ });
1138
+ }
1139
+ };
1140
+ window.addEventListener("message", handleMessage);
1141
+ });
1142
+ window.removeEventListener("message", handleBatchReReady);
1143
+ return batchResult;
1144
+ }
632
1145
  /**
633
1146
  * Send transaction status to the dialog iframe
634
1147
  */
@@ -705,7 +1218,77 @@ var OneAuthClient = class {
705
1218
  const payload = message?.data;
706
1219
  if (message?.type === "PASSKEY_SIGNING_RESULT") {
707
1220
  window.removeEventListener("message", handleMessage);
708
- if (message.success && payload?.signature) {
1221
+ if (message.success && payload?.intentId) {
1222
+ resolve({
1223
+ success: true,
1224
+ intentId: payload.intentId
1225
+ });
1226
+ } else if (message.success && payload?.signature) {
1227
+ resolve({
1228
+ success: true,
1229
+ signature: payload.signature,
1230
+ passkey: payload.passkey,
1231
+ signedHash: payload.signedHash
1232
+ });
1233
+ } else {
1234
+ resolve({
1235
+ success: false,
1236
+ error: message.error || {
1237
+ code: "SIGNING_FAILED",
1238
+ message: "Signing failed"
1239
+ }
1240
+ });
1241
+ }
1242
+ } else if (message?.type === "PASSKEY_CLOSE") {
1243
+ window.removeEventListener("message", handleMessage);
1244
+ cleanup();
1245
+ resolve({
1246
+ success: false,
1247
+ error: {
1248
+ code: "USER_REJECTED",
1249
+ message: "User closed the dialog"
1250
+ }
1251
+ });
1252
+ }
1253
+ };
1254
+ window.addEventListener("message", handleMessage);
1255
+ });
1256
+ }
1257
+ /**
1258
+ * Wait for signing result with auto-refresh support
1259
+ * This method handles both signing results and quote refresh requests from the dialog
1260
+ */
1261
+ waitForSigningWithRefresh(dialog, iframe, cleanup, dialogOrigin, onRefresh) {
1262
+ console.log("[SDK] waitForSigningWithRefresh, expecting origin:", dialogOrigin);
1263
+ return new Promise((resolve) => {
1264
+ const handleMessage = async (event) => {
1265
+ if (event.origin !== dialogOrigin) return;
1266
+ const message = event.data;
1267
+ if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1268
+ console.log("[SDK] Received quote refresh request from dialog");
1269
+ const refreshedData = await onRefresh();
1270
+ if (refreshedData) {
1271
+ iframe.contentWindow?.postMessage({
1272
+ type: "PASSKEY_REFRESH_COMPLETE",
1273
+ ...refreshedData
1274
+ }, dialogOrigin);
1275
+ } else {
1276
+ iframe.contentWindow?.postMessage({
1277
+ type: "PASSKEY_REFRESH_ERROR",
1278
+ error: "Failed to refresh quote"
1279
+ }, dialogOrigin);
1280
+ }
1281
+ return;
1282
+ }
1283
+ const payload = message?.data;
1284
+ if (message?.type === "PASSKEY_SIGNING_RESULT") {
1285
+ window.removeEventListener("message", handleMessage);
1286
+ if (message.success && payload?.intentId) {
1287
+ resolve({
1288
+ success: true,
1289
+ intentId: payload.intentId
1290
+ });
1291
+ } else if (message.success && payload?.signature) {
709
1292
  resolve({
710
1293
  success: true,
711
1294
  signature: payload.signature,
@@ -760,6 +1343,143 @@ var OneAuthClient = class {
760
1343
  dialog.addEventListener("close", handleClose);
761
1344
  });
762
1345
  }
1346
+ /**
1347
+ * Inject a preconnect link tag to pre-warm DNS + TLS for a given URL.
1348
+ */
1349
+ injectPreconnect(url) {
1350
+ try {
1351
+ const origin = new URL(url).origin;
1352
+ if (document.querySelector(`link[rel="preconnect"][href="${origin}"]`)) return;
1353
+ const link = document.createElement("link");
1354
+ link.rel = "preconnect";
1355
+ link.href = origin;
1356
+ link.crossOrigin = "anonymous";
1357
+ document.head.appendChild(link);
1358
+ } catch {
1359
+ }
1360
+ }
1361
+ /**
1362
+ * Wait for the dialog iframe to signal ready without sending init data.
1363
+ * Returns a sendInit function the caller uses once prepare data is available.
1364
+ */
1365
+ waitForDialogReadyDeferred(dialog, iframe, cleanup) {
1366
+ const dialogOrigin = this.getDialogOrigin();
1367
+ return new Promise((resolve) => {
1368
+ let settled = false;
1369
+ const teardown = () => {
1370
+ if (settled) return;
1371
+ settled = true;
1372
+ clearTimeout(readyTimeout);
1373
+ window.removeEventListener("message", handleMessage);
1374
+ dialog.removeEventListener("close", handleClose);
1375
+ };
1376
+ const handleMessage = (event) => {
1377
+ if (event.origin !== dialogOrigin) return;
1378
+ if (event.data?.type === "PASSKEY_READY") {
1379
+ teardown();
1380
+ resolve({
1381
+ ready: true,
1382
+ sendInit: (initMessage) => {
1383
+ iframe.contentWindow?.postMessage(
1384
+ { type: "PASSKEY_INIT", ...initMessage },
1385
+ dialogOrigin
1386
+ );
1387
+ }
1388
+ });
1389
+ } else if (event.data?.type === "PASSKEY_CLOSE") {
1390
+ teardown();
1391
+ cleanup();
1392
+ resolve({ ready: false });
1393
+ }
1394
+ };
1395
+ const handleClose = () => {
1396
+ teardown();
1397
+ resolve({ ready: false });
1398
+ };
1399
+ const readyTimeout = setTimeout(() => {
1400
+ teardown();
1401
+ cleanup();
1402
+ resolve({ ready: false });
1403
+ }, 1e4);
1404
+ window.addEventListener("message", handleMessage);
1405
+ dialog.addEventListener("close", handleClose);
1406
+ });
1407
+ }
1408
+ /**
1409
+ * Prepare an intent by calling the orchestrator for a quote.
1410
+ * Returns the prepare response and the origin tier from the response header.
1411
+ */
1412
+ async prepareIntent(requestBody) {
1413
+ try {
1414
+ const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
1415
+ method: "POST",
1416
+ headers: { "Content-Type": "application/json" },
1417
+ body: JSON.stringify(requestBody)
1418
+ });
1419
+ if (!response.ok) {
1420
+ const errorData = await response.json().catch(() => ({}));
1421
+ const errorMessage = errorData.error || "Failed to prepare intent";
1422
+ if (errorMessage.includes("User not found")) {
1423
+ localStorage.removeItem("1auth-user");
1424
+ }
1425
+ return {
1426
+ success: false,
1427
+ error: {
1428
+ code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
1429
+ message: errorMessage
1430
+ }
1431
+ };
1432
+ }
1433
+ const tier = response.headers.get("X-Origin-Tier");
1434
+ return { success: true, data: await response.json(), tier };
1435
+ } catch (error) {
1436
+ return {
1437
+ success: false,
1438
+ error: {
1439
+ code: "NETWORK_ERROR",
1440
+ message: error instanceof Error ? error.message : "Network error"
1441
+ }
1442
+ };
1443
+ }
1444
+ }
1445
+ /**
1446
+ * Prepare a batch intent by calling the orchestrator for quotes on all intents.
1447
+ */
1448
+ async prepareBatchIntent(requestBody) {
1449
+ try {
1450
+ const response = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
1451
+ method: "POST",
1452
+ headers: { "Content-Type": "application/json" },
1453
+ body: JSON.stringify(requestBody)
1454
+ });
1455
+ if (!response.ok) {
1456
+ const errorData = await response.json().catch(() => ({}));
1457
+ const errorMessage = errorData.error || "Failed to prepare batch intent";
1458
+ if (errorMessage.includes("User not found")) {
1459
+ localStorage.removeItem("1auth-user");
1460
+ }
1461
+ return {
1462
+ success: false,
1463
+ error: errorMessage,
1464
+ failedIntents: errorData.failedIntents
1465
+ };
1466
+ }
1467
+ const tier = response.headers.get("X-Origin-Tier");
1468
+ return { success: true, data: await response.json(), tier };
1469
+ } catch {
1470
+ return { success: false, error: "Network error" };
1471
+ }
1472
+ }
1473
+ /**
1474
+ * Send a prepare error message to the dialog iframe.
1475
+ */
1476
+ sendPrepareError(iframe, error) {
1477
+ const dialogOrigin = this.getDialogOrigin();
1478
+ iframe.contentWindow?.postMessage(
1479
+ { type: "PASSKEY_PREPARE_ERROR", error },
1480
+ dialogOrigin
1481
+ );
1482
+ }
763
1483
  /**
764
1484
  * Poll for intent status
765
1485
  *
@@ -771,9 +1491,7 @@ var OneAuthClient = class {
771
1491
  const response = await fetch(
772
1492
  `${this.config.providerUrl}/api/intent/status/${intentId}`,
773
1493
  {
774
- headers: {
775
- "x-client-id": this.config.clientId
776
- }
1494
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
777
1495
  }
778
1496
  );
779
1497
  if (!response.ok) {
@@ -808,6 +1526,43 @@ var OneAuthClient = class {
808
1526
  };
809
1527
  }
810
1528
  }
1529
+ /**
1530
+ * Get the history of intents for the authenticated user.
1531
+ *
1532
+ * Requires an active session (user must be logged in).
1533
+ *
1534
+ * @example
1535
+ * ```typescript
1536
+ * // Get recent intents
1537
+ * const history = await client.getIntentHistory({ limit: 10 });
1538
+ *
1539
+ * // Filter by status
1540
+ * const pending = await client.getIntentHistory({ status: 'pending' });
1541
+ *
1542
+ * // Filter by date range
1543
+ * const lastWeek = await client.getIntentHistory({
1544
+ * from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
1545
+ * });
1546
+ * ```
1547
+ */
1548
+ async getIntentHistory(options) {
1549
+ const queryParams = new URLSearchParams();
1550
+ if (options?.limit) queryParams.set("limit", String(options.limit));
1551
+ if (options?.offset) queryParams.set("offset", String(options.offset));
1552
+ if (options?.status) queryParams.set("status", options.status);
1553
+ if (options?.from) queryParams.set("from", options.from);
1554
+ if (options?.to) queryParams.set("to", options.to);
1555
+ const url = `${this.config.providerUrl}/api/intent/history${queryParams.toString() ? `?${queryParams}` : ""}`;
1556
+ const response = await fetch(url, {
1557
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {},
1558
+ credentials: "include"
1559
+ });
1560
+ if (!response.ok) {
1561
+ const errorData = await response.json().catch(() => ({}));
1562
+ throw new Error(errorData.error || "Failed to get intent history");
1563
+ }
1564
+ return response.json();
1565
+ }
811
1566
  /**
812
1567
  * Send a swap intent through the Rhinestone orchestrator
813
1568
  *
@@ -869,18 +1624,22 @@ var OneAuthClient = class {
869
1624
  error: error instanceof Error ? error.message : `Unsupported ${label}: ${token} on chain ${options.targetChain}`
870
1625
  };
871
1626
  }
872
- };
873
- const fromTokenResult = resolveToken(options.fromToken, "fromToken");
874
- if (!fromTokenResult.address) {
875
- return {
876
- success: false,
877
- intentId: "",
878
- status: "failed",
879
- error: {
880
- code: "INVALID_TOKEN",
881
- message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
882
- }
883
- };
1627
+ };
1628
+ let fromTokenAddress;
1629
+ if (options.fromToken) {
1630
+ const fromTokenResult = resolveToken(options.fromToken, "fromToken");
1631
+ if (!fromTokenResult.address) {
1632
+ return {
1633
+ success: false,
1634
+ intentId: "",
1635
+ status: "failed",
1636
+ error: {
1637
+ code: "INVALID_TOKEN",
1638
+ message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
1639
+ }
1640
+ };
1641
+ }
1642
+ fromTokenAddress = fromTokenResult.address;
884
1643
  }
885
1644
  const toTokenResult = resolveToken(options.toToken, "toToken");
886
1645
  if (!toTokenResult.address) {
@@ -894,18 +1653,17 @@ var OneAuthClient = class {
894
1653
  }
895
1654
  };
896
1655
  }
897
- const fromTokenAddress = fromTokenResult.address;
898
1656
  const toTokenAddress = toTokenResult.address;
899
1657
  console.log("[SDK sendSwap] Token resolution:", {
900
- fromToken: options.fromToken,
901
- fromTokenAddress,
1658
+ fromToken: options.fromToken ?? "Any",
1659
+ fromTokenAddress: fromTokenAddress ?? "orchestrator picks",
902
1660
  toToken: options.toToken,
903
1661
  toTokenAddress,
904
1662
  targetChain: options.targetChain
905
1663
  });
906
1664
  const formatTokenLabel = (token, fallback) => {
907
1665
  if (!token.startsWith("0x")) {
908
- return token.toUpperCase();
1666
+ return token;
909
1667
  }
910
1668
  try {
911
1669
  return getTokenSymbol(token, options.targetChain);
@@ -913,15 +1671,11 @@ var OneAuthClient = class {
913
1671
  return fallback;
914
1672
  }
915
1673
  };
916
- const fromSymbol = formatTokenLabel(
917
- options.fromToken,
918
- `${options.fromToken.slice(0, 6)}...${options.fromToken.slice(-4)}`
919
- );
920
1674
  const toSymbol = formatTokenLabel(
921
1675
  options.toToken,
922
1676
  `${options.toToken.slice(0, 6)}...${options.toToken.slice(-4)}`
923
1677
  );
924
- const isFromNativeEth = fromTokenAddress === "0x0000000000000000000000000000000000000000";
1678
+ const isFromNativeEth = fromTokenAddress ? fromTokenAddress === "0x0000000000000000000000000000000000000000" : false;
925
1679
  const isToNativeEth = toTokenAddress === "0x0000000000000000000000000000000000000000";
926
1680
  const KNOWN_DECIMALS = {
927
1681
  ETH: 18,
@@ -931,31 +1685,33 @@ var OneAuthClient = class {
931
1685
  USDT0: 6
932
1686
  };
933
1687
  const getDecimals = (symbol, chainId) => {
934
- const upperSymbol = symbol.toUpperCase();
935
1688
  try {
936
- const decimals = (0, import_sdk.getTokenDecimals)(upperSymbol, chainId);
937
- console.log(`[SDK] getTokenDecimals(${upperSymbol}, ${chainId}) = ${decimals}`);
1689
+ const match = getSupportedTokens(chainId).find(
1690
+ (t) => t.symbol.toUpperCase() === symbol.toUpperCase()
1691
+ );
1692
+ if (match) {
1693
+ console.log(`[SDK] getTokenDecimals(${match.symbol}, ${chainId}) = ${match.decimals}`);
1694
+ return match.decimals;
1695
+ }
1696
+ const decimals = (0, import_sdk.getTokenDecimals)(symbol, chainId);
1697
+ console.log(`[SDK] getTokenDecimals(${symbol}, ${chainId}) = ${decimals}`);
938
1698
  return decimals;
939
1699
  } catch (e) {
940
- console.warn(`[SDK] getTokenDecimals failed for ${upperSymbol}, using fallback`, e);
1700
+ const upperSymbol = symbol.toUpperCase();
1701
+ console.warn(`[SDK] getTokenDecimals failed for ${symbol}, using fallback`, e);
941
1702
  return KNOWN_DECIMALS[upperSymbol] ?? 18;
942
1703
  }
943
1704
  };
944
- const fromDecimals = getDecimals(options.fromToken, options.targetChain);
945
1705
  const toDecimals = getDecimals(options.toToken, options.targetChain);
946
- const isBridge = options.fromToken.toUpperCase() === options.toToken.toUpperCase();
947
- let tokenRequests;
948
- if (!isToNativeEth) {
949
- tokenRequests = [{
950
- token: toTokenAddress,
951
- amount: (0, import_viem2.parseUnits)(options.amount, toDecimals).toString()
952
- }];
953
- }
1706
+ const isBridge = options.fromToken ? options.fromToken.toUpperCase() === options.toToken.toUpperCase() : false;
1707
+ const tokenRequests = [{
1708
+ token: toTokenAddress,
1709
+ amount: (0, import_viem2.parseUnits)(options.amount, toDecimals)
1710
+ }];
954
1711
  console.log("[SDK sendSwap] Building intent:", {
955
1712
  isBridge,
956
1713
  isFromNativeEth,
957
1714
  isToNativeEth,
958
- fromDecimals,
959
1715
  toDecimals,
960
1716
  tokenRequests
961
1717
  });
@@ -966,15 +1722,20 @@ var OneAuthClient = class {
966
1722
  {
967
1723
  // Minimal call - just signals to orchestrator we want the tokenRequests delivered
968
1724
  to: toTokenAddress,
969
- value: "0"
1725
+ value: "0",
1726
+ // SDK provides labels so dialog shows "Buy ETH" not "Send ETH / To: 0x000..."
1727
+ label: `Buy ${toSymbol}`,
1728
+ sublabel: `${options.amount} ${toSymbol}`
970
1729
  }
971
1730
  ],
972
1731
  // Request specific output tokens - this is what actually matters for swaps
973
1732
  tokenRequests,
974
1733
  // Constrain orchestrator to use only the fromToken as input
975
1734
  // This ensures the swap uses the correct source token
976
- // Pass the symbol (not address) so orchestrator can resolve per-chain
977
- sourceAssets: options.sourceAssets || [options.fromToken.toUpperCase()],
1735
+ // Use canonical symbol casing from registry (e.g. "MockUSD" not "MOCKUSD")
1736
+ sourceAssets: options.sourceAssets || (options.fromToken ? [options.fromToken] : void 0),
1737
+ // Pass source chain ID so orchestrator knows which chain to look for tokens on
1738
+ sourceChainId: options.sourceChainId,
978
1739
  closeOn: options.closeOn || "preconfirmed",
979
1740
  waitForHash: options.waitForHash,
980
1741
  hashTimeoutMs: options.hashTimeoutMs,
@@ -983,7 +1744,7 @@ var OneAuthClient = class {
983
1744
  return {
984
1745
  ...result,
985
1746
  quote: result.success ? {
986
- fromToken: fromTokenAddress,
1747
+ fromToken: fromTokenAddress ?? options.fromToken,
987
1748
  toToken: toTokenAddress,
988
1749
  amountIn: options.amount,
989
1750
  amountOut: "",
@@ -1025,26 +1786,24 @@ var OneAuthClient = class {
1025
1786
  const themeParams = this.getThemeParams(options?.theme);
1026
1787
  const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
1027
1788
  const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
1028
- const dialogOrigin = this.getDialogOrigin();
1029
- await new Promise((resolve) => {
1030
- const handleReady = (event) => {
1031
- if (event.origin !== dialogOrigin) return;
1032
- if (event.data?.type === "PASSKEY_READY") {
1033
- window.removeEventListener("message", handleReady);
1034
- iframe.contentWindow?.postMessage({
1035
- type: "PASSKEY_INIT",
1036
- mode: "iframe",
1037
- message: options.message,
1038
- challenge: options.challenge || options.message,
1039
- username: options.username,
1040
- description: options.description,
1041
- metadata: options.metadata
1042
- }, dialogOrigin);
1043
- resolve();
1789
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
1790
+ mode: "iframe",
1791
+ message: options.message,
1792
+ challenge: options.challenge || options.message,
1793
+ username: options.username,
1794
+ accountAddress: options.accountAddress,
1795
+ description: options.description,
1796
+ metadata: options.metadata
1797
+ });
1798
+ if (!ready) {
1799
+ return {
1800
+ success: false,
1801
+ error: {
1802
+ code: "USER_REJECTED",
1803
+ message: "User closed the dialog"
1044
1804
  }
1045
1805
  };
1046
- window.addEventListener("message", handleReady);
1047
- });
1806
+ }
1048
1807
  const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
1049
1808
  cleanup();
1050
1809
  if (signingResult.success) {
@@ -1113,31 +1872,29 @@ var OneAuthClient = class {
1113
1872
  const themeParams = this.getThemeParams(options?.theme);
1114
1873
  const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
1115
1874
  const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
1116
- const dialogOrigin = this.getDialogOrigin();
1117
- await new Promise((resolve) => {
1118
- const handleReady = (event) => {
1119
- if (event.origin !== dialogOrigin) return;
1120
- if (event.data?.type === "PASSKEY_READY") {
1121
- window.removeEventListener("message", handleReady);
1122
- iframe.contentWindow?.postMessage({
1123
- type: "PASSKEY_INIT",
1124
- mode: "iframe",
1125
- signingMode: "typedData",
1126
- typedData: {
1127
- domain: options.domain,
1128
- types: options.types,
1129
- primaryType: options.primaryType,
1130
- message: options.message
1131
- },
1132
- challenge: signedHash,
1133
- username: options.username,
1134
- description: options.description
1135
- }, dialogOrigin);
1136
- resolve();
1875
+ const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
1876
+ mode: "iframe",
1877
+ signingMode: "typedData",
1878
+ typedData: {
1879
+ domain: options.domain,
1880
+ types: options.types,
1881
+ primaryType: options.primaryType,
1882
+ message: options.message
1883
+ },
1884
+ challenge: signedHash,
1885
+ username: options.username,
1886
+ accountAddress: options.accountAddress,
1887
+ description: options.description
1888
+ });
1889
+ if (!ready) {
1890
+ return {
1891
+ success: false,
1892
+ error: {
1893
+ code: "USER_REJECTED",
1894
+ message: "User closed the dialog"
1137
1895
  }
1138
1896
  };
1139
- window.addEventListener("message", handleReady);
1140
- });
1897
+ }
1141
1898
  const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
1142
1899
  cleanup();
1143
1900
  if (signingResult.success) {
@@ -1200,7 +1957,7 @@ var OneAuthClient = class {
1200
1957
  iframe.style.borderRadius = "12px";
1201
1958
  iframe.style.boxShadow = "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)";
1202
1959
  iframe.id = `passkey-embed-${requestId}`;
1203
- iframe.allow = "publickey-credentials-get *; publickey-credentials-create *";
1960
+ iframe.allow = "publickey-credentials-get *; publickey-credentials-create *; identity-credentials-get";
1204
1961
  iframe.onload = () => {
1205
1962
  options.onReady?.();
1206
1963
  };
@@ -1291,9 +2048,7 @@ var OneAuthClient = class {
1291
2048
  const response = await fetch(
1292
2049
  `${this.config.providerUrl}/api/users/${encodeURIComponent(username)}/passkeys`,
1293
2050
  {
1294
- headers: {
1295
- "x-client-id": this.config.clientId
1296
- }
2051
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
1297
2052
  }
1298
2053
  );
1299
2054
  if (!response.ok) {
@@ -1312,7 +2067,7 @@ var OneAuthClient = class {
1312
2067
  "Content-Type": "application/json"
1313
2068
  },
1314
2069
  body: JSON.stringify({
1315
- clientId: this.config.clientId,
2070
+ ...this.config.clientId && { clientId: this.config.clientId },
1316
2071
  username: options.username,
1317
2072
  challenge: options.challenge,
1318
2073
  description: options.description,
@@ -1338,6 +2093,50 @@ var OneAuthClient = class {
1338
2093
  `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
1339
2094
  );
1340
2095
  }
2096
+ /**
2097
+ * Wait for the dialog iframe to signal ready, then send init data.
2098
+ * Also handles early close (X button, escape, backdrop) during the ready phase.
2099
+ * Returns true if dialog is ready, false if it was closed before becoming ready.
2100
+ */
2101
+ waitForDialogReady(dialog, iframe, cleanup, initMessage) {
2102
+ const dialogOrigin = this.getDialogOrigin();
2103
+ return new Promise((resolve) => {
2104
+ let settled = false;
2105
+ const teardown = () => {
2106
+ if (settled) return;
2107
+ settled = true;
2108
+ clearTimeout(readyTimeout);
2109
+ window.removeEventListener("message", handleMessage);
2110
+ dialog.removeEventListener("close", handleClose);
2111
+ };
2112
+ const handleMessage = (event) => {
2113
+ if (event.origin !== dialogOrigin) return;
2114
+ if (event.data?.type === "PASSKEY_READY") {
2115
+ teardown();
2116
+ iframe.contentWindow?.postMessage({
2117
+ type: "PASSKEY_INIT",
2118
+ ...initMessage
2119
+ }, dialogOrigin);
2120
+ resolve(true);
2121
+ } else if (event.data?.type === "PASSKEY_CLOSE") {
2122
+ teardown();
2123
+ cleanup();
2124
+ resolve(false);
2125
+ }
2126
+ };
2127
+ const handleClose = () => {
2128
+ teardown();
2129
+ resolve(false);
2130
+ };
2131
+ const readyTimeout = setTimeout(() => {
2132
+ teardown();
2133
+ cleanup();
2134
+ resolve(false);
2135
+ }, 1e4);
2136
+ window.addEventListener("message", handleMessage);
2137
+ dialog.addEventListener("close", handleClose);
2138
+ });
2139
+ }
1341
2140
  /**
1342
2141
  * Create a modal dialog with an iframe inside.
1343
2142
  */
@@ -1374,7 +2173,9 @@ var OneAuthClient = class {
1374
2173
  border-radius: 14px;
1375
2174
  border: none;
1376
2175
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
1377
- transition: width 0.2s ease-out, height 0.15s ease-out;
2176
+ transition: height 0.15s ease-out;
2177
+ max-height: calc(100vh - 100px);
2178
+ max-height: calc(100dvh - 100px);
1378
2179
  }
1379
2180
 
1380
2181
  @media (min-width: 769px) {
@@ -1436,14 +2237,10 @@ var OneAuthClient = class {
1436
2237
  const iframe = document.createElement("iframe");
1437
2238
  iframe.setAttribute(
1438
2239
  "allow",
1439
- "publickey-credentials-get *; publickey-credentials-create *; clipboard-write"
2240
+ "publickey-credentials-get *; publickey-credentials-create *; clipboard-write; identity-credentials-get"
1440
2241
  );
1441
2242
  iframe.setAttribute("aria-label", "Passkey Authentication");
1442
2243
  iframe.setAttribute("tabindex", "0");
1443
- iframe.setAttribute(
1444
- "sandbox",
1445
- "allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
1446
- );
1447
2244
  iframe.setAttribute("src", url);
1448
2245
  iframe.setAttribute("title", "Passkey");
1449
2246
  iframe.style.border = "none";
@@ -1453,10 +2250,8 @@ var OneAuthClient = class {
1453
2250
  const handleMessage = (event) => {
1454
2251
  if (event.origin !== hostUrl.origin) return;
1455
2252
  if (event.data?.type === "PASSKEY_RESIZE") {
1456
- iframe.style.height = `${event.data.height}px`;
1457
- if (event.data.width) {
1458
- iframe.style.width = `${event.data.width}px`;
1459
- }
2253
+ const maxHeight = window.innerHeight - 100;
2254
+ iframe.style.height = `${Math.min(event.data.height, maxHeight)}px`;
1460
2255
  } else if (event.data?.type === "PASSKEY_DISCONNECT") {
1461
2256
  localStorage.removeItem("1auth-user");
1462
2257
  }
@@ -1474,7 +2269,10 @@ var OneAuthClient = class {
1474
2269
  }
1475
2270
  });
1476
2271
  dialog.showModal();
2272
+ let cleanedUp = false;
1477
2273
  const cleanup = () => {
2274
+ if (cleanedUp) return;
2275
+ cleanedUp = true;
1478
2276
  window.removeEventListener("message", handleMessage);
1479
2277
  document.removeEventListener("keydown", handleEscape);
1480
2278
  dialog.close();
@@ -1482,12 +2280,24 @@ var OneAuthClient = class {
1482
2280
  };
1483
2281
  return { dialog, iframe, cleanup };
1484
2282
  }
1485
- waitForModalAuthResponse(_dialog, _iframe, cleanup) {
2283
+ waitForModalAuthResponse(_dialog, iframe, cleanup) {
1486
2284
  const dialogOrigin = this.getDialogOrigin();
1487
2285
  return new Promise((resolve) => {
2286
+ let dialogReady = false;
1488
2287
  const handleMessage = (event) => {
1489
2288
  if (event.origin !== dialogOrigin) return;
1490
2289
  const data = event.data;
2290
+ if (data?.type === "PASSKEY_READY") {
2291
+ dialogReady = true;
2292
+ iframe.contentWindow?.postMessage({
2293
+ type: "PASSKEY_INIT",
2294
+ mode: "iframe"
2295
+ }, dialogOrigin);
2296
+ return;
2297
+ }
2298
+ if (!dialogReady && data?.type === "PASSKEY_CLOSE") {
2299
+ return;
2300
+ }
1491
2301
  if (data?.type === "PASSKEY_LOGIN_RESULT") {
1492
2302
  window.removeEventListener("message", handleMessage);
1493
2303
  cleanup();
@@ -1495,6 +2305,7 @@ var OneAuthClient = class {
1495
2305
  resolve({
1496
2306
  success: true,
1497
2307
  username: data.data?.username,
2308
+ address: data.data?.address,
1498
2309
  user: data.data?.user
1499
2310
  });
1500
2311
  } else {
@@ -1509,7 +2320,8 @@ var OneAuthClient = class {
1509
2320
  if (data.success) {
1510
2321
  resolve({
1511
2322
  success: true,
1512
- username: data.data?.username
2323
+ username: data.data?.username,
2324
+ address: data.data?.address
1513
2325
  });
1514
2326
  } else {
1515
2327
  resolve({
@@ -1517,6 +2329,11 @@ var OneAuthClient = class {
1517
2329
  error: data.error
1518
2330
  });
1519
2331
  }
2332
+ } else if (data?.type === "PASSKEY_RETRY_POPUP") {
2333
+ window.removeEventListener("message", handleMessage);
2334
+ cleanup();
2335
+ const popupUrl = data.data?.url?.replace("mode=iframe", "mode=popup") || `${this.getDialogUrl()}/dialog/auth?mode=popup${this.config.clientId ? `&clientId=${this.config.clientId}` : ""}`;
2336
+ this.waitForPopupAuthResponse(popupUrl).then(resolve);
1520
2337
  } else if (data?.type === "PASSKEY_CLOSE") {
1521
2338
  window.removeEventListener("message", handleMessage);
1522
2339
  cleanup();
@@ -1532,6 +2349,79 @@ var OneAuthClient = class {
1532
2349
  window.addEventListener("message", handleMessage);
1533
2350
  });
1534
2351
  }
2352
+ /**
2353
+ * Open a popup for auth and wait for the result.
2354
+ * Used when iframe mode fails (e.g., due to password manager interference).
2355
+ */
2356
+ waitForPopupAuthResponse(url) {
2357
+ const dialogOrigin = this.getDialogOrigin();
2358
+ const popup = this.openPopup(url);
2359
+ return new Promise((resolve) => {
2360
+ const pollTimer = setInterval(() => {
2361
+ if (popup?.closed) {
2362
+ clearInterval(pollTimer);
2363
+ window.removeEventListener("message", handleMessage);
2364
+ resolve({
2365
+ success: false,
2366
+ error: {
2367
+ code: "USER_CANCELLED",
2368
+ message: "Authentication was cancelled"
2369
+ }
2370
+ });
2371
+ }
2372
+ }, 500);
2373
+ const handleMessage = (event) => {
2374
+ if (event.origin !== dialogOrigin) return;
2375
+ const data = event.data;
2376
+ if (data?.type === "PASSKEY_LOGIN_RESULT") {
2377
+ clearInterval(pollTimer);
2378
+ window.removeEventListener("message", handleMessage);
2379
+ popup?.close();
2380
+ if (data.success) {
2381
+ resolve({
2382
+ success: true,
2383
+ username: data.data?.username,
2384
+ address: data.data?.address,
2385
+ user: data.data?.user
2386
+ });
2387
+ } else {
2388
+ resolve({
2389
+ success: false,
2390
+ error: data.error
2391
+ });
2392
+ }
2393
+ } else if (data?.type === "PASSKEY_REGISTER_RESULT") {
2394
+ clearInterval(pollTimer);
2395
+ window.removeEventListener("message", handleMessage);
2396
+ popup?.close();
2397
+ if (data.success) {
2398
+ resolve({
2399
+ success: true,
2400
+ username: data.data?.username,
2401
+ address: data.data?.address
2402
+ });
2403
+ } else {
2404
+ resolve({
2405
+ success: false,
2406
+ error: data.error
2407
+ });
2408
+ }
2409
+ } else if (data?.type === "PASSKEY_CLOSE") {
2410
+ clearInterval(pollTimer);
2411
+ window.removeEventListener("message", handleMessage);
2412
+ popup?.close();
2413
+ resolve({
2414
+ success: false,
2415
+ error: {
2416
+ code: "USER_CANCELLED",
2417
+ message: "Authentication was cancelled"
2418
+ }
2419
+ });
2420
+ }
2421
+ };
2422
+ window.addEventListener("message", handleMessage);
2423
+ });
2424
+ }
1535
2425
  waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
1536
2426
  const dialogOrigin = this.getDialogOrigin();
1537
2427
  return new Promise((resolve) => {
@@ -1571,6 +2461,84 @@ var OneAuthClient = class {
1571
2461
  window.addEventListener("message", handleMessage);
1572
2462
  });
1573
2463
  }
2464
+ waitForConnectResponse(_dialog, _iframe, cleanup) {
2465
+ const dialogOrigin = this.getDialogOrigin();
2466
+ return new Promise((resolve) => {
2467
+ const handleMessage = (event) => {
2468
+ if (event.origin !== dialogOrigin) return;
2469
+ const data = event.data;
2470
+ if (data?.type === "PASSKEY_CONNECT_RESULT") {
2471
+ window.removeEventListener("message", handleMessage);
2472
+ cleanup();
2473
+ if (data.success) {
2474
+ resolve({
2475
+ success: true,
2476
+ username: data.data?.username,
2477
+ address: data.data?.address,
2478
+ autoConnected: data.data?.autoConnected
2479
+ });
2480
+ } else {
2481
+ resolve({
2482
+ success: false,
2483
+ action: data.data?.action,
2484
+ error: data.error
2485
+ });
2486
+ }
2487
+ } else if (data?.type === "PASSKEY_CLOSE") {
2488
+ window.removeEventListener("message", handleMessage);
2489
+ cleanup();
2490
+ resolve({
2491
+ success: false,
2492
+ action: "cancel",
2493
+ error: {
2494
+ code: "USER_CANCELLED",
2495
+ message: "Connection was cancelled"
2496
+ }
2497
+ });
2498
+ }
2499
+ };
2500
+ window.addEventListener("message", handleMessage);
2501
+ });
2502
+ }
2503
+ waitForConsentResponse(_dialog, _iframe, cleanup) {
2504
+ const dialogOrigin = this.getDialogOrigin();
2505
+ return new Promise((resolve) => {
2506
+ const handleMessage = (event) => {
2507
+ if (event.origin !== dialogOrigin) return;
2508
+ const data = event.data;
2509
+ if (data?.type === "PASSKEY_CONSENT_RESULT") {
2510
+ window.removeEventListener("message", handleMessage);
2511
+ cleanup();
2512
+ if (data.success) {
2513
+ resolve({
2514
+ success: true,
2515
+ data: data.data,
2516
+ grantedAt: data.data?.grantedAt
2517
+ });
2518
+ } else {
2519
+ resolve({
2520
+ success: false,
2521
+ error: data.error ?? {
2522
+ code: "USER_REJECTED",
2523
+ message: "User denied the consent request"
2524
+ }
2525
+ });
2526
+ }
2527
+ } else if (data?.type === "PASSKEY_CLOSE") {
2528
+ window.removeEventListener("message", handleMessage);
2529
+ cleanup();
2530
+ resolve({
2531
+ success: false,
2532
+ error: {
2533
+ code: "USER_CANCELLED",
2534
+ message: "User closed the dialog"
2535
+ }
2536
+ });
2537
+ }
2538
+ };
2539
+ window.addEventListener("message", handleMessage);
2540
+ });
2541
+ }
1574
2542
  waitForModalSigningResponse(requestId, _dialog, _iframe, cleanup) {
1575
2543
  const dialogOrigin = this.getDialogOrigin();
1576
2544
  return new Promise((resolve) => {
@@ -1659,9 +2627,7 @@ var OneAuthClient = class {
1659
2627
  const response = await fetch(
1660
2628
  `${this.config.providerUrl}/api/sign/request/${requestId}`,
1661
2629
  {
1662
- headers: {
1663
- "x-client-id": this.config.clientId
1664
- }
2630
+ headers: this.config.clientId ? { "x-client-id": this.config.clientId } : {}
1665
2631
  }
1666
2632
  );
1667
2633
  if (!response.ok) {
@@ -1765,7 +2731,7 @@ function buildTransactionReview(calls) {
1765
2731
 
1766
2732
  // src/provider.ts
1767
2733
  var DEFAULT_STORAGE_KEY = "1auth-user";
1768
- function createPasskeyProvider(options) {
2734
+ function createOneAuthProvider(options) {
1769
2735
  const { client } = options;
1770
2736
  let chainId = options.chainId;
1771
2737
  const storageKey = options.storageKey || DEFAULT_STORAGE_KEY;
@@ -1781,7 +2747,7 @@ function createPasskeyProvider(options) {
1781
2747
  const raw = localStorage.getItem(storageKey);
1782
2748
  if (!raw) return null;
1783
2749
  const parsed = JSON.parse(raw);
1784
- if (!parsed?.username || !parsed?.address) return null;
2750
+ if (!parsed?.address) return null;
1785
2751
  return parsed;
1786
2752
  } catch {
1787
2753
  return null;
@@ -1796,12 +2762,11 @@ function createPasskeyProvider(options) {
1796
2762
  localStorage.removeItem(storageKey);
1797
2763
  };
1798
2764
  const resolveAccountAddress = async (username) => {
2765
+ const clientId = client.getClientId();
1799
2766
  const response = await fetch(
1800
2767
  `${client.getProviderUrl()}/api/users/${encodeURIComponent(username)}/account`,
1801
2768
  {
1802
- headers: {
1803
- "x-client-id": client.getClientId()
1804
- }
2769
+ headers: clientId ? { "x-client-id": clientId } : {}
1805
2770
  }
1806
2771
  );
1807
2772
  if (!response.ok) {
@@ -1816,12 +2781,29 @@ function createPasskeyProvider(options) {
1816
2781
  if (stored) {
1817
2782
  return [stored.address];
1818
2783
  }
1819
- const result = await client.authWithModal();
1820
- if (!result.success || !result.username) {
1821
- throw new Error(result.error?.message || "Authentication failed");
2784
+ const connectResult = await client.connectWithModal();
2785
+ let username;
2786
+ let address;
2787
+ if (connectResult.success) {
2788
+ username = connectResult.username;
2789
+ address = connectResult.address;
2790
+ } else if (connectResult.action === "switch") {
2791
+ const authResult = await client.authWithModal();
2792
+ if (!authResult.success) {
2793
+ throw new Error(authResult.error?.message || "Authentication failed");
2794
+ }
2795
+ username = authResult.username;
2796
+ address = authResult.address;
2797
+ } else {
2798
+ throw new Error(connectResult.error?.message || "Connection cancelled");
2799
+ }
2800
+ if (!address && username) {
2801
+ address = await resolveAccountAddress(username);
1822
2802
  }
1823
- const address = await resolveAccountAddress(result.username);
1824
- setStoredUser({ username: result.username, address });
2803
+ if (!address) {
2804
+ throw new Error("No account address available");
2805
+ }
2806
+ setStoredUser({ username, address });
1825
2807
  emit("accountsChanged", [address]);
1826
2808
  emit("connect", { chainId: (0, import_viem4.numberToHex)(chainId) });
1827
2809
  return [address];
@@ -1835,11 +2817,11 @@ function createPasskeyProvider(options) {
1835
2817
  const stored = getStoredUser();
1836
2818
  if (stored) return stored;
1837
2819
  const [address] = await connect();
1838
- const username = getStoredUser()?.username;
1839
- if (!username || !address) {
2820
+ if (!address) {
1840
2821
  throw new Error("Failed to resolve user session");
1841
2822
  }
1842
- return { username, address };
2823
+ const user = getStoredUser();
2824
+ return user || { address };
1843
2825
  };
1844
2826
  const parseChainId = (value) => {
1845
2827
  if (typeof value === "number") return value;
@@ -1878,6 +2860,16 @@ function createPasskeyProvider(options) {
1878
2860
  };
1879
2861
  });
1880
2862
  };
2863
+ const normalizeTokenRequests = (requests) => {
2864
+ if (!Array.isArray(requests)) return void 0;
2865
+ return requests.map((r) => {
2866
+ const req = r;
2867
+ return {
2868
+ token: req.token,
2869
+ amount: typeof req.amount === "bigint" ? req.amount : BigInt(String(req.amount || "0"))
2870
+ };
2871
+ });
2872
+ };
1881
2873
  const decodeMessage = (value) => {
1882
2874
  if (!(0, import_viem4.isHex)(value)) return value;
1883
2875
  try {
@@ -1887,9 +2879,13 @@ function createPasskeyProvider(options) {
1887
2879
  }
1888
2880
  };
1889
2881
  const signMessage = async (message) => {
1890
- const { username } = await ensureUser();
2882
+ const user = await ensureUser();
2883
+ if (!user.username && !user.address) {
2884
+ throw new Error("Username or address required for signing.");
2885
+ }
1891
2886
  const result = await client.signMessage({
1892
- username,
2887
+ username: user.username,
2888
+ accountAddress: user.address,
1893
2889
  message
1894
2890
  });
1895
2891
  if (!result.success || !result.signature) {
@@ -1898,10 +2894,14 @@ function createPasskeyProvider(options) {
1898
2894
  return encodeWebAuthnSignature(result.signature);
1899
2895
  };
1900
2896
  const signTypedData = async (typedData) => {
1901
- const { username } = await ensureUser();
2897
+ const user = await ensureUser();
2898
+ if (!user.username && !user.address) {
2899
+ throw new Error("Username or address required for signing.");
2900
+ }
1902
2901
  const data = typeof typedData === "string" ? JSON.parse(typedData) : typedData;
1903
2902
  const result = await client.signTypedData({
1904
- username,
2903
+ username: user.username,
2904
+ accountAddress: user.address,
1905
2905
  domain: data.domain,
1906
2906
  types: data.types,
1907
2907
  primaryType: data.primaryType,
@@ -1916,32 +2916,40 @@ function createPasskeyProvider(options) {
1916
2916
  if (!options.signIntent) {
1917
2917
  return {
1918
2918
  username: payload.username,
2919
+ accountAddress: payload.accountAddress,
1919
2920
  targetChain: payload.targetChain,
1920
- calls: payload.calls
2921
+ calls: payload.calls,
2922
+ tokenRequests: payload.tokenRequests
1921
2923
  };
1922
2924
  }
2925
+ if (!payload.username) {
2926
+ throw new Error("Username required for signed intents. Set a username first.");
2927
+ }
1923
2928
  const signedIntent = await options.signIntent({
1924
2929
  username: payload.username,
1925
2930
  accountAddress: payload.accountAddress,
1926
2931
  targetChain: payload.targetChain,
1927
- calls: payload.calls
2932
+ calls: payload.calls,
2933
+ tokenRequests: payload.tokenRequests
1928
2934
  });
1929
2935
  return { signedIntent };
1930
2936
  };
1931
2937
  const sendIntent = async (payload) => {
1932
- const closeOn = options.waitForHash ?? true ? "completed" : "preconfirmed";
2938
+ const closeOn = options.closeOn ?? (options.waitForHash ?? true ? "completed" : "preconfirmed");
1933
2939
  const intentPayload = await resolveIntentPayload(payload);
1934
2940
  const result = await client.sendIntent({
1935
2941
  ...intentPayload,
2942
+ tokenRequests: payload.tokenRequests,
2943
+ sourceChainId: payload.sourceChainId,
1936
2944
  closeOn,
1937
2945
  waitForHash: options.waitForHash ?? true,
1938
2946
  hashTimeoutMs: options.hashTimeoutMs,
1939
2947
  hashIntervalMs: options.hashIntervalMs
1940
2948
  });
1941
- if (!result.success || !result.transactionHash) {
2949
+ if (!result.success) {
1942
2950
  throw new Error(result.error?.message || "Transaction failed");
1943
2951
  }
1944
- return result.transactionHash;
2952
+ return result.intentId;
1945
2953
  };
1946
2954
  const request = async ({ method, params }) => {
1947
2955
  switch (method) {
@@ -1994,11 +3002,15 @@ function createPasskeyProvider(options) {
1994
3002
  const user = await ensureUser();
1995
3003
  const targetChain = parseChainId(tx.chainId) ?? chainId;
1996
3004
  const calls = normalizeCalls([tx]);
3005
+ const tokenRequests = normalizeTokenRequests(tx.tokenRequests);
3006
+ const txSourceChainId = parseChainId(tx.sourceChainId);
1997
3007
  return sendIntent({
1998
3008
  username: user.username,
1999
3009
  accountAddress: user.address,
2000
3010
  targetChain,
2001
- calls
3011
+ calls,
3012
+ tokenRequests,
3013
+ sourceChainId: txSourceChainId
2002
3014
  });
2003
3015
  }
2004
3016
  case "wallet_sendCalls": {
@@ -2007,22 +3019,131 @@ function createPasskeyProvider(options) {
2007
3019
  const user = await ensureUser();
2008
3020
  const targetChain = parseChainId(payload.chainId) ?? chainId;
2009
3021
  const calls = normalizeCalls(payload.calls || []);
3022
+ const tokenRequests = normalizeTokenRequests(payload.tokenRequests);
3023
+ const sourceChainId = parseChainId(payload.sourceChainId);
2010
3024
  if (!calls.length) throw new Error("No calls provided");
2011
3025
  return sendIntent({
2012
3026
  username: user.username,
2013
3027
  accountAddress: user.address,
2014
3028
  targetChain,
2015
- calls
3029
+ calls,
3030
+ tokenRequests,
3031
+ sourceChainId
2016
3032
  });
2017
3033
  }
2018
3034
  case "wallet_getCapabilities": {
3035
+ const paramList = Array.isArray(params) ? params : [];
3036
+ const requestedChains = paramList[1];
2019
3037
  const chainIds = getSupportedChainIds();
2020
- const tokensByChain = Object.fromEntries(
2021
- chainIds.map((id) => [id, getSupportedTokens(id)])
3038
+ const capabilities = {};
3039
+ for (const chainId2 of chainIds) {
3040
+ const hexChainId = `0x${chainId2.toString(16)}`;
3041
+ if (requestedChains && !requestedChains.includes(hexChainId)) {
3042
+ continue;
3043
+ }
3044
+ capabilities[hexChainId] = {
3045
+ atomic: { status: "supported" },
3046
+ paymasterService: { supported: true },
3047
+ auxiliaryFunds: { supported: true }
3048
+ };
3049
+ }
3050
+ return capabilities;
3051
+ }
3052
+ case "wallet_getAssets": {
3053
+ const user = await ensureUser();
3054
+ if (!user.username) {
3055
+ throw new Error("Username required to fetch assets. Set a username first.");
3056
+ }
3057
+ const clientId = client.getClientId();
3058
+ const response = await fetch(
3059
+ `${client.getProviderUrl()}/api/users/${encodeURIComponent(user.username)}/portfolio`,
3060
+ {
3061
+ headers: clientId ? { "x-client-id": clientId } : {}
3062
+ }
3063
+ );
3064
+ if (!response.ok) {
3065
+ const data = await response.json().catch(() => ({}));
3066
+ throw new Error(data.error || "Failed to get assets");
3067
+ }
3068
+ return response.json();
3069
+ }
3070
+ case "wallet_getCallsStatus": {
3071
+ const paramList = Array.isArray(params) ? params : [];
3072
+ const callsId = paramList[0];
3073
+ if (!callsId) {
3074
+ throw new Error("callsId is required");
3075
+ }
3076
+ const statusClientId = client.getClientId();
3077
+ const response = await fetch(
3078
+ `${client.getProviderUrl()}/api/intent/status/${encodeURIComponent(callsId)}`,
3079
+ {
3080
+ headers: statusClientId ? { "x-client-id": statusClientId } : {}
3081
+ }
2022
3082
  );
3083
+ if (!response.ok) {
3084
+ const data2 = await response.json().catch(() => ({}));
3085
+ throw new Error(data2.error || "Failed to get calls status");
3086
+ }
3087
+ const data = await response.json();
3088
+ const statusMap = {
3089
+ pending: "PENDING",
3090
+ preconfirmed: "PENDING",
3091
+ completed: "CONFIRMED",
3092
+ failed: "CONFIRMED",
3093
+ expired: "CONFIRMED"
3094
+ };
3095
+ return {
3096
+ status: statusMap[data.status] || "PENDING",
3097
+ receipts: data.transactionHash ? [
3098
+ {
3099
+ logs: [],
3100
+ status: data.status === "completed" ? "0x1" : "0x0",
3101
+ blockHash: data.blockHash,
3102
+ blockNumber: data.blockNumber,
3103
+ transactionHash: data.transactionHash
3104
+ }
3105
+ ] : []
3106
+ };
3107
+ }
3108
+ case "wallet_getCallsHistory": {
3109
+ const paramList = Array.isArray(params) ? params : [];
3110
+ const options2 = paramList[0] || {};
3111
+ const queryParams = new URLSearchParams();
3112
+ if (options2.limit) queryParams.set("limit", String(options2.limit));
3113
+ if (options2.offset) queryParams.set("offset", String(options2.offset));
3114
+ if (options2.status) queryParams.set("status", options2.status);
3115
+ if (options2.from) queryParams.set("from", options2.from);
3116
+ if (options2.to) queryParams.set("to", options2.to);
3117
+ const url = `${client.getProviderUrl()}/api/intent/history${queryParams.toString() ? `?${queryParams}` : ""}`;
3118
+ const historyClientId = client.getClientId();
3119
+ const response = await fetch(url, {
3120
+ headers: historyClientId ? { "x-client-id": historyClientId } : {},
3121
+ credentials: "include"
3122
+ });
3123
+ if (!response.ok) {
3124
+ const data2 = await response.json().catch(() => ({}));
3125
+ throw new Error(data2.error || "Failed to get calls history");
3126
+ }
3127
+ const data = await response.json();
3128
+ const statusMap = {
3129
+ pending: "PENDING",
3130
+ preconfirmed: "PENDING",
3131
+ completed: "CONFIRMED",
3132
+ failed: "CONFIRMED",
3133
+ expired: "CONFIRMED"
3134
+ };
2023
3135
  return {
2024
- chains: chainIds,
2025
- tokens: tokensByChain
3136
+ calls: data.intents.map(
3137
+ (intent) => ({
3138
+ callsId: intent.intentId,
3139
+ // intentId is the orchestrator's ID
3140
+ status: statusMap[intent.status] || "PENDING",
3141
+ receipts: intent.transactionHash ? [{ transactionHash: intent.transactionHash }] : [],
3142
+ chainId: `0x${intent.targetChain.toString(16)}`
3143
+ })
3144
+ ),
3145
+ total: data.total,
3146
+ hasMore: data.hasMore
2026
3147
  };
2027
3148
  }
2028
3149
  default:
@@ -2045,6 +3166,7 @@ function createPasskeyProvider(options) {
2045
3166
  disconnect
2046
3167
  };
2047
3168
  }
3169
+ var createPasskeyProvider = createOneAuthProvider;
2048
3170
 
2049
3171
  // src/account.ts
2050
3172
  var import_viem5 = require("viem");
@@ -2147,6 +3269,9 @@ function createPasskeyWalletClient(config) {
2147
3269
  if (!result.success) {
2148
3270
  throw new Error(result.error?.message || "Signing failed");
2149
3271
  }
3272
+ if (!result.signature) {
3273
+ throw new Error("No signature received");
3274
+ }
2150
3275
  return encodeWebAuthnSignature(result.signature);
2151
3276
  };
2152
3277
  const signTransactionImpl = async (transaction) => {
@@ -2167,6 +3292,9 @@ function createPasskeyWalletClient(config) {
2167
3292
  if (!result.success) {
2168
3293
  throw new Error(result.error?.message || "Signing failed");
2169
3294
  }
3295
+ if (!result.signature) {
3296
+ throw new Error("No signature received");
3297
+ }
2170
3298
  return encodeWebAuthnSignature(result.signature);
2171
3299
  };
2172
3300
  const signTypedDataImpl = async (typedData) => {
@@ -2188,6 +3316,9 @@ function createPasskeyWalletClient(config) {
2188
3316
  if (!result.success) {
2189
3317
  throw new Error(result.error?.message || "Signing failed");
2190
3318
  }
3319
+ if (!result.signature) {
3320
+ throw new Error("No signature received");
3321
+ }
2191
3322
  return encodeWebAuthnSignature(result.signature);
2192
3323
  };
2193
3324
  const buildIntentPayload = async (calls, targetChainOverride) => {
@@ -2256,11 +3387,12 @@ function createPasskeyWalletClient(config) {
2256
3387
  * Send multiple calls as a single batched transaction
2257
3388
  */
2258
3389
  async sendCalls(params) {
2259
- const { calls, chainId: targetChain } = params;
3390
+ const { calls, chainId: targetChain, tokenRequests } = params;
2260
3391
  const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
2261
3392
  const intentPayload = await buildIntentPayload(calls, targetChain);
2262
3393
  const result = await provider.sendIntent({
2263
3394
  ...intentPayload,
3395
+ tokenRequests,
2264
3396
  closeOn,
2265
3397
  waitForHash: config.waitForHash ?? true,
2266
3398
  hashTimeoutMs: config.hashTimeoutMs,
@@ -2704,9 +3836,10 @@ function BatchQueueWidget({ onSignAll }) {
2704
3836
 
2705
3837
  // src/verify.ts
2706
3838
  var import_viem7 = require("viem");
2707
- var PASSKEY_MESSAGE_PREFIX = "Passkey Signed Message:\n";
3839
+ var ETHEREUM_MESSAGE_PREFIX = "Ethereum Signed Message:\n";
3840
+ var PASSKEY_MESSAGE_PREFIX = ETHEREUM_MESSAGE_PREFIX;
2708
3841
  function hashMessage2(message) {
2709
- const prefixed = PASSKEY_MESSAGE_PREFIX + message.length.toString() + message;
3842
+ const prefixed = ETHEREUM_MESSAGE_PREFIX + message.length.toString() + message;
2710
3843
  return (0, import_viem7.keccak256)((0, import_viem7.toBytes)(prefixed));
2711
3844
  }
2712
3845
  function verifyMessageHash(message, signedHash) {
@@ -2718,9 +3851,11 @@ function verifyMessageHash(message, signedHash) {
2718
3851
  0 && (module.exports = {
2719
3852
  BatchQueueProvider,
2720
3853
  BatchQueueWidget,
3854
+ ETHEREUM_MESSAGE_PREFIX,
2721
3855
  OneAuthClient,
2722
3856
  PASSKEY_MESSAGE_PREFIX,
2723
3857
  PasskeyProviderClient,
3858
+ createOneAuthProvider,
2724
3859
  createPasskeyAccount,
2725
3860
  createPasskeyProvider,
2726
3861
  createPasskeyWalletClient,