@embarkai/ui-kit 0.4.128 → 0.6.9

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.
@@ -22,14 +22,20 @@ __export(crypto_utils_exports, {
22
22
  bytesToBase64: () => bytesToBase64,
23
23
  bytesToHex: () => bytesToHex,
24
24
  decryptKeyshare: () => decryptKeyshare,
25
+ decryptShareWithDek: () => decryptShareWithDek,
25
26
  deriveBackupPasswordFromPasskey: () => deriveBackupPasswordFromPasskey,
26
27
  deriveKEKFromPasskey: () => deriveKEKFromPasskey,
27
28
  deriveKeyFromPassword: () => deriveKeyFromPassword,
28
29
  encryptKeyshare: () => encryptKeyshare,
29
30
  envelopeDecryptKeyshare: () => envelopeDecryptKeyshare,
31
+ envelopeDecryptKeyshareWithDek: () => envelopeDecryptKeyshareWithDek,
32
+ envelopeDecryptKeyshareWithKek: () => envelopeDecryptKeyshareWithKek,
30
33
  envelopeDecryptKeyshareWithPassword: () => envelopeDecryptKeyshareWithPassword,
31
34
  envelopeEncryptKeyshare: () => envelopeEncryptKeyshare,
32
- envelopeEncryptKeyshareWithPassword: () => envelopeEncryptKeyshareWithPassword
35
+ envelopeEncryptKeyshareBasic: () => envelopeEncryptKeyshareBasic,
36
+ envelopeEncryptKeyshareWithPasskeyKek: () => envelopeEncryptKeyshareWithPasskeyKek,
37
+ envelopeEncryptKeyshareWithPassword: () => envelopeEncryptKeyshareWithPassword,
38
+ unwrapDek: () => unwrapDek
33
39
  });
34
40
  function base64ToBytes(base64) {
35
41
  const binaryString = atob(base64);
@@ -413,6 +419,125 @@ async function decryptKeyshare(encryptedBackup, password) {
413
419
  throw new Error(`Failed to decrypt backup: ${message}`);
414
420
  }
415
421
  }
422
+ function concatBytes(...parts) {
423
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
424
+ let offset = 0;
425
+ for (const part of parts) {
426
+ out.set(part, offset);
427
+ offset += part.length;
428
+ }
429
+ return out;
430
+ }
431
+ async function importAesKey(raw, usage) {
432
+ return crypto.subtle.importKey("raw", raw, "AES-GCM", false, usage);
433
+ }
434
+ async function sealWith(key, plaintext, aadBytes) {
435
+ const iv = crypto.getRandomValues(new Uint8Array(12));
436
+ const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: aadBytes }, key, plaintext);
437
+ return concatBytes(iv, new Uint8Array(ct));
438
+ }
439
+ function aadCandidatesOf(envelope) {
440
+ return envelope.aad?.version >= 2 ? [serializeAad(envelope.aad), serializeAadWithKeyOrder(envelope.aad, LEGACY_V2_KEY_ORDER)] : [void 0];
441
+ }
442
+ async function unwrapDek(envelope, unlock) {
443
+ const wrapped = base64ToBytes(envelope.wrapped_dek);
444
+ let key;
445
+ let iv;
446
+ let ct;
447
+ if (unlock.kind === "password") {
448
+ const salt = wrapped.slice(0, 16);
449
+ iv = wrapped.slice(16, 28);
450
+ ct = wrapped.slice(28);
451
+ key = await deriveKeyFromPassword(unlock.password, salt);
452
+ } else {
453
+ iv = wrapped.slice(0, 12);
454
+ ct = wrapped.slice(12);
455
+ key = await importAesKey(unlock.kek, ["decrypt"]);
456
+ }
457
+ let lastError;
458
+ for (const aadBytes of aadCandidatesOf(envelope)) {
459
+ try {
460
+ const dek = await crypto.subtle.decrypt(
461
+ { name: "AES-GCM", iv, ...aadBytes && { additionalData: aadBytes } },
462
+ key,
463
+ ct
464
+ );
465
+ return { dek: new Uint8Array(dek), aadBytes };
466
+ } catch (error) {
467
+ lastError = error;
468
+ }
469
+ }
470
+ throw new Error("Failed to unwrap backup key: " + (lastError instanceof Error ? lastError.message : String(lastError)));
471
+ }
472
+ async function decryptShareWithDek(envelope, dek, aadBytes) {
473
+ const data = base64ToBytes(envelope.ciphertext_share);
474
+ const key = await importAesKey(dek, ["decrypt"]);
475
+ const plaintext = await crypto.subtle.decrypt(
476
+ { name: "AES-GCM", iv: data.slice(0, 12), ...aadBytes && { additionalData: aadBytes } },
477
+ key,
478
+ data.slice(12)
479
+ );
480
+ return JSON.parse(new TextDecoder().decode(plaintext));
481
+ }
482
+ async function envelopeEncryptKeyshareBasic(data, wrap) {
483
+ const aad = { userId: data.userId, version: 2, deviceId: "embarkai-ui-kit" };
484
+ const aadBytes = serializeAad(aad);
485
+ const dek = crypto.getRandomValues(new Uint8Array(32));
486
+ const ciphertext = await sealWith(await importAesKey(dek, ["encrypt"]), new TextEncoder().encode(JSON.stringify(data)), aadBytes);
487
+ let wrapped;
488
+ try {
489
+ wrapped = await wrap(dek);
490
+ } catch (error) {
491
+ dek.fill(0);
492
+ throw error;
493
+ }
494
+ return {
495
+ envelope: {
496
+ ciphertext_share: bytesToBase64(ciphertext),
497
+ wrapped_dek: wrapped.wrappedDek,
498
+ kdf: { prf: wrapped.kdf.prf, hkdf: wrapped.kdf.hkdf, info: wrapped.kdf.info },
499
+ alg: { data: "aes-256-gcm", wrap: wrapped.kdf.hkdf },
500
+ aad,
501
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
502
+ expiresAt: null,
503
+ encryptionMethod: "server-managed"
504
+ },
505
+ dek
506
+ };
507
+ }
508
+ async function envelopeDecryptKeyshareWithDek(envelope, dek) {
509
+ let lastError;
510
+ for (const aadBytes of aadCandidatesOf(envelope)) {
511
+ try {
512
+ return await decryptShareWithDek(envelope, dek, aadBytes);
513
+ } catch (error) {
514
+ lastError = error;
515
+ }
516
+ }
517
+ throw new Error("Failed to decrypt backup: " + (lastError instanceof Error ? lastError.message : String(lastError)));
518
+ }
519
+ async function envelopeEncryptKeyshareWithPasskeyKek(data, kek, credentialId) {
520
+ const aad = { userId: data.userId, version: 2, deviceId: "embarkai-ui-kit" };
521
+ const aadBytes = serializeAad(aad);
522
+ const dek = crypto.getRandomValues(new Uint8Array(32));
523
+ const ciphertext = await sealWith(await importAesKey(dek, ["encrypt"]), new TextEncoder().encode(JSON.stringify(data)), aadBytes);
524
+ const wrappedDek = await sealWith(await importAesKey(kek, ["encrypt"]), dek, aadBytes);
525
+ return {
526
+ ciphertext_share: bytesToBase64(ciphertext),
527
+ wrapped_dek: bytesToBase64(wrappedDek),
528
+ kdf: { prf: "webauthn-prf", hkdf: "hkdf-sha256", info: "client-share" },
529
+ alg: { data: "aes-256-gcm", wrap: "aes-256-gcm" },
530
+ aad,
531
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
532
+ expiresAt: null,
533
+ encryptionMethod: "passkey",
534
+ credentialId: credentialId || void 0
535
+ };
536
+ }
537
+ async function envelopeDecryptKeyshareWithKek(envelope, kek) {
538
+ const { dek, aadBytes } = await unwrapDek(envelope, { kind: "kek", kek });
539
+ return decryptShareWithDek(envelope, dek, aadBytes);
540
+ }
416
541
  var LEGACY_V2_KEY_ORDER;
417
542
  var init_crypto_utils = __esm({
418
543
  "src/iframe/lib/backup/crypto-utils.ts"() {
@@ -484,10 +609,34 @@ var init_local_backup = __esm({
484
609
  // src/iframe/lib/backup/server-backup.ts
485
610
  var server_backup_exports = {};
486
611
  __export(server_backup_exports, {
612
+ BASIC_BACKUP_NEEDS_NEWER_APP: () => BASIC_BACKUP_NEEDS_NEWER_APP,
613
+ BasicRecoveryError: () => BasicRecoveryError,
614
+ IFRAME_SESSION_REQUIRED: () => IFRAME_SESSION_REQUIRED,
615
+ RECOVERY_KEK_UNAVAILABLE: () => RECOVERY_KEK_UNAVAILABLE,
616
+ VaultError: () => VaultError,
617
+ WALLET_MISMATCH: () => WALLET_MISMATCH,
618
+ WalletMismatchError: () => WalletMismatchError,
487
619
  backupToServer: () => backupToServer,
488
- restoreFromServer: () => restoreFromServer
620
+ backupToServerWithPasskeyKek: () => backupToServerWithPasskeyKek,
621
+ encryptionMethodOfStats: () => encryptionMethodOfStats,
622
+ ensureServerManagedBackup: () => ensureServerManagedBackup,
623
+ getServerBackupMethod: () => getServerBackupMethod,
624
+ restoreErrorForParent: () => restoreErrorForParent,
625
+ restoreFromServer: () => restoreFromServer,
626
+ unwrapRecoveryDek: () => unwrapRecoveryDek,
627
+ upgradeServerManagedBackup: () => upgradeServerManagedBackup,
628
+ wrapRecoveryDek: () => wrapRecoveryDek
489
629
  });
490
- async function getShareVaultToken(tokenClient, scopes, accessToken) {
630
+ function shareUrl(path, owner) {
631
+ return `${SHARE_VAULT_URL}/v1/shares/me${path}${owner ? `?owner=${encodeURIComponent(owner.toLowerCase())}` : ""}`;
632
+ }
633
+ function sameOwner(a, b) {
634
+ return !!a && !!b && a.toLowerCase() === b.toLowerCase();
635
+ }
636
+ function withOwner(envelope, data) {
637
+ return data.ownerAddress ? { ...envelope, ownerAddress: data.ownerAddress } : envelope;
638
+ }
639
+ async function getShareVaultToken(tokenClient, scopes, accessToken, projectId) {
491
640
  const data = await tokenClient.apiCall(
492
641
  "POST",
493
642
  "/api/auth/token/exchange",
@@ -497,7 +646,7 @@ async function getShareVaultToken(tokenClient, scopes, accessToken) {
497
646
  // TODO: embarkai audience ?
498
647
  ttl: 300
499
648
  },
500
- void 0,
649
+ projectId,
501
650
  accessToken
502
651
  );
503
652
  if (!data?.resourceToken) {
@@ -505,40 +654,48 @@ async function getShareVaultToken(tokenClient, scopes, accessToken) {
505
654
  }
506
655
  return data.resourceToken;
507
656
  }
508
- async function uploadShareToVault(tokenClient, encryptedShare, accessToken) {
509
- const token = await getShareVaultToken(tokenClient, ["share:put"], accessToken);
510
- const idempotencyKey = crypto.randomUUID ? crypto.randomUUID() : `backup-${Date.now()}`;
511
- const response = await fetch(`${SHARE_VAULT_URL}/v1/shares/me`, {
657
+ async function uploadShareToVault(tokenClient, encryptedShare, accessToken, projectId, ifMatch) {
658
+ const token = await getShareVaultToken(tokenClient, ["share:put"], accessToken, projectId);
659
+ const idempotencyKey2 = crypto.randomUUID ? crypto.randomUUID() : `backup-${Date.now()}`;
660
+ const response = await fetch(shareUrl("", encryptedShare.ownerAddress), {
512
661
  method: "PUT",
513
662
  headers: {
514
663
  "Content-Type": "application/json",
515
664
  "Authorization": `Bearer ${token}`,
516
- "Idempotency-Key": idempotencyKey
665
+ "Idempotency-Key": idempotencyKey2,
666
+ ...ifMatch ? { "If-Match": ifMatch } : {}
517
667
  },
518
668
  body: JSON.stringify(encryptedShare)
519
669
  });
670
+ if (response.status === 412 && ifMatch) throw new VaultError(412, "The backup changed meanwhile, please try again");
520
671
  if (!response.ok) {
521
672
  const errorText = await response.text();
522
673
  throw new Error(`Failed to upload share: ${response.status} ${response.statusText} - ${errorText}`);
523
674
  }
524
675
  }
525
- async function backupToServer(data, tokenClient, password, accessToken) {
676
+ async function backupToServer(data, tokenClient, password, accessToken, projectId, ifMatch) {
526
677
  let encryptedShare;
527
678
  if (password) {
528
679
  encryptedShare = await envelopeEncryptKeyshareWithPassword(data, password);
529
680
  } else {
530
681
  encryptedShare = await envelopeEncryptKeyshare(data, data.userId);
531
682
  }
532
- await uploadShareToVault(tokenClient, encryptedShare, accessToken);
683
+ await uploadShareToVault(tokenClient, withOwner(encryptedShare, data), accessToken, projectId, ifMatch);
684
+ }
685
+ async function backupToServerWithPasskeyKek(data, kek, credentialId, tokenClient, accessToken, projectId, ifMatch) {
686
+ const envelope = await envelopeEncryptKeyshareWithPasskeyKek(data, kek, credentialId);
687
+ await uploadShareToVault(tokenClient, withOwner(envelope, data), accessToken, projectId, ifMatch);
533
688
  }
534
- async function downloadShareFromVault(tokenClient, accessToken) {
535
- const token = await getShareVaultToken(tokenClient, ["share:get"], accessToken);
536
- const response = await fetch(`${SHARE_VAULT_URL}/v1/shares/me`, {
689
+ async function downloadShareFromVault(tokenClient, accessToken, projectId, purpose, owner) {
690
+ const token = await getShareVaultToken(tokenClient, ["share:get"], accessToken, projectId);
691
+ const response = await fetch(shareUrl("", owner), {
537
692
  method: "GET",
538
693
  headers: {
539
694
  "Authorization": `Bearer ${token}`,
540
695
  "X-Client-Device-Id": "lumia-ui-kit",
541
- "X-Client-Device-Name": "Lumia UI Kit"
696
+ "X-Client-Device-Name": "Lumia UI Kit",
697
+ // a read for an upgrade is still counted by the vault, only labelled so it does not look like a restore
698
+ ...purpose ? { "X-Share-Read-Purpose": purpose } : {}
542
699
  }
543
700
  });
544
701
  if (!response.ok) {
@@ -554,25 +711,427 @@ async function downloadShareFromVault(tokenClient, accessToken) {
554
711
  }
555
712
  return envelope;
556
713
  }
557
- async function restoreFromServer(userId, tokenClient, password, accessToken) {
714
+ async function restoreFromServer(userId, tokenClient, password, accessToken, projectId, basicAllowed = false, expectedOwner) {
558
715
  console.log("[iframe][restoreFromServer] Starting restore for userId:", userId);
559
716
  console.log("[iframe][restoreFromServer] Using password:", !!password);
560
717
  console.log("[iframe][restoreFromServer] Downloading share from vault...");
561
- const envelope = await downloadShareFromVault(tokenClient, accessToken);
718
+ let envelope;
719
+ try {
720
+ envelope = await downloadShareFromVault(tokenClient, accessToken, projectId, void 0, expectedOwner);
721
+ } catch (error) {
722
+ if (String(error?.message).includes(IFRAME_SESSION_REQUIRED)) throw basicNeedsIframeSession();
723
+ throw error;
724
+ }
725
+ if (envelope.encryptionMethod === "server-managed" && !basicAllowed && !password) throw basicNeedsIframeSession();
562
726
  console.log("[iframe][restoreFromServer] Envelope encryption method:", envelope.encryptionMethod);
563
727
  const { envelopeDecryptKeyshare: envelopeDecryptKeyshare2, envelopeDecryptKeyshareWithPassword: envelopeDecryptKeyshareWithPassword2 } = await Promise.resolve().then(() => (init_crypto_utils(), crypto_utils_exports));
564
- const backupData = password ? await envelopeDecryptKeyshareWithPassword2(envelope, password) : await envelopeDecryptKeyshare2(envelope, userId);
728
+ let backupData;
729
+ if (password) {
730
+ backupData = await envelopeDecryptKeyshareWithPassword2(envelope, password);
731
+ } else if (envelope.encryptionMethod === "server-managed") {
732
+ const opened = await openBasicEnvelope(tokenClient, envelope, accessToken, projectId);
733
+ backupData = opened.data;
734
+ if (opened.rewrappedDek && backupData.userId === userId) {
735
+ await storeRewrappedDek(tokenClient, envelope, opened.rewrappedDek, accessToken, projectId, expectedOwner);
736
+ }
737
+ } else {
738
+ backupData = await envelopeDecryptKeyshare2(envelope, userId);
739
+ }
565
740
  if (backupData.userId !== userId) {
566
741
  throw new Error("Server backup does not match current user");
567
742
  }
743
+ if (!envelope.ownerAddress) await bindShareToWallet(tokenClient, envelope, backupData.ownerAddress, accessToken, projectId, expectedOwner);
744
+ if (expectedOwner && !sameOwner(backupData.ownerAddress, expectedOwner)) throw new WalletMismatchError(backupData);
568
745
  console.log("[iframe][restoreFromServer] Decryption successful, returning backup data");
569
746
  return backupData;
570
747
  }
571
- var SHARE_VAULT_URL;
748
+ async function bindShareToWallet(tokenClient, envelope, ownerAddress, accessToken, projectId, readAs) {
749
+ if (!ownerAddress || !envelope.etag) return false;
750
+ try {
751
+ await rewrapShareInVault(tokenClient, { wrapped_dek: envelope.wrapped_dek, ownerAddress }, envelope.etag, accessToken, projectId, readAs ?? ownerAddress);
752
+ console.log("[iframe][server-backup] Backup bound to its wallet:", ownerAddress);
753
+ return true;
754
+ } catch (error) {
755
+ console.warn("[iframe][server-backup] Backup not bound to its wallet:", errorMessage(error));
756
+ return false;
757
+ }
758
+ }
759
+ function restoreErrorForParent(message, parentSupportsIframeSession) {
760
+ return !parentSupportsIframeSession && message.includes(IFRAME_SESSION_REQUIRED) ? BASIC_BACKUP_NEEDS_NEWER_APP : message;
761
+ }
762
+ function basicNeedsIframeSession() {
763
+ return new BasicRecoveryError(true, `${IFRAME_SESSION_REQUIRED}: a Basic backup opens only after signing in again`);
764
+ }
765
+ async function recoveryCall(tokenClient, path, body, accessToken, projectId) {
766
+ try {
767
+ return await tokenClient.apiCall("POST", `/api/auth/backup/recovery-dek/${path}`, body, projectId, accessToken);
768
+ } catch (error) {
769
+ const status = error?.details?.status ?? error?.status;
770
+ const disabled = error?.code === "RECOVERY_DISABLED" || error?.code === "ORIGIN_NOT_ALLOWED" || status === 404;
771
+ throw new BasicRecoveryError(disabled, error?.message || "request failed", typeof status === "number" ? status : void 0);
772
+ }
773
+ }
774
+ async function wrapRecoveryDek(tokenClient, dek, accessToken, projectId) {
775
+ const data = await recoveryCall(tokenClient, "wrap", { dek: bytesToBase64(dek) }, accessToken, projectId);
776
+ if (typeof data?.wrapped_dek !== "string" || !data.wrapped_dek || data?.kdf?.prf !== "server-managed") {
777
+ throw new BasicRecoveryError(false, "invalid response");
778
+ }
779
+ return { wrappedDek: data.wrapped_dek, kdf: { prf: "server-managed", hkdf: data.kdf.hkdf, info: data.kdf.info } };
780
+ }
781
+ async function unwrapRecoveryDek(tokenClient, wrappedDek, accessToken, projectId) {
782
+ const data = await recoveryCall(tokenClient, "unwrap", { wrapped_dek: wrappedDek }, accessToken, projectId);
783
+ const dek = typeof data?.dek === "string" ? base64ToBytes(data.dek) : void 0;
784
+ if (!dek || dek.length !== 32) throw new BasicRecoveryError(false, "invalid response");
785
+ const rewrapped = data.rewrapped_dek;
786
+ return { dek, ...typeof rewrapped === "string" && rewrapped && rewrapped !== wrappedDek ? { rewrappedDek: rewrapped } : {} };
787
+ }
788
+ async function rewrapShareInVault(tokenClient, body, etag, accessToken, projectId, owner) {
789
+ if (!etag) throw new VaultError(428, "A rewrap needs the etag of the share it was computed from");
790
+ const token = await getShareVaultToken(tokenClient, ["share:rewrap"], accessToken, projectId);
791
+ const response = await fetch(shareUrl("/rewrap", owner), {
792
+ method: "POST",
793
+ headers: {
794
+ "Content-Type": "application/json",
795
+ Authorization: `Bearer ${token}`,
796
+ "Idempotency-Key": idempotencyKey("rewrap"),
797
+ "If-Match": etag
798
+ },
799
+ body: JSON.stringify(body)
800
+ });
801
+ if (response.status === 412) throw new VaultError(412, "The backup changed meanwhile");
802
+ if (!response.ok) throw new VaultError(response.status, `Failed to rewrap share: ${response.status} ${await response.text()}`);
803
+ return (await response.json().catch(() => null))?.etag;
804
+ }
805
+ async function storeRewrappedDek(tokenClient, envelope, rewrappedDek, accessToken, projectId, owner) {
806
+ if (!envelope.etag) return;
807
+ try {
808
+ const etag = await rewrapShareInVault(tokenClient, { wrapped_dek: rewrappedDek }, envelope.etag, accessToken, projectId, owner);
809
+ envelope.wrapped_dek = rewrappedDek;
810
+ envelope.etag = etag;
811
+ console.log("[iframe][server-backup] Basic backup: wrapped DEK updated after a key rotation");
812
+ } catch (error) {
813
+ console.warn("[iframe][server-backup] Basic backup: wrapped DEK not updated:", errorMessage(error));
814
+ }
815
+ }
816
+ async function openBasicEnvelope(tokenClient, envelope, accessToken, projectId) {
817
+ const { dek, rewrappedDek } = await unwrapRecoveryDek(tokenClient, envelope.wrapped_dek, accessToken, projectId);
818
+ try {
819
+ return { data: await envelopeDecryptKeyshareWithDek(envelope, dek), ...rewrappedDek ? { rewrappedDek } : {} };
820
+ } finally {
821
+ dek.fill(0);
822
+ }
823
+ }
824
+ async function getVaultRecoveryStats(tokenClient, accessToken, projectId, owner) {
825
+ const token = await getShareVaultToken(tokenClient, ["share:get"], accessToken, projectId);
826
+ const response = await fetch(shareUrl("/recovery/stats", owner), {
827
+ method: "GET",
828
+ headers: { Authorization: `Bearer ${token}` }
829
+ });
830
+ if (response.status === 404) return null;
831
+ if (!response.ok) throw new VaultError(response.status, `Failed to read recovery stats: ${response.status}`);
832
+ return await response.json();
833
+ }
834
+ function encryptionMethodOfStats(stats) {
835
+ if (!stats) return null;
836
+ const method = stats.created?.encryptionMethod;
837
+ const userHeld = method === "password" || method === "passkey" ? method : "passkey";
838
+ if (stats.securityLevel === null) return null;
839
+ if (stats.securityLevel === "basic") return "server-managed";
840
+ if (stats.securityLevel === "advanced") return userHeld;
841
+ if (!stats.created) return null;
842
+ return method === "server-managed" ? "server-managed" : userHeld;
843
+ }
844
+ async function getServerBackupMethod(tokenClient, accessToken, projectId, owner) {
845
+ return encryptionMethodOfStats(await getVaultRecoveryStats(tokenClient, accessToken, projectId, owner));
846
+ }
847
+ async function walletOfUnboundBasicShare(data, tokenClient, accessToken, projectId) {
848
+ try {
849
+ const envelope = await downloadShareFromVault(tokenClient, accessToken, projectId, "rewrap", data.ownerAddress);
850
+ if (envelope.encryptionMethod !== "server-managed") return "unknown";
851
+ if (envelope.ownerAddress) return sameOwner(envelope.ownerAddress, data.ownerAddress) ? "this" : "other";
852
+ const { data: stored } = await openBasicEnvelope(tokenClient, envelope, accessToken, projectId);
853
+ if (stored.userId !== data.userId || !stored.ownerAddress) return "unknown";
854
+ const bound = await bindShareToWallet(tokenClient, envelope, stored.ownerAddress, accessToken, projectId, data.ownerAddress);
855
+ if (sameOwner(stored.ownerAddress, data.ownerAddress)) return "this";
856
+ return bound ? "other" : "other-unbound";
857
+ } catch (error) {
858
+ console.warn("[iframe][server-backup] Basic backup of unknown wallet not checked:", errorMessage(error));
859
+ return "unknown";
860
+ }
861
+ }
862
+ async function ensureServerManagedBackup(data, tokenClient, accessToken, projectId, options = {}) {
863
+ const owner = data?.ownerAddress ?? options.owner;
864
+ for (let attempt = 0; ; attempt++) {
865
+ let stats;
866
+ try {
867
+ stats = await getVaultRecoveryStats(tokenClient, accessToken, projectId, owner);
868
+ } catch (error) {
869
+ return { status: "failed", reason: "vault_unavailable", error: errorMessage(error) };
870
+ }
871
+ const existing = encryptionMethodOfStats(stats);
872
+ if (!existing) break;
873
+ const unbound = stats?.ownerAddress === null;
874
+ if (!unbound || existing !== "server-managed" || options.allowCreate === false || !data) {
875
+ return { status: "exists", encryptionMethod: existing };
876
+ }
877
+ const wallet = await walletOfUnboundBasicShare(data, tokenClient, accessToken, projectId);
878
+ if (wallet === "other") {
879
+ console.warn("[iframe][server-backup] The vault backup belongs to another wallet of this user: creating one for", data.ownerAddress);
880
+ break;
881
+ }
882
+ if (wallet === "other-unbound") {
883
+ if (attempt === 0) continue;
884
+ return { status: "failed", reason: "vault_unavailable", otherWallet: true, error: "The vault backup belongs to another wallet and could not be bound to it" };
885
+ }
886
+ return { status: "exists", encryptionMethod: existing };
887
+ }
888
+ if (options.allowCreate === false) {
889
+ return { status: "failed", reason: "disabled", error: "Basic backups need an iframe session" };
890
+ }
891
+ if (!data) return { status: "failed", reason: "no_keyshare" };
892
+ let built;
893
+ try {
894
+ built = await envelopeEncryptKeyshareBasic(data, (dek2) => wrapRecoveryDek(tokenClient, dek2, accessToken, projectId));
895
+ } catch (error) {
896
+ const disabled = error instanceof BasicRecoveryError && error.disabled;
897
+ return { status: "failed", reason: disabled ? "disabled" : "kek_unavailable", error: errorMessage(error) };
898
+ }
899
+ const { envelope, dek } = built;
900
+ try {
901
+ const roundTrip = await envelopeDecryptKeyshareWithDek(envelope, dek);
902
+ if (roundTrip.keyshare !== data.keyshare || roundTrip.userId !== data.userId) {
903
+ return { status: "failed", reason: "unknown", error: "Backup self-check failed" };
904
+ }
905
+ let unwrapped;
906
+ try {
907
+ unwrapped = await unwrapRecoveryDek(tokenClient, envelope.wrapped_dek, accessToken, projectId);
908
+ } catch (error) {
909
+ if (error instanceof BasicRecoveryError && error.status === 400) return { status: "failed", reason: "unknown", error: "The new wrapped DEK does not open" };
910
+ return { status: "failed", reason: "kek_unavailable", error: errorMessage(error) };
911
+ }
912
+ const opens = unwrapped.dek.length === dek.length && unwrapped.dek.every((byte, i) => byte === dek[i]);
913
+ unwrapped.dek.fill(0);
914
+ if (!opens) return { status: "failed", reason: "unknown", error: "The new wrapped DEK does not open" };
915
+ for (let attempt = 0; attempt < 2; attempt++) {
916
+ if (await uploadShareToVaultCreateOnly(tokenClient, withOwner(envelope, data), accessToken, projectId)) {
917
+ return { status: "created", encryptionMethod: "server-managed" };
918
+ }
919
+ const method = await getServerBackupMethod(tokenClient, accessToken, projectId, data.ownerAddress).catch(() => null);
920
+ if (method) return { status: "exists", encryptionMethod: method };
921
+ }
922
+ return { status: "failed", reason: "vault_unavailable", error: "The vault refused the backup of this wallet" };
923
+ } catch (error) {
924
+ if (error instanceof VaultError && error.status === 400) {
925
+ return { status: "failed", reason: "disabled", error: errorMessage(error) };
926
+ }
927
+ return { status: "failed", reason: error instanceof VaultError ? "vault_unavailable" : "unknown", error: errorMessage(error) };
928
+ } finally {
929
+ dek.fill(0);
930
+ }
931
+ }
932
+ async function uploadShareToVaultCreateOnly(tokenClient, envelope, accessToken, projectId) {
933
+ const token = await getShareVaultToken(tokenClient, ["share:put"], accessToken, projectId);
934
+ const response = await fetch(shareUrl("", envelope.ownerAddress), {
935
+ method: "PUT",
936
+ headers: {
937
+ "Content-Type": "application/json",
938
+ Authorization: `Bearer ${token}`,
939
+ "Idempotency-Key": idempotencyKey("basic-backup"),
940
+ "If-None-Match": "*"
941
+ },
942
+ body: JSON.stringify(envelope)
943
+ });
944
+ if (response.status === 412) return false;
945
+ if (!response.ok) throw new VaultError(response.status, `Failed to upload share: ${response.status} ${await response.text()}`);
946
+ return true;
947
+ }
948
+ async function upgradeServerManagedBackup(data, protection, tokenClient, accessToken, projectId) {
949
+ const envelope = await downloadShareFromVault(tokenClient, accessToken, projectId, "rewrap", data.ownerAddress);
950
+ if (envelope.encryptionMethod !== "server-managed") return { kind: "not-applicable", reason: "not a basic backup", etag: envelope.etag };
951
+ let stored;
952
+ try {
953
+ ;
954
+ ({ data: stored } = await openBasicEnvelope(tokenClient, envelope, accessToken, projectId));
955
+ } catch (error) {
956
+ if (error instanceof BasicRecoveryError && !error.disabled && error.status !== 400) throw error;
957
+ return { kind: "not-applicable", reason: `basic backup cannot be opened: ${error?.message ?? error}`, etag: envelope.etag };
958
+ }
959
+ if (stored.userId === data.userId && stored.ownerAddress && !sameOwner(stored.ownerAddress, data.ownerAddress)) {
960
+ if (!envelope.ownerAddress) await bindShareToWallet(tokenClient, envelope, stored.ownerAddress, accessToken, projectId, data.ownerAddress);
961
+ return { kind: "not-applicable", reason: "vault backup belongs to another wallet" };
962
+ }
963
+ if (stored.userId !== data.userId || stored.keyshare !== data.keyshare) {
964
+ return { kind: "not-applicable", reason: "vault backup does not match the local keyshare", etag: envelope.etag };
965
+ }
966
+ const protectedEnvelope = protection.method === "password" ? await envelopeEncryptKeyshareWithPassword(data, protection.password) : await envelopeEncryptKeyshareWithPasskeyKek(data, protection.kek, protection.credentialId);
967
+ const roundTrip = protection.method === "password" ? await envelopeDecryptKeyshareWithPassword(protectedEnvelope, protection.password) : await envelopeDecryptKeyshareWithKek(protectedEnvelope, protection.kek);
968
+ if (roundTrip.keyshare !== data.keyshare || roundTrip.userId !== data.userId) throw new Error("Backup self-check failed");
969
+ await replaceShareInVault(tokenClient, withOwner(protectedEnvelope, data), envelope.etag, accessToken, projectId);
970
+ return { kind: "upgraded", encryptionMethod: protection.method };
971
+ }
972
+ async function replaceShareInVault(tokenClient, envelope, etag, accessToken, projectId) {
973
+ const token = await getShareVaultToken(tokenClient, ["share:put"], accessToken, projectId);
974
+ const response = await fetch(shareUrl("", envelope.ownerAddress), {
975
+ method: "PUT",
976
+ headers: {
977
+ "Content-Type": "application/json",
978
+ Authorization: `Bearer ${token}`,
979
+ "Idempotency-Key": idempotencyKey("upgrade"),
980
+ ...etag ? { "If-Match": etag } : {}
981
+ },
982
+ body: JSON.stringify(envelope)
983
+ });
984
+ if (response.status === 412) throw new VaultError(412, "The backup changed meanwhile, please try again");
985
+ if (!response.ok) throw new VaultError(response.status, `Failed to upload share: ${response.status} ${await response.text()}`);
986
+ }
987
+ function idempotencyKey(prefix) {
988
+ return crypto.randomUUID ? crypto.randomUUID() : `${prefix}-${Date.now()}-${Math.random()}`;
989
+ }
990
+ function errorMessage(error) {
991
+ return error instanceof Error ? error.message : String(error);
992
+ }
993
+ var SHARE_VAULT_URL, WalletMismatchError, WALLET_MISMATCH, RECOVERY_KEK_UNAVAILABLE, IFRAME_SESSION_REQUIRED, BASIC_BACKUP_NEEDS_NEWER_APP, BasicRecoveryError, VaultError;
572
994
  var init_server_backup = __esm({
573
995
  "src/iframe/lib/backup/server-backup.ts"() {
574
996
  init_crypto_utils();
575
997
  SHARE_VAULT_URL = "https://api.embarkai.io/vault";
998
+ WalletMismatchError = class extends Error {
999
+ constructor(backup) {
1000
+ super(`${WALLET_MISMATCH}: the server backup belongs to another wallet (${backup.ownerAddress})`);
1001
+ __publicField(this, "backup", backup);
1002
+ this.name = "WalletMismatchError";
1003
+ }
1004
+ };
1005
+ WALLET_MISMATCH = "WALLET_MISMATCH";
1006
+ RECOVERY_KEK_UNAVAILABLE = "RECOVERY_KEK_UNAVAILABLE";
1007
+ IFRAME_SESSION_REQUIRED = "iframe_session_required";
1008
+ BASIC_BACKUP_NEEDS_NEWER_APP = "This wallet is backed up by embarkAI and can be restored only in apps that use the latest embarkAI wallet. Sign in to such an app, or restore from a backup file.";
1009
+ BasicRecoveryError = class extends Error {
1010
+ constructor(disabled, detail, status) {
1011
+ super(`${RECOVERY_KEK_UNAVAILABLE}: ${detail}`);
1012
+ __publicField(this, "disabled", disabled);
1013
+ __publicField(this, "status", status);
1014
+ this.name = "BasicRecoveryError";
1015
+ }
1016
+ };
1017
+ VaultError = class extends Error {
1018
+ constructor(status, message) {
1019
+ super(message);
1020
+ __publicField(this, "status", status);
1021
+ this.name = "VaultError";
1022
+ }
1023
+ };
1024
+ }
1025
+ });
1026
+
1027
+ // ../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/regex.js
1028
+ function execTyped(regex, string) {
1029
+ const match = regex.exec(string);
1030
+ return match?.groups;
1031
+ }
1032
+ var init_regex = __esm({
1033
+ "../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/regex.js"() {
1034
+ }
1035
+ });
1036
+
1037
+ // ../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/human-readable/formatAbiParameter.js
1038
+ function formatAbiParameter(abiParameter) {
1039
+ let type = abiParameter.type;
1040
+ if (tupleRegex.test(abiParameter.type) && "components" in abiParameter) {
1041
+ type = "(";
1042
+ const length = abiParameter.components.length;
1043
+ for (let i = 0; i < length; i++) {
1044
+ const component = abiParameter.components[i];
1045
+ type += formatAbiParameter(component);
1046
+ if (i < length - 1)
1047
+ type += ", ";
1048
+ }
1049
+ const result = execTyped(tupleRegex, abiParameter.type);
1050
+ type += `)${result?.array || ""}`;
1051
+ return formatAbiParameter({
1052
+ ...abiParameter,
1053
+ type
1054
+ });
1055
+ }
1056
+ if ("indexed" in abiParameter && abiParameter.indexed)
1057
+ type = `${type} indexed`;
1058
+ if (abiParameter.name)
1059
+ return `${type} ${abiParameter.name}`;
1060
+ return type;
1061
+ }
1062
+ var tupleRegex;
1063
+ var init_formatAbiParameter = __esm({
1064
+ "../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/human-readable/formatAbiParameter.js"() {
1065
+ init_regex();
1066
+ tupleRegex = /^tuple(?<array>(\[(\d*)\])*)$/;
1067
+ }
1068
+ });
1069
+
1070
+ // ../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/human-readable/formatAbiParameters.js
1071
+ function formatAbiParameters(abiParameters) {
1072
+ let params = "";
1073
+ const length = abiParameters.length;
1074
+ for (let i = 0; i < length; i++) {
1075
+ const abiParameter = abiParameters[i];
1076
+ params += formatAbiParameter(abiParameter);
1077
+ if (i !== length - 1)
1078
+ params += ", ";
1079
+ }
1080
+ return params;
1081
+ }
1082
+ var init_formatAbiParameters = __esm({
1083
+ "../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/human-readable/formatAbiParameters.js"() {
1084
+ init_formatAbiParameter();
1085
+ }
1086
+ });
1087
+
1088
+ // ../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/human-readable/formatAbiItem.js
1089
+ function formatAbiItem(abiItem) {
1090
+ if (abiItem.type === "function")
1091
+ return `function ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})${abiItem.stateMutability && abiItem.stateMutability !== "nonpayable" ? ` ${abiItem.stateMutability}` : ""}${abiItem.outputs?.length ? ` returns (${formatAbiParameters(abiItem.outputs)})` : ""}`;
1092
+ if (abiItem.type === "event")
1093
+ return `event ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})`;
1094
+ if (abiItem.type === "error")
1095
+ return `error ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})`;
1096
+ if (abiItem.type === "constructor")
1097
+ return `constructor(${formatAbiParameters(abiItem.inputs)})${abiItem.stateMutability === "payable" ? " payable" : ""}`;
1098
+ if (abiItem.type === "fallback")
1099
+ return `fallback() external${abiItem.stateMutability === "payable" ? " payable" : ""}`;
1100
+ return "receive() external payable";
1101
+ }
1102
+ var init_formatAbiItem = __esm({
1103
+ "../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/human-readable/formatAbiItem.js"() {
1104
+ init_formatAbiParameters();
1105
+ }
1106
+ });
1107
+
1108
+ // ../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/exports/index.js
1109
+ var init_exports = __esm({
1110
+ "../../node_modules/.pnpm/abitype@1.2.3_typescript@6.0.3_zod@4.6.7/node_modules/abitype/dist/esm/exports/index.js"() {
1111
+ init_formatAbiItem();
1112
+ }
1113
+ });
1114
+
1115
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/formatAbiItem.js
1116
+ function formatAbiItem2(abiItem, { includeName = false } = {}) {
1117
+ if (abiItem.type !== "function" && abiItem.type !== "event" && abiItem.type !== "error")
1118
+ throw new InvalidDefinitionTypeError(abiItem.type);
1119
+ return `${abiItem.name}(${formatAbiParams(abiItem.inputs, { includeName })})`;
1120
+ }
1121
+ function formatAbiParams(params, { includeName = false } = {}) {
1122
+ if (!params)
1123
+ return "";
1124
+ return params.map((param) => formatAbiParam(param, { includeName })).join(includeName ? ", " : ",");
1125
+ }
1126
+ function formatAbiParam(param, { includeName }) {
1127
+ if (param.type.startsWith("tuple")) {
1128
+ return `(${formatAbiParams(param.components, { includeName })})${param.type.slice("tuple".length)}`;
1129
+ }
1130
+ return param.type + (includeName && param.name ? ` ${param.name}` : "");
1131
+ }
1132
+ var init_formatAbiItem2 = __esm({
1133
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/formatAbiItem.js"() {
1134
+ init_abi();
576
1135
  }
577
1136
  });
578
1137
 
@@ -701,11 +1260,93 @@ var init_base = __esm({
701
1260
  }
702
1261
  });
703
1262
 
1263
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/abi.js
1264
+ var AbiDecodingDataSizeTooSmallError, AbiDecodingZeroDataError, AbiFunctionSignatureNotFoundError, InvalidAbiDecodingTypeError, InvalidDefinitionTypeError;
1265
+ var init_abi = __esm({
1266
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/abi.js"() {
1267
+ init_formatAbiItem2();
1268
+ init_base();
1269
+ AbiDecodingDataSizeTooSmallError = class extends BaseError {
1270
+ constructor({ data, params, size: size2 }) {
1271
+ super([`Data size of ${size2} bytes is too small for given parameters.`].join("\n"), {
1272
+ metaMessages: [
1273
+ `Params: (${formatAbiParams(params, { includeName: true })})`,
1274
+ `Data: ${data} (${size2} bytes)`
1275
+ ],
1276
+ name: "AbiDecodingDataSizeTooSmallError"
1277
+ });
1278
+ Object.defineProperty(this, "data", {
1279
+ enumerable: true,
1280
+ configurable: true,
1281
+ writable: true,
1282
+ value: void 0
1283
+ });
1284
+ Object.defineProperty(this, "params", {
1285
+ enumerable: true,
1286
+ configurable: true,
1287
+ writable: true,
1288
+ value: void 0
1289
+ });
1290
+ Object.defineProperty(this, "size", {
1291
+ enumerable: true,
1292
+ configurable: true,
1293
+ writable: true,
1294
+ value: void 0
1295
+ });
1296
+ this.data = data;
1297
+ this.params = params;
1298
+ this.size = size2;
1299
+ }
1300
+ };
1301
+ AbiDecodingZeroDataError = class extends BaseError {
1302
+ constructor({ cause } = {}) {
1303
+ super('Cannot decode zero data ("0x") with ABI parameters.', {
1304
+ name: "AbiDecodingZeroDataError",
1305
+ cause
1306
+ });
1307
+ }
1308
+ };
1309
+ AbiFunctionSignatureNotFoundError = class extends BaseError {
1310
+ constructor(signature, { docsPath }) {
1311
+ super([
1312
+ `Encoded function signature "${signature}" not found on ABI.`,
1313
+ "Make sure you are using the correct ABI and that the function exists on it.",
1314
+ `You can look up the signature here: https://4byte.sourcify.dev/?q=${signature}.`
1315
+ ].join("\n"), {
1316
+ docsPath,
1317
+ name: "AbiFunctionSignatureNotFoundError"
1318
+ });
1319
+ }
1320
+ };
1321
+ InvalidAbiDecodingTypeError = class extends BaseError {
1322
+ constructor(type, { docsPath }) {
1323
+ super([
1324
+ `Type "${type}" is not a valid decoding type.`,
1325
+ "Please provide a valid ABI type."
1326
+ ].join("\n"), { docsPath, name: "InvalidAbiDecodingType" });
1327
+ }
1328
+ };
1329
+ InvalidDefinitionTypeError = class extends BaseError {
1330
+ constructor(type) {
1331
+ super([
1332
+ `"${type}" is not a valid definition type.`,
1333
+ 'Valid types: "function", "event", "error"'
1334
+ ].join("\n"), { name: "InvalidDefinitionTypeError" });
1335
+ }
1336
+ };
1337
+ }
1338
+ });
1339
+
704
1340
  // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/data.js
705
- var SizeExceedsPaddingSizeError;
1341
+ var SliceOffsetOutOfBoundsError, SizeExceedsPaddingSizeError;
706
1342
  var init_data = __esm({
707
1343
  "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/data.js"() {
708
1344
  init_base();
1345
+ SliceOffsetOutOfBoundsError = class extends BaseError {
1346
+ constructor({ offset, position, size: size2 }) {
1347
+ super(`Slice ${position === "start" ? "starting" : "ending"} at offset "${offset}" is out-of-bounds (size: ${size2}).`, { name: "SliceOffsetOutOfBoundsError" });
1348
+ }
1349
+ };
709
1350
  SizeExceedsPaddingSizeError = class extends BaseError {
710
1351
  constructor({ size: size2, targetSize, type }) {
711
1352
  super(`${type.charAt(0).toUpperCase()}${type.slice(1).toLowerCase()} size (${size2}) exceeds padding size (${targetSize}).`, { name: "SizeExceedsPaddingSizeError" });
@@ -755,7 +1396,7 @@ var init_pad = __esm({
755
1396
  });
756
1397
 
757
1398
  // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/encoding.js
758
- var IntegerOutOfRangeError, SizeOverflowError;
1399
+ var IntegerOutOfRangeError, InvalidBytesBooleanError, SizeOverflowError;
759
1400
  var init_encoding = __esm({
760
1401
  "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/encoding.js"() {
761
1402
  init_base();
@@ -764,6 +1405,13 @@ var init_encoding = __esm({
764
1405
  super(`Number "${value}" is not in safe ${size2 ? `${size2 * 8}-bit ${signed ? "signed" : "unsigned"} ` : ""}integer range ${max ? `(${min} to ${max})` : `(above ${min})`}`, { name: "IntegerOutOfRangeError" });
765
1406
  }
766
1407
  };
1408
+ InvalidBytesBooleanError = class extends BaseError {
1409
+ constructor(bytes) {
1410
+ super(`Bytes value "${bytes}" is not a valid boolean. The bytes array must contain a single byte of either a 0 or 1 value.`, {
1411
+ name: "InvalidBytesBooleanError"
1412
+ });
1413
+ }
1414
+ };
767
1415
  SizeOverflowError = class extends BaseError {
768
1416
  constructor({ givenSize, maxSize }) {
769
1417
  super(`Size cannot exceed ${maxSize} bytes. Given size: ${givenSize} bytes.`, { name: "SizeOverflowError" });
@@ -772,6 +1420,29 @@ var init_encoding = __esm({
772
1420
  }
773
1421
  });
774
1422
 
1423
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/data/trim.js
1424
+ function trim(hexOrBytes, { dir = "left" } = {}) {
1425
+ let data = typeof hexOrBytes === "string" ? hexOrBytes.replace("0x", "") : hexOrBytes;
1426
+ let sliceLength = 0;
1427
+ for (let i = 0; i < data.length - 1; i++) {
1428
+ if (data[dir === "left" ? i : data.length - i - 1].toString() === "0")
1429
+ sliceLength++;
1430
+ else
1431
+ break;
1432
+ }
1433
+ data = dir === "left" ? data.slice(sliceLength) : data.slice(0, data.length - sliceLength);
1434
+ if (typeof hexOrBytes === "string") {
1435
+ if (data.length === 1 && dir === "right")
1436
+ data = `${data}0`;
1437
+ return `0x${data.length % 2 === 1 ? `0${data}` : data}`;
1438
+ }
1439
+ return data;
1440
+ }
1441
+ var init_trim = __esm({
1442
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/data/trim.js"() {
1443
+ }
1444
+ });
1445
+
775
1446
  // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/encoding/fromHex.js
776
1447
  function assertSize(hexOrBytes, { size: size2 }) {
777
1448
  if (size(hexOrBytes) > size2)
@@ -780,6 +1451,32 @@ function assertSize(hexOrBytes, { size: size2 }) {
780
1451
  maxSize: size2
781
1452
  });
782
1453
  }
1454
+ function hexToBigInt(hex, opts = {}) {
1455
+ const { signed } = opts;
1456
+ if (opts.size)
1457
+ assertSize(hex, { size: opts.size });
1458
+ const value = BigInt(hex);
1459
+ if (!signed)
1460
+ return value;
1461
+ const size2 = Math.ceil((hex.length - 2) / 2);
1462
+ const max = (1n << BigInt(size2) * 8n - 1n) - 1n;
1463
+ if (value <= max)
1464
+ return value;
1465
+ return value - BigInt(`0x${"f".padStart(size2 * 2, "f")}`) - 1n;
1466
+ }
1467
+ function hexToNumber(hex, opts = {}) {
1468
+ const value = hexToBigInt(hex, opts);
1469
+ const number = Number(value);
1470
+ if (!Number.isSafeInteger(number))
1471
+ throw new IntegerOutOfRangeError({
1472
+ max: `${Number.MAX_SAFE_INTEGER}`,
1473
+ min: `${Number.MIN_SAFE_INTEGER}`,
1474
+ signed: opts.signed,
1475
+ size: opts.size,
1476
+ value: `${value}n`
1477
+ });
1478
+ return number;
1479
+ }
783
1480
  var init_fromHex = __esm({
784
1481
  "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/encoding/fromHex.js"() {
785
1482
  init_encoding();
@@ -1248,26 +1945,1116 @@ var init_keccak256 = __esm({
1248
1945
  }
1249
1946
  });
1250
1947
 
1251
- // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_freeGlobal.js
1252
- var freeGlobal = typeof global == "object" && global && global.Object === Object && global;
1253
- var freeGlobal_default = freeGlobal;
1948
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/hashSignature.js
1949
+ function hashSignature(sig) {
1950
+ return hash(sig);
1951
+ }
1952
+ var hash;
1953
+ var init_hashSignature = __esm({
1954
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/hashSignature.js"() {
1955
+ init_toBytes();
1956
+ init_keccak256();
1957
+ hash = (value) => keccak256(toBytes(value));
1958
+ }
1959
+ });
1254
1960
 
1255
- // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_root.js
1256
- var freeSelf = typeof self == "object" && self && self.Object === Object && self;
1257
- var root = freeGlobal_default || freeSelf || Function("return this")();
1258
- var root_default = root;
1961
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/normalizeSignature.js
1962
+ function normalizeSignature(signature) {
1963
+ let active = true;
1964
+ let current = "";
1965
+ let level = 0;
1966
+ let result = "";
1967
+ let valid = false;
1968
+ for (let i = 0; i < signature.length; i++) {
1969
+ const char = signature[i];
1970
+ if (["(", ")", ","].includes(char))
1971
+ active = true;
1972
+ if (char === "(")
1973
+ level++;
1974
+ if (char === ")")
1975
+ level--;
1976
+ if (!active)
1977
+ continue;
1978
+ if (level === 0) {
1979
+ if (char === " " && ["event", "function", "error", ""].includes(result))
1980
+ result = "";
1981
+ else {
1982
+ result += char;
1983
+ if (char === ")") {
1984
+ valid = true;
1985
+ break;
1986
+ }
1987
+ }
1988
+ continue;
1989
+ }
1990
+ if (char === " ") {
1991
+ if (!current.endsWith(",") && !current.endsWith("(")) {
1992
+ current = "";
1993
+ active = false;
1994
+ }
1995
+ continue;
1996
+ }
1997
+ result += char;
1998
+ current += char;
1999
+ }
2000
+ if (!valid)
2001
+ throw new BaseError("Unable to normalize signature.");
2002
+ return result;
2003
+ }
2004
+ var init_normalizeSignature = __esm({
2005
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/normalizeSignature.js"() {
2006
+ init_base();
2007
+ }
2008
+ });
1259
2009
 
1260
- // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_Symbol.js
1261
- var Symbol2 = root_default.Symbol;
1262
- var Symbol_default = Symbol2;
2010
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/toSignature.js
2011
+ var toSignature;
2012
+ var init_toSignature = __esm({
2013
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/toSignature.js"() {
2014
+ init_exports();
2015
+ init_normalizeSignature();
2016
+ toSignature = (def) => {
2017
+ const def_ = (() => {
2018
+ if (typeof def === "string")
2019
+ return def;
2020
+ return formatAbiItem(def);
2021
+ })();
2022
+ return normalizeSignature(def_);
2023
+ };
2024
+ }
2025
+ });
1263
2026
 
1264
- // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_getRawTag.js
1265
- var objectProto = Object.prototype;
1266
- var hasOwnProperty = objectProto.hasOwnProperty;
1267
- var nativeObjectToString = objectProto.toString;
1268
- var symToStringTag = Symbol_default ? Symbol_default.toStringTag : void 0;
1269
- function getRawTag(value) {
1270
- var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
2027
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/toSignatureHash.js
2028
+ function toSignatureHash(fn) {
2029
+ return hashSignature(toSignature(fn));
2030
+ }
2031
+ var init_toSignatureHash = __esm({
2032
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/toSignatureHash.js"() {
2033
+ init_hashSignature();
2034
+ init_toSignature();
2035
+ }
2036
+ });
2037
+
2038
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/lru.js
2039
+ var LruMap;
2040
+ var init_lru = __esm({
2041
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/lru.js"() {
2042
+ LruMap = class extends Map {
2043
+ constructor(size2) {
2044
+ super();
2045
+ Object.defineProperty(this, "maxSize", {
2046
+ enumerable: true,
2047
+ configurable: true,
2048
+ writable: true,
2049
+ value: void 0
2050
+ });
2051
+ this.maxSize = size2;
2052
+ }
2053
+ get(key) {
2054
+ const value = super.get(key);
2055
+ if (super.has(key)) {
2056
+ super.delete(key);
2057
+ super.set(key, value);
2058
+ }
2059
+ return value;
2060
+ }
2061
+ set(key, value) {
2062
+ if (super.has(key))
2063
+ super.delete(key);
2064
+ super.set(key, value);
2065
+ if (this.maxSize && this.size > this.maxSize) {
2066
+ const firstKey = super.keys().next().value;
2067
+ if (firstKey !== void 0)
2068
+ super.delete(firstKey);
2069
+ }
2070
+ return this;
2071
+ }
2072
+ };
2073
+ }
2074
+ });
2075
+
2076
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/address/getAddress.js
2077
+ function checksumAddress(address_, chainId) {
2078
+ if (checksumAddressCache.has(`${address_}.${chainId}`))
2079
+ return checksumAddressCache.get(`${address_}.${chainId}`);
2080
+ const hexAddress = chainId ? `${chainId}${address_.toLowerCase()}` : address_.substring(2).toLowerCase();
2081
+ const hash2 = keccak256(stringToBytes(hexAddress), "bytes");
2082
+ const address = (chainId ? hexAddress.substring(`${chainId}0x`.length) : hexAddress).split("");
2083
+ for (let i = 0; i < 40; i += 2) {
2084
+ if (hash2[i >> 1] >> 4 >= 8 && address[i]) {
2085
+ address[i] = address[i].toUpperCase();
2086
+ }
2087
+ if ((hash2[i >> 1] & 15) >= 8 && address[i + 1]) {
2088
+ address[i + 1] = address[i + 1].toUpperCase();
2089
+ }
2090
+ }
2091
+ const result = `0x${address.join("")}`;
2092
+ checksumAddressCache.set(`${address_}.${chainId}`, result);
2093
+ return result;
2094
+ }
2095
+ var checksumAddressCache;
2096
+ var init_getAddress = __esm({
2097
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/address/getAddress.js"() {
2098
+ init_toBytes();
2099
+ init_keccak256();
2100
+ init_lru();
2101
+ checksumAddressCache = /* @__PURE__ */ new LruMap(8192);
2102
+ }
2103
+ });
2104
+
2105
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/data/slice.js
2106
+ function slice(value, start, end, { strict } = {}) {
2107
+ if (isHex(value, { strict: false }))
2108
+ return sliceHex(value, start, end, {
2109
+ strict
2110
+ });
2111
+ return sliceBytes(value, start, end, {
2112
+ strict
2113
+ });
2114
+ }
2115
+ function assertStartOffset(value, start) {
2116
+ if (typeof start === "number" && start > 0 && start > size(value) - 1)
2117
+ throw new SliceOffsetOutOfBoundsError({
2118
+ offset: start,
2119
+ position: "start",
2120
+ size: size(value)
2121
+ });
2122
+ }
2123
+ function assertEndOffset(value, start, end) {
2124
+ if (typeof start === "number" && typeof end === "number" && size(value) !== end - start) {
2125
+ throw new SliceOffsetOutOfBoundsError({
2126
+ offset: end,
2127
+ position: "end",
2128
+ size: size(value)
2129
+ });
2130
+ }
2131
+ }
2132
+ function sliceBytes(value_, start, end, { strict } = {}) {
2133
+ assertStartOffset(value_, start);
2134
+ const value = value_.slice(start, end);
2135
+ if (strict)
2136
+ assertEndOffset(value, start, end);
2137
+ return value;
2138
+ }
2139
+ function sliceHex(value_, start, end, { strict } = {}) {
2140
+ assertStartOffset(value_, start);
2141
+ const value = `0x${value_.replace("0x", "").slice((start ?? 0) * 2, (end ?? value_.length) * 2)}`;
2142
+ if (strict)
2143
+ assertEndOffset(value, start, end);
2144
+ return value;
2145
+ }
2146
+ var init_slice = __esm({
2147
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/data/slice.js"() {
2148
+ init_data();
2149
+ init_isHex();
2150
+ init_size();
2151
+ }
2152
+ });
2153
+
2154
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/encodeAbiParameters.js
2155
+ function getArrayComponents(type) {
2156
+ const matches = type.match(/^(.*)\[(\d+)?\]$/);
2157
+ return matches ? (
2158
+ // Return `null` if the array is dynamic.
2159
+ [matches[2] ? Number(matches[2]) : null, matches[1]]
2160
+ ) : void 0;
2161
+ }
2162
+ var init_encodeAbiParameters = __esm({
2163
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/encodeAbiParameters.js"() {
2164
+ }
2165
+ });
2166
+
2167
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/toFunctionSelector.js
2168
+ var toFunctionSelector;
2169
+ var init_toFunctionSelector = __esm({
2170
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/hash/toFunctionSelector.js"() {
2171
+ init_slice();
2172
+ init_toSignatureHash();
2173
+ toFunctionSelector = (fn) => slice(toSignatureHash(fn), 0, 4);
2174
+ }
2175
+ });
2176
+
2177
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/cursor.js
2178
+ var NegativeOffsetError, PositionOutOfBoundsError, RecursiveReadLimitExceededError;
2179
+ var init_cursor = __esm({
2180
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/errors/cursor.js"() {
2181
+ init_base();
2182
+ NegativeOffsetError = class extends BaseError {
2183
+ constructor({ offset }) {
2184
+ super(`Offset \`${offset}\` cannot be negative.`, {
2185
+ name: "NegativeOffsetError"
2186
+ });
2187
+ }
2188
+ };
2189
+ PositionOutOfBoundsError = class extends BaseError {
2190
+ constructor({ length, position }) {
2191
+ super(`Position \`${position}\` is out of bounds (\`0 < position < ${length}\`).`, { name: "PositionOutOfBoundsError" });
2192
+ }
2193
+ };
2194
+ RecursiveReadLimitExceededError = class extends BaseError {
2195
+ constructor({ count, limit }) {
2196
+ super(`Recursive read limit of \`${limit}\` exceeded (recursive read count: \`${count}\`).`, { name: "RecursiveReadLimitExceededError" });
2197
+ }
2198
+ };
2199
+ }
2200
+ });
2201
+
2202
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/cursor.js
2203
+ function createCursor(bytes, { recursiveReadLimit = 8192 } = {}) {
2204
+ const cursor = Object.create(staticCursor);
2205
+ cursor.bytes = bytes;
2206
+ cursor.dataView = new DataView(bytes.buffer ?? bytes, bytes.byteOffset, bytes.byteLength);
2207
+ cursor.positionReadCount = /* @__PURE__ */ new Map();
2208
+ cursor.recursiveReadLimit = recursiveReadLimit;
2209
+ return cursor;
2210
+ }
2211
+ var staticCursor;
2212
+ var init_cursor2 = __esm({
2213
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/cursor.js"() {
2214
+ init_cursor();
2215
+ staticCursor = {
2216
+ bytes: new Uint8Array(),
2217
+ dataView: new DataView(new ArrayBuffer(0)),
2218
+ position: 0,
2219
+ positionReadCount: /* @__PURE__ */ new Map(),
2220
+ recursiveReadCount: 0,
2221
+ recursiveReadLimit: Number.POSITIVE_INFINITY,
2222
+ assertReadLimit() {
2223
+ if (this.recursiveReadCount >= this.recursiveReadLimit)
2224
+ throw new RecursiveReadLimitExceededError({
2225
+ count: this.recursiveReadCount + 1,
2226
+ limit: this.recursiveReadLimit
2227
+ });
2228
+ },
2229
+ assertPosition(position) {
2230
+ if (position < 0 || position > this.bytes.length - 1)
2231
+ throw new PositionOutOfBoundsError({
2232
+ length: this.bytes.length,
2233
+ position
2234
+ });
2235
+ },
2236
+ decrementPosition(offset) {
2237
+ if (offset < 0)
2238
+ throw new NegativeOffsetError({ offset });
2239
+ const position = this.position - offset;
2240
+ this.assertPosition(position);
2241
+ this.position = position;
2242
+ },
2243
+ getReadCount(position) {
2244
+ return this.positionReadCount.get(position || this.position) || 0;
2245
+ },
2246
+ incrementPosition(offset) {
2247
+ if (offset < 0)
2248
+ throw new NegativeOffsetError({ offset });
2249
+ const position = this.position + offset;
2250
+ this.assertPosition(position);
2251
+ this.position = position;
2252
+ },
2253
+ inspectByte(position_) {
2254
+ const position = position_ ?? this.position;
2255
+ this.assertPosition(position);
2256
+ return this.bytes[position];
2257
+ },
2258
+ inspectBytes(length, position_) {
2259
+ const position = position_ ?? this.position;
2260
+ this.assertPosition(position + length - 1);
2261
+ return this.bytes.subarray(position, position + length);
2262
+ },
2263
+ inspectUint8(position_) {
2264
+ const position = position_ ?? this.position;
2265
+ this.assertPosition(position);
2266
+ return this.bytes[position];
2267
+ },
2268
+ inspectUint16(position_) {
2269
+ const position = position_ ?? this.position;
2270
+ this.assertPosition(position + 1);
2271
+ return this.dataView.getUint16(position);
2272
+ },
2273
+ inspectUint24(position_) {
2274
+ const position = position_ ?? this.position;
2275
+ this.assertPosition(position + 2);
2276
+ return (this.dataView.getUint16(position) << 8) + this.dataView.getUint8(position + 2);
2277
+ },
2278
+ inspectUint32(position_) {
2279
+ const position = position_ ?? this.position;
2280
+ this.assertPosition(position + 3);
2281
+ return this.dataView.getUint32(position);
2282
+ },
2283
+ pushByte(byte) {
2284
+ this.assertPosition(this.position);
2285
+ this.bytes[this.position] = byte;
2286
+ this.position++;
2287
+ },
2288
+ pushBytes(bytes) {
2289
+ this.assertPosition(this.position + bytes.length - 1);
2290
+ this.bytes.set(bytes, this.position);
2291
+ this.position += bytes.length;
2292
+ },
2293
+ pushUint8(value) {
2294
+ this.assertPosition(this.position);
2295
+ this.bytes[this.position] = value;
2296
+ this.position++;
2297
+ },
2298
+ pushUint16(value) {
2299
+ this.assertPosition(this.position + 1);
2300
+ this.dataView.setUint16(this.position, value);
2301
+ this.position += 2;
2302
+ },
2303
+ pushUint24(value) {
2304
+ this.assertPosition(this.position + 2);
2305
+ this.dataView.setUint16(this.position, value >> 8);
2306
+ this.dataView.setUint8(this.position + 2, value & ~4294967040);
2307
+ this.position += 3;
2308
+ },
2309
+ pushUint32(value) {
2310
+ this.assertPosition(this.position + 3);
2311
+ this.dataView.setUint32(this.position, value);
2312
+ this.position += 4;
2313
+ },
2314
+ readByte() {
2315
+ this.assertReadLimit();
2316
+ this._touch();
2317
+ const value = this.inspectByte();
2318
+ this.position++;
2319
+ return value;
2320
+ },
2321
+ readBytes(length, size2) {
2322
+ this.assertReadLimit();
2323
+ this._touch();
2324
+ const value = this.inspectBytes(length);
2325
+ this.position += size2 ?? length;
2326
+ return value;
2327
+ },
2328
+ readUint8() {
2329
+ this.assertReadLimit();
2330
+ this._touch();
2331
+ const value = this.inspectUint8();
2332
+ this.position += 1;
2333
+ return value;
2334
+ },
2335
+ readUint16() {
2336
+ this.assertReadLimit();
2337
+ this._touch();
2338
+ const value = this.inspectUint16();
2339
+ this.position += 2;
2340
+ return value;
2341
+ },
2342
+ readUint24() {
2343
+ this.assertReadLimit();
2344
+ this._touch();
2345
+ const value = this.inspectUint24();
2346
+ this.position += 3;
2347
+ return value;
2348
+ },
2349
+ readUint32() {
2350
+ this.assertReadLimit();
2351
+ this._touch();
2352
+ const value = this.inspectUint32();
2353
+ this.position += 4;
2354
+ return value;
2355
+ },
2356
+ get remaining() {
2357
+ return this.bytes.length - this.position;
2358
+ },
2359
+ setPosition(position) {
2360
+ const oldPosition = this.position;
2361
+ this.assertPosition(position);
2362
+ this.position = position;
2363
+ return () => this.position = oldPosition;
2364
+ },
2365
+ _touch() {
2366
+ if (this.recursiveReadLimit === Number.POSITIVE_INFINITY)
2367
+ return;
2368
+ const count = this.getReadCount();
2369
+ this.positionReadCount.set(this.position, count + 1);
2370
+ if (count > 0)
2371
+ this.recursiveReadCount++;
2372
+ }
2373
+ };
2374
+ }
2375
+ });
2376
+
2377
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/encoding/fromBytes.js
2378
+ function bytesToBigInt(bytes, opts = {}) {
2379
+ if (typeof opts.size !== "undefined")
2380
+ assertSize(bytes, { size: opts.size });
2381
+ const hex = bytesToHex2(bytes);
2382
+ return hexToBigInt(hex, opts);
2383
+ }
2384
+ function bytesToBool(bytes_, opts = {}) {
2385
+ let bytes = bytes_;
2386
+ if (typeof opts.size !== "undefined") {
2387
+ assertSize(bytes, { size: opts.size });
2388
+ bytes = trim(bytes);
2389
+ }
2390
+ if (bytes.length > 1 || bytes[0] > 1)
2391
+ throw new InvalidBytesBooleanError(bytes);
2392
+ return Boolean(bytes[0]);
2393
+ }
2394
+ function bytesToNumber(bytes, opts = {}) {
2395
+ if (typeof opts.size !== "undefined")
2396
+ assertSize(bytes, { size: opts.size });
2397
+ const hex = bytesToHex2(bytes);
2398
+ return hexToNumber(hex, opts);
2399
+ }
2400
+ function bytesToString(bytes_, opts = {}) {
2401
+ let bytes = bytes_;
2402
+ if (typeof opts.size !== "undefined") {
2403
+ assertSize(bytes, { size: opts.size });
2404
+ bytes = trim(bytes, { dir: "right" });
2405
+ }
2406
+ return new TextDecoder().decode(bytes);
2407
+ }
2408
+ var init_fromBytes = __esm({
2409
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/encoding/fromBytes.js"() {
2410
+ init_encoding();
2411
+ init_trim();
2412
+ init_fromHex();
2413
+ init_toHex();
2414
+ }
2415
+ });
2416
+
2417
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/decodeAbiParameters.js
2418
+ function decodeAbiParameters(params, data) {
2419
+ const bytes = typeof data === "string" ? hexToBytes(data) : data;
2420
+ const cursor = createCursor(bytes);
2421
+ if (size(bytes) === 0 && params.length > 0)
2422
+ throw new AbiDecodingZeroDataError();
2423
+ if (size(data) && size(data) < 32)
2424
+ throw new AbiDecodingDataSizeTooSmallError({
2425
+ data: typeof data === "string" ? data : bytesToHex2(data),
2426
+ params,
2427
+ size: size(data)
2428
+ });
2429
+ let consumed = 0;
2430
+ const values = [];
2431
+ for (let i = 0; i < params.length; ++i) {
2432
+ const param = params[i];
2433
+ if (consumed < bytes.length)
2434
+ cursor.setPosition(consumed);
2435
+ const [data2, consumed_] = decodeParameter(cursor, param, {
2436
+ staticPosition: 0
2437
+ });
2438
+ consumed += consumed_;
2439
+ values.push(data2);
2440
+ }
2441
+ return values;
2442
+ }
2443
+ function decodeParameter(cursor, param, { staticPosition }) {
2444
+ const arrayComponents = getArrayComponents(param.type);
2445
+ if (arrayComponents) {
2446
+ const [length, type] = arrayComponents;
2447
+ return decodeArray(cursor, { ...param, type }, { length, staticPosition });
2448
+ }
2449
+ if (param.type === "tuple")
2450
+ return decodeTuple(cursor, param, { staticPosition });
2451
+ if (param.type === "address")
2452
+ return decodeAddress(cursor);
2453
+ if (param.type === "bool")
2454
+ return decodeBool(cursor);
2455
+ if (param.type.startsWith("bytes"))
2456
+ return decodeBytes(cursor, param, { staticPosition });
2457
+ if (param.type.startsWith("uint") || param.type.startsWith("int"))
2458
+ return decodeNumber(cursor, param);
2459
+ if (param.type === "string")
2460
+ return decodeString(cursor, { staticPosition });
2461
+ throw new InvalidAbiDecodingTypeError(param.type, {
2462
+ docsPath: "/docs/contract/decodeAbiParameters"
2463
+ });
2464
+ }
2465
+ function decodeAddress(cursor) {
2466
+ const value = cursor.readBytes(32);
2467
+ return [checksumAddress(bytesToHex2(sliceBytes(value, -20))), 32];
2468
+ }
2469
+ function decodeArray(cursor, param, { length, staticPosition }) {
2470
+ if (length === null) {
2471
+ const offset = bytesToNumber(cursor.readBytes(sizeOfOffset));
2472
+ const start = staticPosition + offset;
2473
+ const startOfData = start + sizeOfLength;
2474
+ cursor.setPosition(start);
2475
+ const length2 = bytesToNumber(cursor.readBytes(sizeOfLength));
2476
+ const dynamicChild = hasDynamicChild(param);
2477
+ let consumed2 = 0;
2478
+ const value2 = [];
2479
+ for (let i = 0; i < length2; ++i) {
2480
+ cursor.setPosition(startOfData + (dynamicChild ? i * 32 : consumed2));
2481
+ const [data, consumed_] = decodeParameter(cursor, param, {
2482
+ staticPosition: startOfData
2483
+ });
2484
+ consumed2 += consumed_;
2485
+ value2.push(data);
2486
+ if (consumed_ === 0) {
2487
+ cursor.assertReadLimit();
2488
+ cursor._touch();
2489
+ }
2490
+ }
2491
+ cursor.setPosition(staticPosition + 32);
2492
+ return [value2, 32];
2493
+ }
2494
+ if (hasDynamicChild(param)) {
2495
+ const offset = bytesToNumber(cursor.readBytes(sizeOfOffset));
2496
+ const start = staticPosition + offset;
2497
+ const value2 = [];
2498
+ for (let i = 0; i < length; ++i) {
2499
+ cursor.setPosition(start + i * 32);
2500
+ const [data] = decodeParameter(cursor, param, {
2501
+ staticPosition: start
2502
+ });
2503
+ value2.push(data);
2504
+ }
2505
+ cursor.setPosition(staticPosition + 32);
2506
+ return [value2, 32];
2507
+ }
2508
+ let consumed = 0;
2509
+ const value = [];
2510
+ for (let i = 0; i < length; ++i) {
2511
+ const [data, consumed_] = decodeParameter(cursor, param, {
2512
+ staticPosition: staticPosition + consumed
2513
+ });
2514
+ consumed += consumed_;
2515
+ value.push(data);
2516
+ if (consumed_ === 0) {
2517
+ cursor.assertReadLimit();
2518
+ cursor._touch();
2519
+ }
2520
+ }
2521
+ return [value, consumed];
2522
+ }
2523
+ function decodeBool(cursor) {
2524
+ return [bytesToBool(cursor.readBytes(32), { size: 32 }), 32];
2525
+ }
2526
+ function decodeBytes(cursor, param, { staticPosition }) {
2527
+ const [_, size2] = param.type.split("bytes");
2528
+ if (!size2) {
2529
+ const offset = bytesToNumber(cursor.readBytes(32));
2530
+ cursor.setPosition(staticPosition + offset);
2531
+ const length = bytesToNumber(cursor.readBytes(32));
2532
+ if (length === 0) {
2533
+ cursor.setPosition(staticPosition + 32);
2534
+ return ["0x", 32];
2535
+ }
2536
+ const data = cursor.readBytes(length);
2537
+ cursor.setPosition(staticPosition + 32);
2538
+ return [bytesToHex2(data), 32];
2539
+ }
2540
+ const value = bytesToHex2(cursor.readBytes(Number.parseInt(size2, 10), 32));
2541
+ return [value, 32];
2542
+ }
2543
+ function decodeNumber(cursor, param) {
2544
+ const signed = param.type.startsWith("int");
2545
+ const size2 = Number.parseInt(param.type.split("int")[1] || "256", 10);
2546
+ const value = cursor.readBytes(32);
2547
+ return [
2548
+ size2 > 48 ? bytesToBigInt(value, { signed }) : bytesToNumber(value, { signed }),
2549
+ 32
2550
+ ];
2551
+ }
2552
+ function decodeTuple(cursor, param, { staticPosition }) {
2553
+ const hasUnnamedChild = param.components.length === 0 || param.components.some(({ name }) => !name);
2554
+ const value = hasUnnamedChild ? [] : {};
2555
+ let consumed = 0;
2556
+ if (hasDynamicChild(param)) {
2557
+ const offset = bytesToNumber(cursor.readBytes(sizeOfOffset));
2558
+ const start = staticPosition + offset;
2559
+ for (let i = 0; i < param.components.length; ++i) {
2560
+ const component = param.components[i];
2561
+ cursor.setPosition(start + consumed);
2562
+ const [data, consumed_] = decodeParameter(cursor, component, {
2563
+ staticPosition: start
2564
+ });
2565
+ consumed += consumed_;
2566
+ value[hasUnnamedChild ? i : component?.name] = data;
2567
+ }
2568
+ cursor.setPosition(staticPosition + 32);
2569
+ return [value, 32];
2570
+ }
2571
+ for (let i = 0; i < param.components.length; ++i) {
2572
+ const component = param.components[i];
2573
+ const [data, consumed_] = decodeParameter(cursor, component, {
2574
+ staticPosition
2575
+ });
2576
+ value[hasUnnamedChild ? i : component?.name] = data;
2577
+ consumed += consumed_;
2578
+ }
2579
+ return [value, consumed];
2580
+ }
2581
+ function decodeString(cursor, { staticPosition }) {
2582
+ const offset = bytesToNumber(cursor.readBytes(32));
2583
+ const start = staticPosition + offset;
2584
+ cursor.setPosition(start);
2585
+ const length = bytesToNumber(cursor.readBytes(32));
2586
+ if (length === 0) {
2587
+ cursor.setPosition(staticPosition + 32);
2588
+ return ["", 32];
2589
+ }
2590
+ const data = cursor.readBytes(length, 32);
2591
+ const value = bytesToString(data);
2592
+ cursor.setPosition(staticPosition + 32);
2593
+ return [value, 32];
2594
+ }
2595
+ function hasDynamicChild(param) {
2596
+ const { type } = param;
2597
+ if (type === "string")
2598
+ return true;
2599
+ if (type === "bytes")
2600
+ return true;
2601
+ if (type.endsWith("[]"))
2602
+ return true;
2603
+ if (type === "tuple")
2604
+ return param.components?.some(hasDynamicChild);
2605
+ const arrayComponents = getArrayComponents(param.type);
2606
+ if (arrayComponents && hasDynamicChild({ ...param, type: arrayComponents[1] }))
2607
+ return true;
2608
+ return false;
2609
+ }
2610
+ var sizeOfLength, sizeOfOffset;
2611
+ var init_decodeAbiParameters = __esm({
2612
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/decodeAbiParameters.js"() {
2613
+ init_abi();
2614
+ init_getAddress();
2615
+ init_cursor2();
2616
+ init_size();
2617
+ init_slice();
2618
+ init_fromBytes();
2619
+ init_toBytes();
2620
+ init_toHex();
2621
+ init_encodeAbiParameters();
2622
+ sizeOfLength = 32;
2623
+ sizeOfOffset = 32;
2624
+ }
2625
+ });
2626
+
2627
+ // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/decodeFunctionData.js
2628
+ function decodeFunctionData(parameters) {
2629
+ const { abi, data } = parameters;
2630
+ const signature = slice(data, 0, 4);
2631
+ const description = abi.find((x) => x.type === "function" && signature === toFunctionSelector(formatAbiItem2(x)));
2632
+ if (!description)
2633
+ throw new AbiFunctionSignatureNotFoundError(signature, {
2634
+ docsPath: "/docs/contract/decodeFunctionData"
2635
+ });
2636
+ return {
2637
+ functionName: description.name,
2638
+ args: "inputs" in description && description.inputs && description.inputs.length > 0 ? decodeAbiParameters(description.inputs, slice(data, 4)) : void 0
2639
+ };
2640
+ }
2641
+ var init_decodeFunctionData = __esm({
2642
+ "../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/utils/abi/decodeFunctionData.js"() {
2643
+ init_abi();
2644
+ init_slice();
2645
+ init_toFunctionSelector();
2646
+ init_decodeAbiParameters();
2647
+ init_formatAbiItem2();
2648
+ }
2649
+ });
2650
+
2651
+ // src/lib/account-routes.ts
2652
+ var UNLINKABLE = "passkey|email|telegram|google|apple|discord|x|wallet";
2653
+ var ACCOUNT_ROUTES = [
2654
+ { method: "PATCH", path: /^\/api\/auth\/profile$/ },
2655
+ { method: "PUT", path: /^\/api\/auth\/nickname$/ },
2656
+ { method: "POST", path: /^\/api\/auth\/devices\/link$/ },
2657
+ { method: "DELETE", path: /^\/api\/auth\/devices\/[A-Za-z0-9_-]{1,128}$/ },
2658
+ { method: "POST", path: /^\/api\/tss\/wallet\/smart-account\/chain$/ },
2659
+ // linking and unlinking providers (brief 4)
2660
+ { method: "POST", path: /^\/api\/auth\/link\/wallet$/ },
2661
+ { method: "POST", path: /^\/api\/auth\/link\/telegram$/ },
2662
+ { method: "POST", path: /^\/api\/auth\/link\/email\/(send-code|verify-code)$/ },
2663
+ { method: "POST", path: /^\/api\/auth\/link\/passkey\/(begin|complete)$/ },
2664
+ { method: "POST", path: /^\/api\/auth\/link\/(x|discord)\/start$/ },
2665
+ { method: "DELETE", path: /^\/api\/auth\/unlink\/wallet$/, query: ["address"] },
2666
+ { method: "DELETE", path: new RegExp(`^/api/auth/unlink/(${UNLINKABLE})$`), query: ["externalId"] }
2667
+ ];
2668
+ function parseAccountRoute(method, pathWithQuery) {
2669
+ if (typeof pathWithQuery !== "string" || !pathWithQuery.startsWith("/")) return null;
2670
+ const [path, search = ""] = pathWithQuery.split("?", 2);
2671
+ const query = new URLSearchParams(search);
2672
+ const route = ACCOUNT_ROUTES.find((r) => r.method === method && r.path.test(path));
2673
+ if (!route) return null;
2674
+ for (const key of query.keys()) if (!route.query?.includes(key)) return null;
2675
+ return { path, query };
2676
+ }
2677
+ function isLinkRoute(path) {
2678
+ return path.startsWith("/api/auth/link/");
2679
+ }
2680
+
2681
+ // src/lib/errors.ts
2682
+ var AccountAbstractionError = class _AccountAbstractionError extends Error {
2683
+ constructor(message, code, details = {}) {
2684
+ super(message);
2685
+ /** Machine-readable error code for programmatic handling */
2686
+ __publicField(this, "code");
2687
+ __publicField(this, "details");
2688
+ this.code = code;
2689
+ this.details = details;
2690
+ this.name = "LumiaPassportError";
2691
+ if (Error.captureStackTrace) {
2692
+ Error.captureStackTrace(this, _AccountAbstractionError);
2693
+ }
2694
+ }
2695
+ };
2696
+ var _UserRejectedError = class _UserRejectedError extends AccountAbstractionError {
2697
+ constructor(message = "User rejected transaction") {
2698
+ super(message, _UserRejectedError.CODE);
2699
+ this.name = "UserRejectedError";
2700
+ }
2701
+ };
2702
+ __publicField(_UserRejectedError, "CODE", "USER_REJECTED");
2703
+ var UserRejectedError = _UserRejectedError;
2704
+ var _SessionExpiredError = class _SessionExpiredError extends AccountAbstractionError {
2705
+ constructor(message = "Session expired, please log in again") {
2706
+ super(message, _SessionExpiredError.CODE);
2707
+ this.name = "SessionExpiredError";
2708
+ }
2709
+ };
2710
+ __publicField(_SessionExpiredError, "CODE", "SESSION_EXPIRED");
2711
+ var SessionExpiredError = _SessionExpiredError;
2712
+ var _PolicyViolationError = class _PolicyViolationError extends AccountAbstractionError {
2713
+ constructor(message, details = {}) {
2714
+ super(message, _PolicyViolationError.CODE, details);
2715
+ __publicField(this, "policyId");
2716
+ __publicField(this, "rule");
2717
+ __publicField(this, "reason");
2718
+ this.name = "PolicyViolationError";
2719
+ this.policyId = typeof details.policyId === "string" ? details.policyId : "";
2720
+ this.rule = typeof details.rule === "string" ? details.rule : "";
2721
+ this.reason = typeof details.reason === "string" ? details.reason : "";
2722
+ }
2723
+ };
2724
+ __publicField(_PolicyViolationError, "CODE", "POLICY_VIOLATION");
2725
+ var PolicyViolationError = _PolicyViolationError;
2726
+ var ErrorCodes = {
2727
+ MPC_SIGNING_ERROR: "MPC_SIGNING_ERROR",
2728
+ USER_REJECTED: UserRejectedError.CODE,
2729
+ SESSION_EXPIRED: SessionExpiredError.CODE,
2730
+ POLICY_VIOLATION: PolicyViolationError.CODE,
2731
+ POLICY_UNAVAILABLE: "POLICY_UNAVAILABLE",
2732
+ WALLET_REVOKED: "WALLET_REVOKED",
2733
+ DIGEST_MISMATCH: "DIGEST_MISMATCH",
2734
+ MISSING_OPERATION: "MISSING_OPERATION",
2735
+ UNSUPPORTED_CALLDATA: "UNSUPPORTED_CALLDATA",
2736
+ TSS_REQUEST_FAILED: "TSS_REQUEST_FAILED"
2737
+ };
2738
+ function tssErrorFromBody(status, body) {
2739
+ const tssCode = body.error ?? body.code;
2740
+ const code = tssCode ? tssCode.toUpperCase() : ErrorCodes.TSS_REQUEST_FAILED;
2741
+ const message = body.message ?? body.error ?? `API call failed (${status})`;
2742
+ const details = { status };
2743
+ if (body.policyId !== void 0) details.policyId = body.policyId;
2744
+ if (body.rule !== void 0) details.rule = body.rule;
2745
+ if (body.reason !== void 0) details.reason = body.reason;
2746
+ if (body.existingOwnerAddress) details.existingOwnerAddress = body.existingOwnerAddress;
2747
+ return createAccountAbstractionError({ message, code, details });
2748
+ }
2749
+ function createAccountAbstractionError(params) {
2750
+ const { message, code, details = {} } = params;
2751
+ if (!code) {
2752
+ return new Error(message);
2753
+ }
2754
+ switch (code) {
2755
+ case UserRejectedError.CODE:
2756
+ return new UserRejectedError(message);
2757
+ case SessionExpiredError.CODE:
2758
+ return new SessionExpiredError(message);
2759
+ case PolicyViolationError.CODE:
2760
+ return new PolicyViolationError(message, details);
2761
+ default:
2762
+ return new AccountAbstractionError(message, code, details);
2763
+ }
2764
+ }
2765
+
2766
+ // src/iframe/lib/sdk-version.ts
2767
+ var SDK_VERSION_HEADER = "X-Embark-SDK-Version";
2768
+ var IFRAME_SDK_VERSION = `iframe-${"0.6.9"}`;
2769
+ function sdkVersionHeaders() {
2770
+ return { [SDK_VERSION_HEADER]: IFRAME_SDK_VERSION };
2771
+ }
2772
+
2773
+ // src/iframe/lib/iframe-session.ts
2774
+ var IFRAME_SESSION_STORAGE_KEY = "embark-iframe-session";
2775
+ var REFRESH_MARGIN_MS = 60 * 1e3;
2776
+ var UNCONFIRMED_TTL_MS = 10 * 60 * 1e3;
2777
+ function defaultTssUrl() {
2778
+ return "https://api.embarkai.io/tss";
2779
+ }
2780
+ var IframeSession = class {
2781
+ constructor(tssUrl2 = defaultTssUrl, storage = () => typeof localStorage !== "undefined" ? localStorage : void 0) {
2782
+ __publicField(this, "tssUrl", tssUrl2);
2783
+ __publicField(this, "storage", storage);
2784
+ __publicField(this, "refreshing", null);
2785
+ }
2786
+ /** The confirmed session (the user passed the consent step), or null */
2787
+ get() {
2788
+ const session = this.getIncludingUnconfirmed();
2789
+ return session && session.confirmed !== false ? session : null;
2790
+ }
2791
+ /**
2792
+ * The stored session, confirmed or not: for the consent step itself and for the session's own upkeep
2793
+ * (refresh, sign-out). A stale unconfirmed session is revoked and forgotten.
2794
+ */
2795
+ getIncludingUnconfirmed() {
2796
+ const session = this.read();
2797
+ if (session && session.confirmed === false && Date.now() - (session.savedAt ?? 0) > UNCONFIRMED_TTL_MS) {
2798
+ void this.discard(session);
2799
+ return null;
2800
+ }
2801
+ return session;
2802
+ }
2803
+ has() {
2804
+ return !!this.get();
2805
+ }
2806
+ getUserId() {
2807
+ return this.get()?.userId ?? null;
2808
+ }
2809
+ /** Whether `token` is (or was, before a refresh) this session's access token */
2810
+ owns(token) {
2811
+ return !!token && this.read()?.accessToken === token;
2812
+ }
2813
+ /** @param options.confirmed false at the end of a sign-in: the session waits for the consent step */
2814
+ save(tokens, projectId, options = {}) {
2815
+ if (!tokens.refreshToken) throw new Error("Sign-in response has no refresh token");
2816
+ const session = {
2817
+ accessToken: tokens.accessToken,
2818
+ refreshToken: tokens.refreshToken,
2819
+ userId: tokens.userId,
2820
+ expiresAt: Date.now() + tokens.expiresIn * 1e3,
2821
+ projectId,
2822
+ displayName: tokens.displayName ?? null,
2823
+ avatar: tokens.avatar ?? null,
2824
+ providers: tokens.providers ?? [],
2825
+ hasKeyshare: tokens.hasKeyshare,
2826
+ confirmed: options.confirmed !== false,
2827
+ savedAt: Date.now()
2828
+ };
2829
+ this.write(session);
2830
+ return session;
2831
+ }
2832
+ /**
2833
+ * The user passed the consent step for `userId`: the session becomes usable.
2834
+ * @returns whether a session of that user was confirmed
2835
+ */
2836
+ confirm(userId) {
2837
+ const session = this.getIncludingUnconfirmed();
2838
+ if (!session || !userId || session.userId !== userId) return false;
2839
+ if (session.confirmed === false) this.write({ ...session, confirmed: true });
2840
+ return true;
2841
+ }
2842
+ update(patch) {
2843
+ const session = this.read();
2844
+ if (session) this.write({ ...session, ...patch });
2845
+ }
2846
+ clear() {
2847
+ try {
2848
+ this.storage()?.removeItem(IFRAME_SESSION_STORAGE_KEY);
2849
+ } catch {
2850
+ }
2851
+ }
2852
+ info() {
2853
+ const session = this.get();
2854
+ if (!session) return { signedIn: false };
2855
+ return {
2856
+ signedIn: true,
2857
+ userId: session.userId,
2858
+ displayName: session.displayName ?? null,
2859
+ avatar: session.avatar ?? null,
2860
+ providers: session.providers ?? [],
2861
+ hasKeyshare: session.hasKeyshare
2862
+ };
2863
+ }
2864
+ /**
2865
+ * A usable access token: refreshed first when it is about to expire.
2866
+ * @throws SessionExpiredError when there is no session or it cannot be refreshed any more
2867
+ */
2868
+ async getAccessToken() {
2869
+ const session = this.getIncludingUnconfirmed();
2870
+ if (!session) throw new SessionExpiredError("No iframe session, please sign in");
2871
+ if (session.expiresAt - REFRESH_MARGIN_MS > Date.now()) return session.accessToken;
2872
+ const result = await this.refresh();
2873
+ if (result.token) return result.token;
2874
+ if (result.sessionDead) throw new SessionExpiredError();
2875
+ return session.accessToken;
2876
+ }
2877
+ /** Refresh with the session's refresh token (rotation). A 401 ends the session. */
2878
+ refresh() {
2879
+ if (this.refreshing) return this.refreshing;
2880
+ this.refreshing = this.doRefresh().finally(() => {
2881
+ this.refreshing = null;
2882
+ });
2883
+ return this.refreshing;
2884
+ }
2885
+ async doRefresh() {
2886
+ const session = this.getIncludingUnconfirmed();
2887
+ if (!session) return { token: null, sessionDead: true };
2888
+ let response;
2889
+ try {
2890
+ response = await fetch(this.url("/api/auth/refresh", session.projectId), {
2891
+ method: "POST",
2892
+ headers: { "Content-Type": "application/json", ...sdkVersionHeaders() },
2893
+ body: JSON.stringify({ refreshToken: session.refreshToken })
2894
+ });
2895
+ } catch {
2896
+ return { token: null, sessionDead: false };
2897
+ }
2898
+ if (response.status === 401) {
2899
+ const body = await response.json().catch(() => null);
2900
+ if (body) {
2901
+ this.clear();
2902
+ return { token: null, sessionDead: true };
2903
+ }
2904
+ return { token: null, sessionDead: false };
2905
+ }
2906
+ if (!response.ok) return { token: null, sessionDead: false };
2907
+ const data = await response.json();
2908
+ const current = this.read();
2909
+ if (!current || current.refreshToken !== session.refreshToken) {
2910
+ return current ? { token: current.accessToken } : { token: null, sessionDead: true };
2911
+ }
2912
+ this.write({
2913
+ ...current,
2914
+ accessToken: data.accessToken,
2915
+ refreshToken: data.refreshToken || current.refreshToken,
2916
+ expiresAt: Date.now() + data.expiresIn * 1e3,
2917
+ providers: data.providers ?? current.providers
2918
+ });
2919
+ return { token: data.accessToken };
2920
+ }
2921
+ /** Mint a dApp pair for the parent page of `projectId` */
2922
+ async mintDappTokens(projectId) {
2923
+ const call = (token) => fetch(this.url("/api/auth/dapp-token", projectId), {
2924
+ method: "POST",
2925
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...sdkVersionHeaders() },
2926
+ body: JSON.stringify({ projectId })
2927
+ });
2928
+ let response = await call(await this.getAccessToken());
2929
+ if (response.status === 401) {
2930
+ const refreshed = await this.refresh();
2931
+ if (!refreshed.token) {
2932
+ if (refreshed.sessionDead) throw new SessionExpiredError();
2933
+ throw new Error("Token refresh temporarily unavailable, please retry");
2934
+ }
2935
+ response = await call(refreshed.token);
2936
+ }
2937
+ if (!response.ok) {
2938
+ const body = await response.json().catch(() => ({}));
2939
+ throw Object.assign(new Error(body?.message || `dApp token request failed (${response.status})`), {
2940
+ status: response.status,
2941
+ code: body?.error
2942
+ });
2943
+ }
2944
+ return await response.json();
2945
+ }
2946
+ /** Revoke the session at TSS (the dApp pair shares its sessionId, so it ends too) and forget it */
2947
+ async signOut() {
2948
+ const session = this.read();
2949
+ if (!session) return;
2950
+ const token = await this.getAccessToken().catch(() => session.accessToken);
2951
+ this.clear();
2952
+ try {
2953
+ await fetch(this.url("/api/auth/logout", session.projectId), {
2954
+ method: "POST",
2955
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...sdkVersionHeaders() }
2956
+ });
2957
+ } catch (error) {
2958
+ console.warn("[iframe][Session] Logout request failed:", error);
2959
+ }
2960
+ }
2961
+ /** A stale unconfirmed session: forget it and revoke it at TSS (best effort) */
2962
+ async discard(session) {
2963
+ if (this.read()?.refreshToken !== session.refreshToken) return;
2964
+ this.clear();
2965
+ try {
2966
+ await fetch(this.url("/api/auth/logout", session.projectId), {
2967
+ method: "POST",
2968
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${session.accessToken}`, ...sdkVersionHeaders() }
2969
+ });
2970
+ } catch {
2971
+ }
2972
+ }
2973
+ read() {
2974
+ try {
2975
+ const raw = this.storage()?.getItem(IFRAME_SESSION_STORAGE_KEY);
2976
+ if (!raw) return null;
2977
+ const parsed = JSON.parse(raw);
2978
+ return parsed?.accessToken && parsed?.refreshToken && parsed?.userId ? parsed : null;
2979
+ } catch {
2980
+ return null;
2981
+ }
2982
+ }
2983
+ url(path, projectId) {
2984
+ const url = `${this.tssUrl()}${path}`;
2985
+ return projectId ? `${url}${url.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : url;
2986
+ }
2987
+ write(session) {
2988
+ this.storage()?.setItem(IFRAME_SESSION_STORAGE_KEY, JSON.stringify(session));
2989
+ }
2990
+ };
2991
+ var iframeSession = new IframeSession();
2992
+
2993
+ // src/iframe/lib/account-requests.ts
2994
+ function defaultTssUrl2() {
2995
+ return "https://api.embarkai.io/tss";
2996
+ }
2997
+ var PARENT_ORIGIN_ROUTES = /* @__PURE__ */ new Set(["/api/auth/link/passkey/begin", "/api/auth/link/x/start"]);
2998
+ async function sendAccountRequest(request, projectId, session = iframeSession, tssUrl2 = defaultTssUrl2, parentOrigin) {
2999
+ const method = typeof request?.method === "string" ? request.method.toUpperCase() : "";
3000
+ const route = parseAccountRoute(method, typeof request?.path === "string" ? request.path : "");
3001
+ if (!route) {
3002
+ throw new AccountAbstractionError(`Not an account request: ${method} ${String(request?.path)}`, "INVALID_REQUEST");
3003
+ }
3004
+ if (!session.has()) throw new SessionExpiredError("No iframe session, please sign in");
3005
+ let body = request.body;
3006
+ if (PARENT_ORIGIN_ROUTES.has(route.path) && parentOrigin) {
3007
+ body = { ...body && typeof body === "object" ? body : {}, parent_origin: parentOrigin };
3008
+ }
3009
+ const query = new URLSearchParams(route.query);
3010
+ query.set("projectId", projectId);
3011
+ const url = `${tssUrl2()}${route.path}?${query.toString()}`;
3012
+ const send = (token) => fetch(url, {
3013
+ method,
3014
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...sdkVersionHeaders() },
3015
+ ...body !== void 0 && method !== "DELETE" ? { body: JSON.stringify(body) } : {}
3016
+ });
3017
+ let response = await send(await session.getAccessToken());
3018
+ if (response.status === 401) {
3019
+ const refreshed = await session.refresh();
3020
+ if (!refreshed.token) {
3021
+ if (refreshed.sessionDead) throw new SessionExpiredError();
3022
+ throw new Error("Token refresh temporarily unavailable, please retry");
3023
+ }
3024
+ response = await send(refreshed.token);
3025
+ }
3026
+ let data = await response.json().catch(() => null);
3027
+ if (response.ok && data && typeof data === "object" && isLinkRoute(route.path)) {
3028
+ data = keepLinkTokens(data, session);
3029
+ }
3030
+ return { ok: response.ok, status: response.status, data };
3031
+ }
3032
+ function keepLinkTokens(data, session) {
3033
+ const { accessToken: _accessToken, refreshToken: _refreshToken, expiresIn: _expiresIn, ...rest } = data;
3034
+ if (Array.isArray(rest.providers)) session.update({ providers: rest.providers });
3035
+ return rest;
3036
+ }
3037
+
3038
+ // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_freeGlobal.js
3039
+ var freeGlobal = typeof global == "object" && global && global.Object === Object && global;
3040
+ var freeGlobal_default = freeGlobal;
3041
+
3042
+ // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_root.js
3043
+ var freeSelf = typeof self == "object" && self && self.Object === Object && self;
3044
+ var root = freeGlobal_default || freeSelf || Function("return this")();
3045
+ var root_default = root;
3046
+
3047
+ // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_Symbol.js
3048
+ var Symbol2 = root_default.Symbol;
3049
+ var Symbol_default = Symbol2;
3050
+
3051
+ // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_getRawTag.js
3052
+ var objectProto = Object.prototype;
3053
+ var hasOwnProperty = objectProto.hasOwnProperty;
3054
+ var nativeObjectToString = objectProto.toString;
3055
+ var symToStringTag = Symbol_default ? Symbol_default.toStringTag : void 0;
3056
+ function getRawTag(value) {
3057
+ var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
1271
3058
  try {
1272
3059
  value[symToStringTag] = void 0;
1273
3060
  var unmasked = true;
@@ -2102,12 +3889,12 @@ function memoize(func, resolver) {
2102
3889
  throw new TypeError(FUNC_ERROR_TEXT);
2103
3890
  }
2104
3891
  var memoized = function() {
2105
- var args = arguments, key = resolver ? resolver.apply(this, args) : args[0], cache = memoized.cache;
2106
- if (cache.has(key)) {
2107
- return cache.get(key);
3892
+ var args = arguments, key = resolver ? resolver.apply(this, args) : args[0], cache2 = memoized.cache;
3893
+ if (cache2.has(key)) {
3894
+ return cache2.get(key);
2108
3895
  }
2109
3896
  var result = func.apply(this, args);
2110
- memoized.cache = cache.set(key, result) || cache;
3897
+ memoized.cache = cache2.set(key, result) || cache2;
2111
3898
  return result;
2112
3899
  };
2113
3900
  memoized.cache = new (memoize.Cache || MapCache_default)();
@@ -2120,12 +3907,12 @@ var memoize_default = memoize;
2120
3907
  var MAX_MEMOIZE_SIZE = 500;
2121
3908
  function memoizeCapped(func) {
2122
3909
  var result = memoize_default(func, function(key) {
2123
- if (cache.size === MAX_MEMOIZE_SIZE) {
2124
- cache.clear();
3910
+ if (cache2.size === MAX_MEMOIZE_SIZE) {
3911
+ cache2.clear();
2125
3912
  }
2126
3913
  return key;
2127
3914
  });
2128
- var cache = result.cache;
3915
+ var cache2 = result.cache;
2129
3916
  return result;
2130
3917
  }
2131
3918
  var memoizeCapped_default = memoizeCapped;
@@ -2869,136 +4656,63 @@ var ActivityLoggingAPI = class {
2869
4656
  if (!response.ok) {
2870
4657
  throw new Error(`Failed to get audit token: ${await readErrorMessage(response)}`);
2871
4658
  }
2872
- const data = await response.json();
2873
- if (!data?.resourceToken) {
2874
- throw new Error("Invalid audit token response");
2875
- }
2876
- return data.resourceToken;
2877
- }
2878
- async getUserActivityLog(payload, project_id, accessToken) {
2879
- const auditToken = await this.getAuditToken(project_id, accessToken);
2880
- const url = new URL(`${DWH_URL}/wallet/events`);
2881
- const queriesSet = { ...payload, project_id };
2882
- toPairs_default(queriesSet).forEach(([key, value]) => {
2883
- if (value !== void 0 && value !== null) url.searchParams.append(key, String(value));
2884
- });
2885
- const response = await fetch(url.toString(), {
2886
- method: "GET",
2887
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${auditToken}` }
2888
- });
2889
- if (!response.ok) {
2890
- throw new Error(`Failed to get user activity log: ${await readErrorMessage(response)}`);
2891
- }
2892
- return await response.json();
2893
- }
2894
- // IFRAME MESSAGING
2895
- async handleMessage(message, origin) {
2896
- const { type, messageId, projectId, data } = message;
2897
- const { accessToken, payload } = data;
2898
- console.info("[ USER_ACTIVITY_LOG_API Received message ]", type, projectId, payload);
2899
- switch (type) {
2900
- case "USER_ACTIVITY_LOG_GET": {
2901
- const activityLog = await this.getUserActivityLog(payload, projectId, accessToken);
2902
- this.sendResponseFn(messageId, activityLog, origin);
2903
- break;
2904
- }
2905
- default:
2906
- throw new Error(`[USER_ACTIVITY_LOG_API]: Unknown message type: ${type}`);
2907
- }
2908
- }
2909
- };
2910
-
2911
- // src/iframe/lib/assets-policies-api.ts
2912
- var API_URL = window?.__EMBARK_SERVICES__?.tssUrl || "https://api.embarkai.io/tss";
2913
- async function readErrorMessage2(response) {
2914
- try {
2915
- const body = await response.json();
2916
- return body?.message || body?.error || response.statusText;
2917
- } catch {
2918
- return response.statusText;
2919
- }
2920
- }
2921
- var AssetsPoliciesAPI = class {
2922
- constructor(sendResponseFn) {
2923
- __publicField(this, "sendResponseFn");
2924
- this.sendResponseFn = sendResponseFn;
2925
- }
2926
- // API METHODS
2927
- async getUserPolicy(userId, projectId, chainId, accessToken) {
2928
- const url = `${API_URL}/api/tss/${userId}/policy?projectId=${encodeURIComponent(projectId)}&chainId=${chainId}`;
2929
- const response = await fetch(url, {
2930
- method: "GET",
2931
- headers: { Authorization: `Bearer ${accessToken}` }
2932
- });
2933
- if (!response.ok) {
2934
- throw new Error(`Failed to fetch user policy: ${await readErrorMessage2(response)}`);
2935
- }
2936
- return await response.json();
2937
- }
2938
- async putUserPolicy(userId, projectId, chainId, rules, accessToken) {
2939
- const url = `${API_URL}/api/tss/${userId}/policy?projectId=${encodeURIComponent(projectId)}`;
2940
- const response = await fetch(url, {
2941
- method: "PUT",
2942
- body: JSON.stringify({ chainId, rules }),
2943
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
2944
- });
2945
- if (!response.ok) {
2946
- throw new Error(`Failed to update user policy: ${await readErrorMessage2(response)}`);
4659
+ const data = await response.json();
4660
+ if (!data?.resourceToken) {
4661
+ throw new Error("Invalid audit token response");
2947
4662
  }
2948
- return await response.json();
4663
+ return data.resourceToken;
2949
4664
  }
2950
- async cancelPendingUserPolicy(userId, projectId, chainId, accessToken) {
2951
- const url = `${API_URL}/api/tss/${userId}/policy/cancel-pending?projectId=${encodeURIComponent(projectId)}`;
2952
- const response = await fetch(url, {
2953
- method: "POST",
2954
- body: JSON.stringify({ chainId }),
2955
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
4665
+ async getUserActivityLog(payload, project_id, accessToken) {
4666
+ const auditToken = await this.getAuditToken(project_id, accessToken);
4667
+ const url = new URL(`${DWH_URL}/external/wallet/events`);
4668
+ const queriesSet = { ...payload, project_id };
4669
+ toPairs_default(queriesSet).forEach(([key, value]) => {
4670
+ if (value !== void 0 && value !== null) url.searchParams.append(key, String(value));
4671
+ });
4672
+ const response = await fetch(url.toString(), {
4673
+ method: "GET",
4674
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${auditToken}` }
2956
4675
  });
2957
4676
  if (!response.ok) {
2958
- throw new Error(`Failed to cancel pending policy change: ${await readErrorMessage2(response)}`);
4677
+ throw new Error(`Failed to get user activity log: ${await readErrorMessage(response)}`);
2959
4678
  }
2960
4679
  return await response.json();
2961
4680
  }
2962
- async deleteUserPolicy(userId, projectId, chainId, accessToken) {
2963
- const url = `${API_URL}/api/tss/${userId}/policy/delete?projectId=${encodeURIComponent(projectId)}`;
2964
- const response = await fetch(url, {
2965
- method: "POST",
2966
- body: JSON.stringify({ chainId }),
2967
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
4681
+ async getUserSecurityActivity(payload, project_id, accessToken) {
4682
+ const auditToken = await this.getAuditToken(project_id, accessToken);
4683
+ const url = new URL(`${DWH_URL}/external/activity`);
4684
+ toPairs_default(payload).forEach(([key, value]) => {
4685
+ if (value === void 0 || value === null) return;
4686
+ if (Array.isArray(value)) value.forEach((item) => url.searchParams.append(key, String(item)));
4687
+ else url.searchParams.append(key, String(value));
4688
+ });
4689
+ const response = await fetch(url.toString(), {
4690
+ method: "GET",
4691
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${auditToken}` }
2968
4692
  });
2969
4693
  if (!response.ok) {
2970
- throw new Error(`Failed to delete user policy: ${await readErrorMessage2(response)}`);
4694
+ throw new Error(`Failed to get user security activity: ${await readErrorMessage(response)}`);
2971
4695
  }
2972
4696
  return await response.json();
2973
4697
  }
2974
4698
  // IFRAME MESSAGING
2975
4699
  async handleMessage(message, origin) {
2976
4700
  const { type, messageId, projectId, data } = message;
2977
- const { userId, accessToken, chainId, rules } = data;
2978
- console.info("[ USER_ASSETS_POLICY_API Received message ]", type, { userId, chainId });
4701
+ const { accessToken, payload } = data;
4702
+ console.info("[ USER_ACTIVITY_LOG_API Received message ]", type, projectId, payload);
2979
4703
  switch (type) {
2980
- case "USER_ASSETS_POLICY_GET": {
2981
- const policyView = await this.getUserPolicy(userId, projectId, chainId, accessToken);
2982
- this.sendResponseFn(messageId, policyView, origin);
2983
- break;
2984
- }
2985
- case "USER_ASSETS_POLICY_PUT": {
2986
- const policyView = await this.putUserPolicy(userId, projectId, chainId, rules, accessToken);
2987
- this.sendResponseFn(messageId, policyView, origin);
2988
- break;
2989
- }
2990
- case "USER_ASSETS_POLICY_CANCEL_PENDING": {
2991
- const policyView = await this.cancelPendingUserPolicy(userId, projectId, chainId, accessToken);
2992
- this.sendResponseFn(messageId, policyView, origin);
4704
+ case "USER_ACTIVITY_LOG_GET": {
4705
+ const activityLog = await this.getUserActivityLog(payload, projectId, accessToken);
4706
+ this.sendResponseFn(messageId, activityLog, origin);
2993
4707
  break;
2994
4708
  }
2995
- case "USER_ASSETS_POLICY_DELETE": {
2996
- const policyView = await this.deleteUserPolicy(userId, projectId, chainId, accessToken);
2997
- this.sendResponseFn(messageId, policyView, origin);
4709
+ case "USER_ACTIVITY_LOG_SECURITY_GET": {
4710
+ const activity = await this.getUserSecurityActivity(payload, projectId, accessToken);
4711
+ this.sendResponseFn(messageId, activity, origin);
2998
4712
  break;
2999
4713
  }
3000
4714
  default:
3001
- throw new Error(`[USER_ASSETS_POLICY_API]: Unknown message type: ${type}`);
4715
+ throw new Error(`[USER_ACTIVITY_LOG_API]: Unknown message type: ${type}`);
3002
4716
  }
3003
4717
  }
3004
4718
  };
@@ -3052,13 +4766,13 @@ var TemplateEngine = class {
3052
4766
  };
3053
4767
 
3054
4768
  // src/iframe/templates/html/authorization.html
3055
- var authorization_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <!-- 10px overlap -->\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n </div>\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h2-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >{{displayName}}</span\n >\n </div>\n\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusIcon}} {{origin}}</span\n >\n </div>\n\n <!-- Permissions box -->\n <div\n id="data-auth-permissions-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="data-auth-permissions-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Permissions \u25BC\n </div>\n\n <div\n id="data-auth-permissions-content"\n style="width: 100%; height: var(--iframe-permissions-h, 0); overflow: hidden; transition: height 300ms ease"\n >\n <div\n style="\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n <div style="margin-bottom: var(--iframe-gap)">\n <strong>{{displayName}}</strong> by <span class="project-owner">{{displayName}}</span> wants to access your\n <strong>EmbarkAI</strong> account\n </div>\n\n <div style="margin-bottom: var(--iframe-gap)">\n <strong style="margin-bottom: var(--iframe-gap)">Personal user data</strong>\n <ul style="list-style: none">\n <li style="padding-left: var(--iframe-pd)">Wallet address (read-only)</li>\n <li style="padding-left: var(--iframe-pd)">Public transaction history (read-only)</li>\n </ul>\n </div>\n\n <div style="width: 100%">\n <strong style="margin-bottom: var(--iframe-gap)">Permissions</strong>\n <ul style="list-style: none">\n <li style="padding-left: var(--iframe-pd)">Request transaction signatures</li>\n <li style="padding-left: var(--iframe-pd)">Initiate blockchain operations</li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n\n {{#if showSecurityWarning}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F Warning:</strong> This domain is not verified for {{displayName}}\n <div>Expected: {{expectedDomains}}</div>\n <div>Actual: {{origin}}</div>\n </div>\n {{else}}\n <span style="display: block; text-align: center; font-size: 10px; color: var(--iframe-text-secondary)"\n >By continuing you allow the app to use these permissions, you can revoke access at any time.</span\n >\n {{/if}}\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Cancel</button>\n <button class="authorize-btn" {{authorizeButtonState}}>Continue</button>\n </div>\n </div>\n</div>\n';
4769
+ var authorization_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <!-- 10px overlap -->\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n </div>\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h2-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >{{displayName}}</span\n >\n </div>\n\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusIcon}} {{origin}}</span\n >\n </div>\n\n <!-- Permissions box -->\n <div\n id="data-auth-permissions-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="data-auth-permissions-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Permissions \u25BC\n </div>\n\n <div\n id="data-auth-permissions-content"\n style="width: 100%; height: var(--iframe-permissions-h, 0); overflow: hidden; transition: height 300ms ease"\n >\n <div\n style="\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n <div style="margin-bottom: var(--iframe-gap)">\n <strong>{{displayName}}</strong> by <span class="project-owner">{{displayName}}</span> wants to access your\n <strong>embarkAI</strong> account\n </div>\n\n <div style="margin-bottom: var(--iframe-gap)">\n <strong style="margin-bottom: var(--iframe-gap)">Personal user data</strong>\n <ul style="list-style: none">\n <li style="padding-left: var(--iframe-pd)">Wallet address (read-only)</li>\n <li style="padding-left: var(--iframe-pd)">Public transaction history (read-only)</li>\n </ul>\n </div>\n\n <div style="width: 100%">\n <strong style="margin-bottom: var(--iframe-gap)">Permissions</strong>\n <ul style="list-style: none">\n <li style="padding-left: var(--iframe-pd)">Request transaction signatures</li>\n <li style="padding-left: var(--iframe-pd)">Initiate blockchain operations</li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n\n {{#if showSecurityWarning}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F Warning:</strong> This domain is not verified for {{displayName}}\n <div>Expected: {{expectedDomains}}</div>\n <div>Actual: {{origin}}</div>\n </div>\n {{else}}\n <span style="display: block; text-align: center; font-size: 10px; color: var(--iframe-text-secondary)"\n >By continuing you allow the app to use these permissions, you can revoke access at any time.</span\n >\n {{/if}}\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Cancel</button>\n <button class="authorize-btn" {{authorizeButtonState}}>Continue</button>\n </div>\n </div>\n</div>\n';
3056
4770
 
3057
4771
  // src/iframe/templates/html/confirm-tx.html
3058
- var confirm_tx_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <!-- Header with logos -->\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n </div>\n\n <!-- Transaction title -->\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h1-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >Confirm Transaction</span\n >\n </div>\n\n <!-- Application info -->\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusText}} {{origin}}</span\n >\n </div>\n\n <!-- Transaction details box -->\n <div\n id="confirm-transaction-details-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="confirm-transaction-details-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Transaction Details \u25BC\n </div>\n\n <div\n id="confirm-transaction-details-content"\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n max-height: var(--confirm-transaction-details-h, 0px);\n overflow: hidden;\n transition: max-height 300ms ease;\n "\n >\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n {{#if fromAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">From:</strong>\n <a\n href="{{fromAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{fromAddressFull}}"\n >\n <code>{{fromAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if toAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">To:</strong>\n <a\n href="{{toAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{toAddressFull}}"\n >\n <code>{{toAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if hasValue}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Amount:</strong>\n <code style="word-break: break-all; display: block">{{formattedValue}}</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showContractInteraction}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Action:</strong>\n <code style="word-break: break-all; display: block">{{actionDescription}}</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Estimated Cost:</strong>\n <code style="word-break: break-all; display: block; color: #10b981">{{estimatedCost}}</code>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if isSponsored}}\n <div\n style="\n display: flex;\n justify-content: space-between;\n background: var(--iframe-success);\n border-radius: var(--iframe-el-bdrs);\n padding: var(--iframe-gap);\n "\n >\n <span>Gas fees will be paid by the application (no cost to you)</span>\n </div>\n {{/if}}\n </div>\n </div>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showHighRisk}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F {{riskLevel}} RISK</strong>\n {{riskReasons}}\n </div>\n {{else}}\n <div class="security-warning">Please verify this transaction carefully before confirming.</div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div class="trust-app-section">\n <label class="trust-app-label">\n <input type="checkbox" class="trust-app-checkbox" />\n <span>Add this application to trusted list and skip confirmation for future transactions</span>\n </label>\n </div>\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Reject</button>\n <button class="confirm-btn">Confirm</button>\n </div>\n\n <!-- Footer notice -->\n <span\n style="\n display: block;\n padding: var(--iframe-gap);\n text-align: center;\n font-size: 10px;\n color: var(--iframe-text-secondary);\n "\n >\n You can manage trusted applications in your EmbarkAI settings.\n </span>\n </div>\n</div>\n';
4772
+ var confirm_tx_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <!-- Header with logos -->\n <div style="display: flex; align-items: center; gap: 0; justify-content: center">\n <div style="width: 38px; height: 48px; z-index: 10; overflow: visible">\n {{#if displayLogo}}\n <img\n src="{{displayLogo}}"\n alt="{{displayName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >A</span\n >\n {{/if}}\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="width: 38px; height: 48px; z-index: 5; overflow: visible">\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n </div>\n\n <!-- Transaction title -->\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h1-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >Confirm Transaction</span\n >\n </div>\n\n <!-- Application info -->\n <div class="domain-info {{domainStatusClass}}">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace"\n >{{domainStatusText}} {{origin}}</span\n >\n </div>\n\n <!-- Transaction details box -->\n <div\n id="confirm-transaction-details-block"\n aria-expanded="false"\n style="display: flex; flex-direction: column; width: 100%"\n >\n <div\n id="confirm-transaction-details-toggler"\n style="\n cursor: pointer;\n text-align: center;\n list-style: none;\n padding: 0 var(--iframe-pd);\n user-select: none;\n font-size: 12px;\n line-height: 20px;\n "\n >\n Show Transaction Details \u25BC\n </div>\n\n <div\n id="confirm-transaction-details-content"\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n max-height: var(--confirm-transaction-details-h, 0px);\n overflow: hidden;\n transition: max-height 300ms ease;\n "\n >\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n width: 100%;\n padding: var(--iframe-gap);\n border-radius: var(--iframe-el-bdrs);\n background: var(--iframe-info);\n font-size: 10px;\n "\n >\n {{#if fromAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">From:</strong>\n <a\n href="{{fromAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{fromAddressFull}}"\n >\n <code>{{fromAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if toAddress}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">To:</strong>\n <a\n href="{{toAddressExplorerUrl}}"\n target="_blank"\n rel="noopener noreferrer"\n style="word-break: break-all; display: block; color: #3b82f6; text-decoration: none"\n title="{{toAddressFull}}"\n >\n <code>{{toAddress}}</code>\n </a>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if hasValue}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Amount:</strong>\n <code style="word-break: break-all; display: block">{{formattedValue}}</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showContractInteraction}}\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Action:</strong>\n <code style="word-break: break-all; display: block">{{actionDescription}}</code>\n </div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div style="display: flex; justify-content: space-between">\n <strong style="color: var(--iframe-text-secondary); display: block">Estimated Cost:</strong>\n <code style="word-break: break-all; display: block; color: #10b981">{{estimatedCost}}</code>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if isSponsored}}\n <div\n style="\n display: flex;\n justify-content: space-between;\n background: var(--iframe-success);\n border-radius: var(--iframe-el-bdrs);\n padding: var(--iframe-gap);\n "\n >\n <span>Gas fees will be paid by the application (no cost to you)</span>\n </div>\n {{/if}}\n </div>\n </div>\n </div>\n\n <!-- ------------------------------------------------------------ -->\n\n {{#if showHighRisk}}\n <div class="security-warning">\n <strong>\u26A0\uFE0F {{riskLevel}} RISK</strong>\n {{riskReasons}}\n </div>\n {{else}}\n <div class="security-warning">Please verify this transaction carefully before confirming.</div>\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n\n <div class="trust-app-section">\n <label class="trust-app-label">\n <input type="checkbox" class="trust-app-checkbox" />\n <span>Add this application to trusted list and skip confirmation for future transactions</span>\n </label>\n </div>\n\n <!-- Action buttons -->\n <div style="display: flex; gap: var(--iframe-gap); width: 100%; align-items: center">\n <button class="cancel-btn">Reject</button>\n <button class="confirm-btn">Confirm</button>\n </div>\n\n <!-- Footer notice -->\n <span\n style="\n display: block;\n padding: var(--iframe-gap);\n text-align: center;\n font-size: 10px;\n color: var(--iframe-text-secondary);\n "\n >\n You can manage trusted applications in your embarkAI settings.\n </span>\n </div>\n</div>\n';
3059
4773
 
3060
4774
  // src/iframe/templates/html/ready-indicator.html
3061
- var ready_indicator_default = '<div class="ready-indicator">\n <div class="status-icon">\u2713</div>\n <h2>Secure Wallet Ready</h2>\n <p>EmbarkAI is ready for secure operations</p>\n <div class="info">\n <div class="info-row">\n <strong>Origin:</strong>\n <span>{{origin}}</span>\n </div>\n <div class="info-row">\n <strong>Status:</strong>\n <span class="status-active">Active</span>\n </div>\n <div class="info-row">\n <strong>Version:</strong>\n <span>{{iframeVersion}}</span>\n </div>\n <div class="info-row">\n <strong>Documentation:</strong>\n <a\n href="https://docs.embarkai.io"\n target="_blank"\n rel="noopener noreferrer"\n style="color: #667eea; text-decoration: none"\n >docs.embarkai.io</a\n >\n </div>\n </div>\n</div>\n';
4775
+ var ready_indicator_default = '<div class="ready-indicator">\n <div class="status-icon">\u2713</div>\n <h2>Secure Wallet Ready</h2>\n <p>embarkAI is ready for secure operations</p>\n <div class="info">\n <div class="info-row">\n <strong>Origin:</strong>\n <span>{{origin}}</span>\n </div>\n <div class="info-row">\n <strong>Status:</strong>\n <span class="status-active">Active</span>\n </div>\n <div class="info-row">\n <strong>Version:</strong>\n <span>{{iframeVersion}}</span>\n </div>\n <div class="info-row">\n <strong>Documentation:</strong>\n <a\n href="https://docs.embarkai.io"\n target="_blank"\n rel="noopener noreferrer"\n style="color: #667eea; text-decoration: none"\n >docs.embarkai.io</a\n >\n </div>\n </div>\n</div>\n';
3062
4776
 
3063
4777
  // src/iframe/templates/html/sign-eip712-tx.html
3064
4778
  var sign_eip712_tx_default = '<div\n style="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n background: #000000;\n "\n>\n <div\n style="\n display: flex;\n flex-direction: column;\n gap: var(--iframe-gap);\n align-items: center;\n background: var(--iframe-modal-bg);\n border-radius: 20px;\n padding: 20px;\n max-width: 320px;\n width: calc(100% - 40px);\n max-height: 90vh;\n overflow-y: auto;\n "\n >\n <!-- Header -->\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n {{#if metadataLogo}}\n <img\n src="{{metadataLogo}}"\n alt="{{metadataName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >\u{1F510}</span\n >\n {{/if}}\n\n <!-- ------------------------------------------------------------ -->\n <svg\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n fill="none"\n stroke="var(--iframe-text-secondary)"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n >\n <path d="M5 12h14" />\n <path d="m12 5 7 7-7 7" />\n </svg>\n <!-- ------------------------------------------------------------ -->\n\n {{#if userAvatar}}\n <img\n src="{{userAvatar}}"\n alt="{{userName}}"\n style="width: 48px; height: 48px; border-radius: 24px; object-fit: cover"\n />\n {{else}}\n <span\n style="\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: 24px;\n background-color: var(--iframe-button-bg);\n color: var(--iframe-button-text);\n font-size: 14px;\n font-weight: 600;\n "\n >U</span\n >\n {{/if}}\n </div>\n\n <div style="display: flex; align-items: center; gap: var(--iframe-gap); justify-content: center">\n <span\n style="\n width: fit-content;\n font-size: var(--iframe-h2-fz);\n font-weight: var(--iframe-heading-fw);\n line-height: 150%;\n text-align: center;\n "\n >Signature Request</span\n >\n </div>\n\n <div class="domain-info verified">\n <span style="display: block; width: 100%; font-size: 12px; text-align: center; font-family: monospace">\n {{#if isVerifiedOrigin}}\n <span>\u2713</span>\n {{/if}} {{origin}}</span\n >\n </div>\n\n <!-- EIP712 Message Content -->\n <div class="eip712-content">\n <details class="eip712-details">\n <summary>Advanced details</summary>\n\n <div class="section-title">\u{1F4DD} Message</div>\n <div class="eip712-section">\n <div class="section-subtitle">{{primaryType}}</div>\n {{messageFields}}\n </div>\n\n <div class="section-title" style="margin-top: 16px">\u{1F50D} Domain</div>\n <div class="eip712-section">{{domainFields}}</div>\n\n <div class="section-title" style="margin-top: 16px">\u{1F4CB} Full Message</div>\n <pre class="eip712-raw"><code>{{fullMessageJson}}</code></pre>\n </details>\n </div>\n\n <!-- Trust App Option -->\n <div class="trust-app-section">\n <label class="trust-app-label">\n <input type="checkbox" class="trust-app-checkbox" />\n <span>Trust this application and skip confirmation for future signatures</span>\n </label>\n </div>\n\n <!-- Action Buttons -->\n <div class="actions">\n <button class="cancel-btn">Reject</button>\n <button class="confirm-btn">Sign</button>\n </div>\n\n <!-- Footer Notice -->\n <div class="footer-notice">\n <p class="footer-note">Only sign messages from applications you trust.</p>\n </div>\n </div>\n</div>\n';
@@ -3117,7 +4831,7 @@ function buildAuthorizationTemplateData(project, origin, metadata, userProfile)
3117
4831
  const displayLogo = metadata?.logo || project.logoUrl || "";
3118
4832
  const displayDescription = metadata?.description || "";
3119
4833
  const userAvatar = userProfile?.avatar || "";
3120
- const userName = userProfile?.displayName || "EmbarkAI Wallet";
4834
+ const userName = userProfile?.displayName || "embarkAI Wallet";
3121
4835
  return {
3122
4836
  // Display
3123
4837
  displayName,
@@ -3153,7 +4867,7 @@ function buildSignTxTemplateData(project, origin, typedData, metadata, userProfi
3153
4867
  const metadataName = metadata?.name || project.name;
3154
4868
  const metadataLogo = metadata?.logo || project.logoUrl || "";
3155
4869
  const userAvatar = userProfile?.avatar || "";
3156
- const userName = userProfile?.displayName || "EmbarkAI Wallet";
4870
+ const userName = userProfile?.displayName || "embarkAI Wallet";
3157
4871
  const formatFieldValue = (value) => {
3158
4872
  if (Array.isArray(value)) {
3159
4873
  return `[${value.map((v) => formatFieldValue(v)).join(", ")}]`;
@@ -3265,10 +4979,11 @@ function formatApproveAmount(amount) {
3265
4979
  function buildConfirmTxTemplateData(project, origin, transaction, risk, metadata, userProfile, explorerUrl) {
3266
4980
  const isVerifiedOrigin = project.domains.includes(origin);
3267
4981
  const isUserOp = !!transaction.userOpDetails;
4982
+ const isUnknownOperation = transaction.decoded?.kind === "unknown";
3268
4983
  const displayName = metadata?.name || project.name;
3269
4984
  const displayLogo = metadata?.logo || project.logoUrl || "";
3270
4985
  const userAvatar = userProfile?.avatar || "";
3271
- const userName = userProfile?.displayName || "EmbarkAI Wallet";
4986
+ const userName = userProfile?.displayName || "embarkAI Wallet";
3272
4987
  const domainStatusClass = isVerifiedOrigin ? "verified" : "unverified";
3273
4988
  const domainStatusText = isVerifiedOrigin ? "\u2705" : "\u26A0\uFE0F";
3274
4989
  const batchOperations = transaction.userOpDetails?.operations || [];
@@ -3301,9 +5016,11 @@ function buildConfirmTxTemplateData(project, origin, transaction, risk, metadata
3301
5016
  const innerCallData = extractInnerCallData(callData);
3302
5017
  const effectiveCallData = innerCallData || callData;
3303
5018
  const hasContractCall = !!effectiveCallData && effectiveCallData !== "0x" && effectiveCallData.length > 2;
3304
- const showContractInteraction = isBatchUserOp || (isUserOp ? hasContractCall : !!callData && callData !== "0x");
5019
+ const showContractInteraction = isUnknownOperation || isBatchUserOp || (isUserOp ? hasContractCall : !!callData && callData !== "0x");
3305
5020
  let actionDescription = "Contract Interaction";
3306
- if (isBatchUserOp) {
5021
+ if (isUnknownOperation && transaction.decoded?.kind === "unknown") {
5022
+ actionDescription = `Unknown operation (selector ${transaction.decoded.selector})`;
5023
+ } else if (isBatchUserOp) {
3307
5024
  actionDescription = `Batch transaction (${batchOperations.length} operations)`;
3308
5025
  } else if (showContractInteraction) {
3309
5026
  const approveData = parseERC20Approve(callData);
@@ -3347,61 +5064,44 @@ function buildConfirmTxTemplateData(project, origin, transaction, risk, metadata
3347
5064
  };
3348
5065
  }
3349
5066
 
5067
+ // src/iframe/lib/project-card.ts
5068
+ var CARD_TTL_MS = 36e5;
5069
+ function tssUrl() {
5070
+ return typeof window !== "undefined" && window.__EMBARK_SERVICES__?.tssUrl || "https://api.embarkai.io/tss";
5071
+ }
5072
+ var cache = /* @__PURE__ */ new Map();
5073
+ async function fetchProjectCard(projectId, now = Date.now()) {
5074
+ const cached = cache.get(projectId);
5075
+ if (cached && now - cached.fetchedAt < CARD_TTL_MS) {
5076
+ return cached.card;
5077
+ }
5078
+ try {
5079
+ const response = await fetch(`${tssUrl()}/api/tss/project/${encodeURIComponent(projectId)}`);
5080
+ if (!response.ok) {
5081
+ console.warn(`[iframe] Project card unavailable: ${response.status}`);
5082
+ return null;
5083
+ }
5084
+ const card = await response.json();
5085
+ cache.set(projectId, { card, fetchedAt: now });
5086
+ return card;
5087
+ } catch (error) {
5088
+ console.error("[iframe] Project card request failed:", error);
5089
+ return null;
5090
+ }
5091
+ }
5092
+
3350
5093
  // src/iframe/lib/authorization-manager.ts
3351
- var DASHBOARD_URL = (
3352
- // checks for custom service url
3353
- typeof window.__EMBARK_SERVICES__ !== "undefined" && window.__EMBARK_SERVICES__?.dashboardUrl || "https://dashboard.embarkai.io"
3354
- );
3355
5094
  var AuthorizationManager = class {
3356
5095
  constructor() {
3357
5096
  __publicField(this, "STORAGE_KEY_PREFIX", "auth");
3358
- // private readonly METADATA_API_URL = 'https://dashboard.embarkai.io/api/public/project'
3359
- __publicField(this, "METADATA_API_URL", `${DASHBOARD_URL}/api/public/project`);
3360
- __publicField(this, "METADATA_CACHE_TTL", 36e5);
3361
- // 1 hour in milliseconds
3362
- __publicField(this, "metadataCache", /* @__PURE__ */ new Map());
3363
5097
  console.log("[iframe][Auth] Initialized");
3364
5098
  }
3365
- /**
3366
- * Check if project is a trusted authorized app (auto-skip auth dialog)
3367
- */
3368
5099
  async isTrustedAuthorizedApp(projectId) {
3369
- const metadata = await this.fetchProjectMetadata(projectId);
3370
- return metadata?.trustedAuthorizedApp === true;
5100
+ const card = await fetchProjectCard(projectId);
5101
+ return card?.trustedAuthorizedApp === true;
3371
5102
  }
3372
- /**
3373
- * Get project metadata (name, logo, etc.) - uses cache
3374
- */
3375
5103
  async getProjectMetadata(projectId) {
3376
- return this.fetchProjectMetadata(projectId);
3377
- }
3378
- /**
3379
- * Fetch project metadata from public API (with caching)
3380
- */
3381
- async fetchProjectMetadata(projectId) {
3382
- const cached = this.metadataCache.get(projectId);
3383
- const now = Date.now();
3384
- if (cached && now - cached.timestamp < this.METADATA_CACHE_TTL) {
3385
- console.log(`[iframe][Auth] Using cached metadata for project: ${cached.data.name}`);
3386
- return cached.data;
3387
- }
3388
- try {
3389
- const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata`);
3390
- if (!response.ok) {
3391
- console.warn(`[iframe][Auth] Failed to fetch project metadata: ${response.status}`);
3392
- return null;
3393
- }
3394
- const metadata = await response.json();
3395
- console.log(`[iframe][Auth] Fetched metadata for project: ${metadata.name}`);
3396
- this.metadataCache.set(projectId, {
3397
- data: metadata,
3398
- timestamp: now
3399
- });
3400
- return metadata;
3401
- } catch (error) {
3402
- console.error("[iframe][Auth] Error fetching project metadata:", error);
3403
- return null;
3404
- }
5104
+ return fetchProjectCard(projectId);
3405
5105
  }
3406
5106
  /**
3407
5107
  * Check if user has authorized a project
@@ -3470,7 +5170,7 @@ var AuthorizationManager = class {
3470
5170
  */
3471
5171
  async requestAuthorization(project, origin, userProfile) {
3472
5172
  this.showIframe();
3473
- const metadata = await this.fetchProjectMetadata(project.id);
5173
+ const metadata = await fetchProjectCard(project.id);
3474
5174
  return new Promise((resolve) => {
3475
5175
  const modal = this.createAuthorizationModal(project, origin, metadata, userProfile);
3476
5176
  const authorizeBtn = modal.querySelector(".authorize-btn");
@@ -3834,48 +5534,314 @@ function getCloudProviders() {
3834
5534
  init_local_backup();
3835
5535
  init_server_backup();
3836
5536
 
3837
- // src/lib/errors.ts
3838
- var AccountAbstractionError = class _AccountAbstractionError extends Error {
3839
- constructor(message, code) {
3840
- super(message);
3841
- /** Machine-readable error code for programmatic handling */
3842
- __publicField(this, "code");
3843
- this.code = code;
3844
- this.name = "LumiaPassportError";
3845
- if (Error.captureStackTrace) {
3846
- Error.captureStackTrace(this, _AccountAbstractionError);
5537
+ // src/iframe/lib/storage-manager.ts
5538
+ var StorageManager = class {
5539
+ constructor() {
5540
+ __publicField(this, "storage");
5541
+ if (typeof window === "undefined" || !window.localStorage) {
5542
+ throw new Error("localStorage not available in iframe");
3847
5543
  }
5544
+ this.storage = window.localStorage;
5545
+ console.log("[iframe][Storage] Initialized");
5546
+ console.log("[iframe][Storage] Origin:", window.location.origin);
3848
5547
  }
3849
- };
3850
- var _UserRejectedError = class _UserRejectedError extends AccountAbstractionError {
3851
- constructor(message = "User rejected transaction") {
3852
- super(message, _UserRejectedError.CODE);
3853
- this.name = "UserRejectedError";
5548
+ /**
5549
+ * Save keyshare to IFRAME localStorage
5550
+ * CRITICAL: This is the secure isolated storage
5551
+ */
5552
+ saveKeyshare(userId, data) {
5553
+ try {
5554
+ const keyshareKey = `tss.${userId}.keyshare`;
5555
+ this.storage.setItem(
5556
+ keyshareKey,
5557
+ JSON.stringify({
5558
+ keyshare_b64: data.keyshare_b64
5559
+ })
5560
+ );
5561
+ this.storage.setItem(`tss.${userId}.ownerAddress`, data.ownerAddress);
5562
+ this.storage.setItem(`tss.${userId}.sessionId`, data.sessionId);
5563
+ console.log(`[iframe][Storage] \u2705 Saved keyshare for user: ${userId}`);
5564
+ console.log(`[iframe][Storage] Storage origin: ${window.location.origin}`);
5565
+ } catch (error) {
5566
+ console.error("[iframe][Storage] Failed to save keyshare:", error);
5567
+ throw new Error(`Failed to save keyshare: ${error instanceof Error ? error.message : "Unknown error"}`);
5568
+ }
3854
5569
  }
3855
- };
3856
- __publicField(_UserRejectedError, "CODE", "USER_REJECTED");
3857
- var UserRejectedError = _UserRejectedError;
3858
- var _SessionExpiredError = class _SessionExpiredError extends AccountAbstractionError {
3859
- constructor(message = "Session expired, please log in again") {
3860
- super(message, _SessionExpiredError.CODE);
3861
- this.name = "SessionExpiredError";
5570
+ /**
5571
+ * Load keyshare from IFRAME localStorage
5572
+ */
5573
+ loadKeyshare(userId) {
5574
+ try {
5575
+ const keyshareKey = `tss.${userId}.keyshare`;
5576
+ const keyshareData = this.storage.getItem(keyshareKey);
5577
+ if (!keyshareData) {
5578
+ console.log(`[iframe][Storage] No keyshare found for user: ${userId}`);
5579
+ return null;
5580
+ }
5581
+ const parsed = JSON.parse(keyshareData);
5582
+ const ownerAddress = this.storage.getItem(`tss.${userId}.ownerAddress`);
5583
+ const sessionId = this.storage.getItem(`tss.${userId}.sessionId`);
5584
+ if (!ownerAddress || !sessionId) {
5585
+ console.error("[iframe][Storage] Missing owner address or session ID");
5586
+ return null;
5587
+ }
5588
+ return {
5589
+ keyshare_b64: parsed.keyshare_b64,
5590
+ ownerAddress,
5591
+ sessionId
5592
+ };
5593
+ } catch (error) {
5594
+ console.error("[iframe][Storage] Failed to load keyshare:", error);
5595
+ return null;
5596
+ }
5597
+ }
5598
+ /**
5599
+ * Check if keyshare exists for user
5600
+ */
5601
+ hasKeyshare(userId) {
5602
+ const keyshareKey = `tss.${userId}.keyshare`;
5603
+ const ownerAddressKey = `tss.${userId}.ownerAddress`;
5604
+ const hasKeyshare = this.storage.getItem(keyshareKey) !== null;
5605
+ const hasAddress = this.storage.getItem(ownerAddressKey) !== null;
5606
+ return hasKeyshare && hasAddress;
5607
+ }
5608
+ /**
5609
+ * Get owner address for user
5610
+ */
5611
+ getOwnerAddress(userId) {
5612
+ const address = this.storage.getItem(`tss.${userId}.ownerAddress`);
5613
+ return address ? address : null;
5614
+ }
5615
+ /**
5616
+ * Get session ID for user
5617
+ */
5618
+ getSessionId(userId) {
5619
+ return this.storage.getItem(`tss.${userId}.sessionId`);
5620
+ }
5621
+ /**
5622
+ * Remove keyshare for user
5623
+ */
5624
+ removeKeyshare(userId) {
5625
+ try {
5626
+ this.storage.removeItem(`tss.${userId}.keyshare`);
5627
+ this.storage.removeItem(`tss.${userId}.ownerAddress`);
5628
+ this.storage.removeItem(`tss.${userId}.sessionId`);
5629
+ const allKeys = Object.keys(this.storage);
5630
+ const userKeys = allKeys.filter((key) => key.startsWith(`tss.${userId}.`));
5631
+ userKeys.forEach((key) => this.storage.removeItem(key));
5632
+ console.log(`[iframe][Storage] \u2705 Removed keyshare for user: ${userId}`);
5633
+ } catch (error) {
5634
+ console.error("[iframe][Storage] Failed to remove keyshare:", error);
5635
+ throw new Error(`Failed to remove keyshare: ${error instanceof Error ? error.message : "Unknown error"}`);
5636
+ }
5637
+ }
5638
+ /**
5639
+ * Make the stored keyshare the half of the server's one for the current project.
5640
+ *
5641
+ * A keyshare is only usable with the server keyshare it was generated with, and the server keeps
5642
+ * one per project or ecosystem while this storage keeps one per user. A keyshare that does not
5643
+ * match is moved aside, never deleted: it may be the right one for another project, and
5644
+ * restoreArchivedKeyshare brings it back when the server names its owner address again.
5645
+ *
5646
+ * `server.ownerAddress` undefined means an older TSS that does not report it: only the
5647
+ * unambiguous "server has none" case is acted on then.
5648
+ */
5649
+ reconcileWithServer(userId, server) {
5650
+ const current = this.getOwnerAddress(userId);
5651
+ if (!server.hasKeyshare) {
5652
+ if (current) {
5653
+ console.warn(`[iframe][Storage] Server has no keyshare for this project, setting aside ${current}`);
5654
+ this.archiveKeyshare(userId);
5655
+ }
5656
+ return;
5657
+ }
5658
+ const expected = server.ownerAddress;
5659
+ if (!expected) return;
5660
+ if (current && sameAddress(current, expected)) return;
5661
+ if (this.restoreArchivedKeyshare(userId, expected)) return;
5662
+ if (current) {
5663
+ console.warn(`[iframe][Storage] Keyshare ${current} is not the half of server keyshare ${expected}, setting it aside`);
5664
+ this.archiveKeyshare(userId);
5665
+ }
5666
+ }
5667
+ /**
5668
+ * Move the current keyshare aside under its owner address
5669
+ */
5670
+ archiveKeyshare(userId) {
5671
+ const share = this.storage.getItem(`tss.${userId}.keyshare`);
5672
+ const ownerAddress = this.storage.getItem(`tss.${userId}.ownerAddress`);
5673
+ if (!share || !ownerAddress) return;
5674
+ const sessionId = this.storage.getItem(`tss.${userId}.sessionId`);
5675
+ const prefix = archivePrefix(userId, ownerAddress);
5676
+ this.storage.setItem(`${prefix}.share`, share);
5677
+ this.storage.setItem(`${prefix}.ownerAddress`, ownerAddress);
5678
+ if (sessionId !== null) this.storage.setItem(`${prefix}.sessionId`, sessionId);
5679
+ else this.storage.removeItem(`${prefix}.sessionId`);
5680
+ this.storage.removeItem(`tss.${userId}.keyshare`);
5681
+ this.storage.removeItem(`tss.${userId}.ownerAddress`);
5682
+ this.storage.removeItem(`tss.${userId}.sessionId`);
5683
+ }
5684
+ /**
5685
+ * Make the archived keyshare with this owner address current again; false when there is none
5686
+ */
5687
+ restoreArchivedKeyshare(userId, ownerAddress) {
5688
+ const prefix = archivePrefix(userId, ownerAddress);
5689
+ const share = this.storage.getItem(`${prefix}.share`);
5690
+ if (!share) return false;
5691
+ const storedAddress = this.storage.getItem(`${prefix}.ownerAddress`) ?? ownerAddress;
5692
+ const sessionId = this.storage.getItem(`${prefix}.sessionId`);
5693
+ const current = this.getOwnerAddress(userId);
5694
+ if (current && !sameAddress(current, ownerAddress)) this.archiveKeyshare(userId);
5695
+ this.storage.setItem(`tss.${userId}.keyshare`, share);
5696
+ this.storage.setItem(`tss.${userId}.ownerAddress`, storedAddress);
5697
+ if (sessionId !== null) this.storage.setItem(`tss.${userId}.sessionId`, sessionId);
5698
+ else this.storage.removeItem(`tss.${userId}.sessionId`);
5699
+ this.storage.removeItem(`${prefix}.share`);
5700
+ this.storage.removeItem(`${prefix}.sessionId`);
5701
+ this.storage.removeItem(`${prefix}.ownerAddress`);
5702
+ console.log(`[iframe][Storage] \u2705 Restored keyshare ${ownerAddress} for user: ${userId}`);
5703
+ return true;
5704
+ }
5705
+ /**
5706
+ * Stored keyshare of wallet `ownerAddress`: the current one, or the one set aside under its owner address
5707
+ * (another tab may have made another wallet current). Without `ownerAddress`, the current one.
5708
+ * `share` is the item as stored.
5709
+ */
5710
+ getKeyshareOf(userId, ownerAddress) {
5711
+ const current = this.getOwnerAddress(userId);
5712
+ if (!ownerAddress || current && sameAddress(current, ownerAddress)) {
5713
+ const share2 = this.storage.getItem(`tss.${userId}.keyshare`);
5714
+ return share2 && current ? { share: share2, ownerAddress: current, sessionId: this.getSessionId(userId) } : null;
5715
+ }
5716
+ const prefix = archivePrefix(userId, ownerAddress);
5717
+ const share = this.storage.getItem(`${prefix}.share`);
5718
+ if (!share) return null;
5719
+ return {
5720
+ share,
5721
+ ownerAddress: this.storage.getItem(`${prefix}.ownerAddress`) ?? ownerAddress,
5722
+ sessionId: this.storage.getItem(`${prefix}.sessionId`)
5723
+ };
5724
+ }
5725
+ /**
5726
+ * Keyshare of wallet `ownerAddress` for signing: the current one, or the one set aside under its owner address
5727
+ * (another tab may have made a sibling's keyshare current, #31). Without `ownerAddress`, the current one.
5728
+ */
5729
+ loadKeyshareOf(userId, ownerAddress) {
5730
+ if (!ownerAddress) return this.loadKeyshare(userId);
5731
+ const stored = this.getKeyshareOf(userId, ownerAddress);
5732
+ if (!stored?.sessionId) return null;
5733
+ try {
5734
+ const { keyshare_b64 } = JSON.parse(stored.share);
5735
+ if (typeof keyshare_b64 !== "string") return null;
5736
+ return { keyshare_b64, ownerAddress: stored.ownerAddress, sessionId: stored.sessionId };
5737
+ } catch (error) {
5738
+ console.error("[iframe][Storage] Failed to read keyshare:", error);
5739
+ return null;
5740
+ }
5741
+ }
5742
+ /**
5743
+ * Set a keyshare aside under its owner address, unless this storage already holds one of that wallet
5744
+ * (current or set aside): an existing keyshare is never overwritten
5745
+ */
5746
+ keepAside(userId, data) {
5747
+ if (this.getKeyshareOf(userId, data.ownerAddress)) return;
5748
+ const prefix = archivePrefix(userId, data.ownerAddress);
5749
+ this.storage.setItem(`${prefix}.share`, data.share);
5750
+ this.storage.setItem(`${prefix}.ownerAddress`, data.ownerAddress);
5751
+ if (data.sessionId !== null) this.storage.setItem(`${prefix}.sessionId`, data.sessionId);
5752
+ console.log(`[iframe][Storage] Keyshare of another wallet kept aside: ${data.ownerAddress}`);
5753
+ }
5754
+ /**
5755
+ * Make a restored keyshare the current one. A current keyshare of another wallet is set aside, never
5756
+ * overwritten (it may have no backup); a copy of the same wallet set aside before is replaced.
5757
+ */
5758
+ installKeyshare(userId, data) {
5759
+ const current = this.getOwnerAddress(userId);
5760
+ if (current && !sameAddress(current, data.ownerAddress)) this.archiveKeyshare(userId);
5761
+ const prefix = archivePrefix(userId, data.ownerAddress);
5762
+ this.storage.removeItem(`${prefix}.share`);
5763
+ this.storage.removeItem(`${prefix}.sessionId`);
5764
+ this.storage.removeItem(`${prefix}.ownerAddress`);
5765
+ this.storage.setItem(`tss.${userId}.keyshare`, data.share);
5766
+ if (data.sessionId !== null) this.storage.setItem(`tss.${userId}.sessionId`, data.sessionId);
5767
+ else this.storage.removeItem(`tss.${userId}.sessionId`);
5768
+ this.storage.setItem(`tss.${userId}.ownerAddress`, data.ownerAddress);
5769
+ }
5770
+ /**
5771
+ * List all users with keyshares (for debugging)
5772
+ */
5773
+ listUsers() {
5774
+ const allKeys = Object.keys(this.storage);
5775
+ const keyshareKeys = allKeys.filter((key) => key.endsWith(".keyshare"));
5776
+ return keyshareKeys.map((key) => {
5777
+ const match = key.match(/^tss\.(.+)\.keyshare$/);
5778
+ return match ? match[1] : null;
5779
+ }).filter(Boolean);
5780
+ }
5781
+ /**
5782
+ * Get storage statistics (for debugging)
5783
+ */
5784
+ getStats() {
5785
+ const allKeys = Object.keys(this.storage);
5786
+ const users = this.listUsers();
5787
+ let storageSize = 0;
5788
+ allKeys.forEach((key) => {
5789
+ const value = this.storage.getItem(key);
5790
+ if (value) {
5791
+ storageSize += key.length + value.length;
5792
+ }
5793
+ });
5794
+ return {
5795
+ totalKeys: allKeys.length,
5796
+ users: users.length,
5797
+ storageSize
5798
+ };
3862
5799
  }
3863
5800
  };
3864
- __publicField(_SessionExpiredError, "CODE", "SESSION_EXPIRED");
3865
- var SessionExpiredError = _SessionExpiredError;
3866
- var ErrorCodes = {
3867
- MPC_SIGNING_ERROR: "MPC_SIGNING_ERROR",
3868
- USER_REJECTED: UserRejectedError.CODE,
3869
- SESSION_EXPIRED: SessionExpiredError.CODE
3870
- };
5801
+ function sameAddress(a, b) {
5802
+ return a.toLowerCase() === b.toLowerCase();
5803
+ }
5804
+ function archivePrefix(userId, ownerAddress) {
5805
+ return `tss.${userId}.archive.${ownerAddress.toLowerCase()}`;
5806
+ }
3871
5807
 
3872
5808
  // src/iframe/lib/token-refresh-client.ts
5809
+ function parseTssErrorBody(text) {
5810
+ try {
5811
+ const parsed = JSON.parse(text);
5812
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
5813
+ const body = parsed;
5814
+ if (typeof body.error === "string" || typeof body.code === "string") {
5815
+ return body;
5816
+ }
5817
+ }
5818
+ } catch {
5819
+ return null;
5820
+ }
5821
+ return null;
5822
+ }
3873
5823
  var TokenRefreshApiClient = class {
3874
5824
  constructor() {
3875
5825
  __publicField(this, "tssUrl");
3876
5826
  __publicField(this, "pendingTokenRefresh", null);
3877
5827
  this.tssUrl = "https://api.embarkai.io/tss";
3878
5828
  }
5829
+ /**
5830
+ * One GET without token refresh or retry, for lookups that may fail quietly: the response body, or null on any
5831
+ * failure (not OK, network, timeout). A 401 here never rotates the session, so the caller's token stays the one
5832
+ * its next calls use.
5833
+ */
5834
+ async peek(path, projectId, accessToken, timeoutMs = 5e3) {
5835
+ const url = projectId ? `${this.tssUrl}${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : `${this.tssUrl}${path}`;
5836
+ const headers = { ...sdkVersionHeaders() };
5837
+ if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
5838
+ try {
5839
+ const response = await fetch(url, { method: "GET", headers, credentials: "include", signal: typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(timeoutMs) : void 0 });
5840
+ return response.ok ? await response.json() : null;
5841
+ } catch {
5842
+ return null;
5843
+ }
5844
+ }
3879
5845
  /**
3880
5846
  * Make API call with automatic token refresh on 401
3881
5847
  */
@@ -3886,7 +5852,8 @@ var TokenRefreshApiClient = class {
3886
5852
  url = `${url}${separator}projectId=${encodeURIComponent(projectId)}`;
3887
5853
  }
3888
5854
  const headers = {
3889
- "Content-Type": "application/json"
5855
+ "Content-Type": "application/json",
5856
+ ...sdkVersionHeaders()
3890
5857
  };
3891
5858
  if (accessToken) {
3892
5859
  headers["Authorization"] = `Bearer ${accessToken}`;
@@ -3914,8 +5881,9 @@ var TokenRefreshApiClient = class {
3914
5881
  throw new SessionExpiredError();
3915
5882
  }
3916
5883
  if (parseFailed || errorData && (errorData.error_code === "TOKEN_EXPIRED" || errorData.action === "REFRESH_TOKEN")) {
3917
- console.log("[iframe][TokenRefresh] TOKEN_EXPIRED detected, requesting new token from parent...");
3918
- const refresh = await this.requestNewTokenFromParent();
5884
+ const ownSession = iframeSession.owns(accessToken);
5885
+ console.log(`[iframe][TokenRefresh] TOKEN_EXPIRED detected, refreshing ${ownSession ? "the iframe session" : "via the parent"}...`);
5886
+ const refresh = ownSession ? await iframeSession.refresh() : await this.requestNewTokenFromParent();
3919
5887
  if (!refresh.token) {
3920
5888
  if (refresh.sessionDead === false) {
3921
5889
  console.error("[iframe][TokenRefresh] Token refresh temporarily unavailable");
@@ -3939,7 +5907,13 @@ var TokenRefreshApiClient = class {
3939
5907
  }
3940
5908
  if (!retryResponse.ok) {
3941
5909
  const retryErrorText = await retryResponse.text();
3942
- throw new Error(`API call failed after token refresh (${retryResponse.status}): ${retryErrorText}`);
5910
+ const retryErrorBody = parseTssErrorBody(retryErrorText);
5911
+ if (retryErrorBody) {
5912
+ throw tssErrorFromBody(retryResponse.status, retryErrorBody);
5913
+ }
5914
+ throw Object.assign(new Error(`API call failed after token refresh (${retryResponse.status}): ${retryErrorText}`), {
5915
+ status: retryResponse.status
5916
+ });
3943
5917
  }
3944
5918
  return await retryResponse.json();
3945
5919
  }
@@ -3947,7 +5921,11 @@ var TokenRefreshApiClient = class {
3947
5921
  }
3948
5922
  if (!response.ok) {
3949
5923
  const errorText = await response.text();
3950
- throw new Error(`API call failed (${response.status}): ${errorText}`);
5924
+ const errorBody = parseTssErrorBody(errorText);
5925
+ if (errorBody) {
5926
+ throw tssErrorFromBody(response.status, errorBody);
5927
+ }
5928
+ throw Object.assign(new Error(`API call failed (${response.status}): ${errorText}`), { status: response.status });
3951
5929
  }
3952
5930
  return await response.json();
3953
5931
  } catch (error) {
@@ -4012,37 +5990,37 @@ var TokenRefreshApiClient = class {
4012
5990
  // src/iframe/lib/backup-manager.ts
4013
5991
  var LOOCAL_BACKUP_STATUS_KEY = "passport-backup-status";
4014
5992
  var BACKUP_VERSION = "1.0";
5993
+ function storedKeyshare(data) {
5994
+ return { share: data.keyshare, ownerAddress: data.ownerAddress, sessionId: data.sessionId };
5995
+ }
4015
5996
  var BackupManager = class {
4016
5997
  constructor() {
4017
5998
  __publicField(this, "tokenClient");
5999
+ /** in-flight ensureServerBackup per user and wallet: the post-DKG call and the parent's call share one run */
6000
+ __publicField(this, "ensuring", /* @__PURE__ */ new Map());
6001
+ __publicField(this, "storageManager");
4018
6002
  this.tokenClient = new TokenRefreshApiClient();
4019
6003
  console.log("[iframe][BackupManager] Initialized");
4020
6004
  }
6005
+ get keyshares() {
6006
+ return this.storageManager ?? (this.storageManager = new StorageManager());
6007
+ }
4021
6008
  /**
4022
- * Get current keyshare data from iframe storage
6009
+ * Keyshare of wallet `walletOwner` from iframe storage: the current one, or one set aside for another
6010
+ * project (another tab may have made another wallet current). Without `walletOwner`, the current one.
4023
6011
  */
4024
- getCurrentKeyshareBackupData(userId) {
4025
- console.log("[iframe][BackupManager] Getting keyshare data for userId:", userId);
6012
+ getCurrentKeyshareBackupData(userId, walletOwner) {
4026
6013
  try {
4027
- const keyshareData = localStorage.getItem(`tss.${userId}.keyshare`);
4028
- const sessionId = localStorage.getItem(`tss.${userId}.sessionId`);
4029
- const ownerAddress = localStorage.getItem(`tss.${userId}.ownerAddress`);
4030
- console.log("[iframe][BackupManager] localStorage keys check:", {
4031
- hasKeyshare: !!keyshareData,
4032
- hasSession: !!sessionId,
4033
- hasOwner: !!ownerAddress,
4034
- keyshareLength: keyshareData?.length
4035
- });
4036
- if (!keyshareData || !sessionId || !ownerAddress) {
4037
- console.warn("[iframe][BackupManager] Missing keyshare data - cannot create backup");
6014
+ const stored = this.keyshares.getKeyshareOf(userId, walletOwner);
6015
+ if (!stored?.sessionId) {
6016
+ console.warn("[iframe][BackupManager] Missing keyshare data - cannot create backup", { hasKeyshare: !!stored });
4038
6017
  return null;
4039
6018
  }
4040
- console.log("[iframe][BackupManager] Successfully retrieved all keyshare data");
4041
6019
  return {
4042
6020
  userId,
4043
- sessionId,
4044
- keyshare: keyshareData,
4045
- ownerAddress,
6021
+ sessionId: stored.sessionId,
6022
+ keyshare: stored.share,
6023
+ ownerAddress: stored.ownerAddress,
4046
6024
  createdAt: Date.now(),
4047
6025
  version: BACKUP_VERSION
4048
6026
  };
@@ -4081,9 +6059,13 @@ var BackupManager = class {
4081
6059
  /**
4082
6060
  * Create backup using specified method
4083
6061
  */
4084
- async createBackup(userId, request, accessToken) {
4085
- console.log("[iframe][BackupManager] Create backup request:", request);
4086
- const data = this.getCurrentKeyshareBackupData(userId);
6062
+ /**
6063
+ * @param currentServerMethod what the vault holds, when the caller already looked it up (null = no
6064
+ * share); lets the passkey prompt come right after the user's click
6065
+ */
6066
+ async createBackup(userId, request, accessToken, projectId, currentServerMethod, basicAllowed = false, walletOwner) {
6067
+ console.log("[iframe][BackupManager] Create backup request:", { method: request.method });
6068
+ const data = this.getCurrentKeyshareBackupData(userId, walletOwner);
4087
6069
  if (!data) {
4088
6070
  const error = "No keyshare data found for backup";
4089
6071
  console.error("[iframe][BackupManager]", error);
@@ -4095,9 +6077,10 @@ var BackupManager = class {
4095
6077
  };
4096
6078
  }
4097
6079
  try {
6080
+ let serverOutcome;
4098
6081
  switch (request.method) {
4099
6082
  case "server":
4100
- await backupToServer(data, this.tokenClient, request.password, accessToken);
6083
+ serverOutcome = await this.createServerBackup(data, request.password, accessToken, projectId, currentServerMethod, basicAllowed);
4101
6084
  break;
4102
6085
  case "local":
4103
6086
  await backupToLocalFile(data, request.password);
@@ -4117,39 +6100,123 @@ var BackupManager = class {
4117
6100
  return {
4118
6101
  success: true,
4119
6102
  method: request.method,
4120
- timestamp
6103
+ timestamp,
6104
+ ...serverOutcome
4121
6105
  };
4122
6106
  } catch (error) {
4123
- const errorMessage = error.message || "Backup failed";
4124
- console.error("[iframe][BackupManager] Backup failed:", errorMessage);
6107
+ const errorMessage2 = error.message || "Backup failed";
6108
+ console.error("[iframe][BackupManager] Backup failed:", errorMessage2);
4125
6109
  this.updateBackupStatus(userId, request.method, {
4126
- error: errorMessage
6110
+ error: errorMessage2
4127
6111
  });
4128
6112
  return {
4129
6113
  success: false,
4130
6114
  method: request.method,
4131
6115
  timestamp: Date.now(),
4132
- error: errorMessage
6116
+ error: errorMessage2
4133
6117
  };
4134
6118
  }
4135
6119
  }
6120
+ /**
6121
+ * Server backup with a user-held secret (Advanced).
6122
+ * A Basic backup of this keyshare is replaced under a new DEK (Basic → Advanced); anything else is uploaded as before.
6123
+ */
6124
+ async createServerBackup(data, password, accessToken, projectId, knownMethod, basicAllowed = false) {
6125
+ const current = knownMethod !== void 0 ? knownMethod : await getServerBackupMethod(this.tokenClient, accessToken, projectId, data.ownerAddress).catch(() => null);
6126
+ const wasBasic = current === "server-managed";
6127
+ let protection;
6128
+ if (password) {
6129
+ protection = { method: "password", password };
6130
+ } else {
6131
+ const { deriveKEKFromPasskey: deriveKEKFromPasskey2 } = await Promise.resolve().then(() => (init_crypto_utils(), crypto_utils_exports));
6132
+ const { kek, credentialId } = await deriveKEKFromPasskey2(data.userId);
6133
+ protection = { method: "passkey", kek, credentialId };
6134
+ }
6135
+ let ifMatch;
6136
+ if (wasBasic && basicAllowed) {
6137
+ const outcome = await upgradeServerManagedBackup(data, protection, this.tokenClient, accessToken, projectId);
6138
+ if (outcome.kind === "upgraded") {
6139
+ console.log(`[iframe][BackupManager] Basic backup upgraded to ${outcome.encryptionMethod}`);
6140
+ return { encryptionMethod: outcome.encryptionMethod, upgraded: true };
6141
+ }
6142
+ console.warn(`[iframe][BackupManager] Upgrade not applicable (${outcome.reason}), uploading a new backup`);
6143
+ ifMatch = outcome.etag;
6144
+ }
6145
+ if (protection.method === "password") {
6146
+ await backupToServer(data, this.tokenClient, protection.password, accessToken, projectId, ifMatch);
6147
+ } else {
6148
+ await backupToServerWithPasskeyKek(data, protection.kek, protection.credentialId, this.tokenClient, accessToken, projectId, ifMatch);
6149
+ }
6150
+ return { encryptionMethod: protection.method, upgraded: wasBasic };
6151
+ }
6152
+ /** Encryption method of the vault backup of the wallet, null when there is none or the vault cannot tell */
6153
+ getServerBackupMethod(accessToken, projectId, walletOwner) {
6154
+ return getServerBackupMethod(this.tokenClient, accessToken, projectId, walletOwner).catch(() => null);
6155
+ }
6156
+ /**
6157
+ * Owner address of the wallet a request is about: the server's keyshare for the request's project, as TSS
6158
+ * reports it. Not the current keyshare here (another tab may have made a sibling's current, #31), and not
6159
+ * what the parent names (an older parent names nothing): `hint` is used only when TSS does not answer.
6160
+ * undefined when neither knows; backups then use the current keyshare and restores the user's share, as before.
6161
+ */
6162
+ async resolveWalletOwner(userId, hint, accessToken, projectId) {
6163
+ try {
6164
+ const verified = await this.tokenClient.apiCall("GET", "/api/auth/verify", void 0, projectId, accessToken);
6165
+ return ownerNamedFor(userId, verified) ?? hint;
6166
+ } catch (error) {
6167
+ console.warn("[iframe][BackupManager] TSS did not name the wallet of the project:", error instanceof Error ? error.message : String(error));
6168
+ }
6169
+ return hint;
6170
+ }
6171
+ /**
6172
+ * Owner address of the project's wallet for signing and AUTHENTICATE (#31): one quick lookup that never refreshes
6173
+ * the session (the caller goes on with its own token) and gives up after a few seconds. undefined when TSS does
6174
+ * not name it: the caller then uses the current keyshare, as before.
6175
+ */
6176
+ async walletOwnerForProject(userId, accessToken, projectId) {
6177
+ if (!accessToken) return void 0;
6178
+ return ownerNamedFor(userId, await this.tokenClient.peek("/api/auth/verify", projectId, accessToken));
6179
+ }
6180
+ /**
6181
+ * Make sure the vault holds a backup of the wallet's keyshare, creating a Basic one when there is none.
6182
+ * Never throws and never overwrites an existing backup.
6183
+ */
6184
+ ensureServerBackup(userId, accessToken, projectId, basicAllowed = false, walletOwner) {
6185
+ const owner = walletOwner ?? this.keyshares.getOwnerAddress(userId) ?? void 0;
6186
+ const runKey = `${userId}:${owner?.toLowerCase() ?? ""}`;
6187
+ const inFlight = this.ensuring.get(runKey);
6188
+ if (inFlight) return inFlight;
6189
+ const run = ensureServerManagedBackup(this.getCurrentKeyshareBackupData(userId, owner), this.tokenClient, accessToken, projectId, {
6190
+ allowCreate: basicAllowed,
6191
+ owner
6192
+ }).then(async (result) => {
6193
+ if (result.status !== "failed") this.updateBackupStatus(userId, "server", { lastBackup: Date.now(), error: void 0 });
6194
+ console.log("[iframe][BackupManager] ensureServerBackup:", result.status, result.status === "failed" ? result.reason : result.encryptionMethod);
6195
+ return result;
6196
+ }).catch((error) => ({
6197
+ status: "failed",
6198
+ reason: "unknown",
6199
+ error: error instanceof Error ? error.message : String(error)
6200
+ })).finally(() => this.ensuring.delete(runKey));
6201
+ this.ensuring.set(runKey, run);
6202
+ return run;
6203
+ }
4136
6204
  /**
4137
6205
  * Restore keyshare from server backup
4138
6206
  */
4139
- async restoreFromServer(userId, password, accessToken) {
6207
+ async restoreFromServer(userId, password, accessToken, projectId, basicAllowed = false, walletOwner) {
4140
6208
  console.log("[iframe][BackupManager] Restore from server request for userId:", userId);
4141
6209
  try {
4142
6210
  const { restoreFromServer: restoreFromServer2 } = await Promise.resolve().then(() => (init_server_backup(), server_backup_exports));
4143
- const backupData = await restoreFromServer2(userId, this.tokenClient, password, accessToken);
6211
+ const backupData = await restoreFromServer2(userId, this.tokenClient, password, accessToken, projectId, basicAllowed, walletOwner);
4144
6212
  console.log("[iframe][BackupManager] \u2705 Server restore completed");
4145
- localStorage.setItem(`tss.${userId}.keyshare`, backupData.keyshare);
4146
- localStorage.setItem(`tss.${userId}.sessionId`, backupData.sessionId);
4147
- localStorage.setItem(`tss.${userId}.ownerAddress`, backupData.ownerAddress);
6213
+ this.keyshares.installKeyshare(userId, storedKeyshare(backupData));
4148
6214
  console.log("[iframe][BackupManager] Keyshare saved to localStorage");
4149
6215
  return backupData;
4150
6216
  } catch (error) {
4151
- const errorMessage = error.message || "Restore failed";
4152
- console.error("[iframe][BackupManager] Restore failed:", errorMessage);
6217
+ if (error instanceof WalletMismatchError) this.keyshares.keepAside(userId, storedKeyshare(error.backup));
6218
+ const errorMessage2 = error.message || "Restore failed";
6219
+ console.error("[iframe][BackupManager] Restore failed:", errorMessage2);
4153
6220
  throw error;
4154
6221
  }
4155
6222
  }
@@ -4157,9 +6224,9 @@ var BackupManager = class {
4157
6224
  * Encrypt backup data without uploading (for cloud/local backups)
4158
6225
  * Returns encrypted data that parent can upload/download
4159
6226
  */
4160
- async encryptBackupData(userId, password) {
6227
+ async encryptBackupData(userId, password, walletOwner) {
4161
6228
  console.log("[iframe][BackupManager] Encrypting backup data for userId:", userId);
4162
- const data = this.getCurrentKeyshareBackupData(userId);
6229
+ const data = this.getCurrentKeyshareBackupData(userId, walletOwner);
4163
6230
  if (!data) {
4164
6231
  throw new Error("No keyshare data found for backup");
4165
6232
  }
@@ -4179,8 +6246,8 @@ var BackupManager = class {
4179
6246
  * Encrypt current keyshare as EnvelopeEncryptedShare (server/guardian format).
4180
6247
  * Returns JSON string of the envelope.
4181
6248
  */
4182
- async encryptEnvelopeData(userId, password) {
4183
- const data = this.getCurrentKeyshareBackupData(userId);
6249
+ async encryptEnvelopeData(userId, password, walletOwner) {
6250
+ const data = this.getCurrentKeyshareBackupData(userId, walletOwner);
4184
6251
  if (!data) {
4185
6252
  throw new Error("No keyshare found for backup");
4186
6253
  }
@@ -4195,9 +6262,7 @@ var BackupManager = class {
4195
6262
  const { envelopeDecryptKeyshareWithPassword: envelopeDecryptKeyshareWithPassword2 } = await Promise.resolve().then(() => (init_crypto_utils(), crypto_utils_exports));
4196
6263
  const envelope = JSON.parse(envelopeJson);
4197
6264
  const backupData = await envelopeDecryptKeyshareWithPassword2(envelope, password);
4198
- localStorage.setItem(`tss.${userId}.keyshare`, backupData.keyshare);
4199
- localStorage.setItem(`tss.${userId}.sessionId`, backupData.sessionId);
4200
- localStorage.setItem(`tss.${userId}.ownerAddress`, backupData.ownerAddress);
6265
+ this.keyshares.installKeyshare(userId, storedKeyshare(backupData));
4201
6266
  }
4202
6267
  /**
4203
6268
  * Restore keyshare from local file backup
@@ -4208,14 +6273,12 @@ var BackupManager = class {
4208
6273
  const { restoreFromLocalFile: restoreFromLocalFile2 } = await Promise.resolve().then(() => (init_local_backup(), local_backup_exports));
4209
6274
  const backupData = await restoreFromLocalFile2(fileContent, userId, password);
4210
6275
  console.log("[iframe][BackupManager] \u2705 Local file restore completed");
4211
- localStorage.setItem(`tss.${userId}.keyshare`, backupData.keyshare);
4212
- localStorage.setItem(`tss.${userId}.sessionId`, backupData.sessionId);
4213
- localStorage.setItem(`tss.${userId}.ownerAddress`, backupData.ownerAddress);
6276
+ this.keyshares.installKeyshare(userId, storedKeyshare(backupData));
4214
6277
  console.log("[iframe][BackupManager] Keyshare saved to localStorage");
4215
6278
  return backupData;
4216
6279
  } catch (error) {
4217
- const errorMessage = error.message || "Local file restore failed";
4218
- console.error("[iframe][BackupManager] Local file restore failed:", errorMessage);
6280
+ const errorMessage2 = error.message || "Local file restore failed";
6281
+ console.error("[iframe][BackupManager] Local file restore failed:", errorMessage2);
4219
6282
  throw error;
4220
6283
  }
4221
6284
  }
@@ -4226,6 +6289,14 @@ var BackupManager = class {
4226
6289
  return getCloudProviders();
4227
6290
  }
4228
6291
  };
6292
+ function ownerNamedFor(userId, verified) {
6293
+ if (!verified || verified.userId && verified.userId !== userId) return void 0;
6294
+ const owner = verified.ownerAddress;
6295
+ return typeof owner === "string" && /^0x[0-9a-fA-F]{40}$/.test(owner) ? owner : void 0;
6296
+ }
6297
+
6298
+ // src/iframe/main.ts
6299
+ init_server_backup();
4229
6300
 
4230
6301
  // ../../node_modules/.pnpm/dkls23-wasm@0.1.1/node_modules/dkls23-wasm/pkg-web/dkls23_wasm.js
4231
6302
  var dkls23_wasm_exports = {};
@@ -4692,6 +6763,7 @@ async function __wbg_init(module_or_path) {
4692
6763
  var dkls23_wasm_default = __wbg_init;
4693
6764
 
4694
6765
  // ../../node_modules/.pnpm/viem@2.57.12_typescript@6.0.3_zod@4.6.7/node_modules/viem/_esm/index.js
6766
+ init_decodeFunctionData();
4695
6767
  init_keccak256();
4696
6768
 
4697
6769
  // src/iframe/lib/dkg-manager.ts
@@ -4824,8 +6896,8 @@ var DKGManager = class extends TokenRefreshApiClient {
4824
6896
  }
4825
6897
  const pubkeyBytes = bytes.slice(1);
4826
6898
  const pubkeyHex = Array.from(pubkeyBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
4827
- const hash = keccak256(`0x${pubkeyHex}`);
4828
- const addr = hash.slice(-40);
6899
+ const hash2 = keccak256(`0x${pubkeyHex}`);
6900
+ const addr = hash2.slice(-40);
4829
6901
  return this.toChecksumAddress(`0x${addr}`);
4830
6902
  }
4831
6903
  /**
@@ -4835,12 +6907,12 @@ var DKGManager = class extends TokenRefreshApiClient {
4835
6907
  const addr = address.toLowerCase().replace("0x", "");
4836
6908
  const encoder3 = new TextEncoder();
4837
6909
  const addrBytes = encoder3.encode(addr);
4838
- const hash = keccak256(addrBytes).replace("0x", "");
4839
- let checksumAddress = "0x";
6910
+ const hash2 = keccak256(addrBytes).replace("0x", "");
6911
+ let checksumAddress2 = "0x";
4840
6912
  for (let i = 0; i < addr.length; i++) {
4841
- checksumAddress += parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
6913
+ checksumAddress2 += parseInt(hash2[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
4842
6914
  }
4843
- return checksumAddress;
6915
+ return checksumAddress2;
4844
6916
  }
4845
6917
  /**
4846
6918
  * Generate secure domain label for DKG
@@ -4861,7 +6933,7 @@ var DKGManager = class extends TokenRefreshApiClient {
4861
6933
  };
4862
6934
 
4863
6935
  // src/iframe/lib/hyperlane/hyperlane.ts
4864
- var API_URL2 = (
6936
+ var API_URL = (
4865
6937
  // checks for custom service url
4866
6938
  typeof window.__EMBARK_SERVICES__ !== "undefined" && window.__EMBARK_SERVICES__?.assetApiUrl || "https://api.embarkai.io/on-ramp/api"
4867
6939
  );
@@ -4874,7 +6946,7 @@ var HyperlaneAPI = class {
4874
6946
  // API METHODS
4875
6947
  /** available warp routes */
4876
6948
  async getHyperlaneWarpRoutes(accessToken) {
4877
- const result = await fetch(`${API_URL2}/hyperlane/warp-routes`, {
6949
+ const result = await fetch(`${API_URL}/hyperlane/warp-routes`, {
4878
6950
  method: "GET",
4879
6951
  headers: { Authorization: `Bearer ${accessToken}` }
4880
6952
  });
@@ -4883,7 +6955,7 @@ var HyperlaneAPI = class {
4883
6955
  }
4884
6956
  /** bridging quote */
4885
6957
  async estimateHyperlaneQuote(payload, accessToken) {
4886
- const result = await fetch(`${API_URL2}/hyperlane/estimate-quote`, {
6958
+ const result = await fetch(`${API_URL}/hyperlane/estimate-quote`, {
4887
6959
  method: "POST",
4888
6960
  body: JSON.stringify(payload),
4889
6961
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
@@ -4893,7 +6965,7 @@ var HyperlaneAPI = class {
4893
6965
  }
4894
6966
  /** list of pre-cooked TXs by Hyperlane SDK */
4895
6967
  async bridgeHyperlaneAsset(payload, accessToken) {
4896
- const result = await fetch(`${API_URL2}/hyperlane/bridge-asset`, {
6968
+ const result = await fetch(`${API_URL}/hyperlane/bridge-asset`, {
4897
6969
  method: "POST",
4898
6970
  body: JSON.stringify(payload),
4899
6971
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
@@ -4904,7 +6976,7 @@ var HyperlaneAPI = class {
4904
6976
  /** bridge history */
4905
6977
  async getHyperlaneBridgeHistory(pagination, accessToken) {
4906
6978
  const { page, pageSize } = pagination;
4907
- const url = new URL(`${API_URL2}/hyperlane/bridge-history`);
6979
+ const url = new URL(`${API_URL}/hyperlane/bridge-history`);
4908
6980
  if (page) url.searchParams.append("page", page.toString());
4909
6981
  if (pageSize) url.searchParams.append("pageSize", pageSize.toString());
4910
6982
  const result = await fetch(url.toString(), { method: "GET", headers: { Authorization: `Bearer ${accessToken}` } });
@@ -4913,7 +6985,7 @@ var HyperlaneAPI = class {
4913
6985
  }
4914
6986
  /** patch initiated bridge transaction */
4915
6987
  async patchInitiatedBridgeTxStatus(payload, accessToken) {
4916
- const result = await fetch(`${API_URL2}/hyperlane/patch-bridge-tx-status`, {
6988
+ const result = await fetch(`${API_URL}/hyperlane/patch-bridge-tx-status`, {
4917
6989
  method: "PATCH",
4918
6990
  body: JSON.stringify(payload),
4919
6991
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
@@ -4922,7 +6994,7 @@ var HyperlaneAPI = class {
4922
6994
  return response;
4923
6995
  }
4924
6996
  async patchInitiatedBridgeTxOpHashes(payload, accessToken) {
4925
- const result = await fetch(`${API_URL2}/hyperlane/patch-bridge-op-hashes`, {
6997
+ const result = await fetch(`${API_URL}/hyperlane/patch-bridge-op-hashes`, {
4926
6998
  method: "PATCH",
4927
6999
  body: JSON.stringify(payload),
4928
7000
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }
@@ -4966,8 +7038,249 @@ var HyperlaneAPI = class {
4966
7038
  }
4967
7039
  };
4968
7040
 
7041
+ // src/iframe/lib/iframe-sign-in.ts
7042
+ var FLOW_TTL_MS = 15 * 60 * 1e3;
7043
+ var X_AUTHORIZE_ORIGINS = ["https://x.com", "https://twitter.com"];
7044
+ function base64url(bytes) {
7045
+ let binary = "";
7046
+ for (const byte of bytes) binary += String.fromCharCode(byte);
7047
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
7048
+ }
7049
+ function generateFlowSecret() {
7050
+ return base64url(crypto.getRandomValues(new Uint8Array(32)));
7051
+ }
7052
+ async function flowChallengeOf(flowSecret) {
7053
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(flowSecret));
7054
+ return base64url(new Uint8Array(digest));
7055
+ }
7056
+ function isXAuthorizeUrl(value) {
7057
+ try {
7058
+ const url = new URL(value);
7059
+ return X_AUTHORIZE_ORIGINS.includes(url.origin) && url.pathname === "/i/oauth2/authorize";
7060
+ } catch {
7061
+ return false;
7062
+ }
7063
+ }
7064
+ function telegramFields(payload) {
7065
+ if (!payload || typeof payload.id !== "number" || typeof payload.auth_date !== "number" || typeof payload.hash !== "string") return null;
7066
+ const fields = {
7067
+ id: payload.id,
7068
+ first_name: typeof payload.first_name === "string" ? payload.first_name : "",
7069
+ auth_date: payload.auth_date,
7070
+ hash: payload.hash
7071
+ };
7072
+ for (const key of ["last_name", "username", "photo_url"]) {
7073
+ if (typeof payload[key] === "string" && payload[key]) fields[key] = payload[key];
7074
+ }
7075
+ return fields;
7076
+ }
7077
+ function defaultTssUrl3() {
7078
+ return "https://api.embarkai.io/tss";
7079
+ }
7080
+ async function readBody(response) {
7081
+ const text = await response.text().catch(() => "");
7082
+ try {
7083
+ const parsed = JSON.parse(text);
7084
+ return { text, json: parsed && typeof parsed === "object" ? parsed : null };
7085
+ } catch {
7086
+ return { text, json: null };
7087
+ }
7088
+ }
7089
+ function signInError(message, status, body) {
7090
+ return new AccountAbstractionError(message, typeof body?.error === "string" && body.error || "SIGN_IN_FAILED", {
7091
+ status,
7092
+ ...body?.retryAfter !== void 0 ? { retryAfter: body.retryAfter } : {},
7093
+ ...body?.attemptsRemaining !== void 0 ? { attemptsRemaining: body.attemptsRemaining } : {}
7094
+ });
7095
+ }
7096
+ var IframeSignIn = class {
7097
+ constructor(session = iframeSession, tssUrl2 = defaultTssUrl3) {
7098
+ __publicField(this, "session", session);
7099
+ __publicField(this, "tssUrl", tssUrl2);
7100
+ /** one pending flow per parent origin: the latest start wins, as at TSS */
7101
+ __publicField(this, "flows", /* @__PURE__ */ new Map());
7102
+ }
7103
+ /** Whether a flow is pending for this origin (tests, diagnostics); never exposes the secret */
7104
+ hasPendingFlow(origin) {
7105
+ return !!this.activeFlow(origin);
7106
+ }
7107
+ async start(request, origin, projectId) {
7108
+ const flowSecret = generateFlowSecret();
7109
+ const flowChallenge = await flowChallengeOf(flowSecret);
7110
+ if (request?.provider === "email") {
7111
+ const email = typeof request.email === "string" ? request.email.trim() : "";
7112
+ if (!email) throw new AccountAbstractionError("email is required", "INVALID_REQUEST");
7113
+ const response = await this.post("/api/auth/email/send-code", projectId, { email, flow_challenge: flowChallenge });
7114
+ const { json } = await readBody(response);
7115
+ if (response.ok) {
7116
+ this.flows.set(origin, { provider: "email", flowSecret, projectId, email, startedAt: Date.now() });
7117
+ }
7118
+ return { provider: "email", ok: response.ok, status: response.status, data: json ?? {} };
7119
+ }
7120
+ if (request?.provider === "passkey") {
7121
+ const mode = request.mode === "register" ? "register" : "login";
7122
+ const path = mode === "register" ? "/api/auth/passkey/register/begin" : "/api/auth/passkey/login/begin";
7123
+ const body = { flow_challenge: flowChallenge, parent_origin: origin };
7124
+ if (typeof request.username === "string" && request.username) body.username = request.username;
7125
+ const response = await this.post(path, projectId, body);
7126
+ const { text, json } = await readBody(response);
7127
+ if (!response.ok || !json) {
7128
+ const prefix = mode === "register" ? "Begin registration failed" : "Begin auth failed";
7129
+ throw signInError(`${prefix}: ${response.status} ${text}`, response.status, json);
7130
+ }
7131
+ this.flows.set(origin, {
7132
+ provider: "passkey",
7133
+ mode,
7134
+ flowSecret,
7135
+ projectId,
7136
+ challengeId: typeof json.challengeId === "string" ? json.challengeId : void 0,
7137
+ startedAt: Date.now()
7138
+ });
7139
+ return { provider: "passkey", mode, options: json };
7140
+ }
7141
+ if (request?.provider === "wallet") {
7142
+ const address = typeof request.address === "string" ? request.address.trim() : "";
7143
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) throw new AccountAbstractionError("address is required", "INVALID_REQUEST");
7144
+ const response = await this.post("/api/auth/wallet/nonce", projectId, { address, flow_challenge: flowChallenge });
7145
+ const { text, json } = await readBody(response);
7146
+ if (!response.ok || typeof json?.message !== "string") {
7147
+ throw signInError(`Wallet sign-in start failed: ${response.status} ${json?.message || text}`, response.status, json);
7148
+ }
7149
+ this.flows.set(origin, { provider: "wallet", flowSecret, projectId, address, message: json.message, startedAt: Date.now() });
7150
+ return { provider: "wallet", message: json.message };
7151
+ }
7152
+ if (request?.provider === "telegram") {
7153
+ const response = await this.post("/api/auth/telegram/start", projectId, { flow_challenge: flowChallenge, parent_origin: origin });
7154
+ const { text, json } = await readBody(response);
7155
+ if (!response.ok || typeof json?.flow_id !== "string") {
7156
+ throw signInError(`Telegram sign-in start failed: ${response.status} ${json?.message || text}`, response.status, json);
7157
+ }
7158
+ this.flows.set(origin, { provider: "telegram", flowSecret, projectId, flowId: json.flow_id, startedAt: Date.now() });
7159
+ return { provider: "telegram", parentOrigin: typeof json.parent_origin === "string" ? json.parent_origin : origin };
7160
+ }
7161
+ if (request?.provider === "x") {
7162
+ const response = await this.post("/api/auth/x/start", projectId, { flow_challenge: flowChallenge, parent_origin: origin });
7163
+ const { text, json } = await readBody(response);
7164
+ const url = typeof json?.url === "string" ? json.url : "";
7165
+ if (!response.ok || !url) {
7166
+ throw signInError(`X sign-in start failed: ${response.status} ${json?.message || text}`, response.status, json);
7167
+ }
7168
+ if (!isXAuthorizeUrl(url)) throw new AccountAbstractionError("Unexpected X authorization URL", "SIGN_IN_FAILED");
7169
+ this.flows.set(origin, { provider: "x", flowSecret, projectId, startedAt: Date.now() });
7170
+ return { provider: "x", url };
7171
+ }
7172
+ throw new AccountAbstractionError("Unsupported sign-in provider", "UNSUPPORTED_PROVIDER");
7173
+ }
7174
+ async finish(request, origin, projectId) {
7175
+ const flow = this.activeFlow(origin);
7176
+ if (!flow || flow.provider !== request?.provider || flow.projectId !== projectId) {
7177
+ throw new AccountAbstractionError("No sign-in in progress, please start again", "FLOW_NOT_STARTED");
7178
+ }
7179
+ let response;
7180
+ let failurePrefix;
7181
+ if (request.provider === "email") {
7182
+ const code = typeof request.code === "string" ? request.code.trim() : "";
7183
+ if (!code) throw new AccountAbstractionError("code is required", "INVALID_REQUEST");
7184
+ response = await this.post("/api/auth/email/verify-code", projectId, { email: flow.email, code, flow_secret: flow.flowSecret });
7185
+ failurePrefix = "Email verification failed";
7186
+ } else if (request.provider === "wallet") {
7187
+ const signature = typeof request.signature === "string" ? request.signature : "";
7188
+ if (!signature || typeof request.chainId !== "number") {
7189
+ throw new AccountAbstractionError("signature and chainId are required", "INVALID_REQUEST");
7190
+ }
7191
+ response = await this.post("/api/auth/wallet/login", projectId, {
7192
+ address: flow.address,
7193
+ message: flow.message,
7194
+ signature,
7195
+ chainId: request.chainId,
7196
+ ...typeof request.walletName === "string" ? { walletName: request.walletName } : {},
7197
+ ...typeof request.walletType === "string" ? { walletType: request.walletType } : {},
7198
+ flow_secret: flow.flowSecret
7199
+ });
7200
+ failurePrefix = "Wallet login failed";
7201
+ } else if (request.provider === "telegram") {
7202
+ const payload = telegramFields(request.payload);
7203
+ if (!payload) throw new AccountAbstractionError("Telegram payload is required", "INVALID_REQUEST");
7204
+ response = await this.post("/api/auth/telegram/login", projectId, { ...payload, flow_id: flow.flowId, flow_secret: flow.flowSecret });
7205
+ failurePrefix = "Telegram login failed";
7206
+ } else if (request.provider === "x") {
7207
+ const code = typeof request.code === "string" ? request.code.trim() : "";
7208
+ if (!code) throw new AccountAbstractionError("code is required", "INVALID_REQUEST");
7209
+ response = await this.post("/api/auth/exchange-code", projectId, { code, flow_secret: flow.flowSecret });
7210
+ failurePrefix = "X login failed";
7211
+ } else {
7212
+ if (!flow.challengeId || !request.credential) {
7213
+ throw new AccountAbstractionError("credential is required", "INVALID_REQUEST");
7214
+ }
7215
+ const path = flow.mode === "register" ? "/api/auth/passkey/register/complete" : "/api/auth/passkey/login/complete";
7216
+ response = await this.post(path, projectId, {
7217
+ challengeId: flow.challengeId,
7218
+ credential: request.credential,
7219
+ flow_secret: flow.flowSecret
7220
+ });
7221
+ failurePrefix = flow.mode === "register" ? "Registration failed" : "Complete authentication failed";
7222
+ }
7223
+ const { text, json } = await readBody(response);
7224
+ if (!response.ok || !json) {
7225
+ const detail = request.provider === "passkey" && flow.mode === "login" || request.provider === "wallet" ? json?.message || text : text;
7226
+ const body = request.provider === "x" && json && typeof json.code === "string" ? { ...json, error: json.code } : json;
7227
+ throw signInError(`${failurePrefix}: ${response.status} ${detail}`, response.status, body);
7228
+ }
7229
+ this.flows.delete(origin);
7230
+ const login = json;
7231
+ if (!login.accessToken || !login.refreshToken || !login.userId) {
7232
+ throw new AccountAbstractionError("Sign-in response is missing the session", "SIGN_IN_FAILED");
7233
+ }
7234
+ const previous = this.session.getIncludingUnconfirmed();
7235
+ if (previous && previous.refreshToken !== login.refreshToken) void this.revoke(previous.accessToken, previous.projectId);
7236
+ this.session.save(login, projectId, { confirmed: false });
7237
+ const pair = await this.session.mintDappTokens(projectId);
7238
+ return {
7239
+ accessToken: pair.accessToken,
7240
+ refreshToken: pair.refreshToken,
7241
+ userId: login.userId,
7242
+ expiresIn: pair.expiresIn,
7243
+ hasKeyshare: !!login.hasKeyshare,
7244
+ ...login.isNewUser !== void 0 ? { isNewUser: login.isNewUser } : {},
7245
+ displayName: login.displayName ?? null,
7246
+ avatar: login.avatar ?? null,
7247
+ providers: login.providers ?? []
7248
+ };
7249
+ }
7250
+ /** Forget the pending flow of this origin (sign-out, cancel) */
7251
+ cancel(origin) {
7252
+ this.flows.delete(origin);
7253
+ }
7254
+ activeFlow(origin) {
7255
+ const flow = this.flows.get(origin);
7256
+ if (!flow) return null;
7257
+ if (Date.now() - flow.startedAt > FLOW_TTL_MS) {
7258
+ this.flows.delete(origin);
7259
+ return null;
7260
+ }
7261
+ return flow;
7262
+ }
7263
+ post(path, projectId, body) {
7264
+ return fetch(`${this.tssUrl()}${path}?projectId=${encodeURIComponent(projectId)}`, {
7265
+ method: "POST",
7266
+ headers: { "Content-Type": "application/json", ...sdkVersionHeaders() },
7267
+ body: JSON.stringify(body)
7268
+ });
7269
+ }
7270
+ async revoke(accessToken, projectId) {
7271
+ try {
7272
+ const query = projectId ? `?projectId=${encodeURIComponent(projectId)}` : "";
7273
+ await fetch(`${this.tssUrl()}/api/auth/logout${query}`, {
7274
+ method: "POST",
7275
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, ...sdkVersionHeaders() }
7276
+ });
7277
+ } catch {
7278
+ }
7279
+ }
7280
+ };
7281
+
4969
7282
  // src/iframe/lib/onramp/binance.ts
4970
- var API_URL3 = (
7283
+ var API_URL2 = (
4971
7284
  // checks for custom service url
4972
7285
  typeof window.__EMBARK_SERVICES__ !== "undefined" && window.__EMBARK_SERVICES__?.assetApiUrl || "https://api.embarkai.io/on-ramp/api"
4973
7286
  );
@@ -4983,7 +7296,7 @@ var BinanceOnrampAPI = class {
4983
7296
  this.disconnectSse();
4984
7297
  const controller = new AbortController();
4985
7298
  this.sseAbortController = controller;
4986
- const url = `${API_URL3}/sse/events?walletAddress=${encodeURIComponent(walletAddress)}`;
7299
+ const url = `${API_URL2}/sse/events?walletAddress=${encodeURIComponent(walletAddress)}`;
4987
7300
  const start = async () => {
4988
7301
  try {
4989
7302
  const response = await fetch(url, {
@@ -5036,7 +7349,7 @@ var BinanceOnrampAPI = class {
5036
7349
  /** https://developers.binance.com/docs/binance_connect/on-ramp-buy-apis/1.get-trading-pairs */
5037
7350
  async getTradingPairsQuery(accessToken) {
5038
7351
  try {
5039
- const response = await fetch(`${API_URL3}/binance/pairs`, {
7352
+ const response = await fetch(`${API_URL2}/binance/pairs`, {
5040
7353
  method: "POST",
5041
7354
  headers: { "Content-Type": "application/json", ...accessToken && { Authorization: `Bearer ${accessToken}` } }
5042
7355
  });
@@ -5049,7 +7362,7 @@ var BinanceOnrampAPI = class {
5049
7362
  /** https://developers.binance.com/docs/binance_connect/on-ramp-buy-apis/2.get-crypto-networks */
5050
7363
  async getSupportedNetworksQuery(accessToken) {
5051
7364
  try {
5052
- const response = await fetch(`${API_URL3}/binance/networks`, {
7365
+ const response = await fetch(`${API_URL2}/binance/networks`, {
5053
7366
  method: "POST",
5054
7367
  headers: { "Content-Type": "application/json", ...accessToken && { Authorization: `Bearer ${accessToken}` } }
5055
7368
  });
@@ -5061,7 +7374,7 @@ var BinanceOnrampAPI = class {
5061
7374
  }
5062
7375
  async getPaymentMethodsQuery(payload, accessToken) {
5063
7376
  try {
5064
- const response = await fetch(`${API_URL3}/binance/modes`, {
7377
+ const response = await fetch(`${API_URL2}/binance/modes`, {
5065
7378
  method: "POST",
5066
7379
  headers: { "Content-Type": "application/json", ...accessToken && { Authorization: `Bearer ${accessToken}` } },
5067
7380
  body: JSON.stringify(payload)
@@ -5074,7 +7387,7 @@ var BinanceOnrampAPI = class {
5074
7387
  }
5075
7388
  async getQuoteQuery(payload, accessToken) {
5076
7389
  try {
5077
- const response = await fetch(`${API_URL3}/binance/quote`, {
7390
+ const response = await fetch(`${API_URL2}/binance/quote`, {
5078
7391
  method: "POST",
5079
7392
  headers: { "Content-Type": "application/json", ...accessToken && { Authorization: `Bearer ${accessToken}` } },
5080
7393
  body: JSON.stringify(payload)
@@ -5087,7 +7400,7 @@ var BinanceOnrampAPI = class {
5087
7400
  }
5088
7401
  async createPreorderMutation(payload, accessToken) {
5089
7402
  try {
5090
- const response = await fetch(`${API_URL3}/binance/pre-order`, {
7403
+ const response = await fetch(`${API_URL2}/binance/pre-order`, {
5091
7404
  method: "POST",
5092
7405
  headers: { "Content-Type": "application/json", ...accessToken && { Authorization: `Bearer ${accessToken}` } },
5093
7406
  body: JSON.stringify(payload)
@@ -5101,7 +7414,7 @@ var BinanceOnrampAPI = class {
5101
7414
  async getTransactionList(params, accessToken) {
5102
7415
  try {
5103
7416
  const txStatus = Array.isArray(params.txStatuses) ? (params.txStatuses || []).join(",") : null;
5104
- const apiUrl = new URL(`${API_URL3}/binance/transactions`);
7417
+ const apiUrl = new URL(`${API_URL2}/binance/transactions`);
5105
7418
  apiUrl.searchParams.append("walletAddress", params.walletAddress);
5106
7419
  if (!!txStatus?.length) apiUrl.searchParams.append("txStatus", txStatus);
5107
7420
  if (!!params.page) apiUrl.searchParams.append("page", params.page.toString());
@@ -5253,12 +7566,14 @@ var SdkChannelManager = class {
5253
7566
  /**
5254
7567
  * Create a new SDK channel
5255
7568
  */
5256
- createChannel(projectId, origin) {
7569
+ /** @param sdkVersion announced by the parent in SDK_AUTH; absent for SDKs that predate iframe sessions */
7570
+ createChannel(projectId, origin, sdkVersion) {
5257
7571
  const channelToken = this.generateSecureToken();
5258
7572
  const channel = {
5259
7573
  token: channelToken,
5260
7574
  projectId,
5261
7575
  origin,
7576
+ ...sdkVersion ? { sdkVersion } : {},
5262
7577
  createdAt: Date.now(),
5263
7578
  lastActivity: Date.now()
5264
7579
  };
@@ -5296,6 +7611,7 @@ var SdkChannelManager = class {
5296
7611
  * Get channel by token
5297
7612
  */
5298
7613
  getChannel(channelToken) {
7614
+ if (!channelToken) return null;
5299
7615
  return this.channels.get(channelToken) || null;
5300
7616
  }
5301
7617
  /**
@@ -5493,18 +7809,20 @@ var SecureMessenger = class {
5493
7809
  console.error("[iframe][Messenger] No parent window");
5494
7810
  return;
5495
7811
  }
5496
- const errorMessage = error instanceof Error ? error.message : error;
7812
+ const errorMessage2 = error instanceof Error ? error.message : error;
5497
7813
  const errorCode = error instanceof AccountAbstractionError ? error.code : void 0;
7814
+ const errorDetails = error instanceof AccountAbstractionError ? error.details : void 0;
5498
7815
  const response = {
5499
7816
  type: "IFRAME_MSG_ERROR",
5500
7817
  messageId,
5501
7818
  timestamp: Date.now(),
5502
- error: errorMessage,
5503
- code: errorCode
7819
+ error: errorMessage2,
7820
+ code: errorCode,
7821
+ details: errorDetails
5504
7822
  };
5505
7823
  window.parent.postMessage(response, targetOrigin);
5506
7824
  console.log(
5507
- `[iframe][Messenger] Sent error to ${targetOrigin}: ${errorMessage}${errorCode ? ` [${errorCode}]` : ""}`
7825
+ `[iframe][Messenger] Sent error to ${targetOrigin}: ${errorMessage2}${errorCode ? ` [${errorCode}]` : ""}`
5508
7826
  );
5509
7827
  }
5510
7828
  /**
@@ -5552,240 +7870,143 @@ var SecureMessenger = class {
5552
7870
  "GET_TRUSTED_APPS",
5553
7871
  "REMOVE_TRUSTED_APP",
5554
7872
  "CLEAR_AUTHORIZATIONS",
5555
- "CREATE_BACKUP",
5556
- "ENCRYPT_BACKUP_DATA",
5557
- "ENCRYPT_ENVELOPE_DATA",
5558
- "DECRYPT_AND_RESTORE_ENVELOPE",
5559
- "RESTORE_BACKUP",
5560
- "RESTORE_FROM_FILE",
5561
- "GET_BACKUP_STATUS",
5562
- "GET_CLOUD_PROVIDERS",
5563
- "REQUEST_NEW_TOKEN",
5564
- "RESPONSE",
5565
- "ERROR",
5566
- "TRUSTED_APP_REMOVED",
5567
- "TOKEN_REFRESHED"
5568
- ];
5569
- const validTypeGroups = [
5570
- "BINANCE_",
5571
- "RAMPNOW_",
5572
- "FINCHPAY_",
5573
- "HYPERLANE_",
5574
- "USER_ASSETS_POLICY_",
5575
- "USER_ACTIVITY_LOG_"
5576
- ];
5577
- const isTypeValid = validTypes.includes(message.type);
5578
- const isGroupTypeValid = validTypeGroups.some((prefix) => message.type.startsWith(prefix));
5579
- if (!message.type || !isTypeValid && !isGroupTypeValid) return false;
5580
- return !!(message.messageId && message.timestamp);
5581
- }
5582
- /**
5583
- * Validate message structure
5584
- */
5585
- validateMessageStructure(message) {
5586
- return message && typeof message === "object" && typeof message.type === "string" && typeof message.messageId === "string" && typeof message.timestamp === "number" && typeof message.nonce === "string" && typeof message.projectId === "string" && "data" in message;
5587
- }
5588
- /**
5589
- * Check if origin is allowed
5590
- */
5591
- isAllowedOrigin(origin) {
5592
- if (this.allowedOrigins.has(origin)) {
5593
- return true;
5594
- }
5595
- if (this.allowedOrigins.size === 0) {
5596
- const isLocalhost = origin.startsWith("http://localhost:") || origin.startsWith("http://127.0.0.1:");
5597
- const isHttps = origin.startsWith("https://");
5598
- return isHttps || isLocalhost;
5599
- }
5600
- return false;
5601
- }
5602
- /**
5603
- * Compute HMAC-SHA256 for message authentication
5604
- */
5605
- async computeHMAC(message) {
5606
- if (!this.channelToken) {
5607
- throw new Error("No SDK channel token for HMAC");
5608
- }
5609
- const encoder3 = new TextEncoder();
5610
- const payload = JSON.stringify({
5611
- type: message.type,
5612
- messageId: message.messageId,
5613
- timestamp: message.timestamp,
5614
- nonce: message.nonce,
5615
- data: JSON.stringify(message.data)
5616
- });
5617
- const data = encoder3.encode(payload);
5618
- const key = encoder3.encode(this.channelToken);
5619
- const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
5620
- const signature = await crypto.subtle.sign("HMAC", cryptoKey, data);
5621
- return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
5622
- }
5623
- /**
5624
- * Constant-time string comparison (prevent timing attacks)
5625
- */
5626
- constantTimeCompare(a, b) {
5627
- if (a.length !== b.length) return false;
5628
- let result = 0;
5629
- for (let i = 0; i < a.length; i++) {
5630
- result |= a.charCodeAt(i) ^ b.charCodeAt(i);
5631
- }
5632
- return result === 0;
5633
- }
5634
- /**
5635
- * Cleanup old nonces to prevent memory growth
5636
- */
5637
- cleanupOldNonces() {
5638
- if (this.usedNonces.size > 1e3) {
5639
- const noncesArray = Array.from(this.usedNonces);
5640
- this.usedNonces = new Set(noncesArray.slice(-500));
5641
- console.log("[iframe][Messenger] Cleaned up old nonces");
5642
- }
5643
- }
5644
- /**
5645
- * Get messenger statistics
5646
- */
5647
- getStats() {
5648
- return {
5649
- allowedOrigins: this.allowedOrigins.size,
5650
- usedNonces: this.usedNonces.size,
5651
- pendingMessages: this.pendingMessages.size
5652
- };
5653
- }
5654
- };
5655
-
5656
- // src/iframe/lib/storage-manager.ts
5657
- var StorageManager = class {
5658
- constructor() {
5659
- __publicField(this, "storage");
5660
- if (typeof window === "undefined" || !window.localStorage) {
5661
- throw new Error("localStorage not available in iframe");
5662
- }
5663
- this.storage = window.localStorage;
5664
- console.log("[iframe][Storage] Initialized");
5665
- console.log("[iframe][Storage] Origin:", window.location.origin);
5666
- }
5667
- /**
5668
- * Save keyshare to IFRAME localStorage
5669
- * CRITICAL: This is the secure isolated storage
5670
- */
5671
- saveKeyshare(userId, data) {
5672
- try {
5673
- const keyshareKey = `tss.${userId}.keyshare`;
5674
- this.storage.setItem(
5675
- keyshareKey,
5676
- JSON.stringify({
5677
- keyshare_b64: data.keyshare_b64
5678
- })
5679
- );
5680
- this.storage.setItem(`tss.${userId}.ownerAddress`, data.ownerAddress);
5681
- this.storage.setItem(`tss.${userId}.sessionId`, data.sessionId);
5682
- console.log(`[iframe][Storage] \u2705 Saved keyshare for user: ${userId}`);
5683
- console.log(`[iframe][Storage] Storage origin: ${window.location.origin}`);
5684
- } catch (error) {
5685
- console.error("[iframe][Storage] Failed to save keyshare:", error);
5686
- throw new Error(`Failed to save keyshare: ${error instanceof Error ? error.message : "Unknown error"}`);
5687
- }
5688
- }
5689
- /**
5690
- * Load keyshare from IFRAME localStorage
5691
- */
5692
- loadKeyshare(userId) {
5693
- try {
5694
- const keyshareKey = `tss.${userId}.keyshare`;
5695
- const keyshareData = this.storage.getItem(keyshareKey);
5696
- if (!keyshareData) {
5697
- console.log(`[iframe][Storage] No keyshare found for user: ${userId}`);
5698
- return null;
5699
- }
5700
- const parsed = JSON.parse(keyshareData);
5701
- const ownerAddress = this.storage.getItem(`tss.${userId}.ownerAddress`);
5702
- const sessionId = this.storage.getItem(`tss.${userId}.sessionId`);
5703
- if (!ownerAddress || !sessionId) {
5704
- console.error("[iframe][Storage] Missing owner address or session ID");
5705
- return null;
5706
- }
5707
- return {
5708
- keyshare_b64: parsed.keyshare_b64,
5709
- ownerAddress,
5710
- sessionId
5711
- };
5712
- } catch (error) {
5713
- console.error("[iframe][Storage] Failed to load keyshare:", error);
5714
- return null;
5715
- }
7873
+ "CREATE_BACKUP",
7874
+ "ENCRYPT_BACKUP_DATA",
7875
+ "ENCRYPT_ENVELOPE_DATA",
7876
+ "DECRYPT_AND_RESTORE_ENVELOPE",
7877
+ "RESTORE_BACKUP",
7878
+ "RESTORE_FROM_FILE",
7879
+ "GET_BACKUP_STATUS",
7880
+ "ENSURE_SERVER_BACKUP",
7881
+ "GET_CLOUD_PROVIDERS",
7882
+ "SIGN_IN_START",
7883
+ "SIGN_IN_FINISH",
7884
+ "SIGN_OUT",
7885
+ "GET_SESSION",
7886
+ "GET_DAPP_TOKENS",
7887
+ "ACCOUNT_REQUEST",
7888
+ "REQUEST_NEW_TOKEN",
7889
+ "RESPONSE",
7890
+ "ERROR",
7891
+ "TRUSTED_APP_REMOVED",
7892
+ "TOKEN_REFRESHED"
7893
+ ];
7894
+ const validTypeGroups = ["BINANCE_", "RAMPNOW_", "FINCHPAY_", "HYPERLANE_", "USER_POLICY_", "USER_ACTIVITY_LOG_"];
7895
+ const isTypeValid = validTypes.includes(message.type);
7896
+ const isGroupTypeValid = validTypeGroups.some((prefix) => message.type.startsWith(prefix));
7897
+ if (!message.type || !isTypeValid && !isGroupTypeValid) return false;
7898
+ return !!(message.messageId && message.timestamp);
5716
7899
  }
5717
7900
  /**
5718
- * Check if keyshare exists for user
7901
+ * Validate message structure
5719
7902
  */
5720
- hasKeyshare(userId) {
5721
- const keyshareKey = `tss.${userId}.keyshare`;
5722
- const ownerAddressKey = `tss.${userId}.ownerAddress`;
5723
- const hasKeyshare = this.storage.getItem(keyshareKey) !== null;
5724
- const hasAddress = this.storage.getItem(ownerAddressKey) !== null;
5725
- return hasKeyshare && hasAddress;
7903
+ validateMessageStructure(message) {
7904
+ return message && typeof message === "object" && typeof message.type === "string" && typeof message.messageId === "string" && typeof message.timestamp === "number" && typeof message.nonce === "string" && typeof message.projectId === "string" && "data" in message;
5726
7905
  }
5727
7906
  /**
5728
- * Get owner address for user
7907
+ * Check if origin is allowed
5729
7908
  */
5730
- getOwnerAddress(userId) {
5731
- const address = this.storage.getItem(`tss.${userId}.ownerAddress`);
5732
- return address ? address : null;
7909
+ isAllowedOrigin(origin) {
7910
+ if (this.allowedOrigins.has(origin)) {
7911
+ return true;
7912
+ }
7913
+ if (this.allowedOrigins.size === 0) {
7914
+ const isLocalhost = origin.startsWith("http://localhost:") || origin.startsWith("http://127.0.0.1:");
7915
+ const isHttps = origin.startsWith("https://");
7916
+ return isHttps || isLocalhost;
7917
+ }
7918
+ return false;
5733
7919
  }
5734
7920
  /**
5735
- * Get session ID for user
7921
+ * Compute HMAC-SHA256 for message authentication
5736
7922
  */
5737
- getSessionId(userId) {
5738
- return this.storage.getItem(`tss.${userId}.sessionId`);
7923
+ async computeHMAC(message) {
7924
+ if (!this.channelToken) {
7925
+ throw new Error("No SDK channel token for HMAC");
7926
+ }
7927
+ const encoder3 = new TextEncoder();
7928
+ const payload = JSON.stringify({
7929
+ type: message.type,
7930
+ messageId: message.messageId,
7931
+ timestamp: message.timestamp,
7932
+ nonce: message.nonce,
7933
+ data: JSON.stringify(message.data)
7934
+ });
7935
+ const data = encoder3.encode(payload);
7936
+ const key = encoder3.encode(this.channelToken);
7937
+ const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
7938
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, data);
7939
+ return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
5739
7940
  }
5740
7941
  /**
5741
- * Remove keyshare for user
7942
+ * Constant-time string comparison (prevent timing attacks)
5742
7943
  */
5743
- removeKeyshare(userId) {
5744
- try {
5745
- this.storage.removeItem(`tss.${userId}.keyshare`);
5746
- this.storage.removeItem(`tss.${userId}.ownerAddress`);
5747
- this.storage.removeItem(`tss.${userId}.sessionId`);
5748
- const allKeys = Object.keys(this.storage);
5749
- const userKeys = allKeys.filter((key) => key.startsWith(`tss.${userId}.`));
5750
- userKeys.forEach((key) => this.storage.removeItem(key));
5751
- console.log(`[iframe][Storage] \u2705 Removed keyshare for user: ${userId}`);
5752
- } catch (error) {
5753
- console.error("[iframe][Storage] Failed to remove keyshare:", error);
5754
- throw new Error(`Failed to remove keyshare: ${error instanceof Error ? error.message : "Unknown error"}`);
7944
+ constantTimeCompare(a, b) {
7945
+ if (a.length !== b.length) return false;
7946
+ let result = 0;
7947
+ for (let i = 0; i < a.length; i++) {
7948
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
5755
7949
  }
7950
+ return result === 0;
5756
7951
  }
5757
7952
  /**
5758
- * List all users with keyshares (for debugging)
7953
+ * Cleanup old nonces to prevent memory growth
5759
7954
  */
5760
- listUsers() {
5761
- const allKeys = Object.keys(this.storage);
5762
- const keyshareKeys = allKeys.filter((key) => key.endsWith(".keyshare"));
5763
- return keyshareKeys.map((key) => {
5764
- const match = key.match(/^tss\.(.+)\.keyshare$/);
5765
- return match ? match[1] : null;
5766
- }).filter(Boolean);
7955
+ cleanupOldNonces() {
7956
+ if (this.usedNonces.size > 1e3) {
7957
+ const noncesArray = Array.from(this.usedNonces);
7958
+ this.usedNonces = new Set(noncesArray.slice(-500));
7959
+ console.log("[iframe][Messenger] Cleaned up old nonces");
7960
+ }
5767
7961
  }
5768
7962
  /**
5769
- * Get storage statistics (for debugging)
7963
+ * Get messenger statistics
5770
7964
  */
5771
7965
  getStats() {
5772
- const allKeys = Object.keys(this.storage);
5773
- const users = this.listUsers();
5774
- let storageSize = 0;
5775
- allKeys.forEach((key) => {
5776
- const value = this.storage.getItem(key);
5777
- if (value) {
5778
- storageSize += key.length + value.length;
5779
- }
5780
- });
5781
7966
  return {
5782
- totalKeys: allKeys.length,
5783
- users: users.length,
5784
- storageSize
7967
+ allowedOrigins: this.allowedOrigins.size,
7968
+ usedNonces: this.usedNonces.size,
7969
+ pendingMessages: this.pendingMessages.size
5785
7970
  };
5786
7971
  }
5787
7972
  };
5788
7973
 
7974
+ // src/lib/session-messages.ts
7975
+ var SESSION_FREE_MESSAGES = /* @__PURE__ */ new Set([
7976
+ "SDK_AUTH",
7977
+ "SDK_CHANNEL_HEARTBEAT",
7978
+ "SIGN_IN_START",
7979
+ "SIGN_IN_FINISH",
7980
+ "SIGN_OUT",
7981
+ "GET_SESSION",
7982
+ "GET_DAPP_TOKENS",
7983
+ "ACCOUNT_REQUEST"
7984
+ ]);
7985
+
7986
+ // src/iframe/lib/session-auth.ts
7987
+ var CONSENT_MESSAGE = "AUTHENTICATE";
7988
+ async function resolveMessageAuth(data, channelSupportsIframeSession, session, options = {}) {
7989
+ const parentToken = typeof data?.accessToken === "string" && data.accessToken ? data.accessToken : void 0;
7990
+ const parentUserId = typeof data?.userId === "string" && data.userId ? data.userId : void 0;
7991
+ if (parentToken || !channelSupportsIframeSession) {
7992
+ return { mode: "legacy", userId: parentUserId, accessToken: parentToken };
7993
+ }
7994
+ const stored = options.includeUnconfirmed ? session.getIncludingUnconfirmed() : session.get();
7995
+ if (!stored) return { mode: "none" };
7996
+ return { mode: "iframe", userId: stored.userId, accessToken: await session.getAccessToken() };
7997
+ }
7998
+ function applyMessageAuth(data, auth) {
7999
+ const base = { ...data ?? {} };
8000
+ if (auth.mode === "legacy") return base;
8001
+ delete base.accessToken;
8002
+ delete base.userId;
8003
+ if (auth.mode === "none") return base;
8004
+ return { ...base, userId: auth.userId, accessToken: auth.accessToken };
8005
+ }
8006
+ function basicBackupAllowed(auth) {
8007
+ return auth.mode === "iframe";
8008
+ }
8009
+
5789
8010
  // src/iframe/lib/trusted-apps-manager.ts
5790
8011
  var STORAGE_KEY2 = "lumia_passport_trusted_apps";
5791
8012
  var TrustedAppsManager = class {
@@ -5889,11 +8110,159 @@ var TrustedAppsManager = class {
5889
8110
  }
5890
8111
  };
5891
8112
 
8113
+ // src/lib/userop-calls.ts
8114
+ var executeAbi = [
8115
+ {
8116
+ type: "function",
8117
+ name: "execute",
8118
+ stateMutability: "payable",
8119
+ inputs: [
8120
+ { name: "target", type: "address" },
8121
+ { name: "value", type: "uint256" },
8122
+ { name: "data", type: "bytes" }
8123
+ ],
8124
+ outputs: []
8125
+ }
8126
+ ];
8127
+ var executeBatchAbi = [
8128
+ {
8129
+ type: "function",
8130
+ name: "executeBatch",
8131
+ stateMutability: "payable",
8132
+ inputs: [
8133
+ { name: "dest", type: "address[]" },
8134
+ { name: "value", type: "uint256[]" },
8135
+ { name: "func", type: "bytes[]" }
8136
+ ],
8137
+ outputs: []
8138
+ }
8139
+ ];
8140
+ var EXECUTE_SELECTOR2 = "0xb61d27f6";
8141
+ var EXECUTE_BATCH_SELECTOR = "0x47e1da2a";
8142
+ function decodeUserOpCalls(userOp) {
8143
+ const callData = (userOp.callData || "0x").toLowerCase();
8144
+ if (callData === "0x") {
8145
+ return { kind: "deploy" };
8146
+ }
8147
+ const selector = callData.slice(0, 10);
8148
+ try {
8149
+ if (selector === EXECUTE_SELECTOR2) {
8150
+ const decoded = decodeFunctionData({ abi: executeAbi, data: callData });
8151
+ const [to, value, data] = decoded.args;
8152
+ return { kind: "calls", calls: [{ to: to.toLowerCase(), value: value.toString(), data }] };
8153
+ }
8154
+ if (selector === EXECUTE_BATCH_SELECTOR) {
8155
+ const decoded = decodeFunctionData({ abi: executeBatchAbi, data: callData });
8156
+ const [targets, values, datas] = decoded.args;
8157
+ if (targets.length === 0 || targets.length !== values.length || targets.length !== datas.length) {
8158
+ return { kind: "unknown", selector };
8159
+ }
8160
+ return {
8161
+ kind: "calls",
8162
+ calls: targets.map((to, index) => ({ to: to.toLowerCase(), value: values[index].toString(), data: datas[index] }))
8163
+ };
8164
+ }
8165
+ } catch {
8166
+ return { kind: "unknown", selector };
8167
+ }
8168
+ return { kind: "unknown", selector };
8169
+ }
8170
+
8171
+ // src/iframe/lib/userop-display.ts
8172
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
8173
+ function isMinimalSafeOperation(transaction) {
8174
+ if (transaction.decoded && transaction.decoded.kind !== "deploy") {
8175
+ return false;
8176
+ }
8177
+ const to = transaction.to?.toLowerCase();
8178
+ const value = transaction.value?.toString() || "0";
8179
+ const data = transaction.data?.toLowerCase() || "0x";
8180
+ return to === ZERO_ADDRESS && (value === "0" || value === "0x0") && (data === "0x" || data === "0x0");
8181
+ }
8182
+ function isUserOpSigningPayload(signing) {
8183
+ if (signing === null || typeof signing !== "object") return false;
8184
+ const candidate = signing;
8185
+ return candidate.kind === "userOp" && typeof candidate.chainId === "number" && Number.isInteger(candidate.chainId) && candidate.chainId > 0 && typeof candidate.entryPoint === "string" && /^0x[0-9a-fA-F]{40}$/.test(candidate.entryPoint) && candidate.userOp !== null && typeof candidate.userOp === "object" && typeof candidate.userOp.callData === "string" && typeof candidate.userOp.sender === "string";
8186
+ }
8187
+ function effectiveTransaction(transaction) {
8188
+ if (!transaction.signing) {
8189
+ return transaction;
8190
+ }
8191
+ if (!isUserOpSigningPayload(transaction.signing)) {
8192
+ throw new Error("Unsupported signing payload for a transaction");
8193
+ }
8194
+ const { userOp } = transaction.signing;
8195
+ const decoded = decodeUserOpCalls(userOp);
8196
+ const details = {
8197
+ ...transaction.userOpDetails,
8198
+ sender: userOp.sender,
8199
+ callGasLimit: userOp.callGasLimit,
8200
+ verificationGasLimit: userOp.verificationGasLimit,
8201
+ preVerificationGas: userOp.preVerificationGas,
8202
+ maxFeePerGas: userOp.maxFeePerGas,
8203
+ maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,
8204
+ paymaster: userOp.paymaster,
8205
+ factory: userOp.factory,
8206
+ factoryData: userOp.factoryData,
8207
+ nonce: userOp.nonce
8208
+ };
8209
+ if (decoded.kind === "deploy") {
8210
+ return {
8211
+ ...transaction,
8212
+ to: ZERO_ADDRESS,
8213
+ value: "0",
8214
+ data: "0x",
8215
+ decoded,
8216
+ userOpDetails: { ...details, callTarget: ZERO_ADDRESS, value: "0", callData: "0x", operations: void 0 }
8217
+ };
8218
+ }
8219
+ if (decoded.kind === "unknown") {
8220
+ return {
8221
+ ...transaction,
8222
+ to: "",
8223
+ value: "0",
8224
+ data: userOp.callData,
8225
+ decoded,
8226
+ userOpDetails: { ...details, callTarget: "", value: "0", callData: userOp.callData, operations: void 0 }
8227
+ };
8228
+ }
8229
+ const [first] = decoded.calls;
8230
+ const total = decoded.calls.reduce((sum, call) => sum + BigInt(call.value), 0n).toString();
8231
+ const isBatch = decoded.calls.length > 1;
8232
+ return {
8233
+ ...transaction,
8234
+ to: first.to,
8235
+ value: total,
8236
+ data: first.data,
8237
+ decoded,
8238
+ userOpDetails: {
8239
+ ...details,
8240
+ callTarget: first.to,
8241
+ value: total,
8242
+ callData: first.data,
8243
+ operations: isBatch ? decoded.calls.map((call) => ({ to: call.to, value: call.value, data: call.data })) : void 0
8244
+ }
8245
+ };
8246
+ }
8247
+
5892
8248
  // src/iframe/lib/signing-manager.ts
5893
- var DASHBOARD_URL2 = (
5894
- // checks for custom service url
5895
- typeof window.__EMBARK_SERVICES__ !== "undefined" && window.__EMBARK_SERVICES__?.dashboardUrl || "https://dashboard.embarkai.io"
5896
- );
8249
+ function signStartBody(sessionId, digest32, chainPath, payload) {
8250
+ if (!payload) {
8251
+ return { sessionId, digest32, chainPath };
8252
+ }
8253
+ if (payload.kind === "userOp") {
8254
+ return {
8255
+ sessionId,
8256
+ digest32,
8257
+ chainPath,
8258
+ kind: "userOp",
8259
+ chainId: payload.chainId,
8260
+ entryPoint: payload.entryPoint,
8261
+ userOp: payload.userOp
8262
+ };
8263
+ }
8264
+ return { sessionId, digest32, chainPath, kind: "typedData", typedData: payload.typedData };
8265
+ }
5897
8266
  var SigningManager = class extends TokenRefreshApiClient {
5898
8267
  constructor() {
5899
8268
  super();
@@ -5902,11 +8271,6 @@ var SigningManager = class extends TokenRefreshApiClient {
5902
8271
  __publicField(this, "trustedApps");
5903
8272
  __publicField(this, "optedOutApps");
5904
8273
  __publicField(this, "explorerUrl", "https://beam-explorer.lumia.org");
5905
- // private readonly METADATA_API_URL = 'https://dashboard.embarkai.io/api/public/project'
5906
- __publicField(this, "METADATA_API_URL", `${DASHBOARD_URL2}/api/public/project`);
5907
- __publicField(this, "METADATA_CACHE_TTL", 36e5);
5908
- // 1 hour in milliseconds
5909
- __publicField(this, "metadataCache", /* @__PURE__ */ new Map());
5910
8274
  this.storage = new StorageManager();
5911
8275
  this.trustedApps = new TrustedAppsManager();
5912
8276
  this.optedOutApps = new OptedOutAppsManager();
@@ -5925,70 +8289,17 @@ var SigningManager = class extends TokenRefreshApiClient {
5925
8289
  throw new Error("Failed to initialize signing WASM module");
5926
8290
  }
5927
8291
  }
5928
- /**
5929
- * Check if transaction is a minimal safe operation (account deployment)
5930
- *
5931
- * Minimal operations are safe to auto-approve without user consent:
5932
- * - to: 0x0000000000000000000000000000000000000000 (burn address)
5933
- * - value: 0 (no funds transfer)
5934
- * - data: 0x (no contract call)
5935
- *
5936
- * This is used for smart account deployment without spending any funds.
5937
- */
5938
- isMinimalSafeOperation(transaction) {
5939
- const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
5940
- const ZERO_VALUE = "0";
5941
- const EMPTY_DATA = "0x";
5942
- const to = transaction.to?.toLowerCase();
5943
- const value = transaction.value?.toString() || "0";
5944
- const data = transaction.data?.toLowerCase() || "0x";
5945
- const isMinimal = to === ZERO_ADDRESS.toLowerCase() && (value === ZERO_VALUE || value === "0x0") && (data === EMPTY_DATA || data === "0x0");
5946
- if (isMinimal) {
5947
- console.log("[iframe][Sign] Detected minimal safe operation:", {
5948
- to,
5949
- value,
5950
- data,
5951
- reason: "Account deployment - no funds at risk"
5952
- });
5953
- }
5954
- return isMinimal;
5955
- }
5956
- /**
5957
- * Fetch project metadata from public API (with caching)
5958
- */
5959
- async fetchProjectMetadata(projectId) {
5960
- const cached = this.metadataCache.get(projectId);
5961
- const now = Date.now();
5962
- if (cached && now - cached.timestamp < this.METADATA_CACHE_TTL) {
5963
- console.log(`[iframe][Sign] Using cached metadata for project: ${cached.data.name}`);
5964
- return cached.data;
5965
- }
5966
- try {
5967
- const response = await fetch(`${this.METADATA_API_URL}/${projectId}/metadata`);
5968
- if (!response.ok) {
5969
- console.warn(`[iframe][Sign] Failed to fetch project metadata: ${response.status}`);
5970
- return null;
5971
- }
5972
- const metadata = await response.json();
5973
- console.log(`[iframe][Sign] Fetched metadata for project: ${metadata.name}`);
5974
- this.metadataCache.set(projectId, {
5975
- data: metadata,
5976
- timestamp: now
5977
- });
5978
- return metadata;
5979
- } catch (error) {
5980
- console.error("[iframe][Sign] Error fetching project metadata:", error);
5981
- return null;
5982
- }
8292
+ fetchProjectMetadata(projectId) {
8293
+ return fetchProjectCard(projectId);
5983
8294
  }
5984
8295
  /**
5985
8296
  * Sign transaction with user confirmation
5986
8297
  */
5987
- async signTransaction(userId, projectId, origin, transaction, accessToken, userProfile) {
8298
+ async signTransaction(userId, projectId, origin, transaction, accessToken, userProfile, walletOwner) {
5988
8299
  if (!this.wasmLoaded) {
5989
8300
  await this.initialize();
5990
8301
  }
5991
- const keyshareData = this.storage.loadKeyshare(userId);
8302
+ const keyshareData = this.storage.loadKeyshareOf(userId, walletOwner);
5992
8303
  if (!keyshareData) {
5993
8304
  throw new Error("No keyshare found. Please complete DKG first.");
5994
8305
  }
@@ -5999,16 +8310,25 @@ var SigningManager = class extends TokenRefreshApiClient {
5999
8310
  website: origin,
6000
8311
  domains: [origin]
6001
8312
  };
6002
- const isMinimalOperation = this.isMinimalSafeOperation(transaction);
8313
+ const effective = effectiveTransaction(transaction);
8314
+ const isMinimalOperation = isMinimalSafeOperation(effective);
8315
+ if (isMinimalOperation) {
8316
+ console.log("[iframe][Sign] Detected minimal safe operation:", {
8317
+ to: effective.to,
8318
+ value: effective.value,
8319
+ data: effective.data,
8320
+ reason: "Account deployment - no funds at risk"
8321
+ });
8322
+ }
6003
8323
  const isTrusted = this.trustedApps.isTrusted(userId, projectId, origin);
6004
8324
  if (!isTrusted && !isMinimalOperation) {
6005
- const risk = await this.assessRisk(transaction);
8325
+ const risk = await this.assessRisk(effective);
6006
8326
  const confirmResult = await this.showConfirmationDialog(
6007
8327
  userId,
6008
8328
  projectId,
6009
8329
  projectInfo,
6010
8330
  origin,
6011
- transaction,
8331
+ effective,
6012
8332
  risk,
6013
8333
  userProfile
6014
8334
  );
@@ -6023,13 +8343,13 @@ var SigningManager = class extends TokenRefreshApiClient {
6023
8343
  console.log("[iframe][Sign] Auto-approving minimal safe operation (account deployment)");
6024
8344
  }
6025
8345
  const digest32 = transaction.digest32 || this.computeTransactionHash(transaction);
6026
- const signature = await this.performMPCSigning(userId, keyshareData, digest32, projectId, accessToken);
8346
+ const signature = await this.performMPCSigning(userId, keyshareData, digest32, projectId, accessToken, effective.signing);
6027
8347
  return signature;
6028
8348
  }
6029
8349
  /**
6030
8350
  * Perform MPC signing protocol
6031
8351
  */
6032
- async performMPCSigning(userId, keyshareData, digest32, projectId, accessToken) {
8352
+ async performMPCSigning(userId, keyshareData, digest32, projectId, accessToken, payload) {
6033
8353
  const keyshare = keyshareData.keyshare_b64;
6034
8354
  const sessionId = keyshareData.sessionId;
6035
8355
  const digestHex = digest32.startsWith("0x") ? digest32.slice(2) : digest32;
@@ -6045,11 +8365,7 @@ var SigningManager = class extends TokenRefreshApiClient {
6045
8365
  const serverStartResponse = await this.apiCall(
6046
8366
  "POST",
6047
8367
  `/api/tss/${userId}/sign/start`,
6048
- {
6049
- sessionId,
6050
- digest32,
6051
- chainPath: CHAIN_PATH
6052
- },
8368
+ signStartBody(sessionId, digest32, CHAIN_PATH, payload),
6053
8369
  projectId,
6054
8370
  accessToken
6055
8371
  );
@@ -6203,12 +8519,12 @@ var SigningManager = class extends TokenRefreshApiClient {
6203
8519
  computeTransactionHash(transaction) {
6204
8520
  const encoder3 = new TextEncoder();
6205
8521
  const data = encoder3.encode(JSON.stringify(transaction));
6206
- let hash = 0;
8522
+ let hash2 = 0;
6207
8523
  for (let i = 0; i < data.length; i++) {
6208
- hash = (hash << 5) - hash + data[i];
6209
- hash = hash & hash;
8524
+ hash2 = (hash2 << 5) - hash2 + data[i];
8525
+ hash2 = hash2 & hash2;
6210
8526
  }
6211
- const hashHex = Math.abs(hash).toString(16).padStart(64, "0");
8527
+ const hashHex = Math.abs(hash2).toString(16).padStart(64, "0");
6212
8528
  return `0x${hashHex}`;
6213
8529
  }
6214
8530
  formatValue(value) {
@@ -6244,7 +8560,7 @@ var SigningManager = class extends TokenRefreshApiClient {
6244
8560
  /**
6245
8561
  * Sign EIP712 typed data with user confirmation
6246
8562
  */
6247
- async signTypedData(userId, projectId, origin, typedData, digest32, accessToken, userProfile) {
8563
+ async signTypedData(userId, projectId, origin, typedData, digest32, accessToken, userProfile, walletOwner) {
6248
8564
  console.log("[iframe][Sign] EIP712 signing request:", {
6249
8565
  userId,
6250
8566
  projectId,
@@ -6277,11 +8593,14 @@ var SigningManager = class extends TokenRefreshApiClient {
6277
8593
  this.optedOutApps.removeOptedOut(userId, projectId, origin);
6278
8594
  }
6279
8595
  }
6280
- const keyshareData = this.storage.loadKeyshare(userId);
8596
+ const keyshareData = this.storage.loadKeyshareOf(userId, walletOwner);
6281
8597
  if (!keyshareData) {
6282
8598
  throw new Error("No keyshare found. Please complete DKG first.");
6283
8599
  }
6284
- const signature = await this.performMPCSigning(userId, keyshareData, digest32, projectId, accessToken);
8600
+ const signature = await this.performMPCSigning(userId, keyshareData, digest32, projectId, accessToken, {
8601
+ kind: "typedData",
8602
+ typedData
8603
+ });
6285
8604
  console.log("[iframe][Sign] EIP712 signature generated");
6286
8605
  return signature;
6287
8606
  }
@@ -6348,8 +8667,93 @@ var SigningManager = class extends TokenRefreshApiClient {
6348
8667
  }
6349
8668
  };
6350
8669
 
8670
+ // src/iframe/lib/user-policy-api.ts
8671
+ function requirePolicyId(payload) {
8672
+ const policyId = payload?.policyId;
8673
+ if (typeof policyId !== "string" || policyId.length === 0) {
8674
+ throw new Error("policyId is required");
8675
+ }
8676
+ return policyId;
8677
+ }
8678
+ function requireInput(payload) {
8679
+ if (typeof payload?.name !== "string" || !payload.rules) {
8680
+ throw new Error("name and rules are required");
8681
+ }
8682
+ return {
8683
+ name: payload.name,
8684
+ description: payload.description ?? "",
8685
+ enabled: payload.enabled ?? true,
8686
+ allChains: payload.allChains ?? false,
8687
+ chainIds: payload.chainIds ?? [],
8688
+ rules: payload.rules
8689
+ };
8690
+ }
8691
+ var UserPolicyAPI = class extends TokenRefreshApiClient {
8692
+ constructor(sendResponseFn) {
8693
+ super();
8694
+ __publicField(this, "sendResponseFn");
8695
+ this.sendResponseFn = sendResponseFn;
8696
+ }
8697
+ path(userId, suffix = "") {
8698
+ return `/api/tss/${encodeURIComponent(userId)}/policies${suffix}`;
8699
+ }
8700
+ async listUserPolicies(userId, projectId, accessToken) {
8701
+ return this.apiCall("GET", this.path(userId), void 0, projectId, accessToken);
8702
+ }
8703
+ async createUserPolicy(userId, projectId, input, accessToken) {
8704
+ return this.apiCall("POST", this.path(userId), input, projectId, accessToken);
8705
+ }
8706
+ async updateUserPolicy(userId, projectId, policyId, input, accessToken) {
8707
+ return this.apiCall("PUT", this.path(userId, `/${encodeURIComponent(policyId)}`), input, projectId, accessToken);
8708
+ }
8709
+ async deleteUserPolicy(userId, projectId, policyId, accessToken) {
8710
+ return this.apiCall("POST", this.path(userId, `/${encodeURIComponent(policyId)}/delete`), {}, projectId, accessToken);
8711
+ }
8712
+ async cancelPending(userId, projectId, policyId, accessToken) {
8713
+ return this.apiCall("POST", this.path(userId, `/${encodeURIComponent(policyId)}/cancel-pending`), {}, projectId, accessToken);
8714
+ }
8715
+ async handleMessage(message, origin) {
8716
+ const { type, messageId } = message;
8717
+ const data = message.data || {};
8718
+ const accessToken = data.accessToken;
8719
+ const userId = data.userId;
8720
+ const projectId = data.projectId;
8721
+ if (!accessToken) throw new Error("accessToken is required");
8722
+ if (!userId) throw new Error("userId is required");
8723
+ if (!projectId) throw new Error("projectId is required");
8724
+ let view;
8725
+ switch (type) {
8726
+ case "USER_POLICY_LIST":
8727
+ view = await this.listUserPolicies(userId, projectId, accessToken);
8728
+ break;
8729
+ case "USER_POLICY_CREATE":
8730
+ view = await this.createUserPolicy(userId, projectId, requireInput(data.payload), accessToken);
8731
+ break;
8732
+ case "USER_POLICY_UPDATE":
8733
+ view = await this.updateUserPolicy(userId, projectId, requirePolicyId(data.payload), requireInput(data.payload), accessToken);
8734
+ break;
8735
+ case "USER_POLICY_DELETE":
8736
+ view = await this.deleteUserPolicy(userId, projectId, requirePolicyId(data.payload), accessToken);
8737
+ break;
8738
+ case "USER_POLICY_CANCEL_PENDING":
8739
+ view = await this.cancelPending(userId, projectId, requirePolicyId(data.payload), accessToken);
8740
+ break;
8741
+ default:
8742
+ throw new Error(`[USER_POLICY_API]: Unknown message type: ${type}`);
8743
+ }
8744
+ this.sendResponseFn(messageId, view, origin);
8745
+ }
8746
+ };
8747
+
6351
8748
  // src/iframe/main.ts
6352
- var IFRAME_VERSION = "0.4.128";
8749
+ var IFRAME_VERSION = "0.6.9";
8750
+ function walletOwnerOf(data) {
8751
+ const owner = data?.walletOwner;
8752
+ return typeof owner === "string" && /^0x[0-9a-fA-F]{40}$/.test(owner) ? owner : void 0;
8753
+ }
8754
+ function publicBackupInfo(data) {
8755
+ return { userId: data.userId, sessionId: data.sessionId, ownerAddress: data.ownerAddress };
8756
+ }
6353
8757
  var IframeWallet = class {
6354
8758
  // private finchpay: FinchPayRampAPI
6355
8759
  // private rampnow: RampnowOnrampAPI
@@ -6365,8 +8769,9 @@ var IframeWallet = class {
6365
8769
  __publicField(this, "backupManager");
6366
8770
  __publicField(this, "binance");
6367
8771
  __publicField(this, "hyperlane");
6368
- __publicField(this, "assetsPolicies");
8772
+ __publicField(this, "userPolicies");
6369
8773
  __publicField(this, "activityLogging");
8774
+ __publicField(this, "signIn");
6370
8775
  console.log("=".repeat(60));
6371
8776
  console.log(` EmbarkAI Secure Wallet - iframe version ${IFRAME_VERSION}`);
6372
8777
  console.log("=".repeat(60));
@@ -6379,7 +8784,8 @@ var IframeWallet = class {
6379
8784
  this.trustedApps = new TrustedAppsManager();
6380
8785
  this.optedOutApps = new OptedOutAppsManager();
6381
8786
  this.backupManager = new BackupManager();
6382
- this.assetsPolicies = new AssetsPoliciesAPI(this.messenger.sendResponse.bind(this.messenger));
8787
+ this.signIn = new IframeSignIn();
8788
+ this.userPolicies = new UserPolicyAPI(this.messenger.sendResponse.bind(this.messenger));
6383
8789
  this.activityLogging = new ActivityLoggingAPI(this.messenger.sendResponse.bind(this.messenger));
6384
8790
  this.hyperlane = new HyperlaneAPI(this.messenger.sendResponse.bind(this.messenger));
6385
8791
  this.binance = new BinanceOnrampAPI(
@@ -6417,11 +8823,20 @@ var IframeWallet = class {
6417
8823
  }
6418
8824
  }
6419
8825
  async handleMessage(message, origin) {
6420
- const { type, messageId, projectId } = message;
8826
+ const { type, messageId } = message;
6421
8827
  try {
8828
+ let auth = { mode: "legacy" };
8829
+ if (!SESSION_FREE_MESSAGES.has(type)) {
8830
+ const channel = this.sdkChannelManager.getChannel(message.data?.sessionToken);
8831
+ const channelSupportsIframeSession = !!channel && channel.origin === origin && !!channel.sdkVersion;
8832
+ auth = await resolveMessageAuth(message.data, channelSupportsIframeSession, iframeSession, {
8833
+ includeUnconfirmed: type === CONSENT_MESSAGE
8834
+ });
8835
+ message.data = applyMessageAuth(message.data, auth);
8836
+ }
6422
8837
  switch (true) {
6423
- case type.startsWith("USER_ASSETS_POLICY_"):
6424
- await this.assetsPolicies.handleMessage(message, origin);
8838
+ case type.startsWith("USER_POLICY_"):
8839
+ await this.userPolicies.handleMessage(message, origin);
6425
8840
  break;
6426
8841
  case type.startsWith("USER_ACTIVITY_LOG_"):
6427
8842
  await this.activityLogging.handleMessage(message, origin);
@@ -6444,11 +8859,29 @@ var IframeWallet = class {
6444
8859
  case type === "SDK_CHANNEL_HEARTBEAT":
6445
8860
  await this.handleHeartbeat(message, origin);
6446
8861
  break;
8862
+ case type === "SIGN_IN_START":
8863
+ await this.handleSignInStart(message, origin);
8864
+ break;
8865
+ case type === "SIGN_IN_FINISH":
8866
+ await this.handleSignInFinish(message, origin);
8867
+ break;
8868
+ case type === "SIGN_OUT":
8869
+ await this.handleSignOut(message, origin);
8870
+ break;
8871
+ case type === "GET_SESSION":
8872
+ await this.handleGetSession(message, origin);
8873
+ break;
8874
+ case type === "GET_DAPP_TOKENS":
8875
+ await this.handleGetDappTokens(message, origin);
8876
+ break;
8877
+ case type === "ACCOUNT_REQUEST":
8878
+ await this.handleAccountRequest(message, origin);
8879
+ break;
6447
8880
  case type === "AUTHENTICATE":
6448
8881
  await this.handleAuthenticate(message, origin);
6449
8882
  break;
6450
8883
  case type === "START_DKG":
6451
- await this.handleStartDKG(message, origin);
8884
+ await this.handleStartDKG(message, origin, auth);
6452
8885
  break;
6453
8886
  case type === "SIGN_TRANSACTION":
6454
8887
  await this.handleSignTransaction(message, origin);
@@ -6472,16 +8905,19 @@ var IframeWallet = class {
6472
8905
  await this.handleClearAuthorizations(message, origin);
6473
8906
  break;
6474
8907
  case type === "CREATE_BACKUP":
6475
- await this.handleCreateBackup(message, origin);
8908
+ await this.handleCreateBackup(message, origin, auth);
6476
8909
  break;
6477
8910
  case type === "GET_BACKUP_STATUS":
6478
8911
  await this.handleGetBackupStatus(message, origin);
6479
8912
  break;
8913
+ case type === "ENSURE_SERVER_BACKUP":
8914
+ await this.handleEnsureServerBackup(message, origin, auth);
8915
+ break;
6480
8916
  case type === "GET_CLOUD_PROVIDERS":
6481
8917
  await this.handleGetCloudProviders(message, origin);
6482
8918
  break;
6483
8919
  case type === "RESTORE_BACKUP":
6484
- await this.handleRestoreBackup(message, origin);
8920
+ await this.handleRestoreBackup(message, origin, auth);
6485
8921
  break;
6486
8922
  case type === "RESTORE_FROM_FILE":
6487
8923
  await this.handleRestoreFromFile(message, origin);
@@ -6523,15 +8959,19 @@ var IframeWallet = class {
6523
8959
  console.log(`[iframe] HEARTBEAT: valid=${isValid}, channels=${stats.total}`);
6524
8960
  }
6525
8961
  async handleSDKAuth(message, origin) {
6526
- const { projectId } = message.data;
8962
+ const { projectId, sdkVersion } = message.data;
6527
8963
  const { messageId } = message;
6528
- console.log(`[iframe] SDK_AUTH: projectId=${projectId}`);
8964
+ console.log(`[iframe] SDK_AUTH: projectId=${projectId}, sdkVersion=${sdkVersion ?? "legacy"}`);
6529
8965
  const isLocalhost = origin.startsWith("http://localhost:") || origin.startsWith("http://127.0.0.1:");
6530
8966
  const isHttps = origin.startsWith("https://");
6531
8967
  if (!isHttps && !isLocalhost) {
6532
8968
  throw new Error("Only HTTPS origins are allowed (or localhost for dev)");
6533
8969
  }
6534
- const sessionToken = this.sdkChannelManager.createChannel(projectId, origin);
8970
+ const sessionToken = this.sdkChannelManager.createChannel(
8971
+ projectId,
8972
+ origin,
8973
+ typeof sdkVersion === "string" && sdkVersion ? sdkVersion.slice(0, 32) : void 0
8974
+ );
6535
8975
  this.messenger.setChannelToken(sessionToken);
6536
8976
  this.messenger.addAllowedOrigin(origin);
6537
8977
  this.messenger.sendResponse(
@@ -6544,8 +8984,55 @@ var IframeWallet = class {
6544
8984
  );
6545
8985
  console.log(`[iframe] \u2705 SDK authenticated for origin: ${origin.substring(0, 40)}...`);
6546
8986
  }
8987
+ requireChannel(message, origin) {
8988
+ const channel = this.sdkChannelManager.validateChannel(message.data?.sessionToken, origin) ? this.sdkChannelManager.getChannel(message.data.sessionToken) : null;
8989
+ if (!channel) throw new Error("Invalid SDK channel");
8990
+ return channel;
8991
+ }
8992
+ /** Sign-in requests: the parent passes the user input, the iframe holds the flow secret */
8993
+ async handleSignInStart(message, origin) {
8994
+ const channel = this.requireChannel(message, origin);
8995
+ const result = await this.signIn.start(message.data, origin, channel.projectId);
8996
+ this.messenger.sendResponse(message.messageId, { type: "IFRAME_MSG_SIGN_IN_STARTED", ...result }, origin);
8997
+ }
8998
+ /** Finishes the flow: the iframe keeps the session, the parent gets a dApp token pair */
8999
+ async handleSignInFinish(message, origin) {
9000
+ const channel = this.requireChannel(message, origin);
9001
+ const loginResponse = await this.signIn.finish(message.data, origin, channel.projectId);
9002
+ this.messenger.sendResponse(message.messageId, { type: "IFRAME_MSG_SIGN_IN_FINISHED", loginResponse }, origin);
9003
+ }
9004
+ async handleSignOut(message, origin) {
9005
+ this.requireChannel(message, origin);
9006
+ this.signIn.cancel(origin);
9007
+ await iframeSession.signOut();
9008
+ this.messenger.sendResponse(message.messageId, { type: "IFRAME_MSG_SIGNED_OUT", success: true }, origin);
9009
+ }
9010
+ /** Session state and profile basics, never the tokens */
9011
+ async handleGetSession(message, origin) {
9012
+ this.requireChannel(message, origin);
9013
+ this.messenger.sendResponse(message.messageId, { type: "IFRAME_MSG_SESSION", session: iframeSession.info() }, origin);
9014
+ }
9015
+ /** A new dApp pair for the parent (lost storage, dead refresh token); null when the iframe has no session */
9016
+ async handleGetDappTokens(message, origin) {
9017
+ const channel = this.requireChannel(message, origin);
9018
+ const info = iframeSession.info();
9019
+ const tokens = info.signedIn ? {
9020
+ ...await iframeSession.mintDappTokens(channel.projectId),
9021
+ hasKeyshare: !!info.hasKeyshare,
9022
+ displayName: info.displayName ?? null,
9023
+ avatar: info.avatar ?? null,
9024
+ providers: info.providers ?? []
9025
+ } : null;
9026
+ this.messenger.sendResponse(message.messageId, { type: "IFRAME_MSG_DAPP_TOKENS", tokens }, origin);
9027
+ }
9028
+ /** Account changes a dApp token cannot make (profile, nickname, devices, smart account), sent with the iframe session */
9029
+ async handleAccountRequest(message, origin) {
9030
+ const channel = this.requireChannel(message, origin);
9031
+ const result = await sendAccountRequest(message.data, channel.projectId, void 0, void 0, origin);
9032
+ this.messenger.sendResponse(message.messageId, { type: "IFRAME_MSG_ACCOUNT_RESPONSE", ...result }, origin);
9033
+ }
6547
9034
  async handleAuthenticate(message, origin) {
6548
- const { sessionToken, projectId, userId, avatar, displayName } = message.data;
9035
+ const { sessionToken, projectId, userId, avatar, displayName, accessToken } = message.data;
6549
9036
  const { messageId } = message;
6550
9037
  if (!this.sdkChannelManager.validateChannel(sessionToken, origin)) {
6551
9038
  throw new Error("Invalid SDK channel");
@@ -6589,6 +9076,11 @@ var IframeWallet = class {
6589
9076
  console.log(`[iframe] \u2705 Authorization granted: ${userId} -> ${projectId}`);
6590
9077
  }
6591
9078
  }
9079
+ iframeSession.confirm(userId);
9080
+ if (accessToken) {
9081
+ const walletOwner = await this.backupManager.walletOwnerForProject(userId, accessToken, projectId);
9082
+ if (walletOwner) this.storage.reconcileWithServer(userId, { hasKeyshare: true, ownerAddress: walletOwner });
9083
+ }
6592
9084
  const address = this.storage.getOwnerAddress(userId);
6593
9085
  this.messenger.sendResponse(
6594
9086
  messageId,
@@ -6607,7 +9099,7 @@ var IframeWallet = class {
6607
9099
  console.log(`[iframe] \u2139\uFE0F User needs DKG or restore to create/recover keyshare`);
6608
9100
  }
6609
9101
  }
6610
- async handleStartDKG(message, origin) {
9102
+ async handleStartDKG(message, origin, auth) {
6611
9103
  const { sessionToken, userId, projectId, accessToken } = message.data;
6612
9104
  const { messageId } = message;
6613
9105
  if (!this.sdkChannelManager.validateChannel(sessionToken, origin)) {
@@ -6620,12 +9112,27 @@ var IframeWallet = class {
6620
9112
  console.log(`[iframe] START_DKG: userId=${userId}, projectId=${projectId}, hasToken=${!!accessToken}`);
6621
9113
  this.displayLoadingIndicator("Generating secure keys...");
6622
9114
  try {
6623
- const result = await this.dkgManager.performDKG(userId, projectId, accessToken);
6624
- this.storage.saveKeyshare(userId, {
6625
- keyshare_b64: result.keyshare.keyshare_b64,
6626
- ownerAddress: result.ownerAddress,
6627
- sessionId: result.sessionId
6628
- });
9115
+ let ownerAddress;
9116
+ try {
9117
+ const result2 = await this.dkgManager.performDKG(userId, projectId, accessToken);
9118
+ const previous = this.storage.getOwnerAddress(userId);
9119
+ if (previous && previous.toLowerCase() !== result2.ownerAddress.toLowerCase()) {
9120
+ this.storage.archiveKeyshare(userId);
9121
+ }
9122
+ this.storage.saveKeyshare(userId, {
9123
+ keyshare_b64: result2.keyshare.keyshare_b64,
9124
+ ownerAddress: result2.ownerAddress,
9125
+ sessionId: result2.sessionId
9126
+ });
9127
+ ownerAddress = result2.ownerAddress;
9128
+ } catch (error) {
9129
+ const existing = existingOwnerAddressOf(error);
9130
+ const current = this.storage.getOwnerAddress(userId);
9131
+ const isCurrent = !!existing && !!current && current.toLowerCase() === existing.toLowerCase();
9132
+ if (!existing || !isCurrent && !this.storage.restoreArchivedKeyshare(userId, existing)) throw error;
9133
+ ownerAddress = this.storage.getOwnerAddress(userId);
9134
+ }
9135
+ const result = { ownerAddress };
6629
9136
  this.hideLoadingIndicator();
6630
9137
  this.messenger.sendResponse(
6631
9138
  messageId,
@@ -6636,6 +9143,8 @@ var IframeWallet = class {
6636
9143
  origin
6637
9144
  );
6638
9145
  console.log(`[iframe] \u2705 DKG completed: ${result.ownerAddress}`);
9146
+ void this.backupManager.ensureServerBackup(userId, accessToken, projectId, basicBackupAllowed(auth), ownerAddress);
9147
+ if (auth.mode === "iframe") iframeSession.update({ hasKeyshare: true });
6639
9148
  this.sendWalletReadyStatus(userId, origin);
6640
9149
  } catch (error) {
6641
9150
  this.hideLoadingIndicator();
@@ -6654,10 +9163,16 @@ var IframeWallet = class {
6654
9163
  }
6655
9164
  console.log(`[iframe] SIGN_TRANSACTION: userId=${userId}, hasAccessToken=${!!accessToken}`);
6656
9165
  try {
6657
- const signature = await this.signingManager.signTransaction(userId, projectId, origin, transaction, accessToken, {
6658
- avatar,
6659
- displayName
6660
- });
9166
+ const walletOwner = await this.backupManager.walletOwnerForProject(userId, accessToken, projectId);
9167
+ const signature = await this.signingManager.signTransaction(
9168
+ userId,
9169
+ projectId,
9170
+ origin,
9171
+ transaction,
9172
+ accessToken,
9173
+ { avatar, displayName },
9174
+ walletOwner
9175
+ );
6661
9176
  this.messenger.sendResponse(
6662
9177
  messageId,
6663
9178
  {
@@ -6684,6 +9199,7 @@ var IframeWallet = class {
6684
9199
  }
6685
9200
  console.log(`[iframe] SIGN_TYPED_DATA: userId=${userId}, primaryType=${typedData?.primaryType}`);
6686
9201
  try {
9202
+ const walletOwner = await this.backupManager.walletOwnerForProject(userId, accessToken, projectId);
6687
9203
  const signature = await this.signingManager.signTypedData(
6688
9204
  userId,
6689
9205
  projectId,
@@ -6691,7 +9207,8 @@ var IframeWallet = class {
6691
9207
  typedData,
6692
9208
  digest32,
6693
9209
  accessToken,
6694
- { avatar, displayName }
9210
+ { avatar, displayName },
9211
+ walletOwner
6695
9212
  );
6696
9213
  this.messenger.sendResponse(
6697
9214
  messageId,
@@ -6724,11 +9241,14 @@ var IframeWallet = class {
6724
9241
  );
6725
9242
  }
6726
9243
  async handleCheckKeyshare(message, origin) {
6727
- const { sessionToken, userId } = message.data;
9244
+ const { sessionToken, userId, server } = message.data;
6728
9245
  const { messageId } = message;
6729
9246
  if (!this.sdkChannelManager.validateChannel(sessionToken, origin)) {
6730
9247
  throw new Error("Invalid SDK channel");
6731
9248
  }
9249
+ if (server && typeof server.hasKeyshare === "boolean") {
9250
+ this.storage.reconcileWithServer(userId, server);
9251
+ }
6732
9252
  const hasKeyshare = this.storage.hasKeyshare(userId);
6733
9253
  const address = hasKeyshare ? this.storage.getOwnerAddress(userId) : void 0;
6734
9254
  this.messenger.sendResponse(
@@ -6820,9 +9340,9 @@ var IframeWallet = class {
6820
9340
  );
6821
9341
  console.log(`[iframe] \u2705 CLEAR_AUTHORIZATIONS: Cleared ${clearedCount} authorizations for project ${projectId}`);
6822
9342
  }
6823
- async handleCreateBackup(message, origin) {
9343
+ async handleCreateBackup(message, origin, auth) {
6824
9344
  const { sessionToken, userId, backupRequest, accessToken } = message.data;
6825
- const { messageId } = message;
9345
+ const { messageId, projectId } = message;
6826
9346
  console.log(`[iframe] CREATE_BACKUP received:`, {
6827
9347
  userId,
6828
9348
  method: backupRequest?.method,
@@ -6846,7 +9366,9 @@ var IframeWallet = class {
6846
9366
  return;
6847
9367
  }
6848
9368
  try {
6849
- const backupOp = () => this.backupManager.createBackup(userId, backupRequest, accessToken);
9369
+ const walletOwner = await this.resolveWalletOwner(userId, message);
9370
+ const currentServerMethod = backupRequest.method === "server" ? await this.backupManager.getServerBackupMethod(accessToken, projectId, walletOwner) : void 0;
9371
+ const backupOp = () => this.backupManager.createBackup(userId, backupRequest, accessToken, projectId, currentServerMethod, basicBackupAllowed(auth), walletOwner);
6850
9372
  const result = !backupRequest.password && this.isSafari() ? await this.withPasskeyConfirmation("backup", backupOp) : await backupOp();
6851
9373
  this.messenger.sendResponse(
6852
9374
  messageId,
@@ -6866,6 +9388,13 @@ var IframeWallet = class {
6866
9388
  this.messenger.sendError(messageId, error.message || "Backup failed", origin);
6867
9389
  }
6868
9390
  }
9391
+ /**
9392
+ * The wallet a backup request is about (sdk #32, #31): the server's keyshare for the request's project as TSS
9393
+ * reports it, also for older parents that name none; what the parent names only when TSS does not answer
9394
+ */
9395
+ resolveWalletOwner(userId, message) {
9396
+ return this.backupManager.resolveWalletOwner(userId, walletOwnerOf(message.data), message.data?.accessToken, message.projectId);
9397
+ }
6869
9398
  async handleGetBackupStatus(message, origin) {
6870
9399
  const { sessionToken, userId } = message.data;
6871
9400
  const { messageId } = message;
@@ -6885,6 +9414,17 @@ var IframeWallet = class {
6885
9414
  );
6886
9415
  console.log(`[iframe] \u2705 GET_BACKUP_STATUS: Status retrieved`);
6887
9416
  }
9417
+ async handleEnsureServerBackup(message, origin, auth) {
9418
+ const { sessionToken, userId, accessToken } = message.data;
9419
+ const { messageId, projectId } = message;
9420
+ console.log(`[iframe] ENSURE_SERVER_BACKUP: userId=${userId}`);
9421
+ if (!this.sdkChannelManager.validateChannel(sessionToken, origin)) {
9422
+ console.error("[iframe] ENSURE_SERVER_BACKUP: Session validation failed");
9423
+ throw new Error("Invalid SDK channel");
9424
+ }
9425
+ const result = userId ? await this.backupManager.ensureServerBackup(userId, accessToken, projectId, basicBackupAllowed(auth), await this.resolveWalletOwner(userId, message)) : { status: "failed", reason: "unknown", error: "userId is required" };
9426
+ this.messenger.sendResponse(messageId, { type: "IFRAME_MSG_SERVER_BACKUP_ENSURED", result }, origin);
9427
+ }
6888
9428
  async handleGetCloudProviders(message, origin) {
6889
9429
  const { sessionToken } = message.data;
6890
9430
  const { messageId } = message;
@@ -6957,9 +9497,9 @@ var IframeWallet = class {
6957
9497
  isSafari() {
6958
9498
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
6959
9499
  }
6960
- async handleRestoreBackup(message, origin) {
9500
+ async handleRestoreBackup(message, origin, auth) {
6961
9501
  const { sessionToken, userId, password, accessToken } = message.data;
6962
- const { messageId } = message;
9502
+ const { messageId, projectId } = message;
6963
9503
  console.log(`[iframe] RESTORE_BACKUP received:`, {
6964
9504
  userId,
6965
9505
  hasPassword: !!password,
@@ -6978,8 +9518,10 @@ var IframeWallet = class {
6978
9518
  return;
6979
9519
  }
6980
9520
  try {
6981
- const restoreOp = () => this.backupManager.restoreFromServer(userId, password, accessToken);
6982
- const backupData = !password && this.isSafari() ? await this.withPasskeyConfirmation("restore", restoreOp) : await restoreOp();
9521
+ const walletOwner = await this.resolveWalletOwner(userId, message);
9522
+ const restoreOp = () => this.backupManager.restoreFromServer(userId, password, accessToken, projectId, basicBackupAllowed(auth), walletOwner);
9523
+ const needsPasskey = !password && this.isSafari() && await this.backupManager.getServerBackupMethod(accessToken, projectId, walletOwner) !== "server-managed";
9524
+ const backupData = needsPasskey ? await this.withPasskeyConfirmation("restore", restoreOp) : await restoreOp();
6983
9525
  this.messenger.sendResponse(
6984
9526
  messageId,
6985
9527
  {
@@ -6987,7 +9529,8 @@ var IframeWallet = class {
6987
9529
  result: {
6988
9530
  success: true,
6989
9531
  timestamp: Date.now(),
6990
- data: backupData
9532
+ // the keyshare itself never leaves the iframe
9533
+ data: publicBackupInfo(backupData)
6991
9534
  }
6992
9535
  },
6993
9536
  origin
@@ -6996,6 +9539,7 @@ var IframeWallet = class {
6996
9539
  this.sendWalletReadyStatus(userId, origin);
6997
9540
  } catch (error) {
6998
9541
  console.error("[iframe] RESTORE_BACKUP: Exception:", error);
9542
+ const parentSupportsIframeSession = !!this.sdkChannelManager.getChannel(sessionToken)?.sdkVersion;
6999
9543
  this.messenger.sendResponse(
7000
9544
  messageId,
7001
9545
  {
@@ -7003,7 +9547,7 @@ var IframeWallet = class {
7003
9547
  result: {
7004
9548
  success: false,
7005
9549
  timestamp: Date.now(),
7006
- error: error.message || "Restore failed"
9550
+ error: restoreErrorForParent(error.message || "Restore failed", parentSupportsIframeSession)
7007
9551
  }
7008
9552
  },
7009
9553
  origin
@@ -7045,7 +9589,8 @@ var IframeWallet = class {
7045
9589
  result: {
7046
9590
  success: true,
7047
9591
  timestamp: Date.now(),
7048
- data: backupData
9592
+ // the keyshare itself never leaves the iframe
9593
+ data: publicBackupInfo(backupData)
7049
9594
  }
7050
9595
  },
7051
9596
  origin
@@ -7089,7 +9634,7 @@ var IframeWallet = class {
7089
9634
  return;
7090
9635
  }
7091
9636
  try {
7092
- const encryptedData = await this.backupManager.encryptBackupData(userId, password);
9637
+ const encryptedData = await this.backupManager.encryptBackupData(userId, password, await this.resolveWalletOwner(userId, message));
7093
9638
  this.messenger.sendResponse(
7094
9639
  messageId,
7095
9640
  {
@@ -7105,14 +9650,18 @@ var IframeWallet = class {
7105
9650
  }
7106
9651
  }
7107
9652
  async handleEncryptEnvelopeData(message, origin) {
7108
- const { userId, password } = message.data;
9653
+ const { sessionToken, userId, password } = message.data;
7109
9654
  const { messageId } = message;
7110
9655
  if (!userId) {
7111
9656
  this.messenger.sendError(messageId, "userId is required", origin);
7112
9657
  return;
7113
9658
  }
9659
+ if (!this.sdkChannelManager.validateChannel(sessionToken, origin)) {
9660
+ this.messenger.sendError(messageId, "Invalid SDK channel", origin);
9661
+ return;
9662
+ }
7114
9663
  try {
7115
- const envelopeJson = await this.backupManager.encryptEnvelopeData(userId, password);
9664
+ const envelopeJson = await this.backupManager.encryptEnvelopeData(userId, password, await this.resolveWalletOwner(userId, message));
7116
9665
  this.messenger.sendResponse(
7117
9666
  messageId,
7118
9667
  {
@@ -7208,6 +9757,11 @@ var IframeWallet = class {
7208
9757
  this.displayReadyIndicator();
7209
9758
  }
7210
9759
  };
9760
+ function existingOwnerAddressOf(error) {
9761
+ const details = error?.details;
9762
+ if (details?.status !== 409 || typeof details.existingOwnerAddress !== "string") return null;
9763
+ return details.existingOwnerAddress;
9764
+ }
7211
9765
  function toggleAuthPermissions(e) {
7212
9766
  const isToggler = e.target?.id === "data-auth-permissions-toggler";
7213
9767
  if (!isToggler) return;