@rhinestone/1auth 0.6.5 → 0.6.7

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
@@ -18,7 +18,7 @@ import {
18
18
  isTestnet,
19
19
  isTokenAddressSupported,
20
20
  resolveTokenAddress
21
- } from "./chunk-IP2FG4WQ.mjs";
21
+ } from "./chunk-SXISYG2P.mjs";
22
22
 
23
23
  // src/client.ts
24
24
  import { parseUnits, hashTypedData } from "viem";
@@ -28,6 +28,17 @@ var DEFAULT_EMBED_WIDTH = "400px";
28
28
  var DEFAULT_EMBED_HEIGHT = "500px";
29
29
  var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
30
30
  var OneAuthClient = class {
31
+ /**
32
+ * Create a new OneAuthClient.
33
+ *
34
+ * Normalizes the config (filling in default URLs), then immediately injects
35
+ * `<link rel="preconnect">` tags for the passkey domain so that DNS lookup
36
+ * and TLS handshake start before the first dialog is opened, shaving
37
+ * noticeable latency from the first user interaction.
38
+ *
39
+ * @param config - Client configuration including optional providerUrl, dialogUrl,
40
+ * clientId, theme, and redirect settings.
41
+ */
31
42
  constructor(config) {
32
43
  const providerUrl = config.providerUrl || DEFAULT_PROVIDER_URL;
33
44
  const dialogUrl = config.dialogUrl || providerUrl;
@@ -47,7 +58,16 @@ var OneAuthClient = class {
47
58
  this.theme = theme;
48
59
  }
49
60
  /**
50
- * Build theme URL parameters
61
+ * Serialize the active theme into URL query parameters for the dialog.
62
+ *
63
+ * Merges the instance-level theme (set via constructor or `setTheme`) with any
64
+ * per-call override, so callers can temporarily change the appearance for a
65
+ * single dialog invocation without mutating shared state.
66
+ *
67
+ * @param overrideTheme - One-shot theme values that take precedence over the
68
+ * instance theme for this call only.
69
+ * @returns A URL-encoded query string (e.g. `"theme=dark&accent=%230090ff"`),
70
+ * or an empty string if no theme properties are set.
51
71
  */
52
72
  getThemeParams(overrideTheme) {
53
73
  const theme = { ...this.theme, ...overrideTheme };
@@ -61,15 +81,30 @@ var OneAuthClient = class {
61
81
  return params.toString();
62
82
  }
63
83
  /**
64
- * Get the dialog URL (Vite app URL)
65
- * Defaults to providerUrl if dialogUrl is not set
84
+ * Resolve the URL of the dialog app (the Vite/Next.js frontend that renders
85
+ * the WebAuthn UI inside the iframe).
86
+ *
87
+ * `dialogUrl` is a separate config option to support split deployments where
88
+ * the API server and the dialog frontend run at different origins. Falls back
89
+ * to `providerUrl` when not explicitly configured — the common case.
90
+ *
91
+ * @returns The base URL used when constructing all dialog endpoint paths.
66
92
  */
67
93
  getDialogUrl() {
68
94
  return this.config.dialogUrl || this.config.providerUrl;
69
95
  }
70
96
  /**
71
- * Get the origin for message validation
72
- * Uses dialogUrl origin if set, otherwise providerUrl origin
97
+ * Extract the trusted origin used to validate incoming postMessage events.
98
+ *
99
+ * All `window.addEventListener("message", ...)` handlers in this class check
100
+ * `event.origin` against this value to prevent cross-origin spoofing. Wraps
101
+ * `getDialogUrl()` in a `new URL()` parse so that paths and query strings are
102
+ * stripped — only the scheme + host + port are compared.
103
+ *
104
+ * Falls back to the raw dialog URL string if URL parsing fails (e.g. during
105
+ * unit tests with non-standard URL formats).
106
+ *
107
+ * @returns The scheme + host + optional port of the dialog app (e.g. `"https://passkey.1auth.box"`).
73
108
  */
74
109
  getDialogOrigin() {
75
110
  const dialogUrl = this.getDialogUrl();
@@ -91,6 +126,24 @@ var OneAuthClient = class {
91
126
  getClientId() {
92
127
  return this.config.clientId;
93
128
  }
129
+ /**
130
+ * Poll the intent status endpoint until a transaction hash appears or the
131
+ * intent reaches a terminal failure state.
132
+ *
133
+ * This is used after `sendIntent` resolves (with `waitForHash: true`) to
134
+ * continue waiting for the on-chain hash even after the dialog has been
135
+ * dismissed. It is intentionally separate from the in-dialog polling loop so
136
+ * callers that don't need the hash can skip it entirely.
137
+ *
138
+ * Errors during individual poll attempts are swallowed so that transient
139
+ * network issues don't abort the wait prematurely.
140
+ *
141
+ * @param intentId - The Rhinestone intent ID returned by the execute endpoint.
142
+ * @param options.timeoutMs - Maximum wait time in milliseconds (default 120 000).
143
+ * @param options.intervalMs - Time between polls in milliseconds (default 2 000).
144
+ * @returns The transaction hash string, or `undefined` if the deadline was
145
+ * reached or the intent failed/expired before a hash was produced.
146
+ */
94
147
  async waitForTransactionHash(intentId, options = {}) {
95
148
  const timeoutMs = options.timeoutMs ?? 12e4;
96
149
  const intervalMs = options.intervalMs ?? 2e3;
@@ -496,25 +549,7 @@ var OneAuthClient = class {
496
549
  * ```
497
550
  */
498
551
  async sendIntent(options) {
499
- const signedIntent = options.signedIntent ? {
500
- ...options.signedIntent,
501
- merchantId: options.signedIntent.merchantId || options.signedIntent.developerId
502
- } : void 0;
503
- const username = signedIntent?.username || options.username;
504
- const targetChain = signedIntent?.targetChain || options.targetChain;
505
- const calls = signedIntent?.calls || options.calls;
506
- if (signedIntent && !signedIntent.merchantId) {
507
- return {
508
- success: false,
509
- intentId: "",
510
- status: "failed",
511
- error: {
512
- code: "INVALID_OPTIONS",
513
- message: "Signed intent requires developerId (clientId)"
514
- }
515
- };
516
- }
517
- const accountAddress = signedIntent?.accountAddress || options.accountAddress;
552
+ const { username, accountAddress, targetChain, calls } = options;
518
553
  if (!username && !accountAddress) {
519
554
  return {
520
555
  success: false,
@@ -522,7 +557,7 @@ var OneAuthClient = class {
522
557
  status: "failed",
523
558
  error: {
524
559
  code: "INVALID_OPTIONS",
525
- message: "Either username, accountAddress, or signedIntent with user identifier is required"
560
+ message: "Either username or accountAddress is required"
526
561
  }
527
562
  };
528
563
  }
@@ -533,7 +568,7 @@ var OneAuthClient = class {
533
568
  status: "failed",
534
569
  error: {
535
570
  code: "INVALID_OPTIONS",
536
- message: "targetChain and calls are required (either directly or via signedIntent)"
571
+ message: "targetChain and calls are required"
537
572
  }
538
573
  };
539
574
  }
@@ -542,11 +577,11 @@ var OneAuthClient = class {
542
577
  amount: r.amount.toString()
543
578
  }));
544
579
  let prepareResponse;
545
- const requestBody = signedIntent || {
546
- username: options.username,
547
- accountAddress: options.accountAddress,
548
- targetChain: options.targetChain,
549
- calls: options.calls,
580
+ const requestBody = {
581
+ username,
582
+ accountAddress,
583
+ targetChain,
584
+ calls,
550
585
  tokenRequests: serializedTokenRequests,
551
586
  sourceAssets: options.sourceAssets,
552
587
  sourceChainId: options.sourceChainId,
@@ -662,7 +697,6 @@ var OneAuthClient = class {
662
697
  dialogOrigin,
663
698
  // Refresh callback - called when dialog requests a quote refresh
664
699
  async () => {
665
- console.log("[SDK] Dialog requested quote refresh, re-preparing intent");
666
700
  try {
667
701
  const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
668
702
  method: "POST",
@@ -1044,7 +1078,6 @@ var OneAuthClient = class {
1044
1078
  if (event.origin !== dialogOrigin) return;
1045
1079
  const message = event.data;
1046
1080
  if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1047
- console.log("[SDK] Batch dialog requested quote refresh, re-preparing all intents");
1048
1081
  try {
1049
1082
  const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
1050
1083
  method: "POST",
@@ -1142,7 +1175,22 @@ var OneAuthClient = class {
1142
1175
  );
1143
1176
  }
1144
1177
  /**
1145
- * Wait for the signing result without closing the modal
1178
+ * Listen for a signing result that belongs to a specific `requestId`.
1179
+ *
1180
+ * Used by legacy server-side signing request flows (popup/embed/modal) where
1181
+ * the dialog was pre-loaded with a signing request created via the API. The
1182
+ * `requestId` filter is needed because multiple dialogs may be open or there
1183
+ * may be stale messages from a previous dialog in the queue.
1184
+ *
1185
+ * Resolves on `PASSKEY_SIGNING_RESULT` matching `requestId`, or on
1186
+ * `PASSKEY_CLOSE` (user dismissed the dialog).
1187
+ *
1188
+ * @param requestId - The signing request ID to match against incoming messages.
1189
+ * @param dialog - The `<dialog>` element (opened before this call) used to
1190
+ * `showModal()` so the dialog becomes interactive.
1191
+ * @param _iframe - Unused; kept for signature consistency with other wait helpers.
1192
+ * @param cleanup - Idempotent teardown function that closes and removes the dialog.
1193
+ * @returns A `SigningResult` discriminated union.
1146
1194
  */
1147
1195
  waitForIntentSigningResponse(requestId, dialog, _iframe, cleanup) {
1148
1196
  const dialogOrigin = this.getDialogOrigin();
@@ -1187,18 +1235,30 @@ var OneAuthClient = class {
1187
1235
  });
1188
1236
  }
1189
1237
  /**
1190
- * Wait for signing result (simplified - no requestId matching)
1238
+ * Listen for a signing result from the currently open modal dialog.
1239
+ *
1240
+ * Unlike `waitForIntentSigningResponse`, this variant does not filter by
1241
+ * `requestId` because the inline intent flow (sendIntent / signMessage /
1242
+ * signTypedData) owns the dialog exclusively — there is no risk of collisions
1243
+ * from other dialogs.
1244
+ *
1245
+ * Handles two result shapes:
1246
+ * - New "secure flow": dialog executed the intent server-side and returns
1247
+ * only an `intentId` (no signature exposed to the SDK).
1248
+ * - Legacy flow: dialog returns a raw `WebAuthnSignature` for the SDK to
1249
+ * forward to the execute endpoint.
1250
+ *
1251
+ * @param dialog - The `<dialog>` element wrapping the signing iframe.
1252
+ * @param _iframe - Unused; kept for signature consistency with other wait helpers.
1253
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1254
+ * @returns A `SigningResult` extended with an optional `signedHash` field
1255
+ * populated when the dialog performs message signing.
1191
1256
  */
1192
1257
  waitForSigningResponse(dialog, _iframe, cleanup) {
1193
1258
  const dialogOrigin = this.getDialogOrigin();
1194
- console.log("[SDK] waitForSigningResponse, expecting origin:", dialogOrigin);
1195
1259
  return new Promise((resolve) => {
1196
1260
  const handleMessage = (event) => {
1197
- console.log("[SDK] Received message:", event.origin, event.data?.type);
1198
- if (event.origin !== dialogOrigin) {
1199
- console.log("[SDK] Origin mismatch, ignoring. Expected:", dialogOrigin, "Got:", event.origin);
1200
- return;
1201
- }
1261
+ if (event.origin !== dialogOrigin) return;
1202
1262
  const message = event.data;
1203
1263
  const payload = message?.data;
1204
1264
  if (message?.type === "PASSKEY_SIGNING_RESULT") {
@@ -1240,17 +1300,34 @@ var OneAuthClient = class {
1240
1300
  });
1241
1301
  }
1242
1302
  /**
1243
- * Wait for signing result with auto-refresh support
1244
- * This method handles both signing results and quote refresh requests from the dialog
1303
+ * Listen for a signing result while also handling quote-refresh requests.
1304
+ *
1305
+ * Quotes from the Rhinestone orchestrator have a short TTL (typically ~60 s).
1306
+ * When the user takes too long to review and the quote expires, the dialog
1307
+ * sends `PASSKEY_REFRESH_QUOTE`. This method intercepts that message, calls
1308
+ * the provided `onRefresh` callback to fetch a new quote, and replies with
1309
+ * either `PASSKEY_REFRESH_COMPLETE` (success) or `PASSKEY_REFRESH_ERROR`
1310
+ * (failure) — allowing the dialog to stay open and show the updated fees
1311
+ * without requiring a full restart.
1312
+ *
1313
+ * Once the user confirms or rejects, the promise resolves with the signing
1314
+ * result exactly as `waitForSigningResponse` would.
1315
+ *
1316
+ * @param dialog - The `<dialog>` element wrapping the signing iframe.
1317
+ * @param iframe - The iframe element; used to post refresh messages back.
1318
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1319
+ * @param dialogOrigin - Trusted origin for message validation (passed in to
1320
+ * avoid redundant `getDialogOrigin()` calls from the hot path).
1321
+ * @param onRefresh - Async callback that fetches a fresh quote and returns the
1322
+ * updated intent fields, or `null` if the refresh failed.
1323
+ * @returns A `SigningResult` extended with an optional `signedHash`.
1245
1324
  */
1246
1325
  waitForSigningWithRefresh(dialog, iframe, cleanup, dialogOrigin, onRefresh) {
1247
- console.log("[SDK] waitForSigningWithRefresh, expecting origin:", dialogOrigin);
1248
1326
  return new Promise((resolve) => {
1249
1327
  const handleMessage = async (event) => {
1250
1328
  if (event.origin !== dialogOrigin) return;
1251
1329
  const message = event.data;
1252
1330
  if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1253
- console.log("[SDK] Received quote refresh request from dialog");
1254
1331
  const refreshedData = await onRefresh();
1255
1332
  if (refreshedData) {
1256
1333
  iframe.contentWindow?.postMessage({
@@ -1305,7 +1382,20 @@ var OneAuthClient = class {
1305
1382
  });
1306
1383
  }
1307
1384
  /**
1308
- * Wait for the dialog to be closed
1385
+ * Wait for the dialog to be explicitly closed by the user or the iframe.
1386
+ *
1387
+ * Resolves on either:
1388
+ * - `PASSKEY_CLOSE` postMessage from the iframe (user clicked the X button
1389
+ * or the dialog programmatically closed itself after showing a result).
1390
+ * - The native `<dialog> close` event (escape key or `dialog.close()` call).
1391
+ *
1392
+ * Calling `cleanup()` before awaiting this is safe — both resolution paths
1393
+ * call `cleanup()` internally but it is idempotent. The primary use case is
1394
+ * keeping the dialog open while the SDK polls for a transaction hash, then
1395
+ * showing the final success/error state before letting the user dismiss.
1396
+ *
1397
+ * @param dialog - The `<dialog>` element to watch.
1398
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1309
1399
  */
1310
1400
  waitForDialogClose(dialog, cleanup) {
1311
1401
  const dialogOrigin = this.getDialogOrigin();
@@ -1329,7 +1419,17 @@ var OneAuthClient = class {
1329
1419
  });
1330
1420
  }
1331
1421
  /**
1332
- * Inject a preconnect link tag to pre-warm DNS + TLS for a given URL.
1422
+ * Inject a `<link rel="preconnect">` tag for the given URL's origin.
1423
+ *
1424
+ * Browsers use preconnect hints to resolve DNS, perform the TCP handshake,
1425
+ * and negotiate TLS before a resource is actually requested, reducing
1426
+ * time-to-first-byte when the dialog iframe loads. The tag is deduplicated —
1427
+ * if one already exists for the same origin it will not be added again.
1428
+ *
1429
+ * Only called in browser environments (guarded by `typeof document` in the
1430
+ * constructor). Invalid URLs are silently ignored.
1431
+ *
1432
+ * @param url - Any URL whose origin should be preconnected to.
1333
1433
  */
1334
1434
  injectPreconnect(url) {
1335
1435
  try {
@@ -1344,8 +1444,24 @@ var OneAuthClient = class {
1344
1444
  }
1345
1445
  }
1346
1446
  /**
1347
- * Wait for the dialog iframe to signal ready without sending init data.
1348
- * Returns a sendInit function the caller uses once prepare data is available.
1447
+ * Wait for the dialog iframe to signal ready, but defer sending `PASSKEY_INIT`
1448
+ * until the caller decides the time is right.
1449
+ *
1450
+ * This is the deferred variant of `waitForDialogReady`, used by flows that run
1451
+ * the dialog and a background API call in parallel (the "two-phase" approach).
1452
+ * Rather than blocking until both the iframe is ready AND the API call is done,
1453
+ * this method resolves as soon as `PASSKEY_READY` arrives and returns a
1454
+ * `sendInit` callback. The caller invokes `sendInit` once prepare data is
1455
+ * available, which may happen before or after the iframe is ready.
1456
+ *
1457
+ * Timeout: if the iframe never signals ready within 10 seconds (e.g. network
1458
+ * error, origin mismatch), resolves with `{ ready: false }` and calls cleanup.
1459
+ *
1460
+ * @param dialog - The `<dialog>` element wrapping the iframe.
1461
+ * @param iframe - The iframe element that will receive `PASSKEY_INIT`.
1462
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1463
+ * @returns `{ ready: true, sendInit }` when the iframe is ready, or
1464
+ * `{ ready: false }` if the dialog was closed or timed out first.
1349
1465
  */
1350
1466
  waitForDialogReadyDeferred(dialog, iframe, cleanup) {
1351
1467
  const dialogOrigin = this.getDialogOrigin();
@@ -1391,8 +1507,23 @@ var OneAuthClient = class {
1391
1507
  });
1392
1508
  }
1393
1509
  /**
1394
- * Prepare an intent by calling the orchestrator for a quote.
1395
- * Returns the prepare response and the origin tier from the response header.
1510
+ * Call the passkey service to obtain a Rhinestone orchestrator quote for an
1511
+ * intent (a single target-chain transaction or swap).
1512
+ *
1513
+ * The service contacts the Rhinestone orchestrator, which returns a signed
1514
+ * `intentOp` structure containing the quote, fees, and a WebAuthn challenge
1515
+ * the user must sign to authorize execution.
1516
+ *
1517
+ * The `X-Origin-Tier` response header is forwarded to the dialog so it can
1518
+ * display tier-specific UI (e.g. "Free" vs "Premium" badge).
1519
+ *
1520
+ * Side effect: if the server returns "User not found", the cached user entry
1521
+ * is removed from `localStorage` to force re-authentication.
1522
+ *
1523
+ * @param requestBody - Serialized intent options (bigint amounts converted to
1524
+ * strings before this call).
1525
+ * @returns On success: `{ success: true, data: PrepareIntentResponse, tier }`.
1526
+ * On failure: `{ success: false, error: { code, message } }`.
1396
1527
  */
1397
1528
  async prepareIntent(requestBody) {
1398
1529
  try {
@@ -1428,7 +1559,21 @@ var OneAuthClient = class {
1428
1559
  }
1429
1560
  }
1430
1561
  /**
1431
- * Prepare a batch intent by calling the orchestrator for quotes on all intents.
1562
+ * Call the passkey service to obtain orchestrator quotes for all intents in a
1563
+ * batch, returning a single shared WebAuthn challenge.
1564
+ *
1565
+ * The service prepares each intent independently and assembles a merkle tree
1566
+ * whose root becomes the challenge. Signing that root once authorizes every
1567
+ * intent in the batch. Partially-failed batches are supported: the response
1568
+ * includes both `intents` (succeeded) and `failedIntents` (per-intent errors),
1569
+ * so the dialog can still show the successful subset for signing.
1570
+ *
1571
+ * Side effect: same "User not found" localStorage cleanup as `prepareIntent`.
1572
+ *
1573
+ * @param requestBody - Serialized batch options (bigint amounts converted to
1574
+ * strings before this call).
1575
+ * @returns On success: `{ success: true, data: PrepareBatchIntentResponse, tier }`.
1576
+ * On failure: `{ success: false, error, failedIntents? }`.
1432
1577
  */
1433
1578
  async prepareBatchIntent(requestBody) {
1434
1579
  try {
@@ -1456,7 +1601,15 @@ var OneAuthClient = class {
1456
1601
  }
1457
1602
  }
1458
1603
  /**
1459
- * Send a prepare error message to the dialog iframe.
1604
+ * Forward a prepare-phase error to the dialog iframe so it can display a
1605
+ * human-readable failure message instead of hanging on the loading state.
1606
+ *
1607
+ * The dialog listens for `PASSKEY_PREPARE_ERROR` and transitions to an error
1608
+ * view. The user can then dismiss the dialog, at which point
1609
+ * `waitForDialogClose` resolves and the SDK returns the error to the caller.
1610
+ *
1611
+ * @param iframe - The iframe element to post the error to.
1612
+ * @param error - Human-readable error message from the prepare response.
1460
1613
  */
1461
1614
  sendPrepareError(iframe, error) {
1462
1615
  const dialogOrigin = this.getDialogOrigin();
@@ -1639,13 +1792,6 @@ var OneAuthClient = class {
1639
1792
  };
1640
1793
  }
1641
1794
  const toTokenAddress = toTokenResult.address;
1642
- console.log("[SDK sendSwap] Token resolution:", {
1643
- fromToken: options.fromToken ?? "Any",
1644
- fromTokenAddress: fromTokenAddress ?? "orchestrator picks",
1645
- toToken: options.toToken,
1646
- toTokenAddress,
1647
- targetChain: options.targetChain
1648
- });
1649
1795
  const formatTokenLabel = (token, fallback) => {
1650
1796
  if (!token.startsWith("0x")) {
1651
1797
  return token;
@@ -1674,16 +1820,10 @@ var OneAuthClient = class {
1674
1820
  const match = getSupportedTokens(chainId).find(
1675
1821
  (t) => t.symbol.toUpperCase() === symbol.toUpperCase()
1676
1822
  );
1677
- if (match) {
1678
- console.log(`[SDK] getTokenDecimals(${match.symbol}, ${chainId}) = ${match.decimals}`);
1679
- return match.decimals;
1680
- }
1681
- const decimals = getTokenDecimals(symbol, chainId);
1682
- console.log(`[SDK] getTokenDecimals(${symbol}, ${chainId}) = ${decimals}`);
1683
- return decimals;
1684
- } catch (e) {
1823
+ if (match) return match.decimals;
1824
+ return getTokenDecimals(symbol, chainId);
1825
+ } catch {
1685
1826
  const upperSymbol = symbol.toUpperCase();
1686
- console.warn(`[SDK] getTokenDecimals failed for ${symbol}, using fallback`, e);
1687
1827
  return KNOWN_DECIMALS[upperSymbol] ?? 18;
1688
1828
  }
1689
1829
  };
@@ -1693,13 +1833,6 @@ var OneAuthClient = class {
1693
1833
  token: toTokenAddress,
1694
1834
  amount: parseUnits(options.amount, toDecimals)
1695
1835
  }];
1696
- console.log("[SDK sendSwap] Building intent:", {
1697
- isBridge,
1698
- isFromNativeEth,
1699
- isToNativeEth,
1700
- toDecimals,
1701
- tokenRequests
1702
- });
1703
1836
  const result = await this.sendIntent({
1704
1837
  username: options.username,
1705
1838
  targetChain: options.targetChain,
@@ -1960,6 +2093,20 @@ var OneAuthClient = class {
1960
2093
  const iframe = this.createEmbed(request.requestId, embedOptions);
1961
2094
  return this.waitForEmbedResponse(request.requestId, iframe, embedOptions);
1962
2095
  }
2096
+ /**
2097
+ * Create and append a signing iframe to a caller-supplied container element.
2098
+ *
2099
+ * Used by the `signWithEmbed` flow where the integrator wants the signing UI
2100
+ * to appear inline on their page rather than in a modal overlay. The iframe
2101
+ * loads a pre-created signing request URL and fires `options.onReady` once
2102
+ * the page has loaded.
2103
+ *
2104
+ * @param requestId - The signing request ID; used to construct the iframe src
2105
+ * and give it a stable DOM id for later removal via `removeEmbed`.
2106
+ * @param options - Embed configuration including the container element,
2107
+ * optional dimensions, and lifecycle callbacks.
2108
+ * @returns The created `<iframe>` element (already appended to the container).
2109
+ */
1963
2110
  createEmbed(requestId, options) {
1964
2111
  const dialogUrl = this.getDialogUrl();
1965
2112
  const iframe = document.createElement("iframe");
@@ -1977,6 +2124,19 @@ var OneAuthClient = class {
1977
2124
  options.container.appendChild(iframe);
1978
2125
  return iframe;
1979
2126
  }
2127
+ /**
2128
+ * Listen for a signing result from an embedded (inline) signing iframe.
2129
+ *
2130
+ * Matches incoming `PASSKEY_SIGNING_RESULT` messages against `requestId`
2131
+ * to avoid reacting to messages from other iframes on the page. On resolution
2132
+ * (success or failure), the iframe is removed from the DOM and
2133
+ * `embedOptions.onClose` is invoked.
2134
+ *
2135
+ * @param requestId - The signing request ID to match against incoming messages.
2136
+ * @param iframe - The embedded signing iframe.
2137
+ * @param embedOptions - Embed configuration; `onClose` is called after cleanup.
2138
+ * @returns A `SigningResult` discriminated union.
2139
+ */
1980
2140
  waitForEmbedResponse(requestId, iframe, embedOptions) {
1981
2141
  const dialogOrigin = this.getDialogOrigin();
1982
2142
  return new Promise((resolve) => {
@@ -2071,6 +2231,22 @@ var OneAuthClient = class {
2071
2231
  const data = await response.json();
2072
2232
  return data.passkeys;
2073
2233
  }
2234
+ /**
2235
+ * Register a signing request with the passkey service and receive a
2236
+ * short-lived `requestId`.
2237
+ *
2238
+ * Used by the legacy popup, redirect, and embed flows. The passkey service
2239
+ * stores the challenge and metadata server-side so the dialog page can fetch
2240
+ * them by `requestId` without relying on URL parameters alone. This avoids
2241
+ * exposing potentially large challenge payloads in query strings.
2242
+ *
2243
+ * @param options - The signing options (challenge, username, description, etc.).
2244
+ * @param mode - How the dialog will be opened (`"popup"`, `"redirect"`, or `"embed"`).
2245
+ * @param redirectUrl - Only required for `"redirect"` mode; the URL the dialog
2246
+ * will navigate back to after signing.
2247
+ * @returns The created signing request with its unique `requestId`.
2248
+ * @throws If the API call fails.
2249
+ */
2074
2250
  async createSigningRequest(options, mode, redirectUrl) {
2075
2251
  const response = await fetch(
2076
2252
  `${this.config.providerUrl}/api/sign/request`,
@@ -2097,6 +2273,17 @@ var OneAuthClient = class {
2097
2273
  }
2098
2274
  return response.json();
2099
2275
  }
2276
+ /**
2277
+ * Open a centered popup window for the signing or auth dialog.
2278
+ *
2279
+ * Positions the popup near the top of the current window (50px from the top)
2280
+ * and horizontally centered relative to the browser window. Uses `popup=true`
2281
+ * to request a minimal chrome popup (no address bar) on browsers that support
2282
+ * the Window Management API.
2283
+ *
2284
+ * @param url - The full URL to open in the popup.
2285
+ * @returns The popup `Window` reference, or `null` if the browser blocked it.
2286
+ */
2100
2287
  openPopup(url) {
2101
2288
  const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
2102
2289
  const top = window.screenY + 50;
@@ -2107,9 +2294,24 @@ var OneAuthClient = class {
2107
2294
  );
2108
2295
  }
2109
2296
  /**
2110
- * Wait for the dialog iframe to signal ready, then send init data.
2111
- * Also handles early close (X button, escape, backdrop) during the ready phase.
2112
- * Returns true if dialog is ready, false if it was closed before becoming ready.
2297
+ * Wait for the dialog iframe to signal ready, then immediately send init data.
2298
+ *
2299
+ * This is the synchronous-init variant: `initMessage` is available at call
2300
+ * time, so it can be posted as soon as `PASSKEY_READY` arrives. Use
2301
+ * `waitForDialogReadyDeferred` when init data depends on an in-flight API call.
2302
+ *
2303
+ * Also handles early close (X button, escape key, backdrop click) during the
2304
+ * ready phase so those paths resolve cleanly without leaking event listeners.
2305
+ *
2306
+ * Timeout: resolves `false` after 10 seconds if the iframe never signals ready
2307
+ * (e.g. origin mismatch or network error loading the dialog app).
2308
+ *
2309
+ * @param dialog - The `<dialog>` element wrapping the iframe.
2310
+ * @param iframe - The iframe element that will receive `PASSKEY_INIT`.
2311
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
2312
+ * @param initMessage - The `PASSKEY_INIT` payload to send when ready.
2313
+ * @returns `true` if the dialog is ready and init was sent; `false` if the
2314
+ * dialog was closed or timed out before becoming ready.
2113
2315
  */
2114
2316
  waitForDialogReady(dialog, iframe, cleanup, initMessage) {
2115
2317
  const dialogOrigin = this.getDialogOrigin();
@@ -2152,10 +2354,33 @@ var OneAuthClient = class {
2152
2354
  });
2153
2355
  }
2154
2356
  /**
2155
- * Create a modal dialog with a full-viewport iframe inside.
2156
- * All visual chrome (backdrop, positioning, animations) is rendered
2157
- * by the passkey app inside the iframe the SDK just provides
2158
- * a transparent full-screen container.
2357
+ * Create and open a full-viewport `<dialog>` containing a passkey iframe.
2358
+ *
2359
+ * The SDK owns only the transparent outer shell; all visible UI (backdrop,
2360
+ * card, animations, close button) is rendered by the passkey app inside the
2361
+ * iframe. This approach means design changes can be shipped server-side
2362
+ * without updating SDK consumers.
2363
+ *
2364
+ * Lifecycle:
2365
+ * 1. A themed "Loading…" overlay is injected and shown immediately while the
2366
+ * iframe loads, matching the exact visual structure of the passkey app's
2367
+ * TitleBar + IndeterminateLoader so the transition is seamless.
2368
+ * 2. When the iframe sends `PASSKEY_RENDERED`, the overlay fades out and
2369
+ * the iframe becomes fully visible.
2370
+ * 3. The returned `cleanup` function tears down all event listeners,
2371
+ * disconnects the MutationObserver, closes the `<dialog>`, and removes
2372
+ * it from the DOM. It is idempotent — safe to call multiple times.
2373
+ *
2374
+ * Browser quirks handled:
2375
+ * - The 1Password extension sets `inert` on the `<dialog>`, making it
2376
+ * non-interactive. A MutationObserver removes the attribute immediately.
2377
+ * - Password managers may also set `inert` on dialog siblings; cleanup
2378
+ * restores those as well.
2379
+ * - The iframe sandbox includes `allow-popups-to-escape-sandbox` so that
2380
+ * 1Password's popup UI can render outside the sandboxed context.
2381
+ *
2382
+ * @param url - The full URL (including query params) to load in the iframe.
2383
+ * @returns References to the dialog, iframe, and a cleanup function.
2159
2384
  */
2160
2385
  createModalDialog(url) {
2161
2386
  const dialogUrl = this.getDialogUrl();
@@ -2353,6 +2578,24 @@ var OneAuthClient = class {
2353
2578
  };
2354
2579
  return { dialog, iframe, cleanup };
2355
2580
  }
2581
+ /**
2582
+ * Listen for the auth result from the modal dialog.
2583
+ *
2584
+ * Waits for `PASSKEY_READY` before processing any other messages to avoid
2585
+ * acting on stale `PASSKEY_CLOSE` events that may still be in the event queue
2586
+ * from a previously closed dialog.
2587
+ *
2588
+ * Also handles the `PASSKEY_RETRY_POPUP` message: sent by the dialog when a
2589
+ * password manager (e.g. Bitwarden) intercepts the WebAuthn call inside the
2590
+ * iframe. In that case the SDK seamlessly re-opens a standalone popup window,
2591
+ * where WebAuthn works without cross-origin iframe restrictions, and waits
2592
+ * for the result from there instead.
2593
+ *
2594
+ * @param _dialog - Unused; kept for signature consistency.
2595
+ * @param iframe - The iframe element; used to send `PASSKEY_INIT` on ready.
2596
+ * @param cleanup - Idempotent teardown for the modal dialog.
2597
+ * @returns An `AuthResult` discriminated union.
2598
+ */
2356
2599
  waitForModalAuthResponse(_dialog, iframe, cleanup) {
2357
2600
  const dialogOrigin = this.getDialogOrigin();
2358
2601
  return new Promise((resolve) => {
@@ -2469,6 +2712,20 @@ var OneAuthClient = class {
2469
2712
  window.addEventListener("message", handleMessage);
2470
2713
  });
2471
2714
  }
2715
+ /**
2716
+ * Listen for the authenticate result from the modal dialog.
2717
+ *
2718
+ * The authenticate flow combines sign-in/sign-up with optional off-chain
2719
+ * challenge signing. The dialog sends `PASSKEY_AUTHENTICATE_RESULT` on
2720
+ * completion with both user details and, if a challenge was requested, the
2721
+ * WebAuthn signature and the hashed challenge value for server-side
2722
+ * verification.
2723
+ *
2724
+ * @param _dialog - Unused; kept for signature consistency.
2725
+ * @param _iframe - Unused; kept for signature consistency.
2726
+ * @param cleanup - Idempotent teardown for the modal dialog.
2727
+ * @returns An `AuthenticateResult` discriminated union.
2728
+ */
2472
2729
  waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
2473
2730
  const dialogOrigin = this.getDialogOrigin();
2474
2731
  return new Promise((resolve) => {
@@ -2512,6 +2769,22 @@ var OneAuthClient = class {
2512
2769
  window.addEventListener("message", handleMessage);
2513
2770
  });
2514
2771
  }
2772
+ /**
2773
+ * Listen for the connect result from the connect dialog.
2774
+ *
2775
+ * The connect flow is a lightweight "are you still you?" confirmation that
2776
+ * does not require a new passkey ceremony. On success, the dialog returns the
2777
+ * user's username, address, and an `autoConnected` flag indicating whether the
2778
+ * user had previously enabled auto-connect (in which case no UI was shown).
2779
+ *
2780
+ * On failure with `action: "switch"`, the caller should redirect to the full
2781
+ * auth flow because the user has no cached session to confirm.
2782
+ *
2783
+ * @param _dialog - Unused; kept for signature consistency.
2784
+ * @param _iframe - Unused; kept for signature consistency.
2785
+ * @param cleanup - Idempotent teardown for the modal dialog.
2786
+ * @returns A `ConnectResult` discriminated union.
2787
+ */
2515
2788
  waitForConnectResponse(_dialog, _iframe, cleanup) {
2516
2789
  const dialogOrigin = this.getDialogOrigin();
2517
2790
  return new Promise((resolve) => {
@@ -2553,6 +2826,23 @@ var OneAuthClient = class {
2553
2826
  window.addEventListener("message", handleMessage);
2554
2827
  });
2555
2828
  }
2829
+ /**
2830
+ * Listen for the consent result from the consent dialog.
2831
+ *
2832
+ * The consent dialog shows the user which data fields an app is requesting
2833
+ * access to and lets them approve or deny. On approval, `data` contains the
2834
+ * consented field values (e.g. email address, device names) and `grantedAt`
2835
+ * records when consent was given.
2836
+ *
2837
+ * A `PASSKEY_CLOSE` without a prior `PASSKEY_CONSENT_RESULT` is treated as an
2838
+ * explicit user cancellation (distinct from denial, though both yield
2839
+ * `success: false`).
2840
+ *
2841
+ * @param _dialog - Unused; kept for signature consistency.
2842
+ * @param _iframe - Unused; kept for signature consistency.
2843
+ * @param cleanup - Idempotent teardown for the modal dialog.
2844
+ * @returns A `RequestConsentResult` discriminated union.
2845
+ */
2556
2846
  waitForConsentResponse(_dialog, _iframe, cleanup) {
2557
2847
  const dialogOrigin = this.getDialogOrigin();
2558
2848
  return new Promise((resolve) => {
@@ -2592,6 +2882,20 @@ var OneAuthClient = class {
2592
2882
  window.addEventListener("message", handleMessage);
2593
2883
  });
2594
2884
  }
2885
+ /**
2886
+ * Listen for a signing result from a server-request-based modal dialog.
2887
+ *
2888
+ * Similar to `waitForIntentSigningResponse` but intended for the older
2889
+ * `signWithModal` path that pre-creates a signing request via the API (rather
2890
+ * than inlining all data in `PASSKEY_INIT`). Filters by `requestId` to handle
2891
+ * the case where stale messages from a previous dialog may still be in flight.
2892
+ *
2893
+ * @param requestId - The signing request ID to match against incoming messages.
2894
+ * @param _dialog - Unused; kept for signature consistency.
2895
+ * @param _iframe - Unused; kept for signature consistency.
2896
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
2897
+ * @returns A `SigningResult` discriminated union.
2898
+ */
2595
2899
  waitForModalSigningResponse(requestId, _dialog, _iframe, cleanup) {
2596
2900
  const dialogOrigin = this.getDialogOrigin();
2597
2901
  return new Promise((resolve) => {
@@ -2631,6 +2935,21 @@ var OneAuthClient = class {
2631
2935
  window.addEventListener("message", handleMessage);
2632
2936
  });
2633
2937
  }
2938
+ /**
2939
+ * Listen for a signing result from a popup window.
2940
+ *
2941
+ * Polls for popup closure every 500 ms as a fallback to detect when the user
2942
+ * closes the popup without completing the signing flow (no `PASSKEY_CLOSE`
2943
+ * message is fired in that case because the popup's unload event cannot
2944
+ * reliably postMessage to the opener on all browsers).
2945
+ *
2946
+ * On success or cancellation, the popup is programmatically closed and the
2947
+ * interval is cleared.
2948
+ *
2949
+ * @param requestId - The signing request ID to match against incoming messages.
2950
+ * @param popup - The popup `Window` reference returned by `openPopup`.
2951
+ * @returns A `SigningResult` discriminated union.
2952
+ */
2634
2953
  waitForPopupResponse(requestId, popup) {
2635
2954
  const dialogOrigin = this.getDialogOrigin();
2636
2955
  return new Promise((resolve) => {
@@ -2676,6 +2995,17 @@ var OneAuthClient = class {
2676
2995
  window.addEventListener("message", handleMessage);
2677
2996
  });
2678
2997
  }
2998
+ /**
2999
+ * Fetch the completed signing result from the passkey service by request ID.
3000
+ *
3001
+ * Used by the redirect flow: after the passkey service redirects back to
3002
+ * `redirectUrl`, the integrator calls `handleRedirectCallback()`, which
3003
+ * extracts `requestId` from the URL query params and delegates here to
3004
+ * retrieve the full signature from the server.
3005
+ *
3006
+ * @param requestId - The signing request ID from the redirect callback URL.
3007
+ * @returns A `SigningResult` discriminated union.
3008
+ */
2679
3009
  async fetchSigningResult(requestId) {
2680
3010
  const response = await fetch(
2681
3011
  `${this.config.providerUrl}/api/sign/request/${requestId}`,
@@ -2760,6 +3090,7 @@ function createPasskeyAccount(client, params) {
2760
3090
  const domain = {
2761
3091
  name: domainInput.name,
2762
3092
  version: domainInput.version,
3093
+ // viem uses bigint for chainId in typed data; the passkey service expects number
2763
3094
  chainId: typeof domainInput.chainId === "bigint" ? Number(domainInput.chainId) : domainInput.chainId,
2764
3095
  verifyingContract: domainInput.verifyingContract,
2765
3096
  salt: domainInput.salt
@@ -2798,6 +3129,7 @@ import {
2798
3129
  } from "viem";
2799
3130
  import { toAccount as toAccount2 } from "viem/accounts";
2800
3131
  function createPasskeyWalletClient(config) {
3132
+ const resolvedUsername = config.username ?? config.accountAddress;
2801
3133
  const provider = new OneAuthClient({
2802
3134
  providerUrl: config.providerUrl,
2803
3135
  clientId: config.clientId,
@@ -2807,7 +3139,7 @@ function createPasskeyWalletClient(config) {
2807
3139
  const hash = hashMessage(message);
2808
3140
  const result = await provider.signWithModal({
2809
3141
  challenge: hash,
2810
- username: config.username,
3142
+ username: resolvedUsername,
2811
3143
  description: "Sign message",
2812
3144
  transaction: {
2813
3145
  actions: [
@@ -2838,7 +3170,7 @@ function createPasskeyWalletClient(config) {
2838
3170
  const hash = hashCalls(calls);
2839
3171
  const result = await provider.signWithModal({
2840
3172
  challenge: hash,
2841
- username: config.username,
3173
+ username: resolvedUsername,
2842
3174
  description: "Sign transaction",
2843
3175
  transaction: buildTransactionReview(calls)
2844
3176
  });
@@ -2854,7 +3186,7 @@ function createPasskeyWalletClient(config) {
2854
3186
  const hash = hashTypedData2(typedData);
2855
3187
  const result = await provider.signWithModal({
2856
3188
  challenge: hash,
2857
- username: config.username,
3189
+ username: resolvedUsername,
2858
3190
  description: "Sign typed data",
2859
3191
  transaction: {
2860
3192
  actions: [
@@ -2874,7 +3206,7 @@ function createPasskeyWalletClient(config) {
2874
3206
  }
2875
3207
  return encodeWebAuthnSignature(result.signature);
2876
3208
  };
2877
- const buildIntentPayload = async (calls, targetChainOverride) => {
3209
+ const buildIntentPayload = async (calls, targetChainOverride, extra) => {
2878
3210
  const targetChain = targetChainOverride ?? config.chain.id;
2879
3211
  const intentCalls = calls.map((call) => ({
2880
3212
  to: call.to,
@@ -2883,17 +3215,9 @@ function createPasskeyWalletClient(config) {
2883
3215
  label: call.label,
2884
3216
  sublabel: call.sublabel
2885
3217
  }));
2886
- if (config.signIntent) {
2887
- const signedIntent = await config.signIntent({
2888
- username: config.username,
2889
- accountAddress: config.accountAddress,
2890
- targetChain,
2891
- calls: intentCalls
2892
- });
2893
- return { signedIntent };
2894
- }
2895
3218
  return {
2896
- username: config.username,
3219
+ username: resolvedUsername,
3220
+ accountAddress: config.accountAddress,
2897
3221
  targetChain,
2898
3222
  calls: intentCalls
2899
3223
  };
@@ -2940,12 +3264,14 @@ function createPasskeyWalletClient(config) {
2940
3264
  * Send multiple calls as a single batched transaction
2941
3265
  */
2942
3266
  async sendCalls(params) {
2943
- const { calls, chainId: targetChain, tokenRequests } = params;
3267
+ const { calls, chainId: targetChain, tokenRequests, sourceAssets, sourceChainId } = params;
2944
3268
  const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
2945
- const intentPayload = await buildIntentPayload(calls, targetChain);
3269
+ const intentPayload = await buildIntentPayload(calls, targetChain, { tokenRequests, sourceAssets });
2946
3270
  const result = await provider.sendIntent({
2947
3271
  ...intentPayload,
2948
3272
  tokenRequests,
3273
+ sourceAssets,
3274
+ sourceChainId,
2949
3275
  closeOn,
2950
3276
  waitForHash: config.waitForHash ?? true,
2951
3277
  hashTimeoutMs: config.hashTimeoutMs,