| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- /**
- * mavi vault in this browser only (localStorage).
- * Secrets are encrypted; never send vault contents to krypco servers.
- */
- import {
- decryptVaultSecret,
- encryptVaultSecret,
- isVaultRecord,
- type CreatedWallet,
- type VaultRecord,
- walletFromMnemonic,
- } from "@krypco/mavi-core";
- const VAULT_KEY = "mavi_vault_v1";
- const SESSION_KEY = "mavi_unlocked_v1";
- export type UnlockedMavi = {
- address: `0x${string}`;
- mnemonic: string;
- privateKey: `0x${string}`;
- wordCount: 12 | 24;
- };
- export function readVault(): VaultRecord | null {
- if (typeof window === "undefined") return null;
- try {
- const raw = window.localStorage.getItem(VAULT_KEY);
- if (!raw) return null;
- const parsed: unknown = JSON.parse(raw);
- return isVaultRecord(parsed) ? parsed : null;
- } catch {
- return null;
- }
- }
- export function hasVault(): boolean {
- return readVault() !== null;
- }
- export async function saveNewVault(
- password: string,
- wallet: CreatedWallet,
- ): Promise<VaultRecord> {
- const { salt, iv, ciphertext } = await encryptVaultSecret(password, {
- mnemonic: wallet.mnemonic,
- });
- const record: VaultRecord = {
- version: 1,
- brand: "mavi",
- address: wallet.address,
- wordCount: wallet.wordCount,
- createdAt: new Date().toISOString(),
- salt,
- iv,
- ciphertext,
- };
- window.localStorage.setItem(VAULT_KEY, JSON.stringify(record));
- return record;
- }
- export async function unlockVault(password: string): Promise<UnlockedMavi> {
- const record = readVault();
- if (!record) throw new Error("No mavi wallet on this browser yet");
- const secret = await decryptVaultSecret(password, record);
- const wallet = walletFromMnemonic(secret.mnemonic, record.wordCount);
- const unlocked: UnlockedMavi = {
- address: wallet.address,
- mnemonic: wallet.mnemonic,
- privateKey: wallet.privateKey,
- wordCount: wallet.wordCount,
- };
- writeUnlockedSession(unlocked);
- return unlocked;
- }
- function writeUnlockedSession(unlocked: UnlockedMavi): void {
- // Session-only (this browser tab/window group): lock clears it.
- try {
- window.sessionStorage.setItem(
- SESSION_KEY,
- JSON.stringify({
- address: unlocked.address,
- wordCount: unlocked.wordCount,
- mnemonic: unlocked.mnemonic,
- privateKey: unlocked.privateKey,
- unlockedAt: new Date().toISOString(),
- }),
- );
- window.dispatchEvent(new Event("mavi-unlock"));
- } catch {
- /* private browsing quota */
- }
- }
- /** True when this tab already has an unlocked session (no password again). */
- export function isUnlocked(): boolean {
- return readUnlockedSession() !== null;
- }
- /**
- * Prefer existing unlocked session; otherwise unlock with password.
- * Use from Send / any gated action so one unlock covers the whole tab.
- */
- export async function ensureUnlocked(
- password?: string,
- ): Promise<UnlockedMavi> {
- const existing = readUnlockedSession();
- if (existing) return existing;
- if (!password) {
- throw new Error("Unlock mavi first (enter your password).");
- }
- return unlockVault(password);
- }
- export function readUnlockedSession(): UnlockedMavi | null {
- if (typeof window === "undefined") return null;
- try {
- const raw = window.sessionStorage.getItem(SESSION_KEY);
- if (!raw) return null;
- const p = JSON.parse(raw) as Partial<UnlockedMavi>;
- if (
- typeof p.address === "string" &&
- typeof p.mnemonic === "string" &&
- typeof p.privateKey === "string" &&
- (p.wordCount === 12 || p.wordCount === 24)
- ) {
- return {
- address: p.address as `0x${string}`,
- mnemonic: p.mnemonic,
- privateKey: p.privateKey as `0x${string}`,
- wordCount: p.wordCount,
- };
- }
- return null;
- } catch {
- return null;
- }
- }
- export function lockSession(): void {
- if (typeof window === "undefined") return;
- try {
- window.sessionStorage.removeItem(SESSION_KEY);
- } catch {
- /* ignore */
- }
- }
- /** Danger: removes the encrypted vault from this browser. Recovery phrase still works if written down. */
- export function deleteVaultLocal(): void {
- if (typeof window === "undefined") return;
- try {
- window.localStorage.removeItem(VAULT_KEY);
- } catch {
- /* ignore */
- }
- lockSession();
- }
- export function shortAddress(address: string): string {
- if (address.length < 12) return address;
- return `${address.slice(0, 6)}…${address.slice(-4)}`;
- }
|