@rhinestone/1auth 0.6.5 → 0.6.8

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
@@ -199,6 +199,17 @@ var DEFAULT_EMBED_WIDTH = "400px";
199
199
  var DEFAULT_EMBED_HEIGHT = "500px";
200
200
  var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
201
201
  var OneAuthClient = class {
202
+ /**
203
+ * Create a new OneAuthClient.
204
+ *
205
+ * Normalizes the config (filling in default URLs), then immediately injects
206
+ * `<link rel="preconnect">` tags for the passkey domain so that DNS lookup
207
+ * and TLS handshake start before the first dialog is opened, shaving
208
+ * noticeable latency from the first user interaction.
209
+ *
210
+ * @param config - Client configuration including optional providerUrl, dialogUrl,
211
+ * clientId, theme, and redirect settings.
212
+ */
202
213
  constructor(config) {
203
214
  const providerUrl = config.providerUrl || DEFAULT_PROVIDER_URL;
204
215
  const dialogUrl = config.dialogUrl || providerUrl;
@@ -218,7 +229,16 @@ var OneAuthClient = class {
218
229
  this.theme = theme;
219
230
  }
220
231
  /**
221
- * Build theme URL parameters
232
+ * Serialize the active theme into URL query parameters for the dialog.
233
+ *
234
+ * Merges the instance-level theme (set via constructor or `setTheme`) with any
235
+ * per-call override, so callers can temporarily change the appearance for a
236
+ * single dialog invocation without mutating shared state.
237
+ *
238
+ * @param overrideTheme - One-shot theme values that take precedence over the
239
+ * instance theme for this call only.
240
+ * @returns A URL-encoded query string (e.g. `"theme=dark&accent=%230090ff"`),
241
+ * or an empty string if no theme properties are set.
222
242
  */
223
243
  getThemeParams(overrideTheme) {
224
244
  const theme = { ...this.theme, ...overrideTheme };
@@ -232,15 +252,30 @@ var OneAuthClient = class {
232
252
  return params.toString();
233
253
  }
234
254
  /**
235
- * Get the dialog URL (Vite app URL)
236
- * Defaults to providerUrl if dialogUrl is not set
255
+ * Resolve the URL of the dialog app (the Vite/Next.js frontend that renders
256
+ * the WebAuthn UI inside the iframe).
257
+ *
258
+ * `dialogUrl` is a separate config option to support split deployments where
259
+ * the API server and the dialog frontend run at different origins. Falls back
260
+ * to `providerUrl` when not explicitly configured — the common case.
261
+ *
262
+ * @returns The base URL used when constructing all dialog endpoint paths.
237
263
  */
238
264
  getDialogUrl() {
239
265
  return this.config.dialogUrl || this.config.providerUrl;
240
266
  }
241
267
  /**
242
- * Get the origin for message validation
243
- * Uses dialogUrl origin if set, otherwise providerUrl origin
268
+ * Extract the trusted origin used to validate incoming postMessage events.
269
+ *
270
+ * All `window.addEventListener("message", ...)` handlers in this class check
271
+ * `event.origin` against this value to prevent cross-origin spoofing. Wraps
272
+ * `getDialogUrl()` in a `new URL()` parse so that paths and query strings are
273
+ * stripped — only the scheme + host + port are compared.
274
+ *
275
+ * Falls back to the raw dialog URL string if URL parsing fails (e.g. during
276
+ * unit tests with non-standard URL formats).
277
+ *
278
+ * @returns The scheme + host + optional port of the dialog app (e.g. `"https://passkey.1auth.box"`).
244
279
  */
245
280
  getDialogOrigin() {
246
281
  const dialogUrl = this.getDialogUrl();
@@ -262,6 +297,24 @@ var OneAuthClient = class {
262
297
  getClientId() {
263
298
  return this.config.clientId;
264
299
  }
300
+ /**
301
+ * Poll the intent status endpoint until a transaction hash appears or the
302
+ * intent reaches a terminal failure state.
303
+ *
304
+ * This is used after `sendIntent` resolves (with `waitForHash: true`) to
305
+ * continue waiting for the on-chain hash even after the dialog has been
306
+ * dismissed. It is intentionally separate from the in-dialog polling loop so
307
+ * callers that don't need the hash can skip it entirely.
308
+ *
309
+ * Errors during individual poll attempts are swallowed so that transient
310
+ * network issues don't abort the wait prematurely.
311
+ *
312
+ * @param intentId - The Rhinestone intent ID returned by the execute endpoint.
313
+ * @param options.timeoutMs - Maximum wait time in milliseconds (default 120 000).
314
+ * @param options.intervalMs - Time between polls in milliseconds (default 2 000).
315
+ * @returns The transaction hash string, or `undefined` if the deadline was
316
+ * reached or the intent failed/expired before a hash was produced.
317
+ */
265
318
  async waitForTransactionHash(intentId, options = {}) {
266
319
  const timeoutMs = options.timeoutMs ?? 12e4;
267
320
  const intervalMs = options.intervalMs ?? 2e3;
@@ -667,25 +720,7 @@ var OneAuthClient = class {
667
720
  * ```
668
721
  */
669
722
  async sendIntent(options) {
670
- const signedIntent = options.signedIntent ? {
671
- ...options.signedIntent,
672
- merchantId: options.signedIntent.merchantId || options.signedIntent.developerId
673
- } : void 0;
674
- const username = signedIntent?.username || options.username;
675
- const targetChain = signedIntent?.targetChain || options.targetChain;
676
- const calls = signedIntent?.calls || options.calls;
677
- if (signedIntent && !signedIntent.merchantId) {
678
- return {
679
- success: false,
680
- intentId: "",
681
- status: "failed",
682
- error: {
683
- code: "INVALID_OPTIONS",
684
- message: "Signed intent requires developerId (clientId)"
685
- }
686
- };
687
- }
688
- const accountAddress = signedIntent?.accountAddress || options.accountAddress;
723
+ const { username, accountAddress, targetChain, calls } = options;
689
724
  if (!username && !accountAddress) {
690
725
  return {
691
726
  success: false,
@@ -693,7 +728,7 @@ var OneAuthClient = class {
693
728
  status: "failed",
694
729
  error: {
695
730
  code: "INVALID_OPTIONS",
696
- message: "Either username, accountAddress, or signedIntent with user identifier is required"
731
+ message: "Either username or accountAddress is required"
697
732
  }
698
733
  };
699
734
  }
@@ -704,7 +739,7 @@ var OneAuthClient = class {
704
739
  status: "failed",
705
740
  error: {
706
741
  code: "INVALID_OPTIONS",
707
- message: "targetChain and calls are required (either directly or via signedIntent)"
742
+ message: "targetChain and calls are required"
708
743
  }
709
744
  };
710
745
  }
@@ -713,11 +748,11 @@ var OneAuthClient = class {
713
748
  amount: r.amount.toString()
714
749
  }));
715
750
  let prepareResponse;
716
- const requestBody = signedIntent || {
717
- username: options.username,
718
- accountAddress: options.accountAddress,
719
- targetChain: options.targetChain,
720
- calls: options.calls,
751
+ const requestBody = {
752
+ username,
753
+ accountAddress,
754
+ targetChain,
755
+ calls,
721
756
  tokenRequests: serializedTokenRequests,
722
757
  sourceAssets: options.sourceAssets,
723
758
  sourceChainId: options.sourceChainId,
@@ -833,7 +868,6 @@ var OneAuthClient = class {
833
868
  dialogOrigin,
834
869
  // Refresh callback - called when dialog requests a quote refresh
835
870
  async () => {
836
- console.log("[SDK] Dialog requested quote refresh, re-preparing intent");
837
871
  try {
838
872
  const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
839
873
  method: "POST",
@@ -1215,7 +1249,6 @@ var OneAuthClient = class {
1215
1249
  if (event.origin !== dialogOrigin) return;
1216
1250
  const message = event.data;
1217
1251
  if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1218
- console.log("[SDK] Batch dialog requested quote refresh, re-preparing all intents");
1219
1252
  try {
1220
1253
  const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
1221
1254
  method: "POST",
@@ -1313,7 +1346,22 @@ var OneAuthClient = class {
1313
1346
  );
1314
1347
  }
1315
1348
  /**
1316
- * Wait for the signing result without closing the modal
1349
+ * Listen for a signing result that belongs to a specific `requestId`.
1350
+ *
1351
+ * Used by legacy server-side signing request flows (popup/embed/modal) where
1352
+ * the dialog was pre-loaded with a signing request created via the API. The
1353
+ * `requestId` filter is needed because multiple dialogs may be open or there
1354
+ * may be stale messages from a previous dialog in the queue.
1355
+ *
1356
+ * Resolves on `PASSKEY_SIGNING_RESULT` matching `requestId`, or on
1357
+ * `PASSKEY_CLOSE` (user dismissed the dialog).
1358
+ *
1359
+ * @param requestId - The signing request ID to match against incoming messages.
1360
+ * @param dialog - The `<dialog>` element (opened before this call) used to
1361
+ * `showModal()` so the dialog becomes interactive.
1362
+ * @param _iframe - Unused; kept for signature consistency with other wait helpers.
1363
+ * @param cleanup - Idempotent teardown function that closes and removes the dialog.
1364
+ * @returns A `SigningResult` discriminated union.
1317
1365
  */
1318
1366
  waitForIntentSigningResponse(requestId, dialog, _iframe, cleanup) {
1319
1367
  const dialogOrigin = this.getDialogOrigin();
@@ -1358,18 +1406,30 @@ var OneAuthClient = class {
1358
1406
  });
1359
1407
  }
1360
1408
  /**
1361
- * Wait for signing result (simplified - no requestId matching)
1409
+ * Listen for a signing result from the currently open modal dialog.
1410
+ *
1411
+ * Unlike `waitForIntentSigningResponse`, this variant does not filter by
1412
+ * `requestId` because the inline intent flow (sendIntent / signMessage /
1413
+ * signTypedData) owns the dialog exclusively — there is no risk of collisions
1414
+ * from other dialogs.
1415
+ *
1416
+ * Handles two result shapes:
1417
+ * - New "secure flow": dialog executed the intent server-side and returns
1418
+ * only an `intentId` (no signature exposed to the SDK).
1419
+ * - Legacy flow: dialog returns a raw `WebAuthnSignature` for the SDK to
1420
+ * forward to the execute endpoint.
1421
+ *
1422
+ * @param dialog - The `<dialog>` element wrapping the signing iframe.
1423
+ * @param _iframe - Unused; kept for signature consistency with other wait helpers.
1424
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1425
+ * @returns A `SigningResult` extended with an optional `signedHash` field
1426
+ * populated when the dialog performs message signing.
1362
1427
  */
1363
1428
  waitForSigningResponse(dialog, _iframe, cleanup) {
1364
1429
  const dialogOrigin = this.getDialogOrigin();
1365
- console.log("[SDK] waitForSigningResponse, expecting origin:", dialogOrigin);
1366
1430
  return new Promise((resolve) => {
1367
1431
  const handleMessage = (event) => {
1368
- console.log("[SDK] Received message:", event.origin, event.data?.type);
1369
- if (event.origin !== dialogOrigin) {
1370
- console.log("[SDK] Origin mismatch, ignoring. Expected:", dialogOrigin, "Got:", event.origin);
1371
- return;
1372
- }
1432
+ if (event.origin !== dialogOrigin) return;
1373
1433
  const message = event.data;
1374
1434
  const payload = message?.data;
1375
1435
  if (message?.type === "PASSKEY_SIGNING_RESULT") {
@@ -1411,17 +1471,34 @@ var OneAuthClient = class {
1411
1471
  });
1412
1472
  }
1413
1473
  /**
1414
- * Wait for signing result with auto-refresh support
1415
- * This method handles both signing results and quote refresh requests from the dialog
1474
+ * Listen for a signing result while also handling quote-refresh requests.
1475
+ *
1476
+ * Quotes from the Rhinestone orchestrator have a short TTL (typically ~60 s).
1477
+ * When the user takes too long to review and the quote expires, the dialog
1478
+ * sends `PASSKEY_REFRESH_QUOTE`. This method intercepts that message, calls
1479
+ * the provided `onRefresh` callback to fetch a new quote, and replies with
1480
+ * either `PASSKEY_REFRESH_COMPLETE` (success) or `PASSKEY_REFRESH_ERROR`
1481
+ * (failure) — allowing the dialog to stay open and show the updated fees
1482
+ * without requiring a full restart.
1483
+ *
1484
+ * Once the user confirms or rejects, the promise resolves with the signing
1485
+ * result exactly as `waitForSigningResponse` would.
1486
+ *
1487
+ * @param dialog - The `<dialog>` element wrapping the signing iframe.
1488
+ * @param iframe - The iframe element; used to post refresh messages back.
1489
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1490
+ * @param dialogOrigin - Trusted origin for message validation (passed in to
1491
+ * avoid redundant `getDialogOrigin()` calls from the hot path).
1492
+ * @param onRefresh - Async callback that fetches a fresh quote and returns the
1493
+ * updated intent fields, or `null` if the refresh failed.
1494
+ * @returns A `SigningResult` extended with an optional `signedHash`.
1416
1495
  */
1417
1496
  waitForSigningWithRefresh(dialog, iframe, cleanup, dialogOrigin, onRefresh) {
1418
- console.log("[SDK] waitForSigningWithRefresh, expecting origin:", dialogOrigin);
1419
1497
  return new Promise((resolve) => {
1420
1498
  const handleMessage = async (event) => {
1421
1499
  if (event.origin !== dialogOrigin) return;
1422
1500
  const message = event.data;
1423
1501
  if (message?.type === "PASSKEY_REFRESH_QUOTE") {
1424
- console.log("[SDK] Received quote refresh request from dialog");
1425
1502
  const refreshedData = await onRefresh();
1426
1503
  if (refreshedData) {
1427
1504
  iframe.contentWindow?.postMessage({
@@ -1476,7 +1553,20 @@ var OneAuthClient = class {
1476
1553
  });
1477
1554
  }
1478
1555
  /**
1479
- * Wait for the dialog to be closed
1556
+ * Wait for the dialog to be explicitly closed by the user or the iframe.
1557
+ *
1558
+ * Resolves on either:
1559
+ * - `PASSKEY_CLOSE` postMessage from the iframe (user clicked the X button
1560
+ * or the dialog programmatically closed itself after showing a result).
1561
+ * - The native `<dialog> close` event (escape key or `dialog.close()` call).
1562
+ *
1563
+ * Calling `cleanup()` before awaiting this is safe — both resolution paths
1564
+ * call `cleanup()` internally but it is idempotent. The primary use case is
1565
+ * keeping the dialog open while the SDK polls for a transaction hash, then
1566
+ * showing the final success/error state before letting the user dismiss.
1567
+ *
1568
+ * @param dialog - The `<dialog>` element to watch.
1569
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1480
1570
  */
1481
1571
  waitForDialogClose(dialog, cleanup) {
1482
1572
  const dialogOrigin = this.getDialogOrigin();
@@ -1500,7 +1590,17 @@ var OneAuthClient = class {
1500
1590
  });
1501
1591
  }
1502
1592
  /**
1503
- * Inject a preconnect link tag to pre-warm DNS + TLS for a given URL.
1593
+ * Inject a `<link rel="preconnect">` tag for the given URL's origin.
1594
+ *
1595
+ * Browsers use preconnect hints to resolve DNS, perform the TCP handshake,
1596
+ * and negotiate TLS before a resource is actually requested, reducing
1597
+ * time-to-first-byte when the dialog iframe loads. The tag is deduplicated —
1598
+ * if one already exists for the same origin it will not be added again.
1599
+ *
1600
+ * Only called in browser environments (guarded by `typeof document` in the
1601
+ * constructor). Invalid URLs are silently ignored.
1602
+ *
1603
+ * @param url - Any URL whose origin should be preconnected to.
1504
1604
  */
1505
1605
  injectPreconnect(url) {
1506
1606
  try {
@@ -1515,8 +1615,24 @@ var OneAuthClient = class {
1515
1615
  }
1516
1616
  }
1517
1617
  /**
1518
- * Wait for the dialog iframe to signal ready without sending init data.
1519
- * Returns a sendInit function the caller uses once prepare data is available.
1618
+ * Wait for the dialog iframe to signal ready, but defer sending `PASSKEY_INIT`
1619
+ * until the caller decides the time is right.
1620
+ *
1621
+ * This is the deferred variant of `waitForDialogReady`, used by flows that run
1622
+ * the dialog and a background API call in parallel (the "two-phase" approach).
1623
+ * Rather than blocking until both the iframe is ready AND the API call is done,
1624
+ * this method resolves as soon as `PASSKEY_READY` arrives and returns a
1625
+ * `sendInit` callback. The caller invokes `sendInit` once prepare data is
1626
+ * available, which may happen before or after the iframe is ready.
1627
+ *
1628
+ * Timeout: if the iframe never signals ready within 10 seconds (e.g. network
1629
+ * error, origin mismatch), resolves with `{ ready: false }` and calls cleanup.
1630
+ *
1631
+ * @param dialog - The `<dialog>` element wrapping the iframe.
1632
+ * @param iframe - The iframe element that will receive `PASSKEY_INIT`.
1633
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
1634
+ * @returns `{ ready: true, sendInit }` when the iframe is ready, or
1635
+ * `{ ready: false }` if the dialog was closed or timed out first.
1520
1636
  */
1521
1637
  waitForDialogReadyDeferred(dialog, iframe, cleanup) {
1522
1638
  const dialogOrigin = this.getDialogOrigin();
@@ -1562,8 +1678,23 @@ var OneAuthClient = class {
1562
1678
  });
1563
1679
  }
1564
1680
  /**
1565
- * Prepare an intent by calling the orchestrator for a quote.
1566
- * Returns the prepare response and the origin tier from the response header.
1681
+ * Call the passkey service to obtain a Rhinestone orchestrator quote for an
1682
+ * intent (a single target-chain transaction or swap).
1683
+ *
1684
+ * The service contacts the Rhinestone orchestrator, which returns a signed
1685
+ * `intentOp` structure containing the quote, fees, and a WebAuthn challenge
1686
+ * the user must sign to authorize execution.
1687
+ *
1688
+ * The `X-Origin-Tier` response header is forwarded to the dialog so it can
1689
+ * display tier-specific UI (e.g. "Free" vs "Premium" badge).
1690
+ *
1691
+ * Side effect: if the server returns "User not found", the cached user entry
1692
+ * is removed from `localStorage` to force re-authentication.
1693
+ *
1694
+ * @param requestBody - Serialized intent options (bigint amounts converted to
1695
+ * strings before this call).
1696
+ * @returns On success: `{ success: true, data: PrepareIntentResponse, tier }`.
1697
+ * On failure: `{ success: false, error: { code, message } }`.
1567
1698
  */
1568
1699
  async prepareIntent(requestBody) {
1569
1700
  try {
@@ -1599,7 +1730,21 @@ var OneAuthClient = class {
1599
1730
  }
1600
1731
  }
1601
1732
  /**
1602
- * Prepare a batch intent by calling the orchestrator for quotes on all intents.
1733
+ * Call the passkey service to obtain orchestrator quotes for all intents in a
1734
+ * batch, returning a single shared WebAuthn challenge.
1735
+ *
1736
+ * The service prepares each intent independently and assembles a merkle tree
1737
+ * whose root becomes the challenge. Signing that root once authorizes every
1738
+ * intent in the batch. Partially-failed batches are supported: the response
1739
+ * includes both `intents` (succeeded) and `failedIntents` (per-intent errors),
1740
+ * so the dialog can still show the successful subset for signing.
1741
+ *
1742
+ * Side effect: same "User not found" localStorage cleanup as `prepareIntent`.
1743
+ *
1744
+ * @param requestBody - Serialized batch options (bigint amounts converted to
1745
+ * strings before this call).
1746
+ * @returns On success: `{ success: true, data: PrepareBatchIntentResponse, tier }`.
1747
+ * On failure: `{ success: false, error, failedIntents? }`.
1603
1748
  */
1604
1749
  async prepareBatchIntent(requestBody) {
1605
1750
  try {
@@ -1627,7 +1772,15 @@ var OneAuthClient = class {
1627
1772
  }
1628
1773
  }
1629
1774
  /**
1630
- * Send a prepare error message to the dialog iframe.
1775
+ * Forward a prepare-phase error to the dialog iframe so it can display a
1776
+ * human-readable failure message instead of hanging on the loading state.
1777
+ *
1778
+ * The dialog listens for `PASSKEY_PREPARE_ERROR` and transitions to an error
1779
+ * view. The user can then dismiss the dialog, at which point
1780
+ * `waitForDialogClose` resolves and the SDK returns the error to the caller.
1781
+ *
1782
+ * @param iframe - The iframe element to post the error to.
1783
+ * @param error - Human-readable error message from the prepare response.
1631
1784
  */
1632
1785
  sendPrepareError(iframe, error) {
1633
1786
  const dialogOrigin = this.getDialogOrigin();
@@ -1810,13 +1963,6 @@ var OneAuthClient = class {
1810
1963
  };
1811
1964
  }
1812
1965
  const toTokenAddress = toTokenResult.address;
1813
- console.log("[SDK sendSwap] Token resolution:", {
1814
- fromToken: options.fromToken ?? "Any",
1815
- fromTokenAddress: fromTokenAddress ?? "orchestrator picks",
1816
- toToken: options.toToken,
1817
- toTokenAddress,
1818
- targetChain: options.targetChain
1819
- });
1820
1966
  const formatTokenLabel = (token, fallback) => {
1821
1967
  if (!token.startsWith("0x")) {
1822
1968
  return token;
@@ -1845,16 +1991,10 @@ var OneAuthClient = class {
1845
1991
  const match = getSupportedTokens(chainId).find(
1846
1992
  (t) => t.symbol.toUpperCase() === symbol.toUpperCase()
1847
1993
  );
1848
- if (match) {
1849
- console.log(`[SDK] getTokenDecimals(${match.symbol}, ${chainId}) = ${match.decimals}`);
1850
- return match.decimals;
1851
- }
1852
- const decimals = (0, import_sdk.getTokenDecimals)(symbol, chainId);
1853
- console.log(`[SDK] getTokenDecimals(${symbol}, ${chainId}) = ${decimals}`);
1854
- return decimals;
1855
- } catch (e) {
1994
+ if (match) return match.decimals;
1995
+ return (0, import_sdk.getTokenDecimals)(symbol, chainId);
1996
+ } catch {
1856
1997
  const upperSymbol = symbol.toUpperCase();
1857
- console.warn(`[SDK] getTokenDecimals failed for ${symbol}, using fallback`, e);
1858
1998
  return KNOWN_DECIMALS[upperSymbol] ?? 18;
1859
1999
  }
1860
2000
  };
@@ -1864,13 +2004,6 @@ var OneAuthClient = class {
1864
2004
  token: toTokenAddress,
1865
2005
  amount: (0, import_viem2.parseUnits)(options.amount, toDecimals)
1866
2006
  }];
1867
- console.log("[SDK sendSwap] Building intent:", {
1868
- isBridge,
1869
- isFromNativeEth,
1870
- isToNativeEth,
1871
- toDecimals,
1872
- tokenRequests
1873
- });
1874
2007
  const result = await this.sendIntent({
1875
2008
  username: options.username,
1876
2009
  targetChain: options.targetChain,
@@ -2131,6 +2264,20 @@ var OneAuthClient = class {
2131
2264
  const iframe = this.createEmbed(request.requestId, embedOptions);
2132
2265
  return this.waitForEmbedResponse(request.requestId, iframe, embedOptions);
2133
2266
  }
2267
+ /**
2268
+ * Create and append a signing iframe to a caller-supplied container element.
2269
+ *
2270
+ * Used by the `signWithEmbed` flow where the integrator wants the signing UI
2271
+ * to appear inline on their page rather than in a modal overlay. The iframe
2272
+ * loads a pre-created signing request URL and fires `options.onReady` once
2273
+ * the page has loaded.
2274
+ *
2275
+ * @param requestId - The signing request ID; used to construct the iframe src
2276
+ * and give it a stable DOM id for later removal via `removeEmbed`.
2277
+ * @param options - Embed configuration including the container element,
2278
+ * optional dimensions, and lifecycle callbacks.
2279
+ * @returns The created `<iframe>` element (already appended to the container).
2280
+ */
2134
2281
  createEmbed(requestId, options) {
2135
2282
  const dialogUrl = this.getDialogUrl();
2136
2283
  const iframe = document.createElement("iframe");
@@ -2148,6 +2295,19 @@ var OneAuthClient = class {
2148
2295
  options.container.appendChild(iframe);
2149
2296
  return iframe;
2150
2297
  }
2298
+ /**
2299
+ * Listen for a signing result from an embedded (inline) signing iframe.
2300
+ *
2301
+ * Matches incoming `PASSKEY_SIGNING_RESULT` messages against `requestId`
2302
+ * to avoid reacting to messages from other iframes on the page. On resolution
2303
+ * (success or failure), the iframe is removed from the DOM and
2304
+ * `embedOptions.onClose` is invoked.
2305
+ *
2306
+ * @param requestId - The signing request ID to match against incoming messages.
2307
+ * @param iframe - The embedded signing iframe.
2308
+ * @param embedOptions - Embed configuration; `onClose` is called after cleanup.
2309
+ * @returns A `SigningResult` discriminated union.
2310
+ */
2151
2311
  waitForEmbedResponse(requestId, iframe, embedOptions) {
2152
2312
  const dialogOrigin = this.getDialogOrigin();
2153
2313
  return new Promise((resolve) => {
@@ -2242,6 +2402,22 @@ var OneAuthClient = class {
2242
2402
  const data = await response.json();
2243
2403
  return data.passkeys;
2244
2404
  }
2405
+ /**
2406
+ * Register a signing request with the passkey service and receive a
2407
+ * short-lived `requestId`.
2408
+ *
2409
+ * Used by the legacy popup, redirect, and embed flows. The passkey service
2410
+ * stores the challenge and metadata server-side so the dialog page can fetch
2411
+ * them by `requestId` without relying on URL parameters alone. This avoids
2412
+ * exposing potentially large challenge payloads in query strings.
2413
+ *
2414
+ * @param options - The signing options (challenge, username, description, etc.).
2415
+ * @param mode - How the dialog will be opened (`"popup"`, `"redirect"`, or `"embed"`).
2416
+ * @param redirectUrl - Only required for `"redirect"` mode; the URL the dialog
2417
+ * will navigate back to after signing.
2418
+ * @returns The created signing request with its unique `requestId`.
2419
+ * @throws If the API call fails.
2420
+ */
2245
2421
  async createSigningRequest(options, mode, redirectUrl) {
2246
2422
  const response = await fetch(
2247
2423
  `${this.config.providerUrl}/api/sign/request`,
@@ -2268,6 +2444,17 @@ var OneAuthClient = class {
2268
2444
  }
2269
2445
  return response.json();
2270
2446
  }
2447
+ /**
2448
+ * Open a centered popup window for the signing or auth dialog.
2449
+ *
2450
+ * Positions the popup near the top of the current window (50px from the top)
2451
+ * and horizontally centered relative to the browser window. Uses `popup=true`
2452
+ * to request a minimal chrome popup (no address bar) on browsers that support
2453
+ * the Window Management API.
2454
+ *
2455
+ * @param url - The full URL to open in the popup.
2456
+ * @returns The popup `Window` reference, or `null` if the browser blocked it.
2457
+ */
2271
2458
  openPopup(url) {
2272
2459
  const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
2273
2460
  const top = window.screenY + 50;
@@ -2278,9 +2465,24 @@ var OneAuthClient = class {
2278
2465
  );
2279
2466
  }
2280
2467
  /**
2281
- * Wait for the dialog iframe to signal ready, then send init data.
2282
- * Also handles early close (X button, escape, backdrop) during the ready phase.
2283
- * Returns true if dialog is ready, false if it was closed before becoming ready.
2468
+ * Wait for the dialog iframe to signal ready, then immediately send init data.
2469
+ *
2470
+ * This is the synchronous-init variant: `initMessage` is available at call
2471
+ * time, so it can be posted as soon as `PASSKEY_READY` arrives. Use
2472
+ * `waitForDialogReadyDeferred` when init data depends on an in-flight API call.
2473
+ *
2474
+ * Also handles early close (X button, escape key, backdrop click) during the
2475
+ * ready phase so those paths resolve cleanly without leaking event listeners.
2476
+ *
2477
+ * Timeout: resolves `false` after 10 seconds if the iframe never signals ready
2478
+ * (e.g. origin mismatch or network error loading the dialog app).
2479
+ *
2480
+ * @param dialog - The `<dialog>` element wrapping the iframe.
2481
+ * @param iframe - The iframe element that will receive `PASSKEY_INIT`.
2482
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
2483
+ * @param initMessage - The `PASSKEY_INIT` payload to send when ready.
2484
+ * @returns `true` if the dialog is ready and init was sent; `false` if the
2485
+ * dialog was closed or timed out before becoming ready.
2284
2486
  */
2285
2487
  waitForDialogReady(dialog, iframe, cleanup, initMessage) {
2286
2488
  const dialogOrigin = this.getDialogOrigin();
@@ -2323,10 +2525,33 @@ var OneAuthClient = class {
2323
2525
  });
2324
2526
  }
2325
2527
  /**
2326
- * Create a modal dialog with a full-viewport iframe inside.
2327
- * All visual chrome (backdrop, positioning, animations) is rendered
2328
- * by the passkey app inside the iframe the SDK just provides
2329
- * a transparent full-screen container.
2528
+ * Create and open a full-viewport `<dialog>` containing a passkey iframe.
2529
+ *
2530
+ * The SDK owns only the transparent outer shell; all visible UI (backdrop,
2531
+ * card, animations, close button) is rendered by the passkey app inside the
2532
+ * iframe. This approach means design changes can be shipped server-side
2533
+ * without updating SDK consumers.
2534
+ *
2535
+ * Lifecycle:
2536
+ * 1. A themed "Loading…" overlay is injected and shown immediately while the
2537
+ * iframe loads, matching the exact visual structure of the passkey app's
2538
+ * TitleBar + IndeterminateLoader so the transition is seamless.
2539
+ * 2. When the iframe sends `PASSKEY_RENDERED`, the overlay fades out and
2540
+ * the iframe becomes fully visible.
2541
+ * 3. The returned `cleanup` function tears down all event listeners,
2542
+ * disconnects the MutationObserver, closes the `<dialog>`, and removes
2543
+ * it from the DOM. It is idempotent — safe to call multiple times.
2544
+ *
2545
+ * Browser quirks handled:
2546
+ * - The 1Password extension sets `inert` on the `<dialog>`, making it
2547
+ * non-interactive. A MutationObserver removes the attribute immediately.
2548
+ * - Password managers may also set `inert` on dialog siblings; cleanup
2549
+ * restores those as well.
2550
+ * - The iframe sandbox includes `allow-popups-to-escape-sandbox` so that
2551
+ * 1Password's popup UI can render outside the sandboxed context.
2552
+ *
2553
+ * @param url - The full URL (including query params) to load in the iframe.
2554
+ * @returns References to the dialog, iframe, and a cleanup function.
2330
2555
  */
2331
2556
  createModalDialog(url) {
2332
2557
  const dialogUrl = this.getDialogUrl();
@@ -2524,6 +2749,24 @@ var OneAuthClient = class {
2524
2749
  };
2525
2750
  return { dialog, iframe, cleanup };
2526
2751
  }
2752
+ /**
2753
+ * Listen for the auth result from the modal dialog.
2754
+ *
2755
+ * Waits for `PASSKEY_READY` before processing any other messages to avoid
2756
+ * acting on stale `PASSKEY_CLOSE` events that may still be in the event queue
2757
+ * from a previously closed dialog.
2758
+ *
2759
+ * Also handles the `PASSKEY_RETRY_POPUP` message: sent by the dialog when a
2760
+ * password manager (e.g. Bitwarden) intercepts the WebAuthn call inside the
2761
+ * iframe. In that case the SDK seamlessly re-opens a standalone popup window,
2762
+ * where WebAuthn works without cross-origin iframe restrictions, and waits
2763
+ * for the result from there instead.
2764
+ *
2765
+ * @param _dialog - Unused; kept for signature consistency.
2766
+ * @param iframe - The iframe element; used to send `PASSKEY_INIT` on ready.
2767
+ * @param cleanup - Idempotent teardown for the modal dialog.
2768
+ * @returns An `AuthResult` discriminated union.
2769
+ */
2527
2770
  waitForModalAuthResponse(_dialog, iframe, cleanup) {
2528
2771
  const dialogOrigin = this.getDialogOrigin();
2529
2772
  return new Promise((resolve) => {
@@ -2640,6 +2883,20 @@ var OneAuthClient = class {
2640
2883
  window.addEventListener("message", handleMessage);
2641
2884
  });
2642
2885
  }
2886
+ /**
2887
+ * Listen for the authenticate result from the modal dialog.
2888
+ *
2889
+ * The authenticate flow combines sign-in/sign-up with optional off-chain
2890
+ * challenge signing. The dialog sends `PASSKEY_AUTHENTICATE_RESULT` on
2891
+ * completion with both user details and, if a challenge was requested, the
2892
+ * WebAuthn signature and the hashed challenge value for server-side
2893
+ * verification.
2894
+ *
2895
+ * @param _dialog - Unused; kept for signature consistency.
2896
+ * @param _iframe - Unused; kept for signature consistency.
2897
+ * @param cleanup - Idempotent teardown for the modal dialog.
2898
+ * @returns An `AuthenticateResult` discriminated union.
2899
+ */
2643
2900
  waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
2644
2901
  const dialogOrigin = this.getDialogOrigin();
2645
2902
  return new Promise((resolve) => {
@@ -2683,6 +2940,22 @@ var OneAuthClient = class {
2683
2940
  window.addEventListener("message", handleMessage);
2684
2941
  });
2685
2942
  }
2943
+ /**
2944
+ * Listen for the connect result from the connect dialog.
2945
+ *
2946
+ * The connect flow is a lightweight "are you still you?" confirmation that
2947
+ * does not require a new passkey ceremony. On success, the dialog returns the
2948
+ * user's username, address, and an `autoConnected` flag indicating whether the
2949
+ * user had previously enabled auto-connect (in which case no UI was shown).
2950
+ *
2951
+ * On failure with `action: "switch"`, the caller should redirect to the full
2952
+ * auth flow because the user has no cached session to confirm.
2953
+ *
2954
+ * @param _dialog - Unused; kept for signature consistency.
2955
+ * @param _iframe - Unused; kept for signature consistency.
2956
+ * @param cleanup - Idempotent teardown for the modal dialog.
2957
+ * @returns A `ConnectResult` discriminated union.
2958
+ */
2686
2959
  waitForConnectResponse(_dialog, _iframe, cleanup) {
2687
2960
  const dialogOrigin = this.getDialogOrigin();
2688
2961
  return new Promise((resolve) => {
@@ -2724,6 +2997,23 @@ var OneAuthClient = class {
2724
2997
  window.addEventListener("message", handleMessage);
2725
2998
  });
2726
2999
  }
3000
+ /**
3001
+ * Listen for the consent result from the consent dialog.
3002
+ *
3003
+ * The consent dialog shows the user which data fields an app is requesting
3004
+ * access to and lets them approve or deny. On approval, `data` contains the
3005
+ * consented field values (e.g. email address, device names) and `grantedAt`
3006
+ * records when consent was given.
3007
+ *
3008
+ * A `PASSKEY_CLOSE` without a prior `PASSKEY_CONSENT_RESULT` is treated as an
3009
+ * explicit user cancellation (distinct from denial, though both yield
3010
+ * `success: false`).
3011
+ *
3012
+ * @param _dialog - Unused; kept for signature consistency.
3013
+ * @param _iframe - Unused; kept for signature consistency.
3014
+ * @param cleanup - Idempotent teardown for the modal dialog.
3015
+ * @returns A `RequestConsentResult` discriminated union.
3016
+ */
2727
3017
  waitForConsentResponse(_dialog, _iframe, cleanup) {
2728
3018
  const dialogOrigin = this.getDialogOrigin();
2729
3019
  return new Promise((resolve) => {
@@ -2763,6 +3053,20 @@ var OneAuthClient = class {
2763
3053
  window.addEventListener("message", handleMessage);
2764
3054
  });
2765
3055
  }
3056
+ /**
3057
+ * Listen for a signing result from a server-request-based modal dialog.
3058
+ *
3059
+ * Similar to `waitForIntentSigningResponse` but intended for the older
3060
+ * `signWithModal` path that pre-creates a signing request via the API (rather
3061
+ * than inlining all data in `PASSKEY_INIT`). Filters by `requestId` to handle
3062
+ * the case where stale messages from a previous dialog may still be in flight.
3063
+ *
3064
+ * @param requestId - The signing request ID to match against incoming messages.
3065
+ * @param _dialog - Unused; kept for signature consistency.
3066
+ * @param _iframe - Unused; kept for signature consistency.
3067
+ * @param cleanup - Idempotent teardown that closes and removes the dialog.
3068
+ * @returns A `SigningResult` discriminated union.
3069
+ */
2766
3070
  waitForModalSigningResponse(requestId, _dialog, _iframe, cleanup) {
2767
3071
  const dialogOrigin = this.getDialogOrigin();
2768
3072
  return new Promise((resolve) => {
@@ -2802,6 +3106,21 @@ var OneAuthClient = class {
2802
3106
  window.addEventListener("message", handleMessage);
2803
3107
  });
2804
3108
  }
3109
+ /**
3110
+ * Listen for a signing result from a popup window.
3111
+ *
3112
+ * Polls for popup closure every 500 ms as a fallback to detect when the user
3113
+ * closes the popup without completing the signing flow (no `PASSKEY_CLOSE`
3114
+ * message is fired in that case because the popup's unload event cannot
3115
+ * reliably postMessage to the opener on all browsers).
3116
+ *
3117
+ * On success or cancellation, the popup is programmatically closed and the
3118
+ * interval is cleared.
3119
+ *
3120
+ * @param requestId - The signing request ID to match against incoming messages.
3121
+ * @param popup - The popup `Window` reference returned by `openPopup`.
3122
+ * @returns A `SigningResult` discriminated union.
3123
+ */
2805
3124
  waitForPopupResponse(requestId, popup) {
2806
3125
  const dialogOrigin = this.getDialogOrigin();
2807
3126
  return new Promise((resolve) => {
@@ -2847,6 +3166,17 @@ var OneAuthClient = class {
2847
3166
  window.addEventListener("message", handleMessage);
2848
3167
  });
2849
3168
  }
3169
+ /**
3170
+ * Fetch the completed signing result from the passkey service by request ID.
3171
+ *
3172
+ * Used by the redirect flow: after the passkey service redirects back to
3173
+ * `redirectUrl`, the integrator calls `handleRedirectCallback()`, which
3174
+ * extracts `requestId` from the URL query params and delegates here to
3175
+ * retrieve the full signature from the server.
3176
+ *
3177
+ * @param requestId - The signing request ID from the redirect callback URL.
3178
+ * @returns A `SigningResult` discriminated union.
3179
+ */
2850
3180
  async fetchSigningResult(requestId) {
2851
3181
  const response = await fetch(
2852
3182
  `${this.config.providerUrl}/api/sign/request/${requestId}`,
@@ -3132,31 +3462,16 @@ function createOneAuthProvider(options) {
3132
3462
  }
3133
3463
  return encodeWebAuthnSignature(result.signature);
3134
3464
  };
3135
- const resolveIntentPayload = async (payload) => {
3136
- if (!options.signIntent) {
3137
- return {
3138
- username: payload.username,
3139
- accountAddress: payload.accountAddress,
3140
- targetChain: payload.targetChain,
3141
- calls: payload.calls,
3142
- tokenRequests: payload.tokenRequests
3143
- };
3144
- }
3145
- if (!payload.username) {
3146
- throw new Error("Username required for signed intents. Set a username first.");
3147
- }
3148
- const signedIntent = await options.signIntent({
3149
- username: payload.username,
3150
- accountAddress: payload.accountAddress,
3151
- targetChain: payload.targetChain,
3152
- calls: payload.calls,
3153
- tokenRequests: payload.tokenRequests
3154
- });
3155
- return { signedIntent };
3156
- };
3465
+ const resolveIntentPayload = (payload) => ({
3466
+ username: payload.username,
3467
+ accountAddress: payload.accountAddress,
3468
+ targetChain: payload.targetChain,
3469
+ calls: payload.calls,
3470
+ tokenRequests: payload.tokenRequests
3471
+ });
3157
3472
  const sendIntent = async (payload) => {
3158
3473
  const closeOn = options.closeOn ?? (options.waitForHash ?? true ? "completed" : "preconfirmed");
3159
- const intentPayload = await resolveIntentPayload(payload);
3474
+ const intentPayload = resolveIntentPayload(payload);
3160
3475
  const result = await client.sendIntent({
3161
3476
  ...intentPayload,
3162
3477
  tokenRequests: payload.tokenRequests,
@@ -3317,6 +3632,7 @@ function createOneAuthProvider(options) {
3317
3632
  receipts: data.transactionHash ? [
3318
3633
  {
3319
3634
  logs: [],
3635
+ // 0x1 = success, 0x0 = reverted/failed — mirrors EVM receipt status
3320
3636
  status: data.status === "completed" ? "0x1" : "0x0",
3321
3637
  blockHash: data.blockHash,
3322
3638
  blockNumber: data.blockNumber,
@@ -3430,6 +3746,7 @@ function createPasskeyAccount(client, params) {
3430
3746
  const domain = {
3431
3747
  name: domainInput.name,
3432
3748
  version: domainInput.version,
3749
+ // viem uses bigint for chainId in typed data; the passkey service expects number
3433
3750
  chainId: typeof domainInput.chainId === "bigint" ? Number(domainInput.chainId) : domainInput.chainId,
3434
3751
  verifyingContract: domainInput.verifyingContract,
3435
3752
  salt: domainInput.salt
@@ -3464,6 +3781,7 @@ function createPasskeyAccount(client, params) {
3464
3781
  var import_viem6 = require("viem");
3465
3782
  var import_accounts2 = require("viem/accounts");
3466
3783
  function createPasskeyWalletClient(config) {
3784
+ const resolvedUsername = config.username ?? config.accountAddress;
3467
3785
  const provider = new OneAuthClient({
3468
3786
  providerUrl: config.providerUrl,
3469
3787
  clientId: config.clientId,
@@ -3473,7 +3791,7 @@ function createPasskeyWalletClient(config) {
3473
3791
  const hash = (0, import_viem6.hashMessage)(message);
3474
3792
  const result = await provider.signWithModal({
3475
3793
  challenge: hash,
3476
- username: config.username,
3794
+ username: resolvedUsername,
3477
3795
  description: "Sign message",
3478
3796
  transaction: {
3479
3797
  actions: [
@@ -3504,7 +3822,7 @@ function createPasskeyWalletClient(config) {
3504
3822
  const hash = hashCalls(calls);
3505
3823
  const result = await provider.signWithModal({
3506
3824
  challenge: hash,
3507
- username: config.username,
3825
+ username: resolvedUsername,
3508
3826
  description: "Sign transaction",
3509
3827
  transaction: buildTransactionReview(calls)
3510
3828
  });
@@ -3520,7 +3838,7 @@ function createPasskeyWalletClient(config) {
3520
3838
  const hash = (0, import_viem6.hashTypedData)(typedData);
3521
3839
  const result = await provider.signWithModal({
3522
3840
  challenge: hash,
3523
- username: config.username,
3841
+ username: resolvedUsername,
3524
3842
  description: "Sign typed data",
3525
3843
  transaction: {
3526
3844
  actions: [
@@ -3540,7 +3858,7 @@ function createPasskeyWalletClient(config) {
3540
3858
  }
3541
3859
  return encodeWebAuthnSignature(result.signature);
3542
3860
  };
3543
- const buildIntentPayload = async (calls, targetChainOverride) => {
3861
+ const buildIntentPayload = async (calls, targetChainOverride, extra) => {
3544
3862
  const targetChain = targetChainOverride ?? config.chain.id;
3545
3863
  const intentCalls = calls.map((call) => ({
3546
3864
  to: call.to,
@@ -3549,17 +3867,9 @@ function createPasskeyWalletClient(config) {
3549
3867
  label: call.label,
3550
3868
  sublabel: call.sublabel
3551
3869
  }));
3552
- if (config.signIntent) {
3553
- const signedIntent = await config.signIntent({
3554
- username: config.username,
3555
- accountAddress: config.accountAddress,
3556
- targetChain,
3557
- calls: intentCalls
3558
- });
3559
- return { signedIntent };
3560
- }
3561
3870
  return {
3562
- username: config.username,
3871
+ username: resolvedUsername,
3872
+ accountAddress: config.accountAddress,
3563
3873
  targetChain,
3564
3874
  calls: intentCalls
3565
3875
  };
@@ -3606,12 +3916,14 @@ function createPasskeyWalletClient(config) {
3606
3916
  * Send multiple calls as a single batched transaction
3607
3917
  */
3608
3918
  async sendCalls(params) {
3609
- const { calls, chainId: targetChain, tokenRequests } = params;
3919
+ const { calls, chainId: targetChain, tokenRequests, sourceAssets, sourceChainId } = params;
3610
3920
  const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
3611
- const intentPayload = await buildIntentPayload(calls, targetChain);
3921
+ const intentPayload = await buildIntentPayload(calls, targetChain, { tokenRequests, sourceAssets });
3612
3922
  const result = await provider.sendIntent({
3613
3923
  ...intentPayload,
3614
3924
  tokenRequests,
3925
+ sourceAssets,
3926
+ sourceChainId,
3615
3927
  closeOn,
3616
3928
  waitForHash: config.waitForHash ?? true,
3617
3929
  hashTimeoutMs: config.hashTimeoutMs,