import type { RecentAddressesState, RecentAddress } from "@ledgerhq/types-live"; import { RecentAddressesArraySchema } from "./entities/recentAddresses"; export const RECENT_ADDRESSES_COUNT_LIMIT = 12; export type RecentAddressesCache = RecentAddressesState; export interface RecentAddressesStore { addAddress(currency: string, address: string, ensName?: string): void; removeAddress(currency: string, address: string): void; syncAddresses(cache: RecentAddressesCache): void; getAddresses(currency: string): RecentAddress[]; } let recentAddressesStore: RecentAddressesStore | null = null; export function getRecentAddressesStore(): RecentAddressesStore { if (recentAddressesStore === null) { throw new Error( "Recent addresses store instance is null, please call function setupRecentAddressesStore in application initialization", ); } return recentAddressesStore; } export function setupRecentAddressesStore( addressesByCurrency: RecentAddressesCache, onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void, ): void { recentAddressesStore = new RecentAddressesStoreImpl(addressesByCurrency, onAddAddressComplete); } class RecentAddressesStoreImpl implements RecentAddressesStore { private addressesByCurrency: RecentAddressesCache = {}; private readonly onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void; constructor( addressesByCurrency: RecentAddressesCache, onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void, ) { this.addressesByCurrency = this.sanitizeCache(addressesByCurrency); this.onAddAddressComplete = onAddAddressComplete; } private sanitizeCache(cache: RecentAddressesCache): RecentAddressesCache { const sanitized: RecentAddressesCache = {}; for (const currency in cache) { const entries = cache[currency]; const result = RecentAddressesArraySchema.safeParse(entries); sanitized[currency] = result.success ? result.data : []; } return sanitized; } addAddress(currency: string, address: string, ensName?: string): void { this.addAddressToCache(currency, address, Date.now(), true, ensName); } removeAddress(currency: string, address: string): void { if (!this.addressesByCurrency[currency]) return; const addresses = this.addressesByCurrency[currency]; const index = addresses.findIndex(entry => entry.address === address); if (index !== -1) { addresses.splice(index, 1); this.addressesByCurrency[currency] = [...addresses]; this.onAddAddressComplete(this.addressesByCurrency); } } syncAddresses(cache: RecentAddressesCache): void { const previousAddresses = { ...this.addressesByCurrency }; this.addressesByCurrency = this.sanitizeCache(cache); for (const currency in previousAddresses) { const entries = previousAddresses[currency]; for (const entry of entries) { this.addAddressToCache(currency, entry.address, entry.lastUsed, false, entry.ensName); } } this.onAddAddressComplete(this.addressesByCurrency); } getAddresses(currency: string): RecentAddress[] { const addresses = this.addressesByCurrency[currency]; if (!addresses) return []; return addresses.filter( (entry): entry is RecentAddress => !!entry && typeof entry.address === "string" && entry.address.length > 0, ); } private addAddressToCache( currency: string, address: string, timestamp: number, shouldTriggerCallback: boolean, ensName?: string, ): void { if (!this.addressesByCurrency[currency]) { this.addressesByCurrency[currency] = []; } const addresses = this.addressesByCurrency[currency]; const addressIndex = addresses.findIndex(entry => entry.address === address); if (addressIndex !== -1) { addresses.splice(addressIndex, 1); } else if (addresses.length >= RECENT_ADDRESSES_COUNT_LIMIT) { addresses.pop(); } addresses.unshift({ address, lastUsed: timestamp, ensName }); this.addressesByCurrency[currency] = [...addresses]; if (shouldTriggerCallback) { this.onAddAddressComplete(this.addressesByCurrency); } } }