@rhinestone/1auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2213 @@
1
+ import {
2
+ buildTransactionReview,
3
+ createPasskeyProvider,
4
+ encodeWebAuthnSignature,
5
+ getAllSupportedChainsAndTokens,
6
+ getChainById,
7
+ getChainExplorerUrl,
8
+ getChainName,
9
+ getChainRpcUrl,
10
+ getSupportedChainIds,
11
+ getSupportedChains,
12
+ getSupportedTokenSymbols,
13
+ getSupportedTokens,
14
+ getTokenAddress,
15
+ getTokenDecimals,
16
+ getTokenSymbol,
17
+ hashCalls,
18
+ isTestnet,
19
+ isTokenAddressSupported,
20
+ resolveTokenAddress
21
+ } from "./chunk-UXYKIMGZ.mjs";
22
+
23
+ // src/client.ts
24
+ import { parseUnits, hashTypedData } from "viem";
25
+ var POPUP_WIDTH = 450;
26
+ var POPUP_HEIGHT = 600;
27
+ var DEFAULT_EMBED_WIDTH = "400px";
28
+ var DEFAULT_EMBED_HEIGHT = "500px";
29
+ var MODAL_WIDTH = 360;
30
+ var PasskeyProviderClient = class {
31
+ constructor(config) {
32
+ this.config = config;
33
+ this.theme = config.theme || {};
34
+ }
35
+ /**
36
+ * Update the theme configuration at runtime
37
+ */
38
+ setTheme(theme) {
39
+ this.theme = theme;
40
+ }
41
+ /**
42
+ * Build theme URL parameters
43
+ */
44
+ getThemeParams(overrideTheme) {
45
+ const theme = { ...this.theme, ...overrideTheme };
46
+ const params = new URLSearchParams();
47
+ if (theme.mode) {
48
+ params.set("theme", theme.mode);
49
+ }
50
+ if (theme.accent) {
51
+ params.set("accent", theme.accent);
52
+ }
53
+ return params.toString();
54
+ }
55
+ /**
56
+ * Get the dialog URL (Vite app URL)
57
+ * Defaults to providerUrl if dialogUrl is not set
58
+ */
59
+ getDialogUrl() {
60
+ return this.config.dialogUrl || this.config.providerUrl;
61
+ }
62
+ /**
63
+ * Get the origin for message validation
64
+ * Uses dialogUrl origin if set, otherwise providerUrl origin
65
+ */
66
+ getDialogOrigin() {
67
+ const dialogUrl = this.getDialogUrl();
68
+ try {
69
+ return new URL(dialogUrl).origin;
70
+ } catch {
71
+ return dialogUrl;
72
+ }
73
+ }
74
+ /**
75
+ * Get the base provider URL
76
+ */
77
+ getProviderUrl() {
78
+ return this.config.providerUrl;
79
+ }
80
+ /**
81
+ * Get the configured client ID
82
+ */
83
+ getClientId() {
84
+ return this.config.clientId;
85
+ }
86
+ async waitForTransactionHash(intentId, options = {}) {
87
+ const timeoutMs = options.timeoutMs ?? 12e4;
88
+ const intervalMs = options.intervalMs ?? 2e3;
89
+ const deadline = Date.now() + timeoutMs;
90
+ while (Date.now() < deadline) {
91
+ try {
92
+ const response = await fetch(
93
+ `${this.config.providerUrl}/api/intent/status/${intentId}`,
94
+ {
95
+ headers: {
96
+ "x-client-id": this.config.clientId
97
+ }
98
+ }
99
+ );
100
+ if (response.ok) {
101
+ const data = await response.json();
102
+ if (data.transactionHash) {
103
+ return data.transactionHash;
104
+ }
105
+ if (data.status === "failed" || data.status === "expired") {
106
+ return void 0;
107
+ }
108
+ }
109
+ } catch {
110
+ }
111
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
112
+ }
113
+ return void 0;
114
+ }
115
+ /**
116
+ * Show Porto-style "Get started" auth dialog (combines sign in + sign up)
117
+ * This is the recommended method for authentication - shows a modal overlay
118
+ * with both sign in and create account options.
119
+ */
120
+ async authWithModal(options) {
121
+ const dialogUrl = this.getDialogUrl();
122
+ const params = new URLSearchParams({
123
+ clientId: this.config.clientId,
124
+ mode: "iframe"
125
+ });
126
+ if (options?.username) {
127
+ params.set("username", options.username);
128
+ }
129
+ if (options?.oauthEnabled === false) {
130
+ params.set("oauth", "0");
131
+ }
132
+ const themeParams = this.getThemeParams(options?.theme);
133
+ if (themeParams) {
134
+ const themeParsed = new URLSearchParams(themeParams);
135
+ themeParsed.forEach((value, key) => params.set(key, value));
136
+ }
137
+ const url = `${dialogUrl}/dialog/auth?${params.toString()}`;
138
+ const { dialog, iframe, cleanup } = this.createModalDialog(url);
139
+ return this.waitForModalAuthResponse(dialog, iframe, cleanup);
140
+ }
141
+ /**
142
+ * Authenticate a user with an optional challenge to sign.
143
+ *
144
+ * This method combines authentication (sign in / sign up) with optional
145
+ * challenge signing, enabling off-chain login without on-chain transactions.
146
+ *
147
+ * When a challenge is provided:
148
+ * 1. User authenticates (sign in or sign up)
149
+ * 2. The challenge is hashed with a domain separator
150
+ * 3. User signs the hash with their passkey
151
+ * 4. Returns user info + signature for server-side verification
152
+ *
153
+ * The domain separator ("\x19Passkey Signed Message:\n") ensures the signature
154
+ * cannot be reused for transaction signing, preventing phishing attacks.
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * // Authenticate with a login challenge
159
+ * const result = await client.authenticate({
160
+ * challenge: `Login to MyApp\nTimestamp: ${Date.now()}\nNonce: ${crypto.randomUUID()}`
161
+ * });
162
+ *
163
+ * if (result.success && result.signature) {
164
+ * // Verify signature server-side
165
+ * const isValid = await verifyOnServer(
166
+ * result.username,
167
+ * result.signature,
168
+ * result.signedHash
169
+ * );
170
+ * }
171
+ * ```
172
+ */
173
+ async authenticate(options) {
174
+ const dialogUrl = this.getDialogUrl();
175
+ const params = new URLSearchParams({
176
+ clientId: this.config.clientId,
177
+ mode: "iframe"
178
+ });
179
+ if (options?.challenge) {
180
+ params.set("challenge", options.challenge);
181
+ }
182
+ const themeParams = this.getThemeParams(options?.theme);
183
+ if (themeParams) {
184
+ const themeParsed = new URLSearchParams(themeParams);
185
+ themeParsed.forEach((value, key) => params.set(key, value));
186
+ }
187
+ const url = `${dialogUrl}/dialog/authenticate?${params.toString()}`;
188
+ const { dialog, iframe, cleanup } = this.createModalDialog(url);
189
+ return this.waitForAuthenticateResponse(dialog, iframe, cleanup);
190
+ }
191
+ /**
192
+ * Show signing in a modal overlay (Porto-style iframe dialog)
193
+ */
194
+ async signWithModal(options) {
195
+ const dialogUrl = this.getDialogUrl();
196
+ const themeParams = this.getThemeParams(options?.theme);
197
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
198
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
199
+ const dialogOrigin = this.getDialogOrigin();
200
+ await new Promise((resolve) => {
201
+ iframe.onload = () => {
202
+ iframe.contentWindow?.postMessage({
203
+ type: "PASSKEY_INIT",
204
+ mode: "iframe",
205
+ challenge: options.challenge,
206
+ username: options.username,
207
+ description: options.description,
208
+ transaction: options.transaction,
209
+ metadata: options.metadata
210
+ }, dialogOrigin);
211
+ resolve();
212
+ };
213
+ });
214
+ return this.waitForSigningResponse(dialog, iframe, cleanup);
215
+ }
216
+ /**
217
+ * Send an intent to the Rhinestone orchestrator
218
+ *
219
+ * This is the high-level method for cross-chain transactions:
220
+ * 1. Prepares the intent (gets quote from orchestrator)
221
+ * 2. Shows the signing modal with real fees
222
+ * 3. Submits the signed intent for execution
223
+ * 4. Returns the transaction hash
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * const result = await client.sendIntent({
228
+ * username: 'alice',
229
+ * targetChain: 8453, // Base
230
+ * calls: [
231
+ * {
232
+ * to: '0x...',
233
+ * data: '0x...',
234
+ * label: 'Swap ETH for USDC',
235
+ * sublabel: '0.1 ETH → ~250 USDC',
236
+ * },
237
+ * ],
238
+ * });
239
+ *
240
+ * if (result.success) {
241
+ * console.log('Transaction hash:', result.transactionHash);
242
+ * }
243
+ * ```
244
+ */
245
+ async sendIntent(options) {
246
+ const signedIntent = options.signedIntent;
247
+ const username = signedIntent?.username || options.username;
248
+ const targetChain = signedIntent?.targetChain || options.targetChain;
249
+ const calls = signedIntent?.calls || options.calls;
250
+ if (!username && !signedIntent?.accountAddress) {
251
+ return {
252
+ success: false,
253
+ intentId: "",
254
+ status: "failed",
255
+ error: {
256
+ code: "INVALID_OPTIONS",
257
+ message: "Either username, accountAddress, or signedIntent with user identifier is required"
258
+ }
259
+ };
260
+ }
261
+ if (!targetChain || !calls?.length) {
262
+ return {
263
+ success: false,
264
+ intentId: "",
265
+ status: "failed",
266
+ error: {
267
+ code: "INVALID_OPTIONS",
268
+ message: "targetChain and calls are required (either directly or via signedIntent)"
269
+ }
270
+ };
271
+ }
272
+ let prepareResponse;
273
+ try {
274
+ const requestBody = signedIntent || {
275
+ username: options.username,
276
+ targetChain: options.targetChain,
277
+ calls: options.calls,
278
+ tokenRequests: options.tokenRequests,
279
+ sourceAssets: options.sourceAssets,
280
+ clientId: this.config.clientId
281
+ };
282
+ const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
283
+ method: "POST",
284
+ headers: {
285
+ "Content-Type": "application/json"
286
+ },
287
+ body: JSON.stringify(requestBody)
288
+ });
289
+ if (!response.ok) {
290
+ const errorData = await response.json().catch(() => ({}));
291
+ const errorMessage = errorData.error || "Failed to prepare intent";
292
+ if (errorMessage.includes("User not found")) {
293
+ localStorage.removeItem("1auth-user");
294
+ }
295
+ return {
296
+ success: false,
297
+ intentId: "",
298
+ status: "failed",
299
+ error: {
300
+ code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
301
+ message: errorMessage
302
+ }
303
+ };
304
+ }
305
+ prepareResponse = await response.json();
306
+ } catch (error) {
307
+ return {
308
+ success: false,
309
+ intentId: "",
310
+ status: "failed",
311
+ error: {
312
+ code: "NETWORK_ERROR",
313
+ message: error instanceof Error ? error.message : "Network error"
314
+ }
315
+ };
316
+ }
317
+ const dialogUrl = this.getDialogUrl();
318
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe`;
319
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
320
+ const dialogOrigin = this.getDialogOrigin();
321
+ await new Promise((resolve) => {
322
+ const handleReady = (event) => {
323
+ if (event.origin !== dialogOrigin) return;
324
+ if (event.data?.type === "PASSKEY_READY") {
325
+ window.removeEventListener("message", handleReady);
326
+ iframe.contentWindow?.postMessage({
327
+ type: "PASSKEY_INIT",
328
+ mode: "iframe",
329
+ calls,
330
+ chainId: targetChain,
331
+ transaction: prepareResponse.transaction,
332
+ challenge: prepareResponse.challenge,
333
+ username,
334
+ accountAddress: prepareResponse.accountAddress,
335
+ intentId: prepareResponse.intentId
336
+ }, dialogOrigin);
337
+ resolve();
338
+ }
339
+ };
340
+ window.addEventListener("message", handleReady);
341
+ });
342
+ const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
343
+ if (!signingResult.success) {
344
+ return {
345
+ success: false,
346
+ intentId: prepareResponse.intentId,
347
+ status: "failed",
348
+ error: signingResult.error
349
+ };
350
+ }
351
+ let executeResponse;
352
+ try {
353
+ const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
354
+ method: "POST",
355
+ headers: {
356
+ "Content-Type": "application/json"
357
+ },
358
+ body: JSON.stringify({
359
+ intentId: prepareResponse.intentId,
360
+ signature: signingResult.signature,
361
+ passkey: signingResult.passkey
362
+ // Include passkey info for signature encoding
363
+ })
364
+ });
365
+ if (!response.ok) {
366
+ const errorData = await response.json().catch(() => ({}));
367
+ this.sendTransactionStatus(iframe, "failed");
368
+ await this.waitForDialogClose(dialog, cleanup);
369
+ return {
370
+ success: false,
371
+ intentId: prepareResponse.intentId,
372
+ status: "failed",
373
+ error: {
374
+ code: "EXECUTE_FAILED",
375
+ message: errorData.error || "Failed to execute intent"
376
+ }
377
+ };
378
+ }
379
+ executeResponse = await response.json();
380
+ } catch (error) {
381
+ this.sendTransactionStatus(iframe, "failed");
382
+ await this.waitForDialogClose(dialog, cleanup);
383
+ return {
384
+ success: false,
385
+ intentId: prepareResponse.intentId,
386
+ status: "failed",
387
+ error: {
388
+ code: "NETWORK_ERROR",
389
+ message: error instanceof Error ? error.message : "Network error"
390
+ }
391
+ };
392
+ }
393
+ const closeOn = options.closeOn || "preconfirmed";
394
+ const acceptPreconfirmations = closeOn !== "completed";
395
+ let finalStatus = executeResponse.status;
396
+ let finalTxHash = executeResponse.transactionHash;
397
+ if (finalStatus === "pending") {
398
+ this.sendTransactionStatus(iframe, "processing");
399
+ try {
400
+ const waitResponse = await fetch(
401
+ `${this.config.providerUrl}/api/intent/wait/${prepareResponse.intentId}?preconfirm=${acceptPreconfirmations}`,
402
+ {
403
+ headers: {
404
+ "x-client-id": this.config.clientId
405
+ }
406
+ }
407
+ );
408
+ if (waitResponse.ok) {
409
+ const waitResult = await waitResponse.json();
410
+ finalStatus = waitResult.status === "preconfirmed" || waitResult.status === "completed" ? "completed" : waitResult.status;
411
+ finalTxHash = waitResult.transactionHash;
412
+ } else {
413
+ console.error("Wait endpoint failed:", await waitResponse.text());
414
+ }
415
+ } catch (waitError) {
416
+ console.error("Failed to wait for intent:", waitError);
417
+ }
418
+ }
419
+ const displayStatus = finalStatus === "completed" ? "confirmed" : finalStatus;
420
+ this.sendTransactionStatus(iframe, displayStatus, finalTxHash);
421
+ await this.waitForDialogClose(dialog, cleanup);
422
+ if (options.waitForHash && !finalTxHash) {
423
+ const hash = await this.waitForTransactionHash(prepareResponse.intentId, {
424
+ timeoutMs: options.hashTimeoutMs,
425
+ intervalMs: options.hashIntervalMs
426
+ });
427
+ if (hash) {
428
+ finalTxHash = hash;
429
+ finalStatus = "completed";
430
+ } else {
431
+ finalStatus = "failed";
432
+ return {
433
+ success: false,
434
+ intentId: prepareResponse.intentId,
435
+ status: finalStatus,
436
+ transactionHash: finalTxHash,
437
+ operationId: executeResponse.operationId,
438
+ error: {
439
+ code: "HASH_TIMEOUT",
440
+ message: "Timed out waiting for transaction hash"
441
+ }
442
+ };
443
+ }
444
+ }
445
+ return {
446
+ success: finalStatus === "completed",
447
+ intentId: prepareResponse.intentId,
448
+ status: finalStatus,
449
+ transactionHash: finalTxHash,
450
+ operationId: executeResponse.operationId,
451
+ error: executeResponse.error
452
+ };
453
+ }
454
+ /**
455
+ * Send transaction status to the dialog iframe
456
+ */
457
+ sendTransactionStatus(iframe, status, transactionHash) {
458
+ const dialogOrigin = this.getDialogOrigin();
459
+ iframe.contentWindow?.postMessage(
460
+ {
461
+ type: "TRANSACTION_STATUS",
462
+ status,
463
+ transactionHash
464
+ },
465
+ dialogOrigin
466
+ );
467
+ }
468
+ /**
469
+ * Wait for the signing result without closing the modal
470
+ */
471
+ waitForIntentSigningResponse(requestId, dialog, _iframe, cleanup) {
472
+ const dialogOrigin = this.getDialogOrigin();
473
+ return new Promise((resolve) => {
474
+ const handleMessage = (event) => {
475
+ if (event.origin !== dialogOrigin) return;
476
+ const message = event.data;
477
+ const payload = message?.data;
478
+ if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
479
+ window.removeEventListener("message", handleMessage);
480
+ if (message.success && payload.signature) {
481
+ resolve({
482
+ success: true,
483
+ requestId,
484
+ signature: payload.signature,
485
+ passkey: payload.passkey
486
+ // Include passkey info for signature encoding
487
+ });
488
+ } else {
489
+ resolve({
490
+ success: false,
491
+ error: message.error || {
492
+ code: "SIGNING_FAILED",
493
+ message: "Signing failed"
494
+ }
495
+ });
496
+ }
497
+ } else if (message?.type === "PASSKEY_CLOSE") {
498
+ window.removeEventListener("message", handleMessage);
499
+ cleanup();
500
+ resolve({
501
+ success: false,
502
+ error: {
503
+ code: "USER_REJECTED",
504
+ message: "User closed the dialog"
505
+ }
506
+ });
507
+ }
508
+ };
509
+ window.addEventListener("message", handleMessage);
510
+ dialog.showModal();
511
+ });
512
+ }
513
+ /**
514
+ * Wait for signing result (simplified - no requestId matching)
515
+ */
516
+ waitForSigningResponse(dialog, _iframe, cleanup) {
517
+ const dialogOrigin = this.getDialogOrigin();
518
+ console.log("[SDK] waitForSigningResponse, expecting origin:", dialogOrigin);
519
+ return new Promise((resolve) => {
520
+ const handleMessage = (event) => {
521
+ console.log("[SDK] Received message:", event.origin, event.data?.type);
522
+ if (event.origin !== dialogOrigin) {
523
+ console.log("[SDK] Origin mismatch, ignoring. Expected:", dialogOrigin, "Got:", event.origin);
524
+ return;
525
+ }
526
+ const message = event.data;
527
+ const payload = message?.data;
528
+ if (message?.type === "PASSKEY_SIGNING_RESULT") {
529
+ window.removeEventListener("message", handleMessage);
530
+ if (message.success && payload?.signature) {
531
+ resolve({
532
+ success: true,
533
+ signature: payload.signature,
534
+ passkey: payload.passkey,
535
+ signedHash: payload.signedHash
536
+ });
537
+ } else {
538
+ resolve({
539
+ success: false,
540
+ error: message.error || {
541
+ code: "SIGNING_FAILED",
542
+ message: "Signing failed"
543
+ }
544
+ });
545
+ }
546
+ } else if (message?.type === "PASSKEY_CLOSE") {
547
+ window.removeEventListener("message", handleMessage);
548
+ cleanup();
549
+ resolve({
550
+ success: false,
551
+ error: {
552
+ code: "USER_REJECTED",
553
+ message: "User closed the dialog"
554
+ }
555
+ });
556
+ }
557
+ };
558
+ window.addEventListener("message", handleMessage);
559
+ });
560
+ }
561
+ /**
562
+ * Wait for the dialog to be closed
563
+ */
564
+ waitForDialogClose(dialog, cleanup) {
565
+ const dialogOrigin = this.getDialogOrigin();
566
+ return new Promise((resolve) => {
567
+ const handleMessage = (event) => {
568
+ if (event.origin !== dialogOrigin) return;
569
+ if (event.data?.type === "PASSKEY_CLOSE") {
570
+ window.removeEventListener("message", handleMessage);
571
+ cleanup();
572
+ resolve();
573
+ }
574
+ };
575
+ const handleClose = () => {
576
+ window.removeEventListener("message", handleMessage);
577
+ dialog.removeEventListener("close", handleClose);
578
+ cleanup();
579
+ resolve();
580
+ };
581
+ window.addEventListener("message", handleMessage);
582
+ dialog.addEventListener("close", handleClose);
583
+ });
584
+ }
585
+ /**
586
+ * Poll for intent status
587
+ *
588
+ * Use this to check on the status of a submitted intent
589
+ * that hasn't completed yet.
590
+ */
591
+ async getIntentStatus(intentId) {
592
+ try {
593
+ const response = await fetch(
594
+ `${this.config.providerUrl}/api/intent/status/${intentId}`,
595
+ {
596
+ headers: {
597
+ "x-client-id": this.config.clientId
598
+ }
599
+ }
600
+ );
601
+ if (!response.ok) {
602
+ const errorData = await response.json().catch(() => ({}));
603
+ return {
604
+ success: false,
605
+ intentId,
606
+ status: "failed",
607
+ error: {
608
+ code: "STATUS_FAILED",
609
+ message: errorData.error || "Failed to get intent status"
610
+ }
611
+ };
612
+ }
613
+ const data = await response.json();
614
+ return {
615
+ success: data.status === "completed",
616
+ intentId,
617
+ status: data.status,
618
+ transactionHash: data.transactionHash,
619
+ operationId: data.operationId
620
+ };
621
+ } catch (error) {
622
+ return {
623
+ success: false,
624
+ intentId,
625
+ status: "failed",
626
+ error: {
627
+ code: "NETWORK_ERROR",
628
+ message: error instanceof Error ? error.message : "Network error"
629
+ }
630
+ };
631
+ }
632
+ }
633
+ /**
634
+ * Send a swap intent through the Rhinestone orchestrator
635
+ *
636
+ * This is a high-level abstraction for token swaps (including cross-chain):
637
+ * 1. Resolves token symbols to addresses
638
+ * 2. Builds the swap intent with tokenRequests (output-first model)
639
+ * 3. The orchestrator's solver network finds the best route
640
+ * 4. Executes via the standard intent flow
641
+ *
642
+ * NOTE: The `amount` parameter specifies the OUTPUT amount (what the user wants to receive),
643
+ * not the input amount. The orchestrator will calculate the required input from sourceAssets.
644
+ *
645
+ * @example
646
+ * ```typescript
647
+ * // Buy 100 USDC using ETH on Base
648
+ * const result = await client.sendSwap({
649
+ * username: 'alice',
650
+ * targetChain: 8453,
651
+ * fromToken: 'ETH',
652
+ * toToken: 'USDC',
653
+ * amount: '100', // Receive 100 USDC
654
+ * });
655
+ *
656
+ * // Cross-chain: Buy 50 USDC on Base, paying with ETH from any chain
657
+ * const result = await client.sendSwap({
658
+ * username: 'alice',
659
+ * targetChain: 8453, // Base
660
+ * fromToken: 'ETH',
661
+ * toToken: 'USDC',
662
+ * amount: '50', // Receive 50 USDC
663
+ * });
664
+ * ```
665
+ */
666
+ async sendSwap(options) {
667
+ try {
668
+ getChainById(options.targetChain);
669
+ } catch {
670
+ return {
671
+ success: false,
672
+ intentId: "",
673
+ status: "failed",
674
+ error: {
675
+ code: "INVALID_CHAIN",
676
+ message: `Unsupported chain: ${options.targetChain}`
677
+ }
678
+ };
679
+ }
680
+ const resolveToken = (token, label) => {
681
+ try {
682
+ const address = resolveTokenAddress(token, options.targetChain);
683
+ if (!isTokenAddressSupported(address, options.targetChain)) {
684
+ return {
685
+ error: `Unsupported ${label}: ${token} on chain ${options.targetChain}`
686
+ };
687
+ }
688
+ return { address };
689
+ } catch (error) {
690
+ return {
691
+ error: error instanceof Error ? error.message : `Unsupported ${label}: ${token} on chain ${options.targetChain}`
692
+ };
693
+ }
694
+ };
695
+ const fromTokenResult = resolveToken(options.fromToken, "fromToken");
696
+ if (!fromTokenResult.address) {
697
+ return {
698
+ success: false,
699
+ intentId: "",
700
+ status: "failed",
701
+ error: {
702
+ code: "INVALID_TOKEN",
703
+ message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
704
+ }
705
+ };
706
+ }
707
+ const toTokenResult = resolveToken(options.toToken, "toToken");
708
+ if (!toTokenResult.address) {
709
+ return {
710
+ success: false,
711
+ intentId: "",
712
+ status: "failed",
713
+ error: {
714
+ code: "INVALID_TOKEN",
715
+ message: toTokenResult.error || `Unknown toToken: ${options.toToken}`
716
+ }
717
+ };
718
+ }
719
+ const fromTokenAddress = fromTokenResult.address;
720
+ const toTokenAddress = toTokenResult.address;
721
+ console.log("[SDK sendSwap] Token resolution:", {
722
+ fromToken: options.fromToken,
723
+ fromTokenAddress,
724
+ toToken: options.toToken,
725
+ toTokenAddress,
726
+ targetChain: options.targetChain
727
+ });
728
+ const formatTokenLabel = (token, fallback) => {
729
+ if (!token.startsWith("0x")) {
730
+ return token.toUpperCase();
731
+ }
732
+ try {
733
+ return getTokenSymbol(token, options.targetChain);
734
+ } catch {
735
+ return fallback;
736
+ }
737
+ };
738
+ const fromSymbol = formatTokenLabel(
739
+ options.fromToken,
740
+ `${options.fromToken.slice(0, 6)}...${options.fromToken.slice(-4)}`
741
+ );
742
+ const toSymbol = formatTokenLabel(
743
+ options.toToken,
744
+ `${options.toToken.slice(0, 6)}...${options.toToken.slice(-4)}`
745
+ );
746
+ const isFromNativeEth = fromTokenAddress === "0x0000000000000000000000000000000000000000";
747
+ const isToNativeEth = toTokenAddress === "0x0000000000000000000000000000000000000000";
748
+ const KNOWN_DECIMALS = {
749
+ ETH: 18,
750
+ WETH: 18,
751
+ USDC: 6,
752
+ USDT: 6,
753
+ USDT0: 6
754
+ };
755
+ const getDecimals = (symbol, chainId) => {
756
+ const upperSymbol = symbol.toUpperCase();
757
+ try {
758
+ const decimals = getTokenDecimals(upperSymbol, chainId);
759
+ console.log(`[SDK] getTokenDecimals(${upperSymbol}, ${chainId}) = ${decimals}`);
760
+ return decimals;
761
+ } catch (e) {
762
+ console.warn(`[SDK] getTokenDecimals failed for ${upperSymbol}, using fallback`, e);
763
+ return KNOWN_DECIMALS[upperSymbol] ?? 18;
764
+ }
765
+ };
766
+ const fromDecimals = getDecimals(options.fromToken, options.targetChain);
767
+ const toDecimals = getDecimals(options.toToken, options.targetChain);
768
+ const isBridge = options.fromToken.toUpperCase() === options.toToken.toUpperCase();
769
+ let tokenRequests;
770
+ if (!isToNativeEth) {
771
+ tokenRequests = [{
772
+ token: toTokenAddress,
773
+ amount: parseUnits(options.amount, toDecimals).toString()
774
+ }];
775
+ }
776
+ console.log("[SDK sendSwap] Building intent:", {
777
+ isBridge,
778
+ isFromNativeEth,
779
+ isToNativeEth,
780
+ fromDecimals,
781
+ toDecimals,
782
+ tokenRequests
783
+ });
784
+ const result = await this.sendIntent({
785
+ username: options.username,
786
+ targetChain: options.targetChain,
787
+ calls: [
788
+ {
789
+ // Minimal call - just signals to orchestrator we want the tokenRequests delivered
790
+ to: toTokenAddress,
791
+ value: "0"
792
+ }
793
+ ],
794
+ // Request specific output tokens - this is what actually matters for swaps
795
+ tokenRequests,
796
+ // Constrain orchestrator to use only the fromToken as input
797
+ // This ensures the swap uses the correct source token
798
+ // Pass the symbol (not address) so orchestrator can resolve per-chain
799
+ sourceAssets: options.sourceAssets || [options.fromToken.toUpperCase()],
800
+ closeOn: options.closeOn || "preconfirmed",
801
+ waitForHash: options.waitForHash,
802
+ hashTimeoutMs: options.hashTimeoutMs,
803
+ hashIntervalMs: options.hashIntervalMs
804
+ });
805
+ return {
806
+ ...result,
807
+ quote: result.success ? {
808
+ fromToken: fromTokenAddress,
809
+ toToken: toTokenAddress,
810
+ amountIn: options.amount,
811
+ amountOut: "",
812
+ // Filled by orchestrator quote
813
+ rate: ""
814
+ } : void 0
815
+ };
816
+ }
817
+ /**
818
+ * Sign an arbitrary message with the user's passkey
819
+ *
820
+ * This is for off-chain message signing (e.g., authentication challenges,
821
+ * terms acceptance, login signatures), NOT for transaction signing.
822
+ * The message is displayed to the user and signed with their passkey.
823
+ *
824
+ * @example
825
+ * ```typescript
826
+ * // Sign a login challenge
827
+ * const result = await client.signMessage({
828
+ * username: 'alice',
829
+ * message: `Sign in to MyApp\nTimestamp: ${Date.now()}\nNonce: ${crypto.randomUUID()}`,
830
+ * description: 'Verify your identity to continue',
831
+ * });
832
+ *
833
+ * if (result.success) {
834
+ * // Send signature to your backend for verification
835
+ * await fetch('/api/verify', {
836
+ * method: 'POST',
837
+ * body: JSON.stringify({
838
+ * signature: result.signature,
839
+ * message: result.signedMessage,
840
+ * }),
841
+ * });
842
+ * }
843
+ * ```
844
+ */
845
+ async signMessage(options) {
846
+ const dialogUrl = this.getDialogUrl();
847
+ const themeParams = this.getThemeParams(options?.theme);
848
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
849
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
850
+ const dialogOrigin = this.getDialogOrigin();
851
+ await new Promise((resolve) => {
852
+ const handleReady = (event) => {
853
+ if (event.origin !== dialogOrigin) return;
854
+ if (event.data?.type === "PASSKEY_READY") {
855
+ window.removeEventListener("message", handleReady);
856
+ iframe.contentWindow?.postMessage({
857
+ type: "PASSKEY_INIT",
858
+ mode: "iframe",
859
+ message: options.message,
860
+ challenge: options.challenge || options.message,
861
+ username: options.username,
862
+ description: options.description,
863
+ metadata: options.metadata
864
+ }, dialogOrigin);
865
+ resolve();
866
+ }
867
+ };
868
+ window.addEventListener("message", handleReady);
869
+ });
870
+ const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
871
+ cleanup();
872
+ if (signingResult.success) {
873
+ return {
874
+ success: true,
875
+ signature: signingResult.signature,
876
+ signedMessage: options.message,
877
+ signedHash: signingResult.signedHash,
878
+ passkey: signingResult.passkey
879
+ };
880
+ }
881
+ return {
882
+ success: false,
883
+ error: signingResult.error
884
+ };
885
+ }
886
+ /**
887
+ * Sign EIP-712 typed data with the user's passkey
888
+ *
889
+ * This method allows signing structured data following the EIP-712 standard.
890
+ * The typed data is displayed to the user in a human-readable format before signing.
891
+ *
892
+ * @example
893
+ * ```typescript
894
+ * // Sign an ERC-2612 Permit
895
+ * const result = await client.signTypedData({
896
+ * username: 'alice',
897
+ * domain: {
898
+ * name: 'Dai Stablecoin',
899
+ * version: '1',
900
+ * chainId: 1,
901
+ * verifyingContract: '0x6B175474E89094C44Da98b954EecdeCB5BE3830F',
902
+ * },
903
+ * types: {
904
+ * Permit: [
905
+ * { name: 'owner', type: 'address' },
906
+ * { name: 'spender', type: 'address' },
907
+ * { name: 'value', type: 'uint256' },
908
+ * { name: 'nonce', type: 'uint256' },
909
+ * { name: 'deadline', type: 'uint256' },
910
+ * ],
911
+ * },
912
+ * primaryType: 'Permit',
913
+ * message: {
914
+ * owner: '0xabc...',
915
+ * spender: '0xdef...',
916
+ * value: 1000000000000000000n,
917
+ * nonce: 0n,
918
+ * deadline: 1735689600n,
919
+ * },
920
+ * });
921
+ *
922
+ * if (result.success) {
923
+ * console.log('Signed hash:', result.signedHash);
924
+ * }
925
+ * ```
926
+ */
927
+ async signTypedData(options) {
928
+ const signedHash = hashTypedData({
929
+ domain: options.domain,
930
+ types: options.types,
931
+ primaryType: options.primaryType,
932
+ message: options.message
933
+ });
934
+ const dialogUrl = this.getDialogUrl();
935
+ const themeParams = this.getThemeParams(options?.theme);
936
+ const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
937
+ const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
938
+ const dialogOrigin = this.getDialogOrigin();
939
+ await new Promise((resolve) => {
940
+ const handleReady = (event) => {
941
+ if (event.origin !== dialogOrigin) return;
942
+ if (event.data?.type === "PASSKEY_READY") {
943
+ window.removeEventListener("message", handleReady);
944
+ iframe.contentWindow?.postMessage({
945
+ type: "PASSKEY_INIT",
946
+ mode: "iframe",
947
+ signingMode: "typedData",
948
+ typedData: {
949
+ domain: options.domain,
950
+ types: options.types,
951
+ primaryType: options.primaryType,
952
+ message: options.message
953
+ },
954
+ challenge: signedHash,
955
+ username: options.username,
956
+ description: options.description
957
+ }, dialogOrigin);
958
+ resolve();
959
+ }
960
+ };
961
+ window.addEventListener("message", handleReady);
962
+ });
963
+ const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
964
+ cleanup();
965
+ if (signingResult.success) {
966
+ return {
967
+ success: true,
968
+ signature: signingResult.signature,
969
+ signedHash,
970
+ passkey: signingResult.passkey
971
+ };
972
+ }
973
+ return {
974
+ success: false,
975
+ error: signingResult.error
976
+ };
977
+ }
978
+ async signWithPopup(options) {
979
+ const request = await this.createSigningRequest(options, "popup");
980
+ const dialogUrl = this.getDialogUrl();
981
+ const signingUrl = `${dialogUrl}/dialog/sign/${request.requestId}?mode=popup`;
982
+ const popup = this.openPopup(signingUrl);
983
+ if (!popup) {
984
+ return {
985
+ success: false,
986
+ error: {
987
+ code: "POPUP_BLOCKED",
988
+ message: "Popup was blocked by the browser. Please allow popups for this site."
989
+ }
990
+ };
991
+ }
992
+ return this.waitForPopupResponse(request.requestId, popup);
993
+ }
994
+ async signWithRedirect(options, redirectUrl) {
995
+ const finalRedirectUrl = redirectUrl || this.config.redirectUrl;
996
+ if (!finalRedirectUrl) {
997
+ throw new Error(
998
+ "redirectUrl is required for redirect flow. Pass it to signWithRedirect() or set it in the constructor."
999
+ );
1000
+ }
1001
+ const request = await this.createSigningRequest(
1002
+ options,
1003
+ "redirect",
1004
+ finalRedirectUrl
1005
+ );
1006
+ const dialogUrl = this.getDialogUrl();
1007
+ const signingUrl = `${dialogUrl}/dialog/sign/${request.requestId}?mode=redirect&redirectUrl=${encodeURIComponent(finalRedirectUrl)}`;
1008
+ window.location.href = signingUrl;
1009
+ }
1010
+ async signWithEmbed(options, embedOptions) {
1011
+ const request = await this.createSigningRequest(options, "embed");
1012
+ const iframe = this.createEmbed(request.requestId, embedOptions);
1013
+ return this.waitForEmbedResponse(request.requestId, iframe, embedOptions);
1014
+ }
1015
+ createEmbed(requestId, options) {
1016
+ const dialogUrl = this.getDialogUrl();
1017
+ const iframe = document.createElement("iframe");
1018
+ iframe.src = `${dialogUrl}/dialog/sign/${requestId}?mode=iframe`;
1019
+ iframe.style.width = options.width || DEFAULT_EMBED_WIDTH;
1020
+ iframe.style.height = options.height || DEFAULT_EMBED_HEIGHT;
1021
+ iframe.style.border = "none";
1022
+ iframe.style.borderRadius = "12px";
1023
+ iframe.style.boxShadow = "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)";
1024
+ iframe.id = `passkey-embed-${requestId}`;
1025
+ iframe.allow = "publickey-credentials-get *; publickey-credentials-create *";
1026
+ iframe.onload = () => {
1027
+ options.onReady?.();
1028
+ };
1029
+ options.container.appendChild(iframe);
1030
+ return iframe;
1031
+ }
1032
+ waitForEmbedResponse(requestId, iframe, embedOptions) {
1033
+ const dialogOrigin = this.getDialogOrigin();
1034
+ return new Promise((resolve) => {
1035
+ const cleanup = () => {
1036
+ window.removeEventListener("message", handleMessage);
1037
+ iframe.remove();
1038
+ embedOptions.onClose?.();
1039
+ };
1040
+ const handleMessage = (event) => {
1041
+ if (event.origin !== dialogOrigin) {
1042
+ return;
1043
+ }
1044
+ const message = event.data;
1045
+ const payload = message?.data;
1046
+ if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
1047
+ cleanup();
1048
+ if (message.success && payload.signature) {
1049
+ resolve({
1050
+ success: true,
1051
+ requestId,
1052
+ signature: payload.signature
1053
+ });
1054
+ } else {
1055
+ resolve({
1056
+ success: false,
1057
+ requestId,
1058
+ error: message.error
1059
+ });
1060
+ }
1061
+ }
1062
+ };
1063
+ window.addEventListener("message", handleMessage);
1064
+ });
1065
+ }
1066
+ removeEmbed(requestId) {
1067
+ const iframe = document.getElementById(`passkey-embed-${requestId}`);
1068
+ if (iframe) {
1069
+ iframe.remove();
1070
+ }
1071
+ }
1072
+ async handleRedirectCallback() {
1073
+ const params = new URLSearchParams(window.location.search);
1074
+ const requestId = params.get("request_id");
1075
+ const status = params.get("status");
1076
+ const error = params.get("error");
1077
+ const errorMessage = params.get("error_message");
1078
+ if (error) {
1079
+ return {
1080
+ success: false,
1081
+ requestId: requestId || void 0,
1082
+ error: {
1083
+ code: error,
1084
+ message: errorMessage || "Unknown error"
1085
+ }
1086
+ };
1087
+ }
1088
+ if (!requestId) {
1089
+ return {
1090
+ success: false,
1091
+ error: {
1092
+ code: "INVALID_REQUEST",
1093
+ message: "No request_id found in callback URL"
1094
+ }
1095
+ };
1096
+ }
1097
+ if (status !== "completed") {
1098
+ return {
1099
+ success: false,
1100
+ requestId,
1101
+ error: {
1102
+ code: "UNKNOWN",
1103
+ message: `Unexpected status: ${status}`
1104
+ }
1105
+ };
1106
+ }
1107
+ return this.fetchSigningResult(requestId);
1108
+ }
1109
+ /**
1110
+ * Fetch passkeys for a user from the auth provider
1111
+ */
1112
+ async getPasskeys(username) {
1113
+ const response = await fetch(
1114
+ `${this.config.providerUrl}/api/users/${encodeURIComponent(username)}/passkeys`,
1115
+ {
1116
+ headers: {
1117
+ "x-client-id": this.config.clientId
1118
+ }
1119
+ }
1120
+ );
1121
+ if (!response.ok) {
1122
+ const errorData = await response.json().catch(() => ({}));
1123
+ throw new Error(errorData.error || errorData.message || "Failed to fetch passkeys");
1124
+ }
1125
+ const data = await response.json();
1126
+ return data.passkeys;
1127
+ }
1128
+ async createSigningRequest(options, mode, redirectUrl) {
1129
+ const response = await fetch(
1130
+ `${this.config.providerUrl}/api/sign/request`,
1131
+ {
1132
+ method: "POST",
1133
+ headers: {
1134
+ "Content-Type": "application/json"
1135
+ },
1136
+ body: JSON.stringify({
1137
+ clientId: this.config.clientId,
1138
+ username: options.username,
1139
+ challenge: options.challenge,
1140
+ description: options.description,
1141
+ metadata: options.metadata,
1142
+ transaction: options.transaction,
1143
+ mode,
1144
+ redirectUrl
1145
+ })
1146
+ }
1147
+ );
1148
+ if (!response.ok) {
1149
+ const errorData = await response.json().catch(() => ({}));
1150
+ throw new Error(errorData.error || errorData.message || "Failed to create signing request");
1151
+ }
1152
+ return response.json();
1153
+ }
1154
+ openPopup(url) {
1155
+ const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
1156
+ const top = window.screenY + 50;
1157
+ return window.open(
1158
+ url,
1159
+ "passkey-signing",
1160
+ `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
1161
+ );
1162
+ }
1163
+ /**
1164
+ * Create a modal dialog with an iframe inside (Porto-style overlay)
1165
+ */
1166
+ createModalDialog(url) {
1167
+ const dialogUrl = this.getDialogUrl();
1168
+ const hostUrl = new URL(dialogUrl);
1169
+ const dialog = document.createElement("dialog");
1170
+ dialog.dataset.passkey = "";
1171
+ document.body.appendChild(dialog);
1172
+ const style = document.createElement("style");
1173
+ style.textContent = `
1174
+ dialog[data-passkey] {
1175
+ padding: 0;
1176
+ border: none;
1177
+ background: transparent;
1178
+ max-width: none;
1179
+ max-height: none;
1180
+ margin: 0;
1181
+ position: fixed;
1182
+ top: 50px;
1183
+ left: 50%;
1184
+ transform: translateX(-50%);
1185
+ outline: none;
1186
+ }
1187
+
1188
+ dialog[data-passkey]::backdrop {
1189
+ background-color: rgba(0, 0, 0, 0.4);
1190
+ backdrop-filter: blur(8px);
1191
+ -webkit-backdrop-filter: blur(8px);
1192
+ }
1193
+
1194
+ dialog[data-passkey] iframe {
1195
+ background-color: transparent;
1196
+ border-radius: 14px;
1197
+ border: none;
1198
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
1199
+ transition: width 0.2s ease-out, height 0.15s ease-out;
1200
+ }
1201
+
1202
+ @media (min-width: 769px) {
1203
+ dialog[data-passkey] iframe {
1204
+ animation: passkey_zoomIn 0.2s cubic-bezier(0.32, 0.72, 0, 1);
1205
+ }
1206
+ }
1207
+
1208
+ @media (max-width: 768px) {
1209
+ dialog[data-passkey] {
1210
+ width: 100vw !important;
1211
+ height: auto !important;
1212
+ max-height: 90vh !important;
1213
+ max-height: 90dvh !important;
1214
+ top: auto !important;
1215
+ bottom: 0 !important;
1216
+ left: 0 !important;
1217
+ right: 0 !important;
1218
+ transform: none !important;
1219
+ margin: 0 !important;
1220
+ }
1221
+
1222
+ dialog[data-passkey] iframe {
1223
+ animation: passkey_slideFromBottom 0.3s cubic-bezier(0.32, 0.72, 0, 1);
1224
+ border-bottom-left-radius: 0 !important;
1225
+ border-bottom-right-radius: 0 !important;
1226
+ width: 100% !important;
1227
+ max-height: 90vh !important;
1228
+ max-height: 90dvh !important;
1229
+ box-shadow: 0 -4px 32px rgba(0, 0, 0, 0.15) !important;
1230
+ }
1231
+ }
1232
+
1233
+ @keyframes passkey_zoomIn {
1234
+ from {
1235
+ opacity: 0;
1236
+ transform: scale(0.96) translateY(8px);
1237
+ }
1238
+ to {
1239
+ opacity: 1;
1240
+ transform: scale(1) translateY(0);
1241
+ }
1242
+ }
1243
+
1244
+ @keyframes passkey_slideFromBottom {
1245
+ from { transform: translate3d(0, 100%, 0); }
1246
+ to { transform: translate3d(0, 0, 0); }
1247
+ }
1248
+
1249
+ @keyframes passkey_shake {
1250
+ 0%, 100% { transform: translateX(0); }
1251
+ 20% { transform: translateX(-4px); }
1252
+ 40% { transform: translateX(4px); }
1253
+ 60% { transform: translateX(-4px); }
1254
+ 80% { transform: translateX(4px); }
1255
+ }
1256
+ `;
1257
+ dialog.appendChild(style);
1258
+ const iframe = document.createElement("iframe");
1259
+ iframe.setAttribute(
1260
+ "allow",
1261
+ "publickey-credentials-get *; publickey-credentials-create *; clipboard-write"
1262
+ );
1263
+ iframe.setAttribute("aria-label", "Passkey Authentication");
1264
+ iframe.setAttribute("tabindex", "0");
1265
+ iframe.setAttribute(
1266
+ "sandbox",
1267
+ "allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
1268
+ );
1269
+ iframe.setAttribute("src", url);
1270
+ iframe.setAttribute("title", "Passkey");
1271
+ iframe.style.border = "none";
1272
+ iframe.style.height = "400px";
1273
+ iframe.style.width = `${MODAL_WIDTH}px`;
1274
+ dialog.appendChild(iframe);
1275
+ const handleMessage = (event) => {
1276
+ if (event.origin !== hostUrl.origin) return;
1277
+ if (event.data?.type === "PASSKEY_RESIZE") {
1278
+ iframe.style.height = `${event.data.height}px`;
1279
+ if (event.data.width) {
1280
+ iframe.style.width = `${event.data.width}px`;
1281
+ }
1282
+ } else if (event.data?.type === "PASSKEY_DISCONNECT") {
1283
+ localStorage.removeItem("1auth-user");
1284
+ }
1285
+ };
1286
+ window.addEventListener("message", handleMessage);
1287
+ const handleEscape = (event) => {
1288
+ if (event.key === "Escape") {
1289
+ cleanup();
1290
+ }
1291
+ };
1292
+ document.addEventListener("keydown", handleEscape);
1293
+ dialog.addEventListener("click", (event) => {
1294
+ if (event.target === dialog) {
1295
+ cleanup();
1296
+ }
1297
+ });
1298
+ dialog.showModal();
1299
+ const cleanup = () => {
1300
+ window.removeEventListener("message", handleMessage);
1301
+ document.removeEventListener("keydown", handleEscape);
1302
+ dialog.close();
1303
+ dialog.remove();
1304
+ };
1305
+ return { dialog, iframe, cleanup };
1306
+ }
1307
+ waitForModalAuthResponse(_dialog, _iframe, cleanup) {
1308
+ const dialogOrigin = this.getDialogOrigin();
1309
+ return new Promise((resolve) => {
1310
+ const handleMessage = (event) => {
1311
+ if (event.origin !== dialogOrigin) return;
1312
+ const data = event.data;
1313
+ if (data?.type === "PASSKEY_LOGIN_RESULT") {
1314
+ window.removeEventListener("message", handleMessage);
1315
+ cleanup();
1316
+ if (data.success) {
1317
+ resolve({
1318
+ success: true,
1319
+ username: data.data?.username,
1320
+ user: data.data?.user
1321
+ });
1322
+ } else {
1323
+ resolve({
1324
+ success: false,
1325
+ error: data.error
1326
+ });
1327
+ }
1328
+ } else if (data?.type === "PASSKEY_REGISTER_RESULT") {
1329
+ window.removeEventListener("message", handleMessage);
1330
+ cleanup();
1331
+ if (data.success) {
1332
+ resolve({
1333
+ success: true,
1334
+ username: data.data?.username
1335
+ });
1336
+ } else {
1337
+ resolve({
1338
+ success: false,
1339
+ error: data.error
1340
+ });
1341
+ }
1342
+ } else if (data?.type === "PASSKEY_CLOSE") {
1343
+ window.removeEventListener("message", handleMessage);
1344
+ cleanup();
1345
+ resolve({
1346
+ success: false,
1347
+ error: {
1348
+ code: "USER_CANCELLED",
1349
+ message: "Authentication was cancelled"
1350
+ }
1351
+ });
1352
+ }
1353
+ };
1354
+ window.addEventListener("message", handleMessage);
1355
+ });
1356
+ }
1357
+ waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
1358
+ const dialogOrigin = this.getDialogOrigin();
1359
+ return new Promise((resolve) => {
1360
+ const handleMessage = (event) => {
1361
+ if (event.origin !== dialogOrigin) return;
1362
+ const data = event.data;
1363
+ if (data?.type === "PASSKEY_AUTHENTICATE_RESULT") {
1364
+ window.removeEventListener("message", handleMessage);
1365
+ cleanup();
1366
+ if (data.success) {
1367
+ resolve({
1368
+ success: true,
1369
+ username: data.data?.username,
1370
+ user: data.data?.user,
1371
+ accountAddress: data.data?.accountAddress,
1372
+ signature: data.data?.signature,
1373
+ signedHash: data.data?.signedHash
1374
+ });
1375
+ } else {
1376
+ resolve({
1377
+ success: false,
1378
+ error: data.error
1379
+ });
1380
+ }
1381
+ } else if (data?.type === "PASSKEY_CLOSE") {
1382
+ window.removeEventListener("message", handleMessage);
1383
+ cleanup();
1384
+ resolve({
1385
+ success: false,
1386
+ error: {
1387
+ code: "USER_CANCELLED",
1388
+ message: "Authentication was cancelled"
1389
+ }
1390
+ });
1391
+ }
1392
+ };
1393
+ window.addEventListener("message", handleMessage);
1394
+ });
1395
+ }
1396
+ waitForModalSigningResponse(requestId, _dialog, _iframe, cleanup) {
1397
+ const dialogOrigin = this.getDialogOrigin();
1398
+ return new Promise((resolve) => {
1399
+ const handleMessage = (event) => {
1400
+ if (event.origin !== dialogOrigin) return;
1401
+ const message = event.data;
1402
+ const payload = message?.data;
1403
+ if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
1404
+ window.removeEventListener("message", handleMessage);
1405
+ cleanup();
1406
+ if (message.success && payload.signature) {
1407
+ resolve({
1408
+ success: true,
1409
+ requestId,
1410
+ signature: payload.signature
1411
+ });
1412
+ } else {
1413
+ resolve({
1414
+ success: false,
1415
+ requestId,
1416
+ error: message.error
1417
+ });
1418
+ }
1419
+ } else if (message?.type === "PASSKEY_CLOSE") {
1420
+ window.removeEventListener("message", handleMessage);
1421
+ cleanup();
1422
+ resolve({
1423
+ success: false,
1424
+ requestId,
1425
+ error: {
1426
+ code: "USER_REJECTED",
1427
+ message: "Signing was cancelled"
1428
+ }
1429
+ });
1430
+ }
1431
+ };
1432
+ window.addEventListener("message", handleMessage);
1433
+ });
1434
+ }
1435
+ waitForPopupResponse(requestId, popup) {
1436
+ const dialogOrigin = this.getDialogOrigin();
1437
+ return new Promise((resolve) => {
1438
+ const checkClosed = setInterval(() => {
1439
+ if (popup.closed) {
1440
+ clearInterval(checkClosed);
1441
+ window.removeEventListener("message", handleMessage);
1442
+ resolve({
1443
+ success: false,
1444
+ requestId,
1445
+ error: {
1446
+ code: "USER_REJECTED",
1447
+ message: "Popup was closed without completing"
1448
+ }
1449
+ });
1450
+ }
1451
+ }, 500);
1452
+ const handleMessage = (event) => {
1453
+ if (event.origin !== dialogOrigin) {
1454
+ return;
1455
+ }
1456
+ const message = event.data;
1457
+ const payload = message?.data;
1458
+ if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
1459
+ clearInterval(checkClosed);
1460
+ window.removeEventListener("message", handleMessage);
1461
+ popup.close();
1462
+ if (message.success && payload.signature) {
1463
+ resolve({
1464
+ success: true,
1465
+ requestId,
1466
+ signature: payload.signature
1467
+ });
1468
+ } else {
1469
+ resolve({
1470
+ success: false,
1471
+ requestId,
1472
+ error: message.error
1473
+ });
1474
+ }
1475
+ }
1476
+ };
1477
+ window.addEventListener("message", handleMessage);
1478
+ });
1479
+ }
1480
+ async fetchSigningResult(requestId) {
1481
+ const response = await fetch(
1482
+ `${this.config.providerUrl}/api/sign/request/${requestId}`,
1483
+ {
1484
+ headers: {
1485
+ "x-client-id": this.config.clientId
1486
+ }
1487
+ }
1488
+ );
1489
+ if (!response.ok) {
1490
+ return {
1491
+ success: false,
1492
+ requestId,
1493
+ error: {
1494
+ code: "NETWORK_ERROR",
1495
+ message: "Failed to fetch signing result"
1496
+ }
1497
+ };
1498
+ }
1499
+ const data = await response.json();
1500
+ if (data.status === "COMPLETED" && data.signature) {
1501
+ return {
1502
+ success: true,
1503
+ requestId,
1504
+ signature: data.signature
1505
+ };
1506
+ }
1507
+ const errorCode = data.error?.code || "UNKNOWN";
1508
+ return {
1509
+ success: false,
1510
+ requestId,
1511
+ error: {
1512
+ code: errorCode,
1513
+ message: data.error?.message || `Request status: ${data.status}`
1514
+ }
1515
+ };
1516
+ }
1517
+ };
1518
+
1519
+ // src/account.ts
1520
+ import {
1521
+ bytesToString,
1522
+ hexToString,
1523
+ isHex
1524
+ } from "viem";
1525
+ import { toAccount } from "viem/accounts";
1526
+ function createPasskeyAccount(client, params) {
1527
+ const { address, username } = params;
1528
+ const normalizeMessage = (message) => {
1529
+ if (typeof message === "string") return message;
1530
+ const raw = message.raw;
1531
+ if (isHex(raw)) {
1532
+ try {
1533
+ return hexToString(raw);
1534
+ } catch {
1535
+ return raw;
1536
+ }
1537
+ }
1538
+ return bytesToString(raw);
1539
+ };
1540
+ const account = toAccount({
1541
+ address,
1542
+ signMessage: async ({ message }) => {
1543
+ const result = await client.signMessage({
1544
+ username,
1545
+ message: normalizeMessage(message)
1546
+ });
1547
+ if (!result.success || !result.signature) {
1548
+ throw new Error(result.error?.message || "Signing failed");
1549
+ }
1550
+ return encodeWebAuthnSignature(result.signature);
1551
+ },
1552
+ signTransaction: async () => {
1553
+ throw new Error("signTransaction not supported; use sendIntent");
1554
+ },
1555
+ signTypedData: async (typedData) => {
1556
+ if (!typedData.domain || !typedData.types || !typedData.primaryType) {
1557
+ throw new Error("Invalid typed data");
1558
+ }
1559
+ const domainInput = typedData.domain;
1560
+ if (!domainInput.name || !domainInput.version) {
1561
+ throw new Error("Typed data domain must include name and version");
1562
+ }
1563
+ const domain = {
1564
+ name: domainInput.name,
1565
+ version: domainInput.version,
1566
+ chainId: typeof domainInput.chainId === "bigint" ? Number(domainInput.chainId) : domainInput.chainId,
1567
+ verifyingContract: domainInput.verifyingContract,
1568
+ salt: domainInput.salt
1569
+ };
1570
+ const rawTypes = typedData.types;
1571
+ const normalizedTypes = Object.fromEntries(
1572
+ Object.entries(rawTypes).map(([key, fields]) => [
1573
+ key,
1574
+ fields.map((field) => ({ name: field.name, type: field.type }))
1575
+ ])
1576
+ );
1577
+ const result = await client.signTypedData({
1578
+ username,
1579
+ domain,
1580
+ types: normalizedTypes,
1581
+ primaryType: typedData.primaryType,
1582
+ message: typedData.message
1583
+ });
1584
+ if (!result.success || !result.signature) {
1585
+ throw new Error(result.error?.message || "Signing failed");
1586
+ }
1587
+ return encodeWebAuthnSignature(result.signature);
1588
+ }
1589
+ });
1590
+ return {
1591
+ ...account,
1592
+ username
1593
+ };
1594
+ }
1595
+
1596
+ // src/walletClient/index.ts
1597
+ import {
1598
+ createWalletClient,
1599
+ hashMessage,
1600
+ hashTypedData as hashTypedData2
1601
+ } from "viem";
1602
+ import { toAccount as toAccount2 } from "viem/accounts";
1603
+ function createPasskeyWalletClient(config) {
1604
+ const provider = new PasskeyProviderClient({
1605
+ providerUrl: config.providerUrl,
1606
+ clientId: config.clientId,
1607
+ dialogUrl: config.dialogUrl
1608
+ });
1609
+ const signMessageImpl = async (message) => {
1610
+ const hash = hashMessage(message);
1611
+ const result = await provider.signWithModal({
1612
+ challenge: hash,
1613
+ username: config.username,
1614
+ description: "Sign message",
1615
+ transaction: {
1616
+ actions: [
1617
+ {
1618
+ type: "custom",
1619
+ label: "Sign Message",
1620
+ sublabel: typeof message === "string" ? message.slice(0, 50) + (message.length > 50 ? "..." : "") : "Raw message"
1621
+ }
1622
+ ]
1623
+ }
1624
+ });
1625
+ if (!result.success) {
1626
+ throw new Error(result.error?.message || "Signing failed");
1627
+ }
1628
+ return encodeWebAuthnSignature(result.signature);
1629
+ };
1630
+ const signTransactionImpl = async (transaction) => {
1631
+ const calls = [
1632
+ {
1633
+ to: transaction.to,
1634
+ data: transaction.data,
1635
+ value: transaction.value
1636
+ }
1637
+ ];
1638
+ const hash = hashCalls(calls);
1639
+ const result = await provider.signWithModal({
1640
+ challenge: hash,
1641
+ username: config.username,
1642
+ description: "Sign transaction",
1643
+ transaction: buildTransactionReview(calls)
1644
+ });
1645
+ if (!result.success) {
1646
+ throw new Error(result.error?.message || "Signing failed");
1647
+ }
1648
+ return encodeWebAuthnSignature(result.signature);
1649
+ };
1650
+ const signTypedDataImpl = async (typedData) => {
1651
+ const hash = hashTypedData2(typedData);
1652
+ const result = await provider.signWithModal({
1653
+ challenge: hash,
1654
+ username: config.username,
1655
+ description: "Sign typed data",
1656
+ transaction: {
1657
+ actions: [
1658
+ {
1659
+ type: "custom",
1660
+ label: "Sign Data",
1661
+ sublabel: typedData.primaryType || "Typed Data"
1662
+ }
1663
+ ]
1664
+ }
1665
+ });
1666
+ if (!result.success) {
1667
+ throw new Error(result.error?.message || "Signing failed");
1668
+ }
1669
+ return encodeWebAuthnSignature(result.signature);
1670
+ };
1671
+ const account = toAccount2({
1672
+ address: config.accountAddress,
1673
+ signMessage: ({ message }) => signMessageImpl(message),
1674
+ signTransaction: signTransactionImpl,
1675
+ signTypedData: signTypedDataImpl
1676
+ });
1677
+ const client = createWalletClient({
1678
+ account,
1679
+ chain: config.chain,
1680
+ transport: config.transport
1681
+ });
1682
+ const extendedClient = Object.assign(client, {
1683
+ /**
1684
+ * Send a single transaction via intent flow
1685
+ */
1686
+ async sendTransaction(transaction) {
1687
+ const calls = [
1688
+ {
1689
+ to: transaction.to,
1690
+ data: transaction.data,
1691
+ value: transaction.value
1692
+ }
1693
+ ];
1694
+ const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
1695
+ const result = await provider.sendIntent({
1696
+ username: config.username,
1697
+ targetChain: config.chain.id,
1698
+ calls: calls.map((call) => ({
1699
+ to: call.to,
1700
+ data: call.data,
1701
+ value: call.value ? call.value.toString() : "0",
1702
+ label: call.label,
1703
+ sublabel: call.sublabel
1704
+ })),
1705
+ closeOn,
1706
+ waitForHash: config.waitForHash ?? true,
1707
+ hashTimeoutMs: config.hashTimeoutMs,
1708
+ hashIntervalMs: config.hashIntervalMs
1709
+ });
1710
+ if (!result.success || !result.transactionHash) {
1711
+ throw new Error(result.error?.message || "Transaction failed");
1712
+ }
1713
+ return result.transactionHash;
1714
+ },
1715
+ /**
1716
+ * Send multiple calls as a single batched transaction
1717
+ */
1718
+ async sendCalls(params) {
1719
+ const { calls } = params;
1720
+ const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
1721
+ const result = await provider.sendIntent({
1722
+ username: config.username,
1723
+ targetChain: config.chain.id,
1724
+ calls: calls.map((call) => ({
1725
+ to: call.to,
1726
+ data: call.data,
1727
+ value: call.value ? call.value.toString() : "0",
1728
+ label: call.label,
1729
+ sublabel: call.sublabel
1730
+ })),
1731
+ closeOn,
1732
+ waitForHash: config.waitForHash ?? true,
1733
+ hashTimeoutMs: config.hashTimeoutMs,
1734
+ hashIntervalMs: config.hashIntervalMs
1735
+ });
1736
+ if (!result.success || !result.transactionHash) {
1737
+ throw new Error(result.error?.message || "Transaction failed");
1738
+ }
1739
+ return result.transactionHash;
1740
+ }
1741
+ });
1742
+ return extendedClient;
1743
+ }
1744
+
1745
+ // src/batch/BatchQueueContext.tsx
1746
+ import * as React from "react";
1747
+ import { jsx } from "react/jsx-runtime";
1748
+ function getChainName2(chainId) {
1749
+ return getChainName(chainId);
1750
+ }
1751
+ var BatchQueueContext = React.createContext(null);
1752
+ function useBatchQueue() {
1753
+ const context = React.useContext(BatchQueueContext);
1754
+ if (!context) {
1755
+ throw new Error("useBatchQueue must be used within a BatchQueueProvider");
1756
+ }
1757
+ return context;
1758
+ }
1759
+ function generateId() {
1760
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1761
+ }
1762
+ function getStorageKey(username) {
1763
+ return username ? `1auth_batch_queue_${username}` : "1auth_batch_queue_anonymous";
1764
+ }
1765
+ function BatchQueueProvider({
1766
+ client,
1767
+ username,
1768
+ children
1769
+ }) {
1770
+ const [queue, setQueue] = React.useState([]);
1771
+ const [isExpanded, setExpanded] = React.useState(false);
1772
+ const [isSigning, setIsSigning] = React.useState(false);
1773
+ const [animationTrigger, setAnimationTrigger] = React.useState(0);
1774
+ const batchChainId = queue.length > 0 ? queue[0].targetChain : null;
1775
+ React.useEffect(() => {
1776
+ const storageKey = getStorageKey(username);
1777
+ try {
1778
+ const stored = localStorage.getItem(storageKey);
1779
+ if (stored) {
1780
+ const parsed = JSON.parse(stored);
1781
+ if (Array.isArray(parsed) && parsed.every(
1782
+ (item) => typeof item.id === "string" && typeof item.call === "object" && typeof item.targetChain === "number"
1783
+ )) {
1784
+ setQueue(parsed);
1785
+ }
1786
+ }
1787
+ } catch {
1788
+ }
1789
+ }, [username]);
1790
+ React.useEffect(() => {
1791
+ const storageKey = getStorageKey(username);
1792
+ try {
1793
+ if (queue.length > 0) {
1794
+ localStorage.setItem(storageKey, JSON.stringify(queue));
1795
+ } else {
1796
+ localStorage.removeItem(storageKey);
1797
+ }
1798
+ } catch {
1799
+ }
1800
+ }, [queue, username]);
1801
+ const addToBatch = React.useCallback((call, targetChain) => {
1802
+ if (batchChainId !== null && batchChainId !== targetChain) {
1803
+ return {
1804
+ success: false,
1805
+ error: `Batch is set to ${getChainName2(batchChainId)}. Sign current batch first or clear it.`
1806
+ };
1807
+ }
1808
+ const batchedCall = {
1809
+ id: generateId(),
1810
+ call,
1811
+ targetChain,
1812
+ addedAt: Date.now()
1813
+ };
1814
+ setQueue((prev) => [...prev, batchedCall]);
1815
+ setAnimationTrigger((prev) => prev + 1);
1816
+ return { success: true };
1817
+ }, [batchChainId]);
1818
+ const removeFromBatch = React.useCallback((id) => {
1819
+ setQueue((prev) => prev.filter((item) => item.id !== id));
1820
+ }, []);
1821
+ const clearBatch = React.useCallback(() => {
1822
+ setQueue([]);
1823
+ setExpanded(false);
1824
+ }, []);
1825
+ const signAll = React.useCallback(async (username2) => {
1826
+ if (queue.length === 0) {
1827
+ return {
1828
+ success: false,
1829
+ intentId: "",
1830
+ status: "failed",
1831
+ error: {
1832
+ code: "EMPTY_BATCH",
1833
+ message: "No calls in batch to sign"
1834
+ }
1835
+ };
1836
+ }
1837
+ const targetChain = queue[0].targetChain;
1838
+ const calls = queue.map((item) => item.call);
1839
+ setIsSigning(true);
1840
+ try {
1841
+ const result = await client.sendIntent({
1842
+ username: username2,
1843
+ targetChain,
1844
+ calls
1845
+ });
1846
+ if (result.success) {
1847
+ clearBatch();
1848
+ }
1849
+ return result;
1850
+ } finally {
1851
+ setIsSigning(false);
1852
+ }
1853
+ }, [queue, client, clearBatch]);
1854
+ const value = {
1855
+ queue,
1856
+ batchChainId,
1857
+ addToBatch,
1858
+ removeFromBatch,
1859
+ clearBatch,
1860
+ signAll,
1861
+ isExpanded,
1862
+ setExpanded,
1863
+ isSigning,
1864
+ animationTrigger
1865
+ };
1866
+ return /* @__PURE__ */ jsx(BatchQueueContext.Provider, { value, children });
1867
+ }
1868
+
1869
+ // src/batch/BatchQueueWidget.tsx
1870
+ import * as React2 from "react";
1871
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
1872
+ var ANIMATION_STYLES = `
1873
+ @keyframes batch-bounce-in {
1874
+ 0% { transform: scale(0.8); opacity: 0; }
1875
+ 50% { transform: scale(1.05); }
1876
+ 100% { transform: scale(1); opacity: 1; }
1877
+ }
1878
+ @keyframes batch-item-bounce {
1879
+ 0%, 100% { transform: translateX(0); }
1880
+ 25% { transform: translateX(-2px); }
1881
+ 75% { transform: translateX(2px); }
1882
+ }
1883
+ `;
1884
+ function injectStyles() {
1885
+ if (typeof document === "undefined") return;
1886
+ const styleId = "batch-queue-widget-styles";
1887
+ if (document.getElementById(styleId)) return;
1888
+ const style = document.createElement("style");
1889
+ style.id = styleId;
1890
+ style.textContent = ANIMATION_STYLES;
1891
+ document.head.appendChild(style);
1892
+ }
1893
+ var widgetStyles = {
1894
+ position: "fixed",
1895
+ bottom: "16px",
1896
+ right: "16px",
1897
+ zIndex: 50,
1898
+ backgroundColor: "#18181b",
1899
+ color: "#ffffff",
1900
+ borderRadius: "12px",
1901
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
1902
+ minWidth: "240px",
1903
+ maxWidth: "360px",
1904
+ fontFamily: "system-ui, -apple-system, sans-serif",
1905
+ fontSize: "14px",
1906
+ overflow: "hidden"
1907
+ };
1908
+ var headerStyles = {
1909
+ display: "flex",
1910
+ alignItems: "center",
1911
+ justifyContent: "space-between",
1912
+ padding: "12px 16px",
1913
+ cursor: "pointer",
1914
+ userSelect: "none"
1915
+ };
1916
+ var headerLeftStyles = {
1917
+ display: "flex",
1918
+ alignItems: "center",
1919
+ gap: "8px"
1920
+ };
1921
+ var counterBadgeStyles = {
1922
+ backgroundColor: "#ffffff",
1923
+ color: "#18181b",
1924
+ borderRadius: "9999px",
1925
+ padding: "2px 8px",
1926
+ fontSize: "12px",
1927
+ fontWeight: 600
1928
+ };
1929
+ var chainBadgeStyles = {
1930
+ backgroundColor: "rgba(255,255,255,0.15)",
1931
+ color: "#a1a1aa",
1932
+ borderRadius: "4px",
1933
+ padding: "2px 6px",
1934
+ fontSize: "11px"
1935
+ };
1936
+ var signAllButtonStyles = {
1937
+ backgroundColor: "#ffffff",
1938
+ color: "#18181b",
1939
+ border: "none",
1940
+ borderRadius: "6px",
1941
+ padding: "6px 12px",
1942
+ fontSize: "13px",
1943
+ fontWeight: 500,
1944
+ cursor: "pointer",
1945
+ transition: "background-color 0.15s"
1946
+ };
1947
+ var signAllButtonHoverStyles = {
1948
+ backgroundColor: "#e4e4e7"
1949
+ };
1950
+ var signAllButtonDisabledStyles = {
1951
+ opacity: 0.5,
1952
+ cursor: "not-allowed"
1953
+ };
1954
+ var listContainerStyles = {
1955
+ maxHeight: "300px",
1956
+ overflowY: "auto",
1957
+ borderTop: "1px solid #27272a"
1958
+ };
1959
+ var callItemStyles = {
1960
+ display: "flex",
1961
+ alignItems: "flex-start",
1962
+ padding: "10px 16px",
1963
+ borderBottom: "1px solid #27272a",
1964
+ position: "relative"
1965
+ };
1966
+ var callItemLastStyles = {
1967
+ borderBottom: "none"
1968
+ };
1969
+ var callContentStyles = {
1970
+ flex: 1,
1971
+ minWidth: 0
1972
+ };
1973
+ var callLabelStyles = {
1974
+ fontWeight: 500,
1975
+ marginBottom: "2px"
1976
+ };
1977
+ var callSublabelStyles = {
1978
+ color: "#a1a1aa",
1979
+ fontSize: "12px",
1980
+ overflow: "hidden",
1981
+ textOverflow: "ellipsis",
1982
+ whiteSpace: "nowrap"
1983
+ };
1984
+ var removeButtonStyles = {
1985
+ position: "absolute",
1986
+ left: "8px",
1987
+ top: "50%",
1988
+ transform: "translateY(-50%)",
1989
+ backgroundColor: "#dc2626",
1990
+ color: "#ffffff",
1991
+ border: "none",
1992
+ borderRadius: "4px",
1993
+ width: "20px",
1994
+ height: "20px",
1995
+ display: "flex",
1996
+ alignItems: "center",
1997
+ justifyContent: "center",
1998
+ cursor: "pointer",
1999
+ fontSize: "14px",
2000
+ opacity: 0,
2001
+ transition: "opacity 0.15s"
2002
+ };
2003
+ var removeButtonVisibleStyles = {
2004
+ opacity: 1
2005
+ };
2006
+ var clearButtonStyles = {
2007
+ display: "block",
2008
+ width: "100%",
2009
+ padding: "10px 16px",
2010
+ backgroundColor: "transparent",
2011
+ color: "#a1a1aa",
2012
+ border: "none",
2013
+ borderTop: "1px solid #27272a",
2014
+ fontSize: "12px",
2015
+ cursor: "pointer",
2016
+ textAlign: "center",
2017
+ transition: "color 0.15s"
2018
+ };
2019
+ var clearButtonHoverStyles = {
2020
+ color: "#ffffff"
2021
+ };
2022
+ function ChevronIcon({ down }) {
2023
+ return /* @__PURE__ */ jsx2(
2024
+ "svg",
2025
+ {
2026
+ width: "16",
2027
+ height: "16",
2028
+ viewBox: "0 0 24 24",
2029
+ fill: "none",
2030
+ stroke: "currentColor",
2031
+ strokeWidth: "2",
2032
+ strokeLinecap: "round",
2033
+ strokeLinejoin: "round",
2034
+ style: {
2035
+ transition: "transform 0.2s",
2036
+ transform: down ? "rotate(0deg)" : "rotate(-90deg)"
2037
+ },
2038
+ children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" })
2039
+ }
2040
+ );
2041
+ }
2042
+ function BatchQueueWidget({ onSignAll }) {
2043
+ const {
2044
+ queue,
2045
+ batchChainId,
2046
+ removeFromBatch,
2047
+ clearBatch,
2048
+ isExpanded,
2049
+ setExpanded,
2050
+ isSigning,
2051
+ animationTrigger
2052
+ } = useBatchQueue();
2053
+ const [hoveredItemId, setHoveredItemId] = React2.useState(null);
2054
+ const [isSignAllHovered, setIsSignAllHovered] = React2.useState(false);
2055
+ const [isClearHovered, setIsClearHovered] = React2.useState(false);
2056
+ const [shouldAnimate, setShouldAnimate] = React2.useState(false);
2057
+ React2.useEffect(() => {
2058
+ injectStyles();
2059
+ }, []);
2060
+ React2.useEffect(() => {
2061
+ if (animationTrigger > 0) {
2062
+ setShouldAnimate(true);
2063
+ const timer = setTimeout(() => setShouldAnimate(false), 300);
2064
+ return () => clearTimeout(timer);
2065
+ }
2066
+ }, [animationTrigger]);
2067
+ if (queue.length === 0) {
2068
+ return null;
2069
+ }
2070
+ const handleHeaderClick = () => {
2071
+ setExpanded(!isExpanded);
2072
+ };
2073
+ const handleSignAllClick = (e) => {
2074
+ e.stopPropagation();
2075
+ if (!isSigning) {
2076
+ onSignAll();
2077
+ }
2078
+ };
2079
+ const animatedWidgetStyles = {
2080
+ ...widgetStyles,
2081
+ animation: shouldAnimate ? "batch-bounce-in 0.3s ease-out" : void 0
2082
+ };
2083
+ const currentSignAllStyles = {
2084
+ ...signAllButtonStyles,
2085
+ ...isSignAllHovered && !isSigning ? signAllButtonHoverStyles : {},
2086
+ ...isSigning ? signAllButtonDisabledStyles : {}
2087
+ };
2088
+ return /* @__PURE__ */ jsxs("div", { style: animatedWidgetStyles, children: [
2089
+ /* @__PURE__ */ jsxs("div", { style: headerStyles, onClick: handleHeaderClick, children: [
2090
+ /* @__PURE__ */ jsxs("div", { style: headerLeftStyles, children: [
2091
+ /* @__PURE__ */ jsx2(ChevronIcon, { down: isExpanded }),
2092
+ /* @__PURE__ */ jsx2("span", { style: counterBadgeStyles, children: queue.length }),
2093
+ /* @__PURE__ */ jsxs("span", { children: [
2094
+ "call",
2095
+ queue.length !== 1 ? "s" : "",
2096
+ " queued"
2097
+ ] }),
2098
+ batchChainId && /* @__PURE__ */ jsx2("span", { style: chainBadgeStyles, children: getChainName2(batchChainId) })
2099
+ ] }),
2100
+ /* @__PURE__ */ jsx2(
2101
+ "button",
2102
+ {
2103
+ style: currentSignAllStyles,
2104
+ onClick: handleSignAllClick,
2105
+ onMouseEnter: () => setIsSignAllHovered(true),
2106
+ onMouseLeave: () => setIsSignAllHovered(false),
2107
+ disabled: isSigning,
2108
+ children: isSigning ? "Signing..." : "Sign All"
2109
+ }
2110
+ )
2111
+ ] }),
2112
+ isExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
2113
+ /* @__PURE__ */ jsx2("div", { style: listContainerStyles, children: queue.map((item, index) => {
2114
+ const isHovered = hoveredItemId === item.id;
2115
+ const isLast = index === queue.length - 1;
2116
+ return /* @__PURE__ */ jsxs(
2117
+ "div",
2118
+ {
2119
+ style: {
2120
+ ...callItemStyles,
2121
+ ...isLast ? callItemLastStyles : {},
2122
+ paddingLeft: isHovered ? "36px" : "16px",
2123
+ transition: "padding-left 0.15s"
2124
+ },
2125
+ onMouseEnter: () => setHoveredItemId(item.id),
2126
+ onMouseLeave: () => setHoveredItemId(null),
2127
+ children: [
2128
+ /* @__PURE__ */ jsx2(
2129
+ "button",
2130
+ {
2131
+ style: {
2132
+ ...removeButtonStyles,
2133
+ ...isHovered ? removeButtonVisibleStyles : {}
2134
+ },
2135
+ onClick: () => removeFromBatch(item.id),
2136
+ title: "Remove from batch",
2137
+ children: "\xD7"
2138
+ }
2139
+ ),
2140
+ /* @__PURE__ */ jsxs("div", { style: callContentStyles, children: [
2141
+ /* @__PURE__ */ jsx2("div", { style: callLabelStyles, children: item.call.label || "Contract Call" }),
2142
+ item.call.sublabel && /* @__PURE__ */ jsx2("div", { style: callSublabelStyles, children: item.call.sublabel }),
2143
+ !item.call.sublabel && item.call.to && /* @__PURE__ */ jsxs("div", { style: callSublabelStyles, children: [
2144
+ "To: ",
2145
+ item.call.to.slice(0, 6),
2146
+ "...",
2147
+ item.call.to.slice(-4)
2148
+ ] })
2149
+ ] })
2150
+ ]
2151
+ },
2152
+ item.id
2153
+ );
2154
+ }) }),
2155
+ /* @__PURE__ */ jsx2(
2156
+ "button",
2157
+ {
2158
+ style: {
2159
+ ...clearButtonStyles,
2160
+ ...isClearHovered ? clearButtonHoverStyles : {}
2161
+ },
2162
+ onClick: clearBatch,
2163
+ onMouseEnter: () => setIsClearHovered(true),
2164
+ onMouseLeave: () => setIsClearHovered(false),
2165
+ children: "Clear batch"
2166
+ }
2167
+ )
2168
+ ] })
2169
+ ] });
2170
+ }
2171
+
2172
+ // src/verify.ts
2173
+ import { keccak256, toBytes } from "viem";
2174
+ var PASSKEY_MESSAGE_PREFIX = "Passkey Signed Message:\n";
2175
+ function hashMessage2(message) {
2176
+ const prefixed = PASSKEY_MESSAGE_PREFIX + message.length.toString() + message;
2177
+ return keccak256(toBytes(prefixed));
2178
+ }
2179
+ function verifyMessageHash(message, signedHash) {
2180
+ if (!signedHash) return false;
2181
+ const expectedHash = hashMessage2(message);
2182
+ return expectedHash.toLowerCase() === signedHash.toLowerCase();
2183
+ }
2184
+ export {
2185
+ BatchQueueProvider,
2186
+ BatchQueueWidget,
2187
+ PASSKEY_MESSAGE_PREFIX,
2188
+ PasskeyProviderClient,
2189
+ createPasskeyAccount,
2190
+ createPasskeyProvider,
2191
+ createPasskeyWalletClient,
2192
+ encodeWebAuthnSignature,
2193
+ getAllSupportedChainsAndTokens,
2194
+ getChainById,
2195
+ getChainExplorerUrl,
2196
+ getChainName2 as getChainName,
2197
+ getChainRpcUrl,
2198
+ getSupportedChainIds,
2199
+ getSupportedChains,
2200
+ getSupportedTokenSymbols,
2201
+ getSupportedTokens,
2202
+ getTokenAddress,
2203
+ getTokenDecimals,
2204
+ getTokenSymbol,
2205
+ hashCalls,
2206
+ hashMessage2 as hashMessage,
2207
+ isTestnet,
2208
+ isTokenAddressSupported,
2209
+ resolveTokenAddress,
2210
+ useBatchQueue,
2211
+ verifyMessageHash
2212
+ };
2213
+ //# sourceMappingURL=index.mjs.map