@cavos/kit 0.0.1

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.
@@ -0,0 +1,2055 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var starknet = require('starknet');
5
+ var sha256 = require('@noble/hashes/sha256');
6
+ var p256 = require('@noble/curves/p256');
7
+ var hkdf = require('@noble/hashes/hkdf');
8
+ var pbkdf2 = require('@noble/hashes/pbkdf2');
9
+ var utils = require('@noble/hashes/utils');
10
+ var jsxRuntime = require('react/jsx-runtime');
11
+
12
+ // src/react/CavosProvider.tsx
13
+
14
+ // src/crypto/encoding.ts
15
+ var U128_MASK = (1n << 128n) - 1n;
16
+ function u256ToFelts(value) {
17
+ return [value & U128_MASK, value >> 128n];
18
+ }
19
+ function bytesToBigInt(bytes) {
20
+ let out = 0n;
21
+ for (const b of bytes) out = out << 8n | BigInt(b);
22
+ return out;
23
+ }
24
+ function bigIntTo32Bytes(value) {
25
+ const out = new Uint8Array(32);
26
+ let v = value;
27
+ for (let i = 31; i >= 0; i--) {
28
+ out[i] = Number(v & 0xffn);
29
+ v >>= 8n;
30
+ }
31
+ return out;
32
+ }
33
+ function signatureToFelts(sig) {
34
+ const [rLow, rHigh] = u256ToFelts(sig.r);
35
+ const [sLow, sHigh] = u256ToFelts(sig.s);
36
+ return [rLow, rHigh, sLow, sHigh, sig.yParity ? 1n : 0n];
37
+ }
38
+ function recoverYParity(r, s, digest, pubkey) {
39
+ for (const bit of [0, 1]) {
40
+ try {
41
+ const candidate = new p256.p256.Signature(r, s).addRecoveryBit(bit);
42
+ const point = candidate.recoverPublicKey(digest).toAffine();
43
+ if (point.x === pubkey.x && point.y === pubkey.y) {
44
+ return bit === 1;
45
+ }
46
+ } catch {
47
+ }
48
+ }
49
+ throw new Error("kit/signature: could not recover parity for the given pubkey");
50
+ }
51
+
52
+ // src/signer/WebCryptoSigner.ts
53
+ var IDB_NAME = "cavos-kit";
54
+ var IDB_STORE = "device-keys";
55
+ var WebCryptoSigner = class _WebCryptoSigner {
56
+ constructor(privateKey, publicKey, keyId) {
57
+ this.privateKey = privateKey;
58
+ this.publicKey = publicKey;
59
+ this.keyId = keyId;
60
+ }
61
+ /** Create a fresh device key (first run on this device) and persist it. */
62
+ static async create(opts) {
63
+ assertSecureContext();
64
+ const pair = await crypto.subtle.generateKey(
65
+ { name: "ECDSA", namedCurve: "P-256" },
66
+ false,
67
+ // private key is NON-extractable
68
+ ["sign", "verify"]
69
+ );
70
+ const publicKey = await exportPublicKey(pair.publicKey);
71
+ await idbPut(opts.keyId, { privateKey: pair.privateKey, x: publicKey.x, y: publicKey.y });
72
+ return new _WebCryptoSigner(pair.privateKey, publicKey, opts.keyId);
73
+ }
74
+ /** Load an existing device key from storage, or null if none exists yet. */
75
+ static async load(opts) {
76
+ const rec = await idbGet(opts.keyId);
77
+ if (!rec) return null;
78
+ return new _WebCryptoSigner(rec.privateKey, { x: rec.x, y: rec.y }, opts.keyId);
79
+ }
80
+ /** Load the device key, creating one on first use. */
81
+ static async loadOrCreate(opts) {
82
+ return await _WebCryptoSigner.load(opts) ?? await _WebCryptoSigner.create(opts);
83
+ }
84
+ async getPublicKey() {
85
+ return this.publicKey;
86
+ }
87
+ async sign(txHash) {
88
+ const raw = new Uint8Array(
89
+ await crypto.subtle.sign(
90
+ { name: "ECDSA", hash: "SHA-256" },
91
+ this.privateKey,
92
+ txHash
93
+ )
94
+ );
95
+ const r = bytesToBigInt(raw.subarray(0, 32));
96
+ const s = bytesToBigInt(raw.subarray(32, 64));
97
+ const digest = sha256.sha256(txHash);
98
+ const yParity = recoverYParity(r, s, digest, this.publicKey);
99
+ return { r, s, yParity };
100
+ }
101
+ };
102
+ async function exportPublicKey(publicKey) {
103
+ const raw = new Uint8Array(await crypto.subtle.exportKey("raw", publicKey));
104
+ return { x: bytesToBigInt(raw.subarray(1, 33)), y: bytesToBigInt(raw.subarray(33, 65)) };
105
+ }
106
+ function assertSecureContext() {
107
+ const ok = typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined" && (typeof window === "undefined" || window.isSecureContext);
108
+ if (!ok) {
109
+ throw new Error(
110
+ "Cavos: WebCrypto is unavailable. Device keys require a secure context \u2014 use HTTPS, or http://localhost. (For LAN/mobile dev testing, run `next dev --experimental-https`.)"
111
+ );
112
+ }
113
+ }
114
+ function openDb() {
115
+ return new Promise((resolve, reject) => {
116
+ const req = indexedDB.open(IDB_NAME, 1);
117
+ req.onupgradeneeded = () => req.result.createObjectStore(IDB_STORE);
118
+ req.onsuccess = () => resolve(req.result);
119
+ req.onerror = () => reject(req.error);
120
+ });
121
+ }
122
+ async function idbPut(keyId, value) {
123
+ const db = await openDb();
124
+ await tx(db, "readwrite", (store) => store.put(value, keyId));
125
+ db.close();
126
+ }
127
+ async function idbGet(keyId) {
128
+ const db = await openDb();
129
+ const result = await tx(db, "readonly", (store) => store.get(keyId));
130
+ db.close();
131
+ return result ?? null;
132
+ }
133
+ function tx(db, mode, run) {
134
+ return new Promise((resolve, reject) => {
135
+ const store = db.transaction(IDB_STORE, mode).objectStore(IDB_STORE);
136
+ const req = run(store);
137
+ req.onsuccess = () => resolve(req.result);
138
+ req.onerror = () => reject(req.error);
139
+ });
140
+ }
141
+
142
+ // src/chains/starknet/constants.ts
143
+ var STARKNET_NETWORKS = {
144
+ sepolia: {
145
+ chainId: "0x534e5f5345504f4c4941",
146
+ // SN_SEPOLIA
147
+ rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia"
148
+ },
149
+ mainnet: {
150
+ chainId: "0x534e5f4d41494e",
151
+ // SN_MAIN
152
+ rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet"
153
+ }
154
+ };
155
+ var UDC_ADDRESS = "0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf";
156
+ var CAVOS_PAYMASTER_URL = {
157
+ sepolia: "https://sepolia-paymaster.cavos.xyz",
158
+ mainnet: "https://paymaster.cavos.xyz"
159
+ };
160
+ var DEVICE_ACCOUNT_CLASS_HASH = {
161
+ sepolia: "0x6c3c3426667b6d4adda18a0b8d8cc34c495a1ace7276c4470068ad4c324876d",
162
+ mainnet: ""
163
+ };
164
+
165
+ // src/chains/starknet/StarknetAdapter.ts
166
+ var StarknetAdapter = class {
167
+ constructor(opts) {
168
+ this.opts = opts;
169
+ this.chain = "starknet";
170
+ }
171
+ computeAddress({ addressSeed, initialSigner, salt }) {
172
+ return starknet.hash.calculateContractAddressFromHash(
173
+ starknet.num.toHex(salt ?? addressSeed),
174
+ this.opts.classHash,
175
+ this.constructorCalldata(addressSeed, initialSigner),
176
+ 0
177
+ // deployerAddress 0 => deterministic counterfactual address
178
+ );
179
+ }
180
+ /** Single UDC deploy; the constructor registers the first device signer, so
181
+ * the account is ready the moment it is deployed (fits the paymaster's
182
+ * deploy + execute_from_outside bundle). */
183
+ buildDeploy(params) {
184
+ const salt = params.salt ?? params.addressSeed;
185
+ const calldata = this.constructorCalldata(params.addressSeed, params.initialSigner);
186
+ return [
187
+ {
188
+ contractAddress: UDC_ADDRESS,
189
+ entrypoint: "deployContract",
190
+ calldata: [
191
+ this.opts.classHash,
192
+ starknet.num.toHex(salt),
193
+ "0x0",
194
+ // unique = false -> deployer-independent address
195
+ starknet.num.toHex(calldata.length),
196
+ ...calldata
197
+ ]
198
+ }
199
+ ];
200
+ }
201
+ /** Constructor calldata: [address_seed, pub_x_low, pub_x_high, pub_y_low, pub_y_high]. */
202
+ constructorCalldata(addressSeed, initialSigner) {
203
+ return [starknet.num.toHex(addressSeed), ...pubkeyCalldata(initialSigner)];
204
+ }
205
+ buildAddSigner(accountAddress, signer) {
206
+ return { contractAddress: accountAddress, entrypoint: "add_signer", calldata: pubkeyCalldata(signer) };
207
+ }
208
+ buildRemoveSigner(accountAddress, signer) {
209
+ return { contractAddress: accountAddress, entrypoint: "remove_signer", calldata: pubkeyCalldata(signer) };
210
+ }
211
+ async isAuthorizedSigner(accountAddress, signer) {
212
+ if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
213
+ const res = await this.opts.provider.callContract({
214
+ contractAddress: accountAddress,
215
+ entrypoint: "is_authorized_signer",
216
+ calldata: pubkeyCalldata(signer)
217
+ });
218
+ return BigInt(res[0] ?? 0) !== 0n;
219
+ }
220
+ async buildSignature(txHash) {
221
+ if (!this.opts.signer) throw new Error("kit/starknet: signer required to sign");
222
+ const sig = await this.opts.signer.sign(bigIntTo32Bytes(txHash));
223
+ return signatureToFelts(sig).map((f) => starknet.num.toHex(f));
224
+ }
225
+ };
226
+ function pubkeyCalldata(pk) {
227
+ const [xl, xh] = u256ToFelts(pk.x);
228
+ const [yl, yh] = u256ToFelts(pk.y);
229
+ return [starknet.num.toHex(xl), starknet.num.toHex(xh), starknet.num.toHex(yl), starknet.num.toHex(yh)];
230
+ }
231
+ var StarknetDeviceSigner = class extends starknet.Signer {
232
+ constructor(device) {
233
+ super("0x1");
234
+ this.device = device;
235
+ }
236
+ /** Device accounts are not controlled by a single Stark pubkey. */
237
+ async getPubKey() {
238
+ return "0x0";
239
+ }
240
+ /** Sign the computed tx hash silently with the device signer. */
241
+ async signRaw(msgHash) {
242
+ const sig = await this.device.sign(bigIntTo32Bytes(BigInt(msgHash)));
243
+ return signatureToFelts(sig).map((f) => starknet.num.toHex(f));
244
+ }
245
+ };
246
+
247
+ // src/registry/WalletRegistry.ts
248
+ var InMemoryWalletRegistry = class {
249
+ constructor() {
250
+ this.wallets = /* @__PURE__ */ new Map();
251
+ }
252
+ async lookup(userId) {
253
+ return this.wallets.get(userId) ?? null;
254
+ }
255
+ async register(params) {
256
+ this.wallets.set(params.userId, { address: params.address, devices: [params.initialSigner] });
257
+ }
258
+ async addDevice(params) {
259
+ const w = this.wallets.get(params.userId);
260
+ if (w) w.devices = [...w.devices ?? [], params.signer];
261
+ }
262
+ };
263
+
264
+ // src/registry/HttpWalletRegistry.ts
265
+ function toHex(n) {
266
+ return "0x" + n.toString(16);
267
+ }
268
+ function fromHex(s) {
269
+ return BigInt(s);
270
+ }
271
+ var HttpWalletRegistry = class {
272
+ constructor(opts) {
273
+ this.opts = opts;
274
+ }
275
+ async lookup(userId) {
276
+ const url = new URL("/api/wallets", this.opts.baseUrl);
277
+ url.searchParams.set("app_id", this.opts.appId);
278
+ url.searchParams.set("user_social_id", userId);
279
+ url.searchParams.set("network", this.opts.network);
280
+ const res = await fetch(url, { headers: { "Content-Type": "application/json" } });
281
+ if (!res.ok) throw new Error(`registry lookup failed: ${res.status}`);
282
+ const data = await res.json();
283
+ if (!data.found || !data.address) return null;
284
+ const devices = Array.isArray(data.devices) ? data.devices.map((d) => ({
285
+ x: fromHex(d.pub_x),
286
+ y: fromHex(d.pub_y)
287
+ })) : void 0;
288
+ return { address: data.address, devices };
289
+ }
290
+ async register(params) {
291
+ const res = await fetch(new URL("/api/wallets", this.opts.baseUrl), {
292
+ method: "POST",
293
+ headers: { "Content-Type": "application/json" },
294
+ body: JSON.stringify({
295
+ app_id: this.opts.appId,
296
+ user_social_id: params.userId,
297
+ network: this.opts.network,
298
+ address: params.address,
299
+ // Device-signer wallets send their initial signer (no encrypted blob).
300
+ devices: [{ x: toHex(params.initialSigner.x), y: toHex(params.initialSigner.y) }]
301
+ })
302
+ });
303
+ if (!res.ok) {
304
+ const t = await res.text().catch(() => "");
305
+ throw new Error(`registry register failed: ${res.status} ${t}`);
306
+ }
307
+ }
308
+ async addDevice(params) {
309
+ }
310
+ };
311
+
312
+ // src/recovery/HttpRecoveryClient.ts
313
+ function toHex2(n) {
314
+ return "0x" + n.toString(16);
315
+ }
316
+ function fromHex2(s) {
317
+ return BigInt(s);
318
+ }
319
+ function deviceLabel() {
320
+ if (typeof navigator !== "undefined") {
321
+ return navigator.userAgent || "a new device";
322
+ }
323
+ return "a new device";
324
+ }
325
+ var HttpRecoveryClient = class {
326
+ constructor(opts) {
327
+ this.opts = opts;
328
+ }
329
+ async requestDeviceAddition(params) {
330
+ const res = await fetch(new URL("/api/devices/request", this.opts.baseUrl), {
331
+ method: "POST",
332
+ headers: { "Content-Type": "application/json" },
333
+ body: JSON.stringify({
334
+ app_id: this.opts.appId,
335
+ wallet_address: params.accountAddress,
336
+ new_pub_x: toHex2(params.newSigner.x),
337
+ new_pub_y: toHex2(params.newSigner.y),
338
+ device_label: params.deviceLabel ?? deviceLabel(),
339
+ ...params.email ? { email: params.email } : {}
340
+ })
341
+ });
342
+ if (!res.ok) {
343
+ const t = await res.text().catch(() => "");
344
+ throw new Error(`requestDeviceAddition failed: ${res.status} ${t}`);
345
+ }
346
+ const data = await res.json();
347
+ return { requestId: data.request_id };
348
+ }
349
+ async getPendingRequest(requestId) {
350
+ const url = new URL("/api/devices/request", this.opts.baseUrl);
351
+ url.searchParams.set("id", requestId);
352
+ const res = await fetch(url, { headers: { "Content-Type": "application/json" } });
353
+ if (!res.ok) throw new Error(`getPendingRequest failed: ${res.status}`);
354
+ const data = await res.json();
355
+ if (!data.found) return null;
356
+ const status = data.status;
357
+ return {
358
+ requestId: data.request_id,
359
+ appId: data.app_id,
360
+ userId: "",
361
+ // the approving device already knows its own identity
362
+ accountAddress: data.wallet_address,
363
+ newSigner: { x: fromHex2(data.new_pub_x), y: fromHex2(data.new_pub_y) },
364
+ createdAt: data.created_at,
365
+ status
366
+ };
367
+ }
368
+ async confirmDeviceAddition(params) {
369
+ const res = await fetch(
370
+ new URL(`/api/devices/request/${params.requestId}/confirm`, this.opts.baseUrl),
371
+ {
372
+ method: "POST",
373
+ headers: { "Content-Type": "application/json" },
374
+ body: JSON.stringify({ tx_hash: params.txHash })
375
+ }
376
+ );
377
+ if (!res.ok) {
378
+ const t = await res.text().catch(() => "");
379
+ throw new Error(`confirmDeviceAddition failed: ${res.status} ${t}`);
380
+ }
381
+ }
382
+ };
383
+ var BACKUP_KDF_SALT = "cavos-recovery-v1";
384
+ var BACKUP_PBKDF2_ITERATIONS = 21e4;
385
+ var BACKUP_HKDF_INFO = "cavos-backup-signer";
386
+ var CODE_WORDS = 16;
387
+ function generateRecoveryCode() {
388
+ const bytes = utils.randomBytes(CODE_WORDS);
389
+ const words = [];
390
+ for (const b of bytes) words.push(WORDLIST[b]);
391
+ return words.join(" ");
392
+ }
393
+ function deriveBackupKey(code) {
394
+ const normalised = code.trim().replace(/\s+/g, " ").toLowerCase();
395
+ if (!normalised) throw new Error("kit: recovery code is empty");
396
+ const stretched = pbkdf2.pbkdf2(
397
+ sha256.sha256,
398
+ new TextEncoder().encode(normalised),
399
+ new TextEncoder().encode(BACKUP_KDF_SALT),
400
+ { c: BACKUP_PBKDF2_ITERATIONS, dkLen: 32 }
401
+ );
402
+ const seed = hkdf.hkdf(sha256.sha256, stretched, void 0, BACKUP_HKDF_INFO, 32);
403
+ const d = bytesToBigInt(seed) % p256.p256.CURVE.n;
404
+ if (d === 0n) throw new Error("kit: derived backup key is zero (retry with a new code)");
405
+ const priv = bigIntTo32Bytes(d);
406
+ const pub = p256.p256.getPublicKey(priv, false);
407
+ return {
408
+ privateKey: priv,
409
+ publicKey: { x: bytesToBigInt(pub.subarray(1, 33)), y: bytesToBigInt(pub.subarray(33, 65)) }
410
+ };
411
+ }
412
+ var BackupSigner = class _BackupSigner {
413
+ constructor(privateKey, publicKey) {
414
+ this.privateKey = privateKey;
415
+ this.publicKeyValue = publicKey;
416
+ }
417
+ /** Build a signer from a recovery code (derive + wrap in one step). */
418
+ static fromCode(code) {
419
+ const { privateKey, publicKey } = deriveBackupKey(code);
420
+ return new _BackupSigner(privateKey, publicKey);
421
+ }
422
+ async getPublicKey() {
423
+ return this.publicKeyValue;
424
+ }
425
+ async sign(txHash) {
426
+ const digest = sha256.sha256(txHash);
427
+ const sig = p256.p256.sign(digest, this.privateKey);
428
+ const yParity = recoverYParity(sig.r, sig.s, digest, this.publicKeyValue);
429
+ return { r: sig.r, s: sig.s, yParity };
430
+ }
431
+ };
432
+ var WORDLIST = [
433
+ "able",
434
+ "acid",
435
+ "amber",
436
+ "apple",
437
+ "arch",
438
+ "arrow",
439
+ "ashen",
440
+ "atlas",
441
+ "axis",
442
+ "badge",
443
+ "baker",
444
+ "balm",
445
+ "banner",
446
+ "basin",
447
+ "beacon",
448
+ "bench",
449
+ "beryl",
450
+ "birch",
451
+ "blade",
452
+ "bloom",
453
+ "bluer",
454
+ "border",
455
+ "brave",
456
+ "brick",
457
+ "brook",
458
+ "cabin",
459
+ "candle",
460
+ "carbon",
461
+ "cargo",
462
+ "cedar",
463
+ "chalk",
464
+ "charm",
465
+ "chrome",
466
+ "cipher",
467
+ "clam",
468
+ "clasp",
469
+ "cliff",
470
+ "clock",
471
+ "cobia",
472
+ "comet",
473
+ "coral",
474
+ "cotton",
475
+ "coves",
476
+ "crane",
477
+ "crest",
478
+ "crow",
479
+ "crystal",
480
+ "curio",
481
+ "dawn",
482
+ "delta",
483
+ "denim",
484
+ "depth",
485
+ "dewy",
486
+ "digger",
487
+ "docks",
488
+ "dover",
489
+ "drift",
490
+ "dunes",
491
+ "eagle",
492
+ "ember",
493
+ "echo",
494
+ "eden",
495
+ "elite",
496
+ "ethic",
497
+ "fable",
498
+ "falcon",
499
+ "fawn",
500
+ "feather",
501
+ "fern",
502
+ "fjord",
503
+ "flame",
504
+ "flint",
505
+ "forest",
506
+ "forge",
507
+ "frost",
508
+ "garnet",
509
+ "gemini",
510
+ "glade",
511
+ "glider",
512
+ "glow",
513
+ "granite",
514
+ "grove",
515
+ "guppy",
516
+ "harbor",
517
+ "haven",
518
+ "hazel",
519
+ "helio",
520
+ "heron",
521
+ "hickory",
522
+ "honey",
523
+ "horizon",
524
+ "ivory",
525
+ "jade",
526
+ "jasper",
527
+ "kestrel",
528
+ "knot",
529
+ "lagoon",
530
+ "lattice",
531
+ "laurel",
532
+ "lavender",
533
+ "lemon",
534
+ "linden",
535
+ "loon",
536
+ "luger",
537
+ "lumen",
538
+ "lunar",
539
+ "mango",
540
+ "maple",
541
+ "marble",
542
+ "marsh",
543
+ "meadow",
544
+ "mercy",
545
+ "mistle",
546
+ "monsoon",
547
+ "morning",
548
+ "moss",
549
+ "nacre",
550
+ "nectar",
551
+ "needle",
552
+ "nimbus",
553
+ "nova",
554
+ "ocean",
555
+ "onyx",
556
+ "orbit",
557
+ "otter",
558
+ "palm",
559
+ "panda",
560
+ "pansy",
561
+ "papaya",
562
+ "passage",
563
+ "pebble",
564
+ "pelican",
565
+ "pepper",
566
+ "petal",
567
+ "piano",
568
+ "pierce",
569
+ "pilot",
570
+ "pioneer",
571
+ "platinum",
572
+ "plume",
573
+ "poplar",
574
+ "porpoise",
575
+ "prairie",
576
+ "prism",
577
+ "pulsar",
578
+ "quartz",
579
+ "quasar",
580
+ "quill",
581
+ "quiver",
582
+ "raven",
583
+ "reef",
584
+ "relic",
585
+ "ridge",
586
+ "ripple",
587
+ "robin",
588
+ "rocket",
589
+ "rouge",
590
+ "ruby",
591
+ "saffron",
592
+ "sage",
593
+ "sail",
594
+ "salmon",
595
+ "sapphire",
596
+ "scarab",
597
+ "shadow",
598
+ "shale",
599
+ "sienna",
600
+ "silica",
601
+ "silver",
602
+ "skyline",
603
+ "slate",
604
+ "sonar",
605
+ "spruce",
606
+ "starling",
607
+ "stone",
608
+ "sugar",
609
+ "summit",
610
+ "sunset",
611
+ "swan",
612
+ "tangent",
613
+ "tarragon",
614
+ "temple",
615
+ "thistle",
616
+ "thrush",
617
+ "tiger",
618
+ "topaz",
619
+ "tundra",
620
+ "turtle",
621
+ "umber",
622
+ "union",
623
+ "valley",
624
+ "vapor",
625
+ "vector",
626
+ "velvet",
627
+ "violet",
628
+ "vortex",
629
+ "walnut",
630
+ "whale",
631
+ "winter",
632
+ "wisp",
633
+ "wisteria",
634
+ "xenon",
635
+ "yarrow",
636
+ "zephyr",
637
+ "zinc",
638
+ "zodiac",
639
+ "anchor",
640
+ "basil",
641
+ "cider",
642
+ "daisy",
643
+ "elfin",
644
+ "ferry",
645
+ "gimlet",
646
+ "halcyon",
647
+ "indigo",
648
+ "juniper",
649
+ "kindle",
650
+ "lilac",
651
+ "mantis",
652
+ "nylon",
653
+ "oracle",
654
+ "parch",
655
+ "quokka",
656
+ "ramble",
657
+ "thatch",
658
+ "ultra",
659
+ "vivid",
660
+ "xylo",
661
+ "yodel",
662
+ "zesty",
663
+ "arbor",
664
+ "bliss",
665
+ "calyx",
666
+ "dwindle",
667
+ "folio",
668
+ "globe",
669
+ "hymn",
670
+ "ionic",
671
+ "jolly",
672
+ "knack",
673
+ "lyric",
674
+ "myrtle",
675
+ "noble",
676
+ "plumb",
677
+ "quaint",
678
+ "rustic",
679
+ "satin",
680
+ "timber",
681
+ "urge",
682
+ "vault",
683
+ "whimsy",
684
+ "yearn",
685
+ "zenith",
686
+ "ash",
687
+ "beach",
688
+ "dusk"
689
+ ];
690
+ function deriveAddressSeed({ userId, appSalt }) {
691
+ const h = starknet.hash.computePoseidonHashOnElements([feltFromString(userId), feltFromString(appSalt)]);
692
+ return BigInt(h);
693
+ }
694
+ function feltFromString(s) {
695
+ const bytes = new TextEncoder().encode(s);
696
+ const chunks = [];
697
+ for (let i = 0; i < bytes.length; i += 31) {
698
+ let w = 0n;
699
+ for (const b of bytes.subarray(i, i + 31)) w = w << 8n | BigInt(b);
700
+ chunks.push(w);
701
+ }
702
+ if (chunks.length === 0) return 0n;
703
+ if (chunks.length === 1) return chunks[0];
704
+ return BigInt(starknet.hash.computePoseidonHashOnElements(chunks));
705
+ }
706
+
707
+ // src/Cavos.ts
708
+ var Cavos = class _Cavos {
709
+ constructor(identity, address, status, account, adapter, devicePubkey) {
710
+ this.identity = identity;
711
+ this.address = address;
712
+ this.status = status;
713
+ this.account = account;
714
+ this.adapter = adapter;
715
+ this.devicePubkey = devicePubkey;
716
+ /** Request id of the pending device-addition, when status is needs-device-approval. */
717
+ this.pendingRequestId = null;
718
+ }
719
+ static async connect(opts) {
720
+ const identity = opts.identity ?? await opts.auth?.authenticate();
721
+ if (!identity) throw new Error("kit: connect requires `identity` or `auth`");
722
+ const classHash = opts.classHash ?? DEVICE_ACCOUNT_CLASS_HASH[opts.network];
723
+ if (!classHash) throw new Error(`kit: no DeviceAccount class hash for ${opts.network}`);
724
+ const provider = new starknet.RpcProvider({
725
+ nodeUrl: opts.rpcUrl ?? STARKNET_NETWORKS[opts.network].rpcUrl
726
+ });
727
+ const paymaster = new starknet.PaymasterRpc({
728
+ nodeUrl: opts.paymasterUrl ?? CAVOS_PAYMASTER_URL[opts.network],
729
+ headers: { "x-paymaster-api-key": opts.paymasterApiKey }
730
+ });
731
+ const addressSeed = deriveAddressSeed({ userId: identity.userId, appSalt: opts.appSalt });
732
+ const signer = opts.createSigner ? await opts.createSigner(`${identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
733
+ const devicePubkey = await signer.getPublicKey();
734
+ const adapter = new StarknetAdapter({ classHash, signer, provider });
735
+ const makeAccount = (address2) => new starknet.Account({
736
+ provider,
737
+ address: address2,
738
+ signer: new StarknetDeviceSigner(signer),
739
+ paymaster,
740
+ cairoVersion: "1"
741
+ });
742
+ const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
743
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry);
744
+ const recovery = opts.recovery ?? (opts.appId ? new HttpRecoveryClient({ baseUrl: backendUrl, appId: opts.appId }) : null);
745
+ const existing = await registry.lookup(identity.userId);
746
+ if (existing) {
747
+ const account2 = makeAccount(existing.address);
748
+ const isSigner2 = await adapter.isAuthorizedSigner(existing.address, devicePubkey);
749
+ const cavos = new _Cavos(
750
+ identity,
751
+ existing.address,
752
+ isSigner2 ? "ready" : "needs-device-approval",
753
+ account2,
754
+ adapter,
755
+ devicePubkey
756
+ );
757
+ if (!isSigner2 && recovery) {
758
+ const dedup = lastDeviceRequest.get(identity.userId);
759
+ const fresh = dedup && Date.now() - dedup.requestedAt < DEVICE_REQUEST_DEDUP_MS;
760
+ try {
761
+ if (fresh) {
762
+ cavos.pendingRequestId = dedup.requestId;
763
+ } else {
764
+ const { requestId } = await recovery.requestDeviceAddition({
765
+ userId: identity.userId,
766
+ accountAddress: existing.address,
767
+ newSigner: devicePubkey,
768
+ ...identity.email ? { email: identity.email } : {}
769
+ });
770
+ cavos.pendingRequestId = requestId;
771
+ lastDeviceRequest.set(identity.userId, { requestId, requestedAt: Date.now() });
772
+ }
773
+ } catch (e) {
774
+ console.warn("[Cavos] requestDeviceAddition failed:", e);
775
+ }
776
+ }
777
+ return cavos;
778
+ }
779
+ const address = adapter.computeAddress({ addressSeed, initialSigner: devicePubkey });
780
+ const account = makeAccount(address);
781
+ const alreadyDeployed = await isDeployed(provider, address);
782
+ if (!alreadyDeployed) {
783
+ const deploymentData = {
784
+ address,
785
+ class_hash: classHash,
786
+ salt: starknet.num.toHex(addressSeed),
787
+ calldata: adapter.constructorCalldata(addressSeed, devicePubkey),
788
+ version: 1
789
+ };
790
+ const deployRes = await account.executePaymasterTransaction([], {
791
+ feeMode: { mode: "sponsored" },
792
+ deploymentData
793
+ });
794
+ try {
795
+ await provider.waitForTransaction(deployRes.transaction_hash);
796
+ } catch (e) {
797
+ console.warn("[Cavos] deploy receipt wait failed:", e);
798
+ }
799
+ }
800
+ await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
801
+ let isSigner;
802
+ try {
803
+ isSigner = await adapter.isAuthorizedSigner(address, devicePubkey);
804
+ } catch (e) {
805
+ console.warn("[Cavos] isAuthorizedSigner read failed:", e);
806
+ isSigner = !alreadyDeployed;
807
+ }
808
+ return new _Cavos(
809
+ identity,
810
+ address,
811
+ isSigner ? "ready" : "needs-device-approval",
812
+ account,
813
+ adapter,
814
+ devicePubkey
815
+ );
816
+ }
817
+ /** This device's public key (e.g. to request addition to an existing wallet). */
818
+ get publicKey() {
819
+ return this.devicePubkey;
820
+ }
821
+ /** Execute a sponsored (gasless) multicall, signed silently by the device. */
822
+ async execute(calls) {
823
+ if (this.status !== "ready") {
824
+ throw new Error("kit: this device is not yet an authorized signer of the wallet");
825
+ }
826
+ const res = await this.account.executePaymasterTransaction(calls, {
827
+ feeMode: { mode: "sponsored" }
828
+ });
829
+ return { transactionHash: res.transaction_hash };
830
+ }
831
+ /** Authorize an additional device signer (sponsored). Self-submitted. */
832
+ async addSigner(pubkey) {
833
+ return this.execute([this.adapter.buildAddSigner(this.address, pubkey)]);
834
+ }
835
+ /**
836
+ * Register a self-custodial backup signer derived from `code`, so the account
837
+ * can be recovered after the user loses every device. Idempotent: if the
838
+ * derived backup key is already an authorised signer, this is a no-op.
839
+ *
840
+ * The code never leaves the device — only its deterministic public key is
841
+ * added on-chain as an ordinary signer. Sponsor this like any other
842
+ * add_signer (gasless). Returns the transaction hash (or undefined when the
843
+ * backup was already set up).
844
+ */
845
+ async setupRecovery(code) {
846
+ const { publicKey: backupPubkey } = deriveBackupKey(code);
847
+ const already = await this.adapter.isAuthorizedSigner(this.address, backupPubkey);
848
+ if (already) return void 0;
849
+ return this.addSigner(backupPubkey);
850
+ }
851
+ /**
852
+ * Recover an account after losing every device signer. Derives the backup key
853
+ * from `code`, uses it (not the new device key) to sign an `add_signer` for
854
+ * the new device, and returns a ready Cavos bound to the new device. The
855
+ * account address is unchanged.
856
+ *
857
+ * Self-custodial: only someone holding the code (i.e. the rightful owner) can
858
+ * re-derive the backup key. The backend never sees the code.
859
+ */
860
+ static async recover(opts) {
861
+ const classHash = opts.classHash ?? DEVICE_ACCOUNT_CLASS_HASH[opts.network];
862
+ if (!classHash) throw new Error(`kit: no DeviceAccount class hash for ${opts.network}`);
863
+ const provider = new starknet.RpcProvider({
864
+ nodeUrl: opts.rpcUrl ?? STARKNET_NETWORKS[opts.network].rpcUrl
865
+ });
866
+ const paymaster = new starknet.PaymasterRpc({
867
+ nodeUrl: opts.paymasterUrl ?? CAVOS_PAYMASTER_URL[opts.network],
868
+ headers: { "x-paymaster-api-key": opts.paymasterApiKey }
869
+ });
870
+ const signer = opts.createSigner ? await opts.createSigner(`${opts.identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${opts.identity.userId}:${opts.appSalt}` });
871
+ const devicePubkey = await signer.getPublicKey();
872
+ const backup = BackupSigner.fromCode(opts.code);
873
+ const backupAdapter = new StarknetAdapter({ classHash, signer: backup, provider });
874
+ const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
875
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry);
876
+ const existing = await registry.lookup(opts.identity.userId);
877
+ if (!existing) {
878
+ throw new Error("kit: no account found for this identity \u2014 nothing to recover");
879
+ }
880
+ const backupAccount = new starknet.Account({
881
+ provider,
882
+ address: existing.address,
883
+ signer: new StarknetDeviceSigner(backup),
884
+ paymaster,
885
+ cairoVersion: "1"
886
+ });
887
+ const alreadyAuthed = await backupAdapter.isAuthorizedSigner(existing.address, devicePubkey);
888
+ if (!alreadyAuthed) {
889
+ const res = await backupAccount.executePaymasterTransaction(
890
+ [backupAdapter.buildAddSigner(existing.address, devicePubkey)],
891
+ { feeMode: { mode: "sponsored" } }
892
+ );
893
+ try {
894
+ await provider.waitForTransaction(res.transaction_hash);
895
+ } catch (e) {
896
+ console.warn("[Cavos] recovery add_signer receipt wait failed:", e);
897
+ }
898
+ }
899
+ const adapter = new StarknetAdapter({ classHash, signer, provider });
900
+ const account = new starknet.Account({
901
+ provider,
902
+ address: existing.address,
903
+ signer: new StarknetDeviceSigner(signer),
904
+ paymaster,
905
+ cairoVersion: "1"
906
+ });
907
+ return new _Cavos(opts.identity, existing.address, "ready", account, adapter, devicePubkey);
908
+ }
909
+ };
910
+ var defaultRegistry = new InMemoryWalletRegistry();
911
+ var DEVICE_REQUEST_DEDUP_MS = 5 * 60 * 1e3;
912
+ var lastDeviceRequest = /* @__PURE__ */ new Map();
913
+ async function isDeployed(provider, address) {
914
+ try {
915
+ const classHash = await provider.getClassHashAt(address);
916
+ return !!classHash && classHash !== "0x0";
917
+ } catch {
918
+ return false;
919
+ }
920
+ }
921
+ var CavosAuth = class {
922
+ constructor(opts = {}) {
923
+ this.opts = opts;
924
+ /** Most recent nonce sent to the backend (for the pending OAuth/OTP request). */
925
+ this.pendingNonce = null;
926
+ this.last = null;
927
+ this.backendUrl = opts.backendUrl ?? "https://cavos.xyz";
928
+ }
929
+ /** Redirect URL for Google login (open it; user returns to your redirectUri). */
930
+ async getGoogleOAuthUrl(redirectUri) {
931
+ return this.oauthUrl("google", redirectUri);
932
+ }
933
+ /** Redirect URL for Apple login. */
934
+ async getAppleOAuthUrl(redirectUri) {
935
+ return this.oauthUrl("apple", redirectUri);
936
+ }
937
+ async oauthUrl(provider, redirectUri) {
938
+ if (typeof window === "undefined") throw new Error("kit/auth: OAuth requires a browser");
939
+ const params = new URLSearchParams({
940
+ nonce: this.freshNonce(),
941
+ redirect_uri: redirectUri ?? window.location.href,
942
+ ...this.opts.appId ? { app_id: this.opts.appId } : {}
943
+ });
944
+ const { url } = await this.get(`/api/oauth/${provider}?${params}`);
945
+ return url;
946
+ }
947
+ /**
948
+ * Resolve the identity from an OAuth callback. The auth data is carried in the
949
+ * `auth_data` (or `zk_auth_data`) query param on return. We only extract `sub`.
950
+ */
951
+ async handleCallback(authDataOrSearch) {
952
+ const authData = extractAuthData(authDataOrSearch);
953
+ return this.identityFromAuthData(authData, "oauth");
954
+ }
955
+ /** Send a one-time code to an email (Firebase OTP). */
956
+ async sendOtp(email) {
957
+ await this.post("/api/oauth/firebase/otp/request", {
958
+ email,
959
+ nonce: this.freshNonce(),
960
+ ...this.opts.appId ? { app_id: this.opts.appId } : {}
961
+ });
962
+ }
963
+ /** Send a passwordless magic-link sign-in email (Firebase). */
964
+ async sendMagicLink(email) {
965
+ await this.post("/api/oauth/firebase/magic-link", {
966
+ email,
967
+ nonce: this.freshNonce(),
968
+ ...this.opts.appId ? { app_id: this.opts.appId } : {},
969
+ ...typeof window !== "undefined" ? { redirect_uri: window.location.href } : {}
970
+ });
971
+ }
972
+ /** Verify the OTP and resolve the identity. */
973
+ async verifyOtp(email, code) {
974
+ const res = await this.post("/api/oauth/firebase/otp/verify", {
975
+ email,
976
+ code,
977
+ nonce: this.consumeNonce(),
978
+ ...this.opts.appId ? { app_id: this.opts.appId } : {}
979
+ });
980
+ return this.identityFromAuthData(res.id_token ?? res.jwt ?? res.token ?? JSON.stringify(res), "otp", email);
981
+ }
982
+ /** AuthProvider: returns the identity resolved by the last login step. */
983
+ async authenticate() {
984
+ if (!this.last) throw new Error("kit/auth: no identity yet \u2014 complete a login first");
985
+ return this.last;
986
+ }
987
+ // ── internals ──────────────────────────────────────────────────────────────
988
+ /**
989
+ * Build an `Identity` from whatever the backend returned. The Cavos backend
990
+ * wraps the user id in a JWT (its `sub` claim); for the device model we only
991
+ * need that stable id — the signature is never checked on-chain.
992
+ */
993
+ async identityFromAuthData(authData, provider, emailOverride) {
994
+ let token = authData;
995
+ try {
996
+ const parsed = JSON.parse(authData);
997
+ token = parsed.id_token ?? parsed.jwt ?? parsed.token ?? authData;
998
+ } catch {
999
+ }
1000
+ const claims = parseJwt(token);
1001
+ return this.remember({
1002
+ userId: String(claims.sub ?? claims.user_id ?? claims.uid),
1003
+ email: claims.email ?? emailOverride,
1004
+ provider: claims.firebase?.sign_in_provider ?? claims.provider ?? provider
1005
+ });
1006
+ }
1007
+ /** Generate (and remember) the nonce the Cavos backend expects on requests. */
1008
+ freshNonce() {
1009
+ const bytes = crypto.getRandomValues(new Uint8Array(31));
1010
+ const h = starknet.hash.computePoseidonHashOnElements([bytesToChunks(bytes)]);
1011
+ this.pendingNonce = starknet.num.toHex(h);
1012
+ return this.pendingNonce;
1013
+ }
1014
+ /** Return the pending nonce (for the verify step), clearing it. */
1015
+ consumeNonce() {
1016
+ if (!this.pendingNonce) return this.freshNonce();
1017
+ const n = this.pendingNonce;
1018
+ this.pendingNonce = null;
1019
+ return n;
1020
+ }
1021
+ remember(id) {
1022
+ this.last = id;
1023
+ return id;
1024
+ }
1025
+ async get(path) {
1026
+ const r = await fetch(`${this.backendUrl}${path}`);
1027
+ if (!r.ok) throw new Error(`kit/auth: ${path} -> ${r.status} ${await r.text()}`);
1028
+ return r.json();
1029
+ }
1030
+ async post(path, body) {
1031
+ const r = await fetch(`${this.backendUrl}${path}`, {
1032
+ method: "POST",
1033
+ headers: { "Content-Type": "application/json" },
1034
+ body: JSON.stringify(body)
1035
+ });
1036
+ if (!r.ok) throw new Error(`kit/auth: ${path} -> ${r.status} ${await r.text()}`);
1037
+ return r.json();
1038
+ }
1039
+ };
1040
+ function extractAuthData(input) {
1041
+ if (input.includes("auth_data=") || input.includes("zk_auth_data=")) {
1042
+ const params = new URLSearchParams(input.startsWith("?") ? input : `?${input}`);
1043
+ return params.get("auth_data") ?? params.get("zk_auth_data") ?? input;
1044
+ }
1045
+ return input;
1046
+ }
1047
+ function parseJwt(jwt) {
1048
+ const part = jwt.split(".")[1];
1049
+ if (!part) throw new Error("kit/auth: malformed JWT");
1050
+ const json = atob(part.replace(/-/g, "+").replace(/_/g, "/"));
1051
+ return JSON.parse(json);
1052
+ }
1053
+ function bytesToChunks(bytes) {
1054
+ let w = 0n;
1055
+ for (const b of bytes.subarray(0, 31)) w = w << 8n | BigInt(b);
1056
+ return w;
1057
+ }
1058
+ var cavosLogoBase64 = "iVBORw0KGgoAAAANSUhEUgAAADMAAAA/CAYAAABNY/BRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAlZSURBVHgB7VpbaCRZGf6r6lR3VV+STieZTBKCmZlO59LDisZBR2Q3D8v6MMrCgswirIqi4LI+7OKyIDp4YUAUXWFdxBurIIw+rcIgroMsKqvoMATdzSbpJEwmM8nk0rn3pbrrVJ39T3X3TE/S6a5TXVn2Yb88hKquc/nPOd/3X6oAjgfK4ODA47FY7AO1N9vb20/395+8wH+HY4AM/kOJx+PhcLjtjzs7O5naHyRJ2orFOq/29PRoeEnAZ/htDJ+g1dvbu8mYDWjUAztg27bM73d1da7jJQWfDfLTGN4XHR4aulE0siDhXz3g7oBRyAWHh5P/gbJBvs3Br474zO2hoTM/YGB9CJhEGj8sKcyi5xKJ05fwkoFP8MsY5dTAwMOKrDxvW7aryeGRYyoh3xkYOPkR8EkQ/DBGQUIH9Wjk79Qsihwb2TRN2haN/7evry+I1yq0iFaNcQjf1dW1VTLyaIgkRmjGSNHI0Y6O2BpemdCiILRiDOcJ5UQuGnlZ2JD73RAUBB37eQNaFASvDR2pQuX6LrOtc3yFoQU4gmDT88OJxAvQgiB4NUbp7+//mCTL37Ityxc14sIhK/L3sd8PgkdB8GIMSSQSSizW/i9qGn76CZlSk8ba2ycHBwe5GAjvtuhE+AA0GAzs4jmn3nlSH6wiCOGwvgpl/gjtkIgxFcIPv1EyCorksyE1wxAjX4jgOK/jhQUCc3T7oEP4kWTiG8w2P85aJHzTwSTggjCRTJx+FgQEwa0xysDAwDhI8mUkqg3vAlBYbELUH+O4KXDJHzfGkPHxcaktGrlOqZCHh62trXzt9d7eXl5AeOWSWaJtbdE3K2M2NajZxBzCF4vGtsE9PHPPE9u2SlAmcS0MBswEt8DjbOSzNJVKuRKERsZUCD/0ulHIB0UJbzN2F+rOj22BACRJJsVCrm1kZPg1aCIIDXcGCf8cMHvCi4e3aGmy3n3DMCZBHIptmY8lk4mnGz10lDHE8cSS8iOLWsKEV4gC2dzelXq/FQrGH2RZ3M9y4SGK8jIGtUNwBH/q9UomJiYAPfwkpd48vKIQ3AH7rwCH081CofAnEgiCB8ilokEx3ZipzvPQAweuHcJnMhsbRj5niRC+Bjxcu4PFjB2o4yN2d3e3mc02+XMgDO5Q92lqbKyuINQa4xB+JJm8hoSPYK7uKdhTFEXOZ7PPw9E7Ku/u7b0gKbIEHsAFAZW1A+d5FQ4IwgMDjowkn2GMPuo9pGeUBDT79srK76GB515ZWfl1IKBR/jx4AK4Cr/JcSCROfan2ftUYnvqexf8vWR49PM7cVtQA2d3cPA/l7T/KGN6/sr29M4HcIeDpuOGWWNQOqIFfnThx4hRU+FP1rAxV4v+0VPREePQdVlDTsJ38+J3V1esuJmjh7vwbV/diIKjhKjMvBjmC0N3dNV8ZT3HO7djY2AaG9B2yCE8kpwbG1IAmMQZv5nK5R5aWlvYqHbsJWvjYEp6Iro6O9ms49kOmWUK7mCSSazKwqaZHtqam3u6RRkaGrimy+gnsWiCCtqlZMueyueyVXM74OVcoKB8tC8ThtItEIifa26NfDunhJwMBNQHlhXVnFq5msWS8Vu2sFXhSpWPo5zjq5u/jfRyE1Nt74pOlkrXDPbebBqg2Cs3nd9u7u+cWFxcNqKgSePQXFfCxOdkZqlu4WCwmQqFAhFJ3fWLgit7RjhFJIp09PZ1/wZAd3ELpjGMGLcPIcHLdpMVfLizc+nbNzyJGyRUDdF1XL+l69Cso9zFgFqYQplMMcDUfRYWt7c3POE8nk0OvYL7wOVwbUUVAUQSm6SGZlgqXZ+dufhPKBXA32aTzzdCZMz9Ug8GvY626KsNCqoa7YkuK8ovZ2bmv8obcAHt0dHSxZOT6eSAH4rCxGol9Bm5Mz8ycq/bZ4HnHt6RSo/8rGoWzFW/iKfIgajA9m06P8fbVQcn09PSgFopyQ9zn6PchY/IkmSXjw5gNvgTNnZ2VTCZ/UzGEz8GDj2BUC0WUiiFOjFftxHm/iCnIQEDTVCw6eCWzhOWhZ5ADoQYTlPEtdEwl8ufREE+OEudnBTSdrK2t9UF5l53ou3ZAury8vFIq0aeIQqrqIgqpVCwwXI8XG7S3u7vjLxtGgS+YF2MYUQNKMVd4IpPJ8Be990KoQ6s3Pz//O0lRX2VeXy1gq0i47Slo0B5zmSclt1J1ANiKx6JXFm7dehUO8PKgMU4oPTMz8wQqVAYjcy/Jk2RjIMs/YIDDKy9hQJnCXEQGD7tiO4TXbqfT6c/Cfd90D/XONd82FUPqvrIgiGeD1DQhFNIuwuHdYRgZX3R8iDAY1ZHwqJaDUJb1Q7w+iqR8NGl7e21EDerC2SCeA9D10IV6v4VC4U8xW/gE25jEkY2NjQSUCV93NRpJIl1ZycxZlvk12WWoUwtN00br3VdV9RQIQiEqOuXiF9bX129Cg5yp6STT6YWfoh/9Gy62qFy31bvphCsC4DzBsf+cXlj8LTQRpWbGOIIwOzv7qB7S90X4U6lNawduh0U0Eg2x9VB4Oz03V/0SqiVjOPi2kmyucDKohQlI7g2Kx+OB2utoNKq51jCJEz4s89weKt8bNGvilgsUw30zu7MzjrGQ5/IQL4C4fNRWiUZ2dzcfgkpx0k0jEWJbt1ZWJlGpLskeq5FuwXMrrPE+e+fO2lsgUCQRVSkJQ+3vYQ50ncdHcBzA48Uk5R+z8/M/AdF0AMTgRNgYqX4UHVjRa3n1KOAZxGJixEQP/whUImGR9l7KM06Evbq6hoIQEhIEhzNHsgZDej3EFYtL971IWARea0342iOTzxeyDxPiCIIrYvOsEOprAOPCkslsnp+amuK74ekIt1I4s27evP1Pm9kvYoQg0OwwDVBQMINilzE/4Z86euZiq1VAOZ2ef46QwDSv+YInMCrL5Mbs3ByvH3BL3/WvmqrgR0J9e3o6pelREBcERoN6BGZm07xu4Nl/VeFHfdb5gg/fADiCwIsM7pqhIRhRYI2sEzwS/iD8KjbT/f397b3dvU+rgaCb6j3jL6b2s9nHMLPlX3H44rP8rJyzpeXlq6i9r0CzgNApCsk/w928Bj4Z4vQL/oEbIGOE8EVM6JaOdidYWyJkGiPxp6FO6vteg1NETKVSWHbFkP9BRPn92uf8xHG8oHHez9+9e/esacbjtT9gShBbXV0drYzrayjE8Q6F7uAm3ZgyLQAAAABJRU5ErkJggg==";
1059
+ var STYLE_ID = "cavos-modal-styles-v2";
1060
+ function injectStyles() {
1061
+ if (typeof document === "undefined") return;
1062
+ if (document.getElementById(STYLE_ID)) return;
1063
+ const el = document.createElement("style");
1064
+ el.id = STYLE_ID;
1065
+ el.textContent = `
1066
+ @keyframes cavos-fade { from { opacity:0; } to { opacity:1; } }
1067
+ @keyframes cavos-up {
1068
+ from { opacity:0; transform:translateY(12px) scale(0.985); }
1069
+ to { opacity:1; transform:translateY(0) scale(1); }
1070
+ }
1071
+ @keyframes cavos-sheet {
1072
+ from { transform:translateY(100%); }
1073
+ to { transform:translateY(0); }
1074
+ }
1075
+ @keyframes cavos-spin {
1076
+ from { transform:rotate(0deg); } to { transform:rotate(360deg); }
1077
+ }
1078
+ @keyframes cavos-pop {
1079
+ 0% { transform:scale(0.5); opacity:0; }
1080
+ 60% { transform:scale(1.12); opacity:1; }
1081
+ 100% { transform:scale(1); opacity:1; }
1082
+ }
1083
+ @keyframes cavos-check-draw {
1084
+ from { stroke-dashoffset: 30; }
1085
+ to { stroke-dashoffset: 0; }
1086
+ }
1087
+ @keyframes cavos-ring-glow {
1088
+ 0%,100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.4); }
1089
+ 50% { box-shadow: 0 0 0 10px rgba(34,197,94,0); }
1090
+ }
1091
+ .cavos-check-circle {
1092
+ animation: cavos-pop 0.45s cubic-bezier(.22,1,.36,1) forwards;
1093
+ }
1094
+ .cavos-check-path {
1095
+ stroke-dasharray: 30;
1096
+ stroke-dashoffset: 30;
1097
+ animation: cavos-check-draw 0.35s 0.2s cubic-bezier(.22,1,.36,1) forwards;
1098
+ }
1099
+ .cavos-input-inner:focus { outline:none; }
1100
+ .cavos-provider:active { transform:scale(0.99); }
1101
+ .cavos-primary-btn:active { transform:scale(0.98); }
1102
+ .cavos-otp-input {
1103
+ width: 100%;
1104
+ text-align: center;
1105
+ font-size: 22px;
1106
+ font-weight: 600;
1107
+ background: transparent;
1108
+ border: none;
1109
+ outline: none;
1110
+ color: inherit;
1111
+ font-family: inherit;
1112
+ caret-color: transparent;
1113
+ }
1114
+ .cavos-otp-box {
1115
+ transition: border-color 0.15s, box-shadow 0.15s, transform 0.1s;
1116
+ }
1117
+ .cavos-otp-box-filled {
1118
+ transform: scale(1.04);
1119
+ }
1120
+ .cavos-divider-line {
1121
+ flex: 1;
1122
+ height: 1px;
1123
+ }
1124
+ /* Theme-aware interaction styles. The overlay carries data-cavos-theme so
1125
+ hovers/focus rings stay legible in both light and dark \u2014 the previous
1126
+ rgba(0,0,0,\u2026) rules disappeared against the dark background. */
1127
+ [data-cavos-theme="light"] .cavos-input-row:focus-within {
1128
+ border-color: rgba(0,0,0,0.35) !important;
1129
+ box-shadow: 0 0 0 3px rgba(0,0,0,0.06) !important;
1130
+ }
1131
+ [data-cavos-theme="dark"] .cavos-input-row:focus-within {
1132
+ border-color: rgba(255,255,255,0.45) !important;
1133
+ box-shadow: 0 0 0 3px rgba(255,255,255,0.08) !important;
1134
+ }
1135
+ [data-cavos-theme="light"] .cavos-provider:hover { background: rgba(0,0,0,0.035) !important; }
1136
+ [data-cavos-theme="dark"] .cavos-provider:hover { background: rgba(255,255,255,0.07) !important; }
1137
+ [data-cavos-theme="light"] .cavos-close:hover { background: rgba(0,0,0,0.05) !important; }
1138
+ [data-cavos-theme="dark"] .cavos-close:hover { background: rgba(255,255,255,0.08) !important; }
1139
+ [data-cavos-theme="light"] .cavos-sub-btn:hover { color: rgba(0,0,0,0.6) !important; }
1140
+ [data-cavos-theme="dark"] .cavos-sub-btn:hover { color: rgba(255,255,255,0.65) !important; }
1141
+ [data-cavos-theme="light"] .cavos-submit-btn:hover { color: rgba(0,0,0,0.8) !important; }
1142
+ [data-cavos-theme="dark"] .cavos-submit-btn:hover { color: rgba(255,255,255,0.85) !important; }
1143
+ [data-cavos-theme="light"] .cavos-primary-btn:hover { opacity: 0.88; }
1144
+ [data-cavos-theme="dark"] .cavos-primary-btn:hover { opacity: 0.9; }
1145
+ [data-cavos-theme="light"] .cavos-divider-line { background: rgba(0,0,0,0.08); }
1146
+ [data-cavos-theme="dark"] .cavos-divider-line { background: rgba(255,255,255,0.1); }
1147
+ `;
1148
+ document.head.appendChild(el);
1149
+ }
1150
+ var GoogleIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [
1151
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z", fill: "#4285F4" }),
1152
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.184l-2.908-2.258c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 009 18z", fill: "#34A853" }),
1153
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.964 10.707A5.41 5.41 0 013.682 9c0-.593.102-1.17.282-1.707V4.961H.957A8.996 8.996 0 000 9c0 1.452.348 2.827.957 4.039l3.007-2.332z", fill: "#FBBC05" }),
1154
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 00.957 4.961L3.964 7.293C4.672 5.163 6.656 3.58 9 3.58z", fill: "#EA4335" })
1155
+ ] });
1156
+ var AppleIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 814 1000", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76 0-103.7 40.8-165.9 40.8s-105-57.8-155.5-127.4C46 790.7 0 663 0 541.8c0-194.3 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z" }) });
1157
+ var EmailIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
1158
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
1159
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 01-2.06 0L2 7" })
1160
+ ] });
1161
+ var CavosLogo = ({ invert }) => /* @__PURE__ */ jsxRuntime.jsx("img", { src: `data:image/png;base64,${cavosLogoBase64}`, alt: "Cavos", style: { width: "auto", height: "16px", objectFit: "contain", opacity: 0.55, flexShrink: 0, filter: invert ? "invert(1)" : "none" } });
1162
+ function Spinner({ size = 16, color = "#888" }) {
1163
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1164
+ "svg",
1165
+ {
1166
+ width: size,
1167
+ height: size,
1168
+ viewBox: "0 0 24 24",
1169
+ fill: "none",
1170
+ style: { animation: "cavos-spin 0.75s linear infinite", flexShrink: 0 },
1171
+ children: [
1172
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeOpacity: "0.2", strokeWidth: "2.5" }),
1173
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2a10 10 0 0110 10", stroke: color, strokeWidth: "2.5", strokeLinecap: "round" })
1174
+ ]
1175
+ }
1176
+ );
1177
+ }
1178
+ function useIsMobile() {
1179
+ const [isMobile, setIsMobile] = react.useState(false);
1180
+ react.useEffect(() => {
1181
+ const mq = window.matchMedia("(max-width: 640px)");
1182
+ setIsMobile(mq.matches);
1183
+ const handler = (e) => setIsMobile(e.matches);
1184
+ mq.addEventListener("change", handler);
1185
+ return () => mq.removeEventListener("change", handler);
1186
+ }, []);
1187
+ return isMobile;
1188
+ }
1189
+ var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif';
1190
+ function baseOverlay(isMobile) {
1191
+ return {
1192
+ position: "fixed",
1193
+ inset: 0,
1194
+ background: "rgba(0,0,0,0.5)",
1195
+ backdropFilter: "blur(10px)",
1196
+ WebkitBackdropFilter: "blur(10px)",
1197
+ zIndex: 9999,
1198
+ display: "flex",
1199
+ alignItems: isMobile ? "flex-end" : "center",
1200
+ justifyContent: "center",
1201
+ padding: isMobile ? "0" : "16px",
1202
+ animation: "cavos-fade 0.18s ease",
1203
+ fontFamily: FONT
1204
+ };
1205
+ }
1206
+ function lightCard(isMobile, bg) {
1207
+ return isMobile ? {
1208
+ background: bg,
1209
+ borderRadius: "20px 20px 0 0",
1210
+ width: "100%",
1211
+ maxWidth: "100%",
1212
+ boxShadow: "0 -8px 40px rgba(0,0,0,0.18)",
1213
+ animation: "cavos-sheet 0.32s cubic-bezier(0.22,1,0.36,1)",
1214
+ overflow: "hidden"
1215
+ } : {
1216
+ background: bg,
1217
+ borderRadius: "22px",
1218
+ width: "100%",
1219
+ maxWidth: "400px",
1220
+ boxShadow: "0 24px 60px rgba(0,0,0,0.22), 0 0 0 1px rgba(0,0,0,0.05)",
1221
+ animation: "cavos-up 0.25s cubic-bezier(0.22,1,0.36,1)",
1222
+ overflow: "hidden"
1223
+ };
1224
+ }
1225
+ function providerBtn(textColor) {
1226
+ const isLight = textColor === "#111111" || textColor === "#111";
1227
+ return {
1228
+ width: "100%",
1229
+ display: "flex",
1230
+ alignItems: "center",
1231
+ justifyContent: "center",
1232
+ gap: "10px",
1233
+ padding: "12px 16px",
1234
+ background: isLight ? "#fff" : "rgba(255,255,255,0.04)",
1235
+ border: `1px solid ${isLight ? "rgba(0,0,0,0.12)" : "rgba(255,255,255,0.12)"}`,
1236
+ borderRadius: "12px",
1237
+ cursor: "pointer",
1238
+ fontSize: "14px",
1239
+ fontWeight: 500,
1240
+ color: textColor,
1241
+ fontFamily: "inherit",
1242
+ transition: "background 0.15s, transform 0.1s, border-color 0.15s",
1243
+ position: "relative"
1244
+ };
1245
+ }
1246
+ function footerBar(textColor) {
1247
+ const isLight = textColor === "#111111" || textColor === "#111";
1248
+ return {
1249
+ borderTop: `1px solid ${isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"}`,
1250
+ padding: "12px 20px",
1251
+ display: "flex",
1252
+ alignItems: "center",
1253
+ justifyContent: "center",
1254
+ gap: "5px",
1255
+ fontSize: "11px",
1256
+ color: isLight ? "rgba(0,0,0,0.28)" : "rgba(255,255,255,0.28)"
1257
+ };
1258
+ }
1259
+ function closeBtn(textColor) {
1260
+ return {
1261
+ position: "absolute",
1262
+ top: "16px",
1263
+ right: "16px",
1264
+ width: "28px",
1265
+ height: "28px",
1266
+ borderRadius: "50%",
1267
+ border: "none",
1268
+ background: "transparent",
1269
+ cursor: "pointer",
1270
+ display: "flex",
1271
+ alignItems: "center",
1272
+ justifyContent: "center",
1273
+ color: textColor === "#111111" || textColor === "#111" ? "rgba(0,0,0,0.3)" : "rgba(255,255,255,0.4)",
1274
+ padding: 0,
1275
+ transition: "background 0.15s"
1276
+ };
1277
+ }
1278
+ function mobileHandle() {
1279
+ return {
1280
+ width: "36px",
1281
+ height: "4px",
1282
+ borderRadius: "2px",
1283
+ background: "rgba(0,0,0,0.15)",
1284
+ margin: "12px auto 0"
1285
+ };
1286
+ }
1287
+ var CloseX = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: [
1288
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1289
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1290
+ ] });
1291
+ function CavosAuthModal({
1292
+ open,
1293
+ onClose,
1294
+ appName,
1295
+ providers = ["google", "apple", "email"],
1296
+ emailMode = "magic-link",
1297
+ primaryColor = "#0A0908",
1298
+ theme = "light"
1299
+ }) {
1300
+ const {
1301
+ login,
1302
+ sendMagicLink,
1303
+ sendOtp,
1304
+ verifyOtp,
1305
+ isAuthenticated,
1306
+ address,
1307
+ walletStatus,
1308
+ authError,
1309
+ clearAuthError,
1310
+ resendDeviceApproval,
1311
+ recover
1312
+ } = useCavos();
1313
+ const isMobile = useIsMobile();
1314
+ const isLight = theme !== "dark";
1315
+ const backgroundColor = isLight ? "#ffffff" : "#111111";
1316
+ const textColor = isLight ? "#111111" : "#ffffff";
1317
+ const subTextColor = isLight ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.4)";
1318
+ const inputBg = isLight ? "#fff" : "rgba(255,255,255,0.06)";
1319
+ const inputBorder = isLight ? "1px solid rgba(0,0,0,0.12)" : "1px solid rgba(255,255,255,0.12)";
1320
+ const errBg = isLight ? "rgba(239,68,68,0.06)" : "rgba(239,68,68,0.08)";
1321
+ const errColor = isLight ? "#dc2626" : "#f87171";
1322
+ const errBorder = isLight ? "rgba(239,68,68,0.18)" : "rgba(239,68,68,0.2)";
1323
+ const card = lightCard(isMobile, backgroundColor);
1324
+ const overlay = baseOverlay(isMobile);
1325
+ const handle = mobileHandle();
1326
+ const footer = footerBar(textColor);
1327
+ const close = closeBtn(textColor);
1328
+ const pBtn = providerBtn(textColor);
1329
+ const [screen, setScreen] = react.useState("select");
1330
+ const [email, setEmail] = react.useState("");
1331
+ const [otpCode, setOtpCode] = react.useState("");
1332
+ const [error, setError] = react.useState("");
1333
+ const [busy, setBusy] = react.useState(false);
1334
+ const [deployState, setDeployState] = react.useState("loading");
1335
+ const [resendCountdown, setResendCountdown] = react.useState(0);
1336
+ const [deviceResendBusy, setDeviceResendBusy] = react.useState(false);
1337
+ const [recoverCode, setRecoverCode] = react.useState("");
1338
+ const [recoverBusy, setRecoverBusy] = react.useState(false);
1339
+ const doneHandledRef = react.useRef(false);
1340
+ const closeTimerRef = react.useRef(null);
1341
+ const otpBoxRefs = react.useRef([null, null, null, null, null, null]);
1342
+ const setOtpDigit = (i, raw) => {
1343
+ const digit = raw.replace(/\D/g, "").slice(-1);
1344
+ setOtpCode((prev) => {
1345
+ const chars = prev.split("");
1346
+ while (chars.length < 6) chars.push("");
1347
+ chars[i] = digit;
1348
+ const next = chars.join("").slice(0, 6);
1349
+ if (digit && i < 5) {
1350
+ const target = i + 1;
1351
+ otpBoxRefs.current[target]?.focus();
1352
+ }
1353
+ return next;
1354
+ });
1355
+ };
1356
+ const onOtpKeyDown = (i, e) => {
1357
+ if (e.key === "Backspace" && !otpCode[i] && i > 0) {
1358
+ otpBoxRefs.current[i - 1]?.focus();
1359
+ e.preventDefault();
1360
+ } else if (e.key === "ArrowLeft" && i > 0) {
1361
+ otpBoxRefs.current[i - 1]?.focus();
1362
+ e.preventDefault();
1363
+ } else if (e.key === "ArrowRight" && i < 5) {
1364
+ otpBoxRefs.current[i + 1]?.focus();
1365
+ e.preventDefault();
1366
+ }
1367
+ };
1368
+ const onOtpPaste = (e) => {
1369
+ const text = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, 6);
1370
+ if (!text) return;
1371
+ e.preventDefault();
1372
+ setOtpCode(text);
1373
+ const focusIdx = Math.min(text.length, 5);
1374
+ otpBoxRefs.current[focusIdx]?.focus();
1375
+ };
1376
+ react.useEffect(() => {
1377
+ injectStyles();
1378
+ }, []);
1379
+ react.useEffect(() => {
1380
+ if (!authError) return;
1381
+ setBusy(false);
1382
+ setDeployState("loading");
1383
+ if (screen !== "device-approval") setScreen("select");
1384
+ setError(authError);
1385
+ clearAuthError();
1386
+ }, [authError, clearAuthError, screen]);
1387
+ const triggerDone = react.useCallback((addr) => {
1388
+ if (doneHandledRef.current) return;
1389
+ doneHandledRef.current = true;
1390
+ setScreen("deploying");
1391
+ setDeployState("done");
1392
+ closeTimerRef.current = setTimeout(() => {
1393
+ onClose();
1394
+ setScreen("select");
1395
+ setDeployState("loading");
1396
+ setEmail("");
1397
+ setOtpCode("");
1398
+ setError("");
1399
+ doneHandledRef.current = false;
1400
+ }, 1600);
1401
+ }, [onClose]);
1402
+ react.useEffect(() => {
1403
+ if (!open) return;
1404
+ if (walletStatus.isDeploying && screen !== "deploying" && screen !== "verify") {
1405
+ setScreen("deploying");
1406
+ setDeployState("loading");
1407
+ doneHandledRef.current = false;
1408
+ }
1409
+ if (isAuthenticated && address && walletStatus.isReady) {
1410
+ triggerDone(address);
1411
+ }
1412
+ if (walletStatus.awaitingApproval && screen !== "device-approval" && screen !== "recover") {
1413
+ setScreen("device-approval");
1414
+ doneHandledRef.current = false;
1415
+ }
1416
+ }, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, screen, triggerDone]);
1417
+ react.useEffect(() => () => {
1418
+ if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
1419
+ }, []);
1420
+ const handleClose = () => {
1421
+ if (screen === "deploying") return;
1422
+ setScreen("select");
1423
+ setEmail("");
1424
+ setOtpCode("");
1425
+ setError("");
1426
+ doneHandledRef.current = false;
1427
+ onClose();
1428
+ };
1429
+ const handleOAuth = async (provider) => {
1430
+ setError("");
1431
+ setBusy(true);
1432
+ try {
1433
+ await login(provider);
1434
+ } catch (e) {
1435
+ const msg = e instanceof Error ? e.message : "Authentication failed.";
1436
+ setError(msg);
1437
+ setBusy(false);
1438
+ }
1439
+ };
1440
+ const handleMagicLinkSubmit = async (e) => {
1441
+ e.preventDefault();
1442
+ if (!email) return;
1443
+ setError("");
1444
+ setBusy(true);
1445
+ try {
1446
+ await sendMagicLink(email);
1447
+ setScreen("verify");
1448
+ setResendCountdown(60);
1449
+ } catch (e2) {
1450
+ setError(e2 instanceof Error ? e2.message : "Failed to send magic link.");
1451
+ } finally {
1452
+ setBusy(false);
1453
+ }
1454
+ };
1455
+ const handleOtpRequestSubmit = async (e) => {
1456
+ e.preventDefault();
1457
+ if (!email) return;
1458
+ setError("");
1459
+ setBusy(true);
1460
+ try {
1461
+ await sendOtp(email);
1462
+ setOtpCode("");
1463
+ setScreen("otp-code");
1464
+ setResendCountdown(60);
1465
+ } catch (e2) {
1466
+ setError(e2 instanceof Error ? e2.message : "Failed to send code.");
1467
+ } finally {
1468
+ setBusy(false);
1469
+ }
1470
+ };
1471
+ const handleOtpVerifySubmit = async (e) => {
1472
+ e.preventDefault();
1473
+ if (!email || otpCode.length !== 6) return;
1474
+ setError("");
1475
+ setBusy(true);
1476
+ try {
1477
+ await verifyOtp(email, otpCode);
1478
+ setScreen("deploying");
1479
+ setDeployState("loading");
1480
+ } catch (e2) {
1481
+ setError(e2 instanceof Error ? e2.message : "Invalid or expired code.");
1482
+ setBusy(false);
1483
+ }
1484
+ };
1485
+ const handleResend = async () => {
1486
+ if (resendCountdown > 0) return;
1487
+ setError("");
1488
+ try {
1489
+ if (emailMode === "otp") {
1490
+ await sendOtp(email);
1491
+ setOtpCode("");
1492
+ } else {
1493
+ await sendMagicLink(email);
1494
+ }
1495
+ setResendCountdown(60);
1496
+ } catch (e) {
1497
+ setError(e instanceof Error ? e.message : "Failed to resend.");
1498
+ }
1499
+ };
1500
+ react.useEffect(() => {
1501
+ if (resendCountdown <= 0) return;
1502
+ const t = setTimeout(() => setResendCountdown((c) => c - 1), 1e3);
1503
+ return () => clearTimeout(t);
1504
+ }, [resendCountdown]);
1505
+ if (!open) return null;
1506
+ const handleDeviceResend = async () => {
1507
+ if (resendCountdown > 0 || deviceResendBusy) return;
1508
+ setDeviceResendBusy(true);
1509
+ setError("");
1510
+ try {
1511
+ await resendDeviceApproval();
1512
+ setResendCountdown(60);
1513
+ } catch (e) {
1514
+ setError(e instanceof Error ? e.message : "Failed to resend. Try again.");
1515
+ } finally {
1516
+ setDeviceResendBusy(false);
1517
+ }
1518
+ };
1519
+ if (screen === "device-approval") {
1520
+ const expired = walletStatus.awaitingApproval && !walletStatus.pendingRequestId;
1521
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
1522
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1523
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
1524
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 32px" : "48px 24px 32px", textAlign: "center" }, children: [
1525
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: expired ? "rgba(239,68,68,0.08)" : "rgba(59,130,246,0.08)", border: `1px solid ${expired ? "rgba(239,68,68,0.18)" : "rgba(59,130,246,0.18)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: expired ? "#dc2626" : "#3b82f6", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1526
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2.5" }),
1527
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 18h.01" })
1528
+ ] }) }),
1529
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: expired ? "Approval link expired" : "Approve this device" }),
1530
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: expired ? "The approval link is no longer valid. Request a new one below." : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1531
+ "For your security, we sent an email to approve this device.",
1532
+ appName ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1533
+ " Open it on a device where you're already signed in to your ",
1534
+ appName,
1535
+ " account."
1536
+ ] }) : null
1537
+ ] }) }),
1538
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: error }),
1539
+ expired ? /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", opacity: deviceResendBusy ? 0.65 : 1 }, onClick: handleDeviceResend, disabled: deviceResendBusy, children: [
1540
+ deviceResendBusy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 15, color: "#fff" }),
1541
+ deviceResendBusy ? "Sending\u2026" : "Request a new link"
1542
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: isLight ? "rgba(0,0,0,0.03)" : "rgba(255,255,255,0.04)", border: `1px solid ${isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"}`, borderRadius: "12px", padding: "14px 16px", display: "flex", alignItems: "center", gap: 10, justifyContent: "center", marginBottom: "14px" }, children: [
1543
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 14, color: subTextColor }),
1544
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "12px", color: subTextColor }, children: "Waiting for approval\u2026" })
1545
+ ] }),
1546
+ !expired && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: resendCountdown > 0 ? "default" : "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: 0, fontFamily: "inherit", transition: "color 0.15s", opacity: resendCountdown > 0 ? 0.6 : 1 }, onClick: handleDeviceResend, disabled: resendCountdown > 0 || deviceResendBusy, children: resendCountdown > 0 ? `Resend email in ${resendCountdown}s` : "Resend approval email" }),
1547
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "14px 0 0", fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
1548
+ setScreen("recover");
1549
+ setError("");
1550
+ }, children: "Sign in with recovery phrase instead" })
1551
+ ] }),
1552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1553
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1554
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1555
+ ] })
1556
+ ] }) });
1557
+ }
1558
+ const handleRecoverSubmit = async (e) => {
1559
+ e.preventDefault();
1560
+ if (!recoverCode.trim()) return;
1561
+ setRecoverBusy(true);
1562
+ setError("");
1563
+ try {
1564
+ await recover(recoverCode);
1565
+ setScreen("deploying");
1566
+ setDeployState("loading");
1567
+ } catch (e2) {
1568
+ setError(e2 instanceof Error ? e2.message : "Sign-in failed. Check your recovery phrase and try again.");
1569
+ setRecoverBusy(false);
1570
+ }
1571
+ };
1572
+ if (screen === "recover") {
1573
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, onClick: (e) => {
1574
+ if (e.target === e.currentTarget) handleClose();
1575
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
1576
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1577
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
1578
+ setScreen("device-approval");
1579
+ setError("");
1580
+ setRecoverCode("");
1581
+ }, "aria-label": "Back", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" }) }) }),
1582
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
1583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 22px 32px" : "52px 22px 22px" }, children: [
1584
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 6px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em", textAlign: "center" }, children: "Sign in with recovery phrase" }),
1585
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, textAlign: "center", lineHeight: 1.5 }, children: "Enter the recovery phrase you saved when you set up this account." }),
1586
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "10px 14px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: error }),
1587
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleRecoverSubmit, style: { display: "flex", flexDirection: "column", gap: "10px" }, children: [
1588
+ /* @__PURE__ */ jsxRuntime.jsx(
1589
+ "textarea",
1590
+ {
1591
+ className: "cavos-input-inner",
1592
+ style: { width: "100%", padding: "12px 14px", border: inputBorder, borderRadius: "12px", fontSize: "14px", color: textColor, background: inputBg, fontFamily: "inherit", boxSizing: "border-box", resize: "vertical", minHeight: 72, lineHeight: 1.5 },
1593
+ placeholder: "Enter your recovery phrase",
1594
+ value: recoverCode,
1595
+ onChange: (e) => setRecoverCode(e.target.value),
1596
+ required: true,
1597
+ disabled: recoverBusy,
1598
+ autoFocus: true
1599
+ }
1600
+ ),
1601
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "submit", className: "cavos-primary-btn", style: { width: "100%", padding: "12px", marginTop: "4px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", transition: "opacity 0.15s, transform 0.1s", opacity: recoverBusy ? 0.65 : 1 }, disabled: recoverBusy, children: [
1602
+ recoverBusy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 15, color: "#fff" }),
1603
+ recoverBusy ? "Restoring access\u2026" : "Restore access"
1604
+ ] })
1605
+ ] })
1606
+ ] }),
1607
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1608
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1609
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1610
+ ] })
1611
+ ] }) });
1612
+ }
1613
+ if (screen === "deploying") {
1614
+ const isDone = deployState === "done";
1615
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: card, children: [
1616
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1617
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "52px 28px 44px", display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", gap: "16px" }, children: [
1618
+ isDone ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cavos-check-circle", style: { width: 64, height: 64, borderRadius: "50%", background: "rgba(34,197,94,0.12)", border: "1.5px solid rgba(34,197,94,0.35)", display: "flex", alignItems: "center", justifyContent: "center", animation: "cavos-ring-glow 1.5s ease-out" }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { className: "cavos-check-path", points: "20 6 9 17 4 12", stroke: "#22c55e", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 64, height: 64, borderRadius: "50%", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.04)", border: `1.5px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.08)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 26, color: isLight ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.6)" }) }),
1619
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1620
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: isDone ? "You're all set" : "Setting up your account" }),
1621
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "6px 0 0", fontSize: "13px", color: subTextColor }, children: isDone ? "Your account is ready" : "This only takes a moment\u2026" })
1622
+ ] })
1623
+ ] }),
1624
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1625
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1626
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1627
+ ] })
1628
+ ] }) });
1629
+ }
1630
+ if (screen === "verify") {
1631
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, onClick: (e) => {
1632
+ if (e.target === e.currentTarget) handleClose();
1633
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
1634
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1635
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
1636
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 32px" : "40px 24px 24px", textAlign: "center" }, children: [
1637
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: "rgba(34,197,94,0.08)", border: "1px solid rgba(34,197,94,0.18)", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "#22c55e", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }) }),
1638
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Check your inbox" }),
1639
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0 0 22px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: [
1640
+ "We sent a sign-in link to ",
1641
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: textColor, fontWeight: 500 }, children: email }),
1642
+ ".",
1643
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
1644
+ "Open it on this device to continue."
1645
+ ] }),
1646
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: error }),
1647
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: resendCountdown > 0 ? isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.08)" : primaryColor, color: resendCountdown > 0 ? subTextColor : "#fff", fontSize: "14px", fontWeight: 500, cursor: resendCountdown > 0 ? "default" : "pointer", fontFamily: "inherit", transition: "opacity 0.15s", marginBottom: "8px" }, onClick: handleResend, disabled: resendCountdown > 0, children: resendCountdown > 0 ? `Resend in ${resendCountdown}s` : "Resend link" }),
1648
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "8px 0 0", fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
1649
+ setScreen("magic-link");
1650
+ setError("");
1651
+ }, children: "Use a different email" })
1652
+ ] }),
1653
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1654
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1655
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1656
+ ] })
1657
+ ] }) });
1658
+ }
1659
+ if (screen === "otp-code") {
1660
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, onClick: (e) => {
1661
+ if (e.target === e.currentTarget) handleClose();
1662
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
1663
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1664
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
1665
+ setScreen("magic-link");
1666
+ setError("");
1667
+ setOtpCode("");
1668
+ }, "aria-label": "Back", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" }) }) }),
1669
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
1670
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 22px 32px" : "52px 22px 22px", textAlign: "center" }, children: [
1671
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Enter your code" }),
1672
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, lineHeight: 1.5 }, children: [
1673
+ "We sent a 6-digit code to ",
1674
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: textColor, fontWeight: 500 }, children: email }),
1675
+ "."
1676
+ ] }),
1677
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "10px 14px", fontSize: "13px", color: errColor, marginBottom: "14px", textAlign: "left" }, children: error }),
1678
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleOtpVerifySubmit, style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [
1679
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: "8px", justifyContent: "center" }, onPaste: onOtpPaste, children: [0, 1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsxRuntime.jsx(
1680
+ "input",
1681
+ {
1682
+ ref: (el) => {
1683
+ otpBoxRefs.current[i] = el;
1684
+ },
1685
+ className: `cavos-input-inner cavos-otp-box ${otpCode[i] ? "cavos-otp-box-filled" : ""}`,
1686
+ style: {
1687
+ width: "44px",
1688
+ height: "52px",
1689
+ padding: 0,
1690
+ border: inputBorder,
1691
+ borderRadius: "12px",
1692
+ fontSize: "22px",
1693
+ fontWeight: 600,
1694
+ color: textColor,
1695
+ background: inputBg,
1696
+ fontFamily: "inherit",
1697
+ boxSizing: "border-box",
1698
+ textAlign: "center"
1699
+ },
1700
+ type: "text",
1701
+ inputMode: "numeric",
1702
+ autoComplete: i === 0 ? "one-time-code" : "off",
1703
+ maxLength: 1,
1704
+ value: otpCode[i] ?? "",
1705
+ onChange: (e) => setOtpDigit(i, e.target.value),
1706
+ onKeyDown: (e) => onOtpKeyDown(i, e),
1707
+ onFocus: (e) => e.target.select(),
1708
+ disabled: busy,
1709
+ autoFocus: i === 0
1710
+ },
1711
+ i
1712
+ )) }),
1713
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "submit", className: "cavos-primary-btn", style: { width: "100%", padding: "12px", marginTop: "4px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", transition: "opacity 0.15s, transform 0.1s", opacity: busy || otpCode.length !== 6 ? 0.65 : 1 }, disabled: busy || otpCode.length !== 6, children: [
1714
+ busy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 15, color: "#fff" }),
1715
+ busy ? "Verifying\u2026" : "Continue"
1716
+ ] })
1717
+ ] }),
1718
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: resendCountdown > 0 ? isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.08)" : "transparent", color: resendCountdown > 0 ? subTextColor : primaryColor, fontSize: "14px", fontWeight: 500, cursor: resendCountdown > 0 ? "default" : "pointer", fontFamily: "inherit", transition: "opacity 0.15s", marginTop: "8px" }, onClick: handleResend, disabled: resendCountdown > 0 || busy, children: resendCountdown > 0 ? `Resend in ${resendCountdown}s` : "Resend code" }),
1719
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "8px 0 0", fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
1720
+ setScreen("magic-link");
1721
+ setError("");
1722
+ setOtpCode("");
1723
+ }, children: "Use a different email" })
1724
+ ] }),
1725
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1726
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1727
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1728
+ ] })
1729
+ ] }) });
1730
+ }
1731
+ if (screen === "magic-link") {
1732
+ const isOtp = emailMode === "otp";
1733
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, onClick: (e) => {
1734
+ if (e.target === e.currentTarget) handleClose();
1735
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
1736
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1737
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
1738
+ setScreen("select");
1739
+ setError("");
1740
+ }, "aria-label": "Back", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" }) }) }),
1741
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
1742
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 22px 32px" : "52px 22px 22px" }, children: [
1743
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 6px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em", textAlign: "center" }, children: "Sign in with email" }),
1744
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, textAlign: "center", lineHeight: 1.5 }, children: isOtp ? "We'll send a secure sign-in code to your inbox." : "We'll send a secure sign-in link to your inbox." }),
1745
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "10px 14px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: error }),
1746
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: isOtp ? handleOtpRequestSubmit : handleMagicLinkSubmit, style: { display: "flex", flexDirection: "column", gap: "10px" }, children: [
1747
+ /* @__PURE__ */ jsxRuntime.jsx("input", { className: "cavos-input-inner", style: { width: "100%", padding: "12px 14px", border: inputBorder, borderRadius: "12px", fontSize: "14px", color: textColor, background: inputBg, fontFamily: "inherit", boxSizing: "border-box", transition: "border-color 0.15s" }, type: "email", placeholder: "your@email.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: busy, autoFocus: true }),
1748
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "submit", className: "cavos-primary-btn", style: { width: "100%", padding: "12px", marginTop: "4px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", transition: "opacity 0.15s, transform 0.1s", opacity: busy ? 0.65 : 1 }, disabled: busy, children: [
1749
+ busy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 15, color: "#fff" }),
1750
+ busy ? "Sending\u2026" : isOtp ? "Send code" : "Send magic link"
1751
+ ] })
1752
+ ] })
1753
+ ] }),
1754
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1755
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1756
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1757
+ ] })
1758
+ ] }) });
1759
+ }
1760
+ const showEmail = providers.includes("email");
1761
+ const showGoogle = providers.includes("google");
1762
+ const showApple = providers.includes("apple");
1763
+ const hasSocial = showGoogle || showApple;
1764
+ const btnIcon = (icon, busyIcon, color) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { position: "absolute", left: "16px", width: 20, height: 20, display: "flex", alignItems: "center", justifyContent: "center", color, flexShrink: 0 }, children: busy ? busyIcon : icon });
1765
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, onClick: (e) => {
1766
+ if (e.target === e.currentTarget) handleClose();
1767
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
1768
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
1769
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
1770
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "24px 20px 32px" : "40px 24px 24px" }, children: [
1771
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 22px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em", textAlign: "center" }, children: appName ? `Sign in to ${appName}` : "Log in or sign up" }),
1772
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "10px" }, children: [
1773
+ showGoogle && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-provider", style: { ...pBtn, ...busy ? { opacity: 0.55, cursor: "not-allowed" } : {} }, onClick: () => handleOAuth("google"), disabled: busy, children: [
1774
+ btnIcon(/* @__PURE__ */ jsxRuntime.jsx(GoogleIcon, {}), /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 14, color: "#888" }), "#4285F4"),
1775
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Continue with Google" })
1776
+ ] }),
1777
+ showApple && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-provider", style: { ...pBtn, ...busy ? { opacity: 0.55, cursor: "not-allowed" } : {} }, onClick: () => handleOAuth("apple"), disabled: busy, children: [
1778
+ btnIcon(/* @__PURE__ */ jsxRuntime.jsx(AppleIcon, {}), /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 14, color: isLight ? "#111" : "#fff" }), isLight ? "#111" : "#fff"),
1779
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Continue with Apple" })
1780
+ ] }),
1781
+ showEmail && hasSocial && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", margin: "6px 0 2px" }, children: [
1782
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.08)" } }),
1783
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "12px", color: subTextColor, textTransform: "uppercase", letterSpacing: "0.05em" }, children: "or" }),
1784
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.08)" } })
1785
+ ] }),
1786
+ showEmail && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-provider", style: { ...pBtn, cursor: busy ? "not-allowed" : "pointer" }, onClick: () => setScreen("magic-link"), disabled: busy, children: [
1787
+ btnIcon(/* @__PURE__ */ jsxRuntime.jsx(EmailIcon, {}), /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 14, color: subTextColor }), subTextColor),
1788
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Continue with email" })
1789
+ ] })
1790
+ ] }),
1791
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: "14px", background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor }, children: error }),
1792
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "18px 0 0", fontSize: "11px", color: subTextColor, textAlign: "center", lineHeight: 1.55 }, children: [
1793
+ "By continuing you agree to the",
1794
+ " ",
1795
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://cavos.xyz/user-privacy", target: "_blank", rel: "noopener noreferrer", style: { color: isLight ? "rgba(0,0,0,0.45)" : "rgba(255,255,255,0.45)", textDecoration: "underline" }, children: "Privacy Policy" }),
1796
+ " & ",
1797
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://cavos.xyz/user-terms", target: "_blank", rel: "noopener noreferrer", style: { color: isLight ? "rgba(0,0,0,0.45)" : "rgba(255,255,255,0.45)", textDecoration: "underline" }, children: "Terms" }),
1798
+ "."
1799
+ ] })
1800
+ ] }),
1801
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
1802
+ /* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
1803
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
1804
+ ] })
1805
+ ] }) });
1806
+ }
1807
+ function useCavosAuth() {
1808
+ const { openModal, closeModal, isAuthenticated, address, user, walletStatus, logout } = useCavos();
1809
+ return { openModal, closeModal, isAuthenticated, address, user, walletStatus, logout };
1810
+ }
1811
+ var CavosContext = react.createContext(null);
1812
+ var INITIAL_STATUS = {
1813
+ isDeploying: false,
1814
+ isReady: false,
1815
+ needsDeviceApproval: false,
1816
+ awaitingApproval: false,
1817
+ pendingRequestId: null
1818
+ };
1819
+ function CavosProvider({ config, modal, children }) {
1820
+ const [auth] = react.useState(
1821
+ () => new CavosAuth({ appId: config.appId, backendUrl: config.authBackendUrl })
1822
+ );
1823
+ const [cavos, setCavos] = react.useState(null);
1824
+ const [identity, setIdentity] = react.useState(null);
1825
+ const [walletStatus, setWalletStatus] = react.useState(INITIAL_STATUS);
1826
+ const [isLoading, setIsLoading] = react.useState(false);
1827
+ const [authError, setAuthError] = react.useState(null);
1828
+ const [modalOpen, setModalOpen] = react.useState(false);
1829
+ const [branding, setBranding] = react.useState({});
1830
+ const configRef = react.useRef(config);
1831
+ react.useEffect(() => {
1832
+ configRef.current = config;
1833
+ }, [config]);
1834
+ react.useEffect(() => {
1835
+ if (!config.appId || typeof window === "undefined") return;
1836
+ const base = config.authBackendUrl ?? "https://cavos.xyz";
1837
+ fetch(`${base}/api/oauth/firebase/app-branding?app_id=${encodeURIComponent(config.appId)}`).then((r) => r.ok ? r.json() : null).then((d) => {
1838
+ if (d?.name) setBranding((b) => ({ ...b, appName: d.name }));
1839
+ if (d?.logo_url) setBranding((b) => ({ ...b, appLogo: d.logo_url }));
1840
+ }).catch(() => {
1841
+ });
1842
+ }, [config.appId, config.authBackendUrl]);
1843
+ const openModal = react.useCallback(() => setModalOpen(true), []);
1844
+ const closeModal = react.useCallback(() => setModalOpen(false), []);
1845
+ const clearAuthError = react.useCallback(() => setAuthError(null), []);
1846
+ const resendDeviceApproval = react.useCallback(async () => {
1847
+ if (!identity || !cavos || !cavos.pendingRequestId) return;
1848
+ const backendUrl = configRef.current.authBackendUrl ?? "https://cavos.xyz";
1849
+ if (!configRef.current.appId) return;
1850
+ const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: configRef.current.appId });
1851
+ await recovery.requestDeviceAddition({
1852
+ userId: identity.userId,
1853
+ accountAddress: cavos.address,
1854
+ newSigner: cavos.publicKey,
1855
+ ...identity.email ? { email: identity.email } : {}
1856
+ });
1857
+ }, [identity, cavos]);
1858
+ const connect = react.useCallback(async (id) => {
1859
+ setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
1860
+ const c = await Cavos.connect({
1861
+ network: configRef.current.network,
1862
+ identity: id,
1863
+ appSalt: configRef.current.appSalt,
1864
+ paymasterApiKey: configRef.current.paymasterApiKey,
1865
+ ...configRef.current.appId ? { appId: configRef.current.appId } : {},
1866
+ ...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
1867
+ ...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
1868
+ });
1869
+ setCavos(c);
1870
+ setIdentity(id);
1871
+ setWalletStatus({
1872
+ isDeploying: false,
1873
+ isReady: c.status === "ready",
1874
+ needsDeviceApproval: c.status === "needs-device-approval",
1875
+ awaitingApproval: c.status === "needs-device-approval" && !!c.pendingRequestId,
1876
+ pendingRequestId: c.pendingRequestId
1877
+ });
1878
+ modal?.onSuccess?.(c.address);
1879
+ return c;
1880
+ }, [modal]);
1881
+ react.useEffect(() => {
1882
+ if (typeof window === "undefined") return;
1883
+ const params = new URLSearchParams(window.location.search);
1884
+ const authData = params.get("auth_data") || params.get("zk_auth_data");
1885
+ if (!authData) return;
1886
+ setModalOpen(true);
1887
+ setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
1888
+ let cancelled = false;
1889
+ (async () => {
1890
+ setIsLoading(true);
1891
+ try {
1892
+ await handleCallback(authData);
1893
+ if (!cancelled) window.history.replaceState({}, document.title, window.location.pathname);
1894
+ } catch (e) {
1895
+ console.error("[CavosProvider] OAuth callback error:", e);
1896
+ if (!cancelled) {
1897
+ const msg = e instanceof Error ? e.message : "Sign-in failed. Please try again.";
1898
+ setAuthError(msg);
1899
+ setWalletStatus(INITIAL_STATUS);
1900
+ }
1901
+ } finally {
1902
+ if (!cancelled) setIsLoading(false);
1903
+ }
1904
+ })();
1905
+ return () => {
1906
+ cancelled = true;
1907
+ };
1908
+ }, []);
1909
+ const handleCallback = react.useCallback(async (authData) => {
1910
+ const id = await auth.handleCallback(authData);
1911
+ await connect(id);
1912
+ }, [auth, connect]);
1913
+ const login = react.useCallback(async (provider) => {
1914
+ if (typeof window === "undefined") throw new Error("OAuth requires a browser");
1915
+ setAuthError(null);
1916
+ const url = await (provider === "google" ? auth.getGoogleOAuthUrl(window.location.origin + window.location.pathname) : auth.getAppleOAuthUrl(window.location.origin + window.location.pathname));
1917
+ window.location.href = url;
1918
+ }, [auth]);
1919
+ const sendMagicLink = react.useCallback(async (email) => {
1920
+ await auth.sendMagicLink(email);
1921
+ }, [auth]);
1922
+ const sendOtp = react.useCallback(async (email) => {
1923
+ await auth.sendOtp(email);
1924
+ }, [auth]);
1925
+ const verifyOtp = react.useCallback(async (email, code) => {
1926
+ setAuthError(null);
1927
+ const id = await auth.verifyOtp(email, code);
1928
+ await connect(id);
1929
+ }, [auth, connect]);
1930
+ const execute = react.useCallback(async (calls) => {
1931
+ if (!cavos) throw new Error("Not logged in");
1932
+ return cavos.execute(calls);
1933
+ }, [cavos]);
1934
+ const addSigner = react.useCallback(
1935
+ async (pubkey) => {
1936
+ if (!cavos) throw new Error("Not logged in");
1937
+ return cavos.addSigner(pubkey);
1938
+ },
1939
+ [cavos]
1940
+ );
1941
+ const setupRecovery = react.useCallback(async () => {
1942
+ if (!cavos) throw new Error("Not logged in");
1943
+ const code = generateRecoveryCode();
1944
+ await cavos.setupRecovery(code);
1945
+ return code;
1946
+ }, [cavos]);
1947
+ const recover = react.useCallback(async (code) => {
1948
+ if (!identity) throw new Error("Sign in first so we know which account to recover.");
1949
+ setAuthError(null);
1950
+ setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
1951
+ try {
1952
+ const c = await Cavos.recover({
1953
+ code,
1954
+ identity,
1955
+ network: configRef.current.network,
1956
+ appSalt: configRef.current.appSalt,
1957
+ paymasterApiKey: configRef.current.paymasterApiKey,
1958
+ ...configRef.current.appId ? { appId: configRef.current.appId } : {},
1959
+ ...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
1960
+ ...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
1961
+ });
1962
+ setCavos(c);
1963
+ setWalletStatus({ ...INITIAL_STATUS, isReady: true });
1964
+ modal?.onSuccess?.(c.address);
1965
+ } catch (e) {
1966
+ const msg = e instanceof Error ? e.message : "Recovery failed. Check your code and try again.";
1967
+ setAuthError(msg);
1968
+ setWalletStatus(INITIAL_STATUS);
1969
+ throw e;
1970
+ }
1971
+ }, [identity, modal]);
1972
+ react.useEffect(() => {
1973
+ if (!walletStatus.awaitingApproval || !walletStatus.pendingRequestId || !identity) return;
1974
+ if (!configRef.current.appId) return;
1975
+ const backendUrl = configRef.current.authBackendUrl ?? "https://cavos.xyz";
1976
+ const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: configRef.current.appId });
1977
+ const requestId = walletStatus.pendingRequestId;
1978
+ let cancelled = false;
1979
+ const tick = async () => {
1980
+ try {
1981
+ const r = await recovery.getPendingRequest(requestId);
1982
+ if (cancelled || !r) return;
1983
+ if (r.status === "expired") {
1984
+ setWalletStatus((s) => ({ ...s, pendingRequestId: null }));
1985
+ return;
1986
+ }
1987
+ if (r.status === "approved") {
1988
+ await connect(identity);
1989
+ }
1990
+ } catch {
1991
+ }
1992
+ };
1993
+ const interval = setInterval(tick, 4e3);
1994
+ return () => {
1995
+ cancelled = true;
1996
+ clearInterval(interval);
1997
+ };
1998
+ }, [walletStatus.awaitingApproval, walletStatus.pendingRequestId, identity, connect]);
1999
+ const logout = react.useCallback(() => {
2000
+ setCavos(null);
2001
+ setIdentity(null);
2002
+ setWalletStatus(INITIAL_STATUS);
2003
+ setAuthError(null);
2004
+ }, []);
2005
+ const value = {
2006
+ openModal,
2007
+ closeModal,
2008
+ isAuthenticated: !!cavos,
2009
+ user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
2010
+ address: cavos?.address ?? null,
2011
+ walletStatus,
2012
+ isLoading,
2013
+ authError,
2014
+ clearAuthError,
2015
+ login,
2016
+ sendMagicLink,
2017
+ sendOtp,
2018
+ verifyOtp,
2019
+ handleCallback,
2020
+ execute,
2021
+ addSigner,
2022
+ resendDeviceApproval,
2023
+ setupRecovery,
2024
+ recover,
2025
+ logout
2026
+ };
2027
+ return /* @__PURE__ */ jsxRuntime.jsxs(CavosContext.Provider, { value, children: [
2028
+ children,
2029
+ modal !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
2030
+ CavosAuthModal,
2031
+ {
2032
+ open: modalOpen,
2033
+ onClose: closeModal,
2034
+ appName: branding.appName ?? modal.appName,
2035
+ appLogo: branding.appLogo ?? modal.appLogo,
2036
+ providers: modal.providers,
2037
+ emailMode: modal.emailMode,
2038
+ primaryColor: modal.primaryColor,
2039
+ theme: modal.theme
2040
+ }
2041
+ )
2042
+ ] });
2043
+ }
2044
+ function useCavos() {
2045
+ const ctx = react.useContext(CavosContext);
2046
+ if (!ctx) throw new Error("useCavos must be used within a CavosProvider");
2047
+ return ctx;
2048
+ }
2049
+
2050
+ exports.CavosAuthModal = CavosAuthModal;
2051
+ exports.CavosProvider = CavosProvider;
2052
+ exports.useCavos = useCavos;
2053
+ exports.useCavosAuth = useCavosAuth;
2054
+ //# sourceMappingURL=index.js.map
2055
+ //# sourceMappingURL=index.js.map