@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.
- package/README.md +6 -7
- package/dist/iframe/_headers +7 -11
- package/dist/iframe/index.html +1 -1
- package/dist/iframe/kyc/sumsub.html +1 -1
- package/dist/iframe/main.js +3200 -646
- package/dist/iframe/oauth/telegram.html +1 -1
- package/dist/iframe/oauth/telegram.js +19 -3
- package/dist/iframe/oauth/x.html +1 -1
- package/dist/iframe/oauth/x.js +60 -0
- package/dist/index.cjs +33671 -31717
- package/dist/index.d.cts +810 -293
- package/dist/index.d.ts +810 -293
- package/dist/index.js +33538 -31572
- package/dist/styles.css +1 -1
- package/package.json +5 -3
package/dist/iframe/main.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
511
|
-
const response = await fetch(
|
|
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":
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
1252
|
-
|
|
1253
|
-
|
|
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/
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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/
|
|
1261
|
-
var
|
|
1262
|
-
var
|
|
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/
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
var
|
|
1269
|
-
|
|
1270
|
-
|
|
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],
|
|
2106
|
-
if (
|
|
2107
|
-
return
|
|
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 =
|
|
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 (
|
|
2124
|
-
|
|
3910
|
+
if (cache2.size === MAX_MEMOIZE_SIZE) {
|
|
3911
|
+
cache2.clear();
|
|
2125
3912
|
}
|
|
2126
3913
|
return key;
|
|
2127
3914
|
});
|
|
2128
|
-
var
|
|
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
|
|
4663
|
+
return data.resourceToken;
|
|
2949
4664
|
}
|
|
2950
|
-
async
|
|
2951
|
-
const
|
|
2952
|
-
const
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
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
|
|
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
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
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
|
|
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 {
|
|
2978
|
-
console.info("[
|
|
4701
|
+
const { accessToken, payload } = data;
|
|
4702
|
+
console.info("[ USER_ACTIVITY_LOG_API Received message ]", type, projectId, payload);
|
|
2979
4703
|
switch (type) {
|
|
2980
|
-
case "
|
|
2981
|
-
const
|
|
2982
|
-
this.sendResponseFn(messageId,
|
|
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 "
|
|
2996
|
-
const
|
|
2997
|
-
this.sendResponseFn(messageId,
|
|
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(`[
|
|
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>
|
|
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
|
|
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>
|
|
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 || "
|
|
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 || "
|
|
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 || "
|
|
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 (
|
|
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
|
|
3370
|
-
return
|
|
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
|
|
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
|
|
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/
|
|
3838
|
-
var
|
|
3839
|
-
constructor(
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
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
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
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
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
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
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
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
|
-
|
|
3918
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
4028
|
-
|
|
4029
|
-
|
|
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:
|
|
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
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
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
|
|
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
|
|
4124
|
-
console.error("[iframe][BackupManager] Backup failed:",
|
|
6107
|
+
const errorMessage2 = error.message || "Backup failed";
|
|
6108
|
+
console.error("[iframe][BackupManager] Backup failed:", errorMessage2);
|
|
4125
6109
|
this.updateBackupStatus(userId, request.method, {
|
|
4126
|
-
error:
|
|
6110
|
+
error: errorMessage2
|
|
4127
6111
|
});
|
|
4128
6112
|
return {
|
|
4129
6113
|
success: false,
|
|
4130
6114
|
method: request.method,
|
|
4131
6115
|
timestamp: Date.now(),
|
|
4132
|
-
error:
|
|
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
|
-
|
|
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
|
-
|
|
4152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4218
|
-
console.error("[iframe][BackupManager] Local file restore failed:",
|
|
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
|
|
4828
|
-
const addr =
|
|
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
|
|
4839
|
-
let
|
|
6910
|
+
const hash2 = keccak256(addrBytes).replace("0x", "");
|
|
6911
|
+
let checksumAddress2 = "0x";
|
|
4840
6912
|
for (let i = 0; i < addr.length; i++) {
|
|
4841
|
-
|
|
6913
|
+
checksumAddress2 += parseInt(hash2[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
4842
6914
|
}
|
|
4843
|
-
return
|
|
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
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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
|
|
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 = `${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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
|
-
|
|
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
|
|
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:
|
|
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}: ${
|
|
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
|
-
"
|
|
5563
|
-
"
|
|
5564
|
-
"
|
|
5565
|
-
"
|
|
5566
|
-
"
|
|
5567
|
-
"
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
"
|
|
5571
|
-
"
|
|
5572
|
-
"
|
|
5573
|
-
"
|
|
5574
|
-
"
|
|
5575
|
-
|
|
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
|
-
*
|
|
7901
|
+
* Validate message structure
|
|
5719
7902
|
*/
|
|
5720
|
-
|
|
5721
|
-
|
|
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
|
-
*
|
|
7907
|
+
* Check if origin is allowed
|
|
5729
7908
|
*/
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
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
|
-
*
|
|
7921
|
+
* Compute HMAC-SHA256 for message authentication
|
|
5736
7922
|
*/
|
|
5737
|
-
|
|
5738
|
-
|
|
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
|
-
*
|
|
7942
|
+
* Constant-time string comparison (prevent timing attacks)
|
|
5742
7943
|
*/
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
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
|
-
*
|
|
7953
|
+
* Cleanup old nonces to prevent memory growth
|
|
5759
7954
|
*/
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
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
|
|
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
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
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
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
8522
|
+
let hash2 = 0;
|
|
6207
8523
|
for (let i = 0; i < data.length; i++) {
|
|
6208
|
-
|
|
6209
|
-
|
|
8524
|
+
hash2 = (hash2 << 5) - hash2 + data[i];
|
|
8525
|
+
hash2 = hash2 & hash2;
|
|
6210
8526
|
}
|
|
6211
|
-
const hashHex = Math.abs(
|
|
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.
|
|
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.
|
|
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, "
|
|
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.
|
|
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
|
|
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("
|
|
6424
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
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
|
|
6658
|
-
|
|
6659
|
-
|
|
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
|
|
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
|
|
6982
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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;
|