diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index a994b4a..41bdd4a 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -178,6 +178,16 @@ class HttpHandler(BaseHTTPRequestHandler):
                 self.server.msg_id_counter += 1
             args_dict["err_messages"] = err_messages_with_ids
 
+        if self.path:
+            parsed = parse.urlparse(self.path)
+            url_split = parsed.path.split("/")
+            if len(url_split) > 1 and url_split[1]:
+                args_dict["current_page"] = url_split[1]
+            else:
+                args_dict["current_page"] = "index"
+        else:
+            args_dict["current_page"] = "index"
+
         shutdown_token = os.urandom(8).hex()
         self.server.session_tokens["shutdown"] = shutdown_token
         args_dict["shutdown_token"] = shutdown_token
@@ -410,7 +420,6 @@ class HttpHandler(BaseHTTPRequestHandler):
         return self.render_template(
             template,
             {
-                "refresh": 30,
                 "active_swaps": [
                     (
                         s[0].hex(),
diff --git a/basicswap/js_server.py b/basicswap/js_server.py
index 194b846..ebd021a 100644
--- a/basicswap/js_server.py
+++ b/basicswap/js_server.py
@@ -983,37 +983,49 @@ def js_readurl(self, url_split, post_string, is_json) -> bytes:
 def js_active(self, url_split, post_string, is_json) -> bytes:
     swap_client = self.server.swap_client
     swap_client.checkSystemStatus()
-    filters = {"sort_by": "created_at", "sort_dir": "desc"}
+
+    filters = {
+        "sort_by": "created_at", 
+        "sort_dir": "desc",
+        "with_available_or_active": True,
+        "with_extra_info": True
+    }
+
     EXCLUDED_STATES = [
-        "Completed",
-        "Expired",
-        "Timed-out",
-        "Abandoned",
         "Failed, refunded",
         "Failed, swiped",
         "Failed",
         "Error",
-        "received",
+        "Expired",
+        "Timed-out",
+        "Abandoned",
+        "Completed"
     ]
+
     all_bids = []
     processed_bid_ids = set()
-
     try:
         received_bids = swap_client.listBids(filters=filters)
         sent_bids = swap_client.listBids(sent=True, filters=filters)
+
         for bid in received_bids + sent_bids:
             try:
                 bid_id_hex = bid[2].hex()
                 if bid_id_hex in processed_bid_ids:
                     continue
-                bid_state = strBidState(bid[5])
-                tx_state_a = strTxState(bid[7])
-                tx_state_b = strTxState(bid[8])
-                if bid_state in EXCLUDED_STATES:
-                    continue
+
                 offer = swap_client.getOffer(bid[3])
                 if not offer:
                     continue
+
+                bid_state = strBidState(bid[5])
+
+                if bid_state in EXCLUDED_STATES:
+                    continue
+
+                tx_state_a = strTxState(bid[7])
+                tx_state_b = strTxState(bid[8])
+
                 swap_data = {
                     "bid_id": bid_id_hex,
                     "offer_id": bid[3].hex(),
@@ -1040,6 +1052,7 @@ def js_active(self, url_split, post_string, is_json) -> bytes:
                 continue
     except Exception:
         return bytes(json.dumps([]), "UTF-8")
+
     return bytes(json.dumps(all_bids), "UTF-8")
 
 
diff --git a/basicswap/static/js/bids_available.js b/basicswap/static/js/bids_available.js
index 7e31cca..3cb11e8 100644
--- a/basicswap/static/js/bids_available.js
+++ b/basicswap/static/js/bids_available.js
@@ -1,4 +1,3 @@
-// Constants and State
 const PAGE_SIZE = 50;
 const COIN_NAME_TO_SYMBOL = {
     'Bitcoin': 'BTC',
@@ -16,7 +15,6 @@ const COIN_NAME_TO_SYMBOL = {
     'Dogecoin': 'DOGE'
 };
 
-// Global state
 const state = {
     dentities: new Map(),
     currentPage: 1,
@@ -27,7 +25,6 @@ const state = {
     refreshPromise: null
 };
 
-// DOM
 const elements = {
     bidsBody: document.getElementById('bids-body'),
     prevPageButton: document.getElementById('prevPage'),
@@ -40,125 +37,6 @@ const elements = {
     statusText: document.getElementById('status-text')
 };
 
-// Identity Manager
-const IdentityManager = {
-    cache: new Map(),
-    pendingRequests: new Map(),
-    retryDelay: 2000,
-    maxRetries: 3,
-    cacheTimeout: 5 * 60 * 1000, // 5 minutes
-
-    async getIdentityData(address) {
-        if (!address) {
-            return { address: '' };
-        }
-
-        const cachedData = this.getCachedIdentity(address);
-        if (cachedData) {
-            return { ...cachedData, address };
-        }
-
-        if (this.pendingRequests.has(address)) {
-            const pendingData = await this.pendingRequests.get(address);
-            return { ...pendingData, address };
-        }
-
-        const request = this.fetchWithRetry(address);
-        this.pendingRequests.set(address, request);
-
-        try {
-            const data = await request;
-            this.cache.set(address, {
-                data,
-                timestamp: Date.now()
-            });
-            return { ...data, address };
-        } catch (error) {
-            console.warn(`Error fetching identity for ${address}:`, error);
-            return { address };
-        } finally {
-            this.pendingRequests.delete(address);
-        }
-    },
-
-    getCachedIdentity(address) {
-        const cached = this.cache.get(address);
-        if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
-            return cached.data;
-        }
-        if (cached) {
-            this.cache.delete(address);
-        }
-        return null;
-    },
-
-    async fetchWithRetry(address, attempt = 1) {
-        try {
-            const response = await fetch(`/json/identities/${address}`, {
-                signal: AbortSignal.timeout(5000)
-            });
-
-            if (!response.ok) {
-                throw new Error(`HTTP error! status: ${response.status}`);
-            }
-
-            const data = await response.json();
-            return {
-                ...data,
-                address,
-                num_sent_bids_successful: safeParseInt(data.num_sent_bids_successful),
-                num_recv_bids_successful: safeParseInt(data.num_recv_bids_successful),
-                num_sent_bids_failed: safeParseInt(data.num_sent_bids_failed),
-                num_recv_bids_failed: safeParseInt(data.num_recv_bids_failed),
-                num_sent_bids_rejected: safeParseInt(data.num_sent_bids_rejected),
-                num_recv_bids_rejected: safeParseInt(data.num_recv_bids_rejected),
-                label: data.label || '',
-                note: data.note || '',
-                automation_override: safeParseInt(data.automation_override)
-            };
-        } catch (error) {
-            if (attempt >= this.maxRetries) {
-                console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
-                return {
-                    address,
-                    num_sent_bids_successful: 0,
-                    num_recv_bids_successful: 0,
-                    num_sent_bids_failed: 0,
-                    num_recv_bids_failed: 0,
-                    num_sent_bids_rejected: 0,
-                    num_recv_bids_rejected: 0,
-                    label: '',
-                    note: '',
-                    automation_override: 0
-                };
-            }
-
-            await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
-            return this.fetchWithRetry(address, attempt + 1);
-        }
-    },
-
-    clearCache() {
-        this.cache.clear();
-        this.pendingRequests.clear();
-    },
-
-    removeFromCache(address) {
-        this.cache.delete(address);
-        this.pendingRequests.delete(address);
-    },
-
-    cleanup() {
-        const now = Date.now();
-        for (const [address, cached] of this.cache.entries()) {
-            if (now - cached.timestamp >= this.cacheTimeout) {
-                this.cache.delete(address);
-            }
-        }
-    }
-};
-
-// Util
 const formatTimeAgo = (timestamp) => {
     const now = Math.floor(Date.now() / 1000);
     const diff = now - timestamp;
@@ -342,108 +220,6 @@ const createIdentityTooltip = (identity) => {
     `;
 };
 
-// WebSocket Manager
-const WebSocketManager = {
-    ws: null,
-    processingQueue: false,
-    reconnectTimeout: null,
-    maxReconnectAttempts: 5,
-    reconnectAttempts: 0,
-    reconnectDelay: 5000,
-
-    initialize() {
-        this.connect();
-        this.startHealthCheck();
-    },
-
-    connect() {
-    if (this.ws?.readyState === WebSocket.OPEN) return;
-
-    try {
-        let wsPort;
-
-        if (typeof getWebSocketConfig === 'function') {
-            const wsConfig = getWebSocketConfig();
-            wsPort = wsConfig?.port || wsConfig?.fallbackPort;
-        }
-        if (!wsPort && window.config?.port) {
-            wsPort = window.config.port;
-        }
-        if (!wsPort) {
-            wsPort = window.ws_port || '11700';
-        }
-        console.log("Using WebSocket port:", wsPort);
-        this.ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
-        this.setupEventHandlers();
-    } catch (error) {
-        console.error('WebSocket connection error:', error);
-        this.handleReconnect();
-    }
-},
-
-    setupEventHandlers() {
-        this.ws.onopen = () => {
-            state.wsConnected = true;
-            this.reconnectAttempts = 0;
-            updateConnectionStatus('connected');
-            console.log('🟢  WebSocket connection established for Bid Requests');
-            updateBidsTable({ resetPage: true, refreshData: true });
-        };
-
-        this.ws.onmessage = () => {
-            if (!this.processingQueue) {
-                this.processingQueue = true;
-                setTimeout(async () => {
-                    try {
-                        if (!state.isRefreshing) {
-                            await updateBidsTable({ resetPage: false, refreshData: true });
-                        }
-                    } finally {
-                        this.processingQueue = false;
-                    }
-                }, 200);
-            }
-        };
-
-        this.ws.onclose = () => {
-            state.wsConnected = false;
-            updateConnectionStatus('disconnected');
-            this.handleReconnect();
-        };
-
-        this.ws.onerror = () => {
-            updateConnectionStatus('error');
-        };
-    },
-
-    startHealthCheck() {
-        setInterval(() => {
-            if (this.ws?.readyState !== WebSocket.OPEN) {
-                this.handleReconnect();
-            }
-        }, 30000);
-    },
-
-    handleReconnect() {
-        if (this.reconnectTimeout) {
-            clearTimeout(this.reconnectTimeout);
-        }
-
-        this.reconnectAttempts++;
-        if (this.reconnectAttempts <= this.maxReconnectAttempts) {
-            const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
-            this.reconnectTimeout = setTimeout(() => this.connect(), delay);
-        } else {
-            updateConnectionStatus('error');
-            setTimeout(() => {
-                this.reconnectAttempts = 0;
-                this.connect();
-            }, 60000);
-        }
-    }
-};
-
-// UI
 const updateConnectionStatus = (status) => {
     const { statusDot, statusText } = elements;
     if (!statusDot || !statusText) return;
@@ -864,7 +640,6 @@ async function updateBidsTable(options = {}) {
     }
 }
 
-// Event
 const setupEventListeners = () => {
 if (elements.refreshBidsButton) {
     elements.refreshBidsButton.addEventListener('click', async () => {
@@ -904,8 +679,8 @@ if (elements.refreshBidsButton) {
     }
 };
 
-// Init
-document.addEventListener('DOMContentLoaded', () => {
+document.addEventListener('DOMContentLoaded', async () => {
     WebSocketManager.initialize();
     setupEventListeners();
+    await updateBidsTable({ resetPage: true, refreshData: true });
 });
diff --git a/basicswap/static/js/bids_sentreceived.js b/basicswap/static/js/bids_sentreceived.js
index 2ea8d3b..cdbebca 100644
--- a/basicswap/static/js/bids_sentreceived.js
+++ b/basicswap/static/js/bids_sentreceived.js
@@ -1,4 +1,3 @@
-// Constants and State
 const PAGE_SIZE = 50;
 const state = {
     currentPage: {
@@ -167,262 +166,225 @@ const EventManager = {
 };
 
 function cleanup() {
-    console.log('Starting cleanup process');
-    EventManager.clearAll();
-
-    const exportSentButton = document.getElementById('exportSentBids');
-    const exportReceivedButton = document.getElementById('exportReceivedBids');
-
-    if (exportSentButton) {
-        exportSentButton.remove();
-    }
-
-    if (exportReceivedButton) {
-        exportReceivedButton.remove();
-    }
-
-    if (window.TooltipManager) {
-    const originalCleanup = window.TooltipManager.cleanup;
-    window.TooltipManager.cleanup = function() {
-        originalCleanup.call(window.TooltipManager);
-
-        setTimeout(() => {
-            forceTooltipDOMCleanup();
-
-            const detachedTooltips = document.querySelectorAll('[id^="tooltip-"]');
-            detachedTooltips.forEach(tooltip => {
-                const tooltipId = tooltip.id;
-                const trigger = document.querySelector(`[data-tooltip-target="${tooltipId}"]`);
-                if (!trigger || !document.body.contains(trigger)) {
-                    tooltip.remove();
-                }
-            });
-        }, 10);
-    };
-}
-
-    WebSocketManager.cleanup();
-    if (searchTimeout) {
-        clearTimeout(searchTimeout);
-        searchTimeout = null;
-    }
-    state.data = {
-        sent: [],
-        received: []
-    };
-    IdentityManager.clearCache();
-    Object.keys(elements).forEach(key => {
-        elements[key] = null;
-    });
+    //console.log('Starting comprehensive cleanup process for bids table');
     
-    console.log('Cleanup completed');
+    try {
+        if (searchTimeout) {
+            clearTimeout(searchTimeout);
+            searchTimeout = null;
+        }
+
+        if (state.refreshPromise) {
+            state.isRefreshing = false;
+        }
+
+        if (window.WebSocketManager) {
+            WebSocketManager.disconnect();
+        }
+
+        cleanupTooltips();
+        forceTooltipDOMCleanup();
+        
+        if (window.TooltipManager) {
+            window.TooltipManager.cleanup();
+        }
+
+        tooltipIdsToCleanup.clear();
+
+        const cleanupTableBody = (tableId) => {
+            const tbody = document.getElementById(tableId);
+            if (!tbody) return;
+
+            const rows = tbody.querySelectorAll('tr');
+            rows.forEach(row => {
+                if (window.CleanupManager) {
+                    CleanupManager.removeListenersByElement(row);
+                } else {
+                    EventManager.removeAll(row);
+                }
+                Array.from(row.attributes).forEach(attr => {
+                    if (attr.name.startsWith('data-')) {
+                        row.removeAttribute(attr.name);
+                    }
+                });
+            });
+            while (tbody.firstChild) {
+                tbody.removeChild(tbody.firstChild);
+            }
+        };
+
+        cleanupTableBody('sent-tbody');
+        cleanupTableBody('received-tbody');
+
+        if (window.CleanupManager) {
+            CleanupManager.clearAll();
+        } else {
+            EventManager.clearAll();
+        }
+
+        const clearAllAnimationFrames = () => {
+            const rafList = window.requestAnimationFrameList;
+            if (Array.isArray(rafList)) {
+                rafList.forEach(id => {
+                    cancelAnimationFrame(id);
+                });
+                window.requestAnimationFrameList = [];
+            }
+        };
+        clearAllAnimationFrames();
+
+        state.data = {
+            sent: [],
+            received: []
+        };
+        
+        state.currentPage = {
+            sent: 1,
+            received: 1
+        };
+        
+        state.isLoading = false;
+        state.isRefreshing = false;
+        state.wsConnected = false;
+        state.refreshPromise = null;
+
+        state.filters = {
+            state: -1,
+            sort_by: 'created_at',
+            sort_dir: 'desc',
+            with_expired: true,
+            searchQuery: '',
+            coin_from: 'any',
+            coin_to: 'any'
+        };
+
+        if (window.IdentityManager) {
+            IdentityManager.clearCache();
+        }
+
+        if (window.CacheManager) {
+            CacheManager.cleanup(true);
+        }
+
+        if (window.MemoryManager) {
+            MemoryManager.forceCleanup();
+        }
+
+        Object.keys(elements).forEach(key => {
+            elements[key] = null;
+        });
+
+        console.log('Comprehensive cleanup completed');
+    } catch (error) {
+        console.error('Error during cleanup process:', error);
+
+        try {
+            if (window.EventManager) EventManager.clearAll();
+            if (window.CleanupManager) CleanupManager.clearAll();
+            if (window.WebSocketManager) WebSocketManager.disconnect();
+
+            state.data = { sent: [], received: [] };
+            state.isLoading = false;
+
+            Object.keys(elements).forEach(key => {
+                elements[key] = null;
+            });
+        } catch (e) {
+            console.error('Failsafe cleanup also failed:', e);
+        }
+    }
 }
 
-document.addEventListener('beforeunload', cleanup);
-document.addEventListener('visibilitychange', () => {
+window.cleanupBidsTable = cleanup;
+
+CleanupManager.addListener(document, 'visibilitychange', () => {
     if (document.hidden) {
-        WebSocketManager.pause();
+        //console.log('Page hidden - pausing WebSocket and optimizing memory');
+
+        if (WebSocketManager && typeof WebSocketManager.pause === 'function') {
+            WebSocketManager.pause();
+        } else if (WebSocketManager && typeof WebSocketManager.disconnect === 'function') {
+            WebSocketManager.disconnect();
+        }
+
+        if (window.TooltipManager && typeof window.TooltipManager.cleanup === 'function') {
+            window.TooltipManager.cleanup();
+        }
+        
+        // Run memory optimization
+        if (window.MemoryManager) {
+            MemoryManager.forceCleanup();
+        }
     } else {
-        WebSocketManager.resume();
+
+        if (WebSocketManager && typeof WebSocketManager.resume === 'function') {
+            WebSocketManager.resume();
+        } else if (WebSocketManager && typeof WebSocketManager.connect === 'function') {
+            WebSocketManager.connect();
+        }
+
+        const lastUpdateTime = state.lastRefresh || 0;
+        const now = Date.now();
+        const refreshInterval = 5 * 60 * 1000; // 5 minutes
+        
+        if (now - lastUpdateTime > refreshInterval) {
+            setTimeout(() => {
+                updateBidsTable();
+            }, 500);
+        }
     }
 });
 
-// WebSocket Management
-const WebSocketManager = {
-    ws: null,
-    processingQueue: false,
-    reconnectTimeout: null,
-    maxReconnectAttempts: 5,
-    reconnectAttempts: 0,
-    reconnectDelay: 5000,
-    healthCheckInterval: null,
-    isPaused: false,
-    lastMessageTime: Date.now(),
+CleanupManager.addListener(window, 'beforeunload', () => {
+    cleanup();
+});
+
+function cleanupRow(row) {
+    if (!row) return;
+
+    const tooltipTriggers = row.querySelectorAll('[data-tooltip-target]');
+    tooltipTriggers.forEach(trigger => {
+        if (window.TooltipManager) {
+            window.TooltipManager.destroy(trigger);
+        }
+    });
+
+    if (window.CleanupManager) {
+        CleanupManager.removeListenersByElement(row);
+    } else {
+        EventManager.removeAll(row);
+    }
+
+    row.removeAttribute('data-offer-id');
+    row.removeAttribute('data-bid-id');
+
+    while (row.firstChild) {
+        const child = row.firstChild;
+        row.removeChild(child);
+    }
+}
+
+function optimizeMemoryUsage() {
+    const MAX_BIDS_IN_MEMORY = 500;
     
-    initialize() {
-        this.connect();
-        this.startHealthCheck();
-    },
+    ['sent', 'received'].forEach(type => {
+        if (state.data[type] && state.data[type].length > MAX_BIDS_IN_MEMORY) {
+            console.log(`Trimming ${type} bids data from ${state.data[type].length} to ${MAX_BIDS_IN_MEMORY}`);
+            state.data[type] = state.data[type].slice(0, MAX_BIDS_IN_MEMORY);
+        }
+    });
 
-    isConnected() {
-        return this.ws?.readyState === WebSocket.OPEN;
-    },
+    cleanupOffscreenTooltips();
 
-    connect() {
-    if (this.isConnected() || this.isPaused) return;
-
-    if (this.ws) {
-        this.cleanupConnection();
+    if (window.IdentityManager && typeof IdentityManager.limitCacheSize === 'function') {
+        IdentityManager.limitCacheSize(100);
     }
 
-    try {
-
-        let wsPort;
-        
-        if (typeof getWebSocketConfig === 'function') {
-            const wsConfig = getWebSocketConfig();
-            wsPort = wsConfig?.port || wsConfig?.fallbackPort;
-        }
-
-        if (!wsPort && window.config?.port) {
-            wsPort = window.config.port;
-        }
-
-        if (!wsPort) {
-            wsPort = window.ws_port || '11700';
-        }
-
-        console.log("Using WebSocket port:", wsPort);
-        this.ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
-        this.setupEventHandlers();
-    } catch (error) {
-        console.error('WebSocket connection error:', error);
-        this.handleReconnect();
+    if (window.MemoryManager) {
+        MemoryManager.forceCleanup();
     }
-},
+}
 
-    setupEventHandlers() {
-        if (!this.ws) return;
-        
-        this.ws.onopen = () => {
-            state.wsConnected = true;
-            this.reconnectAttempts = 0;
-            this.lastMessageTime = Date.now();
-            updateConnectionStatus('connected');
-            console.log('🟢  WebSocket connection established for Sent Bids / Received Bids');
-            updateBidsTable();
-        };
-
-        this.ws.onmessage = () => {
-            this.lastMessageTime = Date.now();
-            if (this.isPaused) return;
-            
-            if (!this.processingQueue) {
-                this.processingQueue = true;
-                setTimeout(async () => {
-                    try {
-                        if (!state.isRefreshing) {
-                            await updateBidsTable();
-                        }
-                    } finally {
-                        this.processingQueue = false;
-                    }
-                }, 200);
-            }
-        };
-
-        this.ws.onclose = () => {
-            state.wsConnected = false;
-            updateConnectionStatus('disconnected');
-            if (!this.isPaused) {
-                this.handleReconnect();
-            }
-        };
-
-        this.ws.onerror = () => {
-            updateConnectionStatus('error');
-        };
-    },
-
-    startHealthCheck() {
-        this.stopHealthCheck();
-        
-        this.healthCheckInterval = setInterval(() => {
-            if (this.isPaused) return;
-
-            const timeSinceLastMessage = Date.now() - this.lastMessageTime;
-            if (timeSinceLastMessage > 120000) {
-                console.log('WebSocket connection appears stale. Reconnecting...');
-                this.cleanupConnection();
-                this.connect();
-                return;
-            }
-            
-            if (!this.isConnected()) {
-                this.handleReconnect();
-            }
-        }, 30000);
-    },
-
-    stopHealthCheck() {
-        if (this.healthCheckInterval) {
-            clearInterval(this.healthCheckInterval);
-            this.healthCheckInterval = null;
-        }
-    },
-
-    handleReconnect() {
-        if (this.reconnectTimeout) {
-            clearTimeout(this.reconnectTimeout);
-            this.reconnectTimeout = null;
-        }
-
-        if (this.isPaused) return;
-
-        this.reconnectAttempts++;
-        if (this.reconnectAttempts <= this.maxReconnectAttempts) {
-            const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
-            //console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
-            this.reconnectTimeout = setTimeout(() => this.connect(), delay);
-        } else {
-            updateConnectionStatus('error');
-            //console.log('Maximum reconnection attempts reached. Will try again in 60 seconds.');
-            setTimeout(() => {
-                this.reconnectAttempts = 0;
-                this.connect();
-            }, 60000);
-        }
-    },
-
-    cleanupConnection() {
-        if (this.ws) {
-            this.ws.onopen = null;
-            this.ws.onmessage = null;
-            this.ws.onclose = null;
-            this.ws.onerror = null;
-            if (this.ws.readyState === WebSocket.OPEN) {
-                try {
-                    this.ws.close(1000, 'Cleanup');
-                } catch (e) {
-                    console.warn('Error closing WebSocket:', e);
-                }
-            }
-            this.ws = null;
-        }
-    },
-
-    pause() {
-        this.isPaused = true;
-        //console.log('WebSocket operations paused');
-        if (this.reconnectTimeout) {
-            clearTimeout(this.reconnectTimeout);
-            this.reconnectTimeout = null;
-        }
-    },
-
-    resume() {
-        if (!this.isPaused) return;
-        this.isPaused = false;
-        //console.log('WebSocket operations resumed');
-        this.lastMessageTime = Date.now();
-        if (!this.isConnected()) {
-            this.reconnectAttempts = 0;
-            this.connect();
-        }
-    },
-
-    cleanup() {
-        this.isPaused = true;
-        this.stopHealthCheck();
-        if (this.reconnectTimeout) {
-            clearTimeout(this.reconnectTimeout);
-            this.reconnectTimeout = null;
-        }
-        this.cleanupConnection();
-    }
-};
-
-// Core
 const safeParseInt = (value) => {
     const parsed = parseInt(value);
     return isNaN(parsed) ? 0 : parsed;
@@ -528,7 +490,6 @@ function coinMatches(offerCoin, filterCoin) {
     return false;
 }
 
-// State
 function hasActiveFilters() {
     const coinFromSelect = document.getElementById('coin_from');
     const coinToSelect = document.getElementById('coin_to');
@@ -596,11 +557,58 @@ function filterAndSortData(bids) {
             const searchStr = state.filters.searchQuery.toLowerCase();
             const matchesBidId = bid.bid_id.toLowerCase().includes(searchStr);
             const matchesIdentity = bid.addr_from?.toLowerCase().includes(searchStr);
-            const identity = IdentityManager.cache.get(bid.addr_from);
-            const label = identity?.data?.label || '';
+
+            let label = '';
+            try {
+                if (window.IdentityManager) {
+
+                    let identity = null;
+
+                    if (IdentityManager.cache && typeof IdentityManager.cache.get === 'function') {
+                        identity = IdentityManager.cache.get(bid.addr_from);
+                    }
+
+                    if (identity && identity.label) {
+                        label = identity.label;
+                    } else if (identity && identity.data && identity.data.label) {
+                        label = identity.data.label;
+                    }
+
+                    if (!label && bid.identity) {
+                        label = bid.identity.label || '';
+                    }
+                }
+            } catch (e) {
+                console.warn('Error accessing identity for search:', e);
+            }
+            
             const matchesLabel = label.toLowerCase().includes(searchStr);
 
-            if (!(matchesBidId || matchesIdentity || matchesLabel)) {
+            let matchesDisplayedLabel = false;
+            if (!matchesLabel && document) {
+                try {
+                    const tableId = state.currentTab === 'sent' ? 'sent' : 'received';
+                    const cells = document.querySelectorAll(`#${tableId} a[href^="/identity/"]`);
+                    
+                    for (const cell of cells) {
+
+                        const href = cell.getAttribute('href');
+                        const cellAddress = href ? href.split('/').pop() : '';
+
+                        if (cellAddress === bid.addr_from) {
+                            const cellText = cell.textContent.trim().toLowerCase();
+                            if (cellText.includes(searchStr)) {
+                                matchesDisplayedLabel = true;
+                                break;
+                            }
+                        }
+                    }
+                } catch (e) {
+                    console.warn('Error checking displayed labels:', e);
+                }
+            }
+            
+            if (!(matchesBidId || matchesIdentity || matchesLabel || matchesDisplayedLabel)) {
                 return false;
             }
         }
@@ -615,6 +623,37 @@ function filterAndSortData(bids) {
     });
 }
 
+async function preloadIdentitiesForSearch(bids) {
+    if (!window.IdentityManager || typeof IdentityManager.getIdentityData !== 'function') {
+        return;
+    }
+    
+    try {
+        const addresses = new Set();
+        bids.forEach(bid => {
+            if (bid.addr_from) {
+                addresses.add(bid.addr_from);
+            }
+        });
+
+        const BATCH_SIZE = 20;
+        const addressArray = Array.from(addresses);
+        
+        for (let i = 0; i < addressArray.length; i += BATCH_SIZE) {
+            const batch = addressArray.slice(i, i + BATCH_SIZE);
+            await Promise.all(batch.map(addr => IdentityManager.getIdentityData(addr)));
+
+            if (i + BATCH_SIZE < addressArray.length) {
+                await new Promise(resolve => setTimeout(resolve, 10));
+            }
+        }
+        
+        console.log(`Preloaded ${addressArray.length} identities for search`);
+    } catch (error) {
+        console.error('Error preloading identities:', error);
+    }
+}
+
 function updateCoinFilterImages() {
     const coinToSelect = document.getElementById('coin_to');
     const coinFromSelect = document.getElementById('coin_from');
@@ -709,108 +748,6 @@ const updateConnectionStatus = (status) => {
     });
 };
 
-// Identity
-const IdentityManager = {
-    cache: new Map(),
-    pendingRequests: new Map(),
-    retryDelay: 2000,
-    maxRetries: 3,
-    cacheTimeout: 5 * 60 * 1000,
-    maxCacheSize: 500,
-
-    async getIdentityData(address) {
-        if (!address) return { address: '' };
-
-        const cachedData = this.getCachedIdentity(address);
-        if (cachedData) return { ...cachedData, address };
-
-        if (this.pendingRequests.has(address)) {
-            try {
-                const pendingData = await this.pendingRequests.get(address);
-                return { ...pendingData, address };
-            } catch (error) {
-                this.pendingRequests.delete(address);
-            }
-        }
-
-        const request = this.fetchWithRetry(address);
-        this.pendingRequests.set(address, request);
-
-        try {
-            const data = await request;
-
-            this.trimCacheIfNeeded();
-
-            this.cache.set(address, {
-                data,
-                timestamp: Date.now()
-            });
-
-            return { ...data, address };
-        } catch (error) {
-            console.warn(`Error fetching identity for ${address}:`, error);
-            return { address };
-        } finally {
-            this.pendingRequests.delete(address);
-        }
-    },
-
-    getCachedIdentity(address) {
-        const cached = this.cache.get(address);
-        if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
-            cached.timestamp = Date.now();
-            return cached.data;
-        }
-        if (cached) {
-            this.cache.delete(address);
-        }
-        return null;
-    },
-
-    trimCacheIfNeeded() {
-        if (this.cache.size > this.maxCacheSize) {
-
-            const entries = Array.from(this.cache.entries());
-            const sortedByAge = entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
-
-            const toRemove = Math.ceil(this.maxCacheSize * 0.2);
-            for (let i = 0; i < toRemove && i < sortedByAge.length; i++) {
-                this.cache.delete(sortedByAge[i][0]);
-            }
-            console.log(`Trimmed identity cache: removed ${toRemove} oldest entries`);
-        }
-    },
-
-    clearCache() {
-        this.cache.clear();
-        this.pendingRequests.clear();
-    },
-
-    async fetchWithRetry(address, attempt = 1) {
-        try {
-            const controller = new AbortController();
-            const timeoutId = setTimeout(() => controller.abort(), 10000);
-            
-            const response = await fetch(`/json/identities/${address}`, { 
-                signal: controller.signal 
-            });
-            
-            clearTimeout(timeoutId);
-            
-            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
-            return await response.json();
-        } catch (error) {
-            if (attempt >= this.maxRetries) {
-                console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
-                return { address };
-            }
-            await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
-            return this.fetchWithRetry(address, attempt + 1);
-        }
-    }
-};
-
-// Stats
 const processIdentityStats = (identity) => {
     if (!identity) return null;
 
@@ -910,7 +847,6 @@ const createIdentityTooltipContent = (identity) => {
     `;
 };
 
-// Table
 let tooltipIdsToCleanup = new Set();
 
 const cleanupTooltips = () => {
@@ -1097,14 +1033,14 @@ const createTableRow = async (bid) => {
            <!-- Status Column -->
            <td class="py-3 px-6">
             <div class="relative flex justify-center" data-tooltip-target="tooltip-status-${uniqueId}">
-                <span class="w-full lg:w-7/8 xl:w-2/3 px-2.5 py-1 inline-flex items-center justify-center rounded-full text-xs font-medium bold ${getStatusClass(bid.bid_state)}">
+                <span class="w-full lg:w-7/8 xl:w-2/3 px-2.5 py-1 inline-flex items-center justify-center text-center rounded-full text-xs font-medium bold ${getStatusClass(bid.bid_state)}">
                 ${bid.bid_state}
                 </span>
               </div>
             </td>
 
             <!-- Actions Column -->
-            <td class="py-3 pr-4 pl-3">
+            <td class="py-3 pr-4">
                 <div class="flex justify-center">
                     <a href="/bid/${bid.bid_id}"
                         class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-blue-500 text-white border border-blue-500 hover:bg-blue-600 transition duration-200">
@@ -1357,7 +1293,6 @@ function implementVirtualizedRows() {
     });
 }
 
-// Fetching
 let activeFetchController = null;
 
 const fetchBids = async () => {
@@ -1432,21 +1367,20 @@ const fetchBids = async () => {
 
 const updateBidsTable = async () => {
     if (state.isLoading) {
-        //console.log('Already loading, skipping update');
         return;
     }
 
     try {
-        //console.log('Starting updateBidsTable for tab:', state.currentTab);
-        //console.log('Current filters:', state.filters);
-
         state.isLoading = true;
         updateLoadingState(true);
 
         const bids = await fetchBids();
-
-       //console.log('Fetched bids:', bids.length);
-
+        
+        // Add identity preloading if we're searching
+        if (state.filters.searchQuery && state.filters.searchQuery.length > 0) {
+            await preloadIdentitiesForSearch(bids);
+        }
+        
         state.data[state.currentTab] = bids;
         state.currentPage[state.currentTab] = 1;
 
@@ -1503,7 +1437,6 @@ const updatePaginationControls = (type) => {
     }
 };
 
-// Filter
 let searchTimeout;
 function handleSearch(event) {
     if (searchTimeout) {
@@ -1708,7 +1641,6 @@ const setupRefreshButtons = () => {
     });
 };
 
-// Tabs
 const switchTab = (tabId) => {
     if (state.isLoading) return;
 
@@ -1925,15 +1857,22 @@ function setupMemoryMonitoring() {
     const intervalId = setInterval(() => {
         if (document.hidden) {
             console.log('Tab hidden - running memory optimization');
-            IdentityManager.trimCacheIfNeeded();
-            if (window.TooltipManager) {
+
+            if (window.IdentityManager) {
+                if (typeof IdentityManager.limitCacheSize === 'function') {
+                    IdentityManager.limitCacheSize(100);
+                }
+            }
+
+            if (window.TooltipManager && typeof window.TooltipManager.cleanup === 'function') {
                 window.TooltipManager.cleanup();
             }
+
             if (state.data.sent.length > 1000) {
                 console.log('Trimming sent bids data');
                 state.data.sent = state.data.sent.slice(0, 1000);
             }
-            
+
             if (state.data.received.length > 1000) {
                 console.log('Trimming received bids data');
                 state.data.received = state.data.received.slice(0, 1000);
@@ -1942,6 +1881,7 @@ function setupMemoryMonitoring() {
             cleanupTooltips();
         }
     }, MEMORY_CHECK_INTERVAL);
+
     document.addEventListener('beforeunload', () => {
         clearInterval(intervalId);
     }, { once: true });
@@ -1985,6 +1925,12 @@ function initialize() {
         updateBidsTable();
     }, 100);
 
+    setInterval(() => {
+        if ((state.data.sent.length + state.data.received.length) > 1000) {
+            optimizeMemoryUsage();
+        }
+    }, 5 * 60 * 1000); // Check every 5 minutes
+
     window.cleanupBidsTable = cleanup;
 }
 
diff --git a/basicswap/static/js/bids_export.js b/basicswap/static/js/bids_sentreceived_export.js
similarity index 100%
rename from basicswap/static/js/bids_export.js
rename to basicswap/static/js/bids_sentreceived_export.js
diff --git a/basicswap/static/js/coin_icons.js b/basicswap/static/js/coin_icons.js
deleted file mode 100644
index f8db7e7..0000000
--- a/basicswap/static/js/coin_icons.js
+++ /dev/null
@@ -1,68 +0,0 @@
-document.addEventListener('DOMContentLoaded', () => {
-
-    const selectCache = {};
-
-    function updateSelectCache(select) {
-        const selectedOption = select.options[select.selectedIndex];
-        const image = selectedOption.getAttribute('data-image');
-        const name = selectedOption.textContent.trim();
-        selectCache[select.id] = { image, name };
-    }
-
-    function setSelectData(select) {
-        const selectedOption = select.options[select.selectedIndex];
-        const image = selectedOption.getAttribute('data-image') || '';
-        const name = selectedOption.textContent.trim();
-        select.style.backgroundImage = image ? `url(${image}?${new Date().getTime()})` : '';
-
-        const selectImage = select.nextElementSibling.querySelector('.select-image');
-        if (selectImage) {
-            selectImage.src = image;
-        }
-
-        const selectNameElement = select.nextElementSibling.querySelector('.select-name');
-        if (selectNameElement) {
-            selectNameElement.textContent = name;
-        }
-
-        updateSelectCache(select);
-    }
-
-    const selectIcons = document.querySelectorAll('.custom-select .select-icon');
-    const selectImages = document.querySelectorAll('.custom-select .select-image');
-    const selectNames = document.querySelectorAll('.custom-select .select-name');
-
-    selectIcons.forEach(icon => icon.style.display = 'none');
-    selectImages.forEach(image => image.style.display = 'none');
-    selectNames.forEach(name => name.style.display = 'none');
-
-    function setupCustomSelect(select) {
-        const options = select.querySelectorAll('option');
-        const selectIcon = select.parentElement.querySelector('.select-icon');
-        const selectImage = select.parentElement.querySelector('.select-image');
-
-        options.forEach(option => {
-            const image = option.getAttribute('data-image');
-            if (image) {
-                option.style.backgroundImage = `url(${image})`;
-            }
-        });
-
-        const storedValue = localStorage.getItem(select.name);
-        if (storedValue && select.value == '-1') {
-            select.value = storedValue;
-        }
-
-        select.addEventListener('change', () => {
-            setSelectData(select);
-            localStorage.setItem(select.name, select.value);
-        });
-
-        setSelectData(select);
-        selectIcon.style.display = 'none';
-        selectImage.style.display = 'none';
-    }
-
-    const customSelects = document.querySelectorAll('.custom-select select');
-    customSelects.forEach(setupCustomSelect);
-});
\ No newline at end of file
diff --git a/basicswap/static/js/global.js b/basicswap/static/js/global.js
new file mode 100644
index 0000000..5892e98
--- /dev/null
+++ b/basicswap/static/js/global.js
@@ -0,0 +1,199 @@
+document.addEventListener('DOMContentLoaded', function() {
+    const burger = document.querySelectorAll('.navbar-burger');
+    const menu = document.querySelectorAll('.navbar-menu');
+
+    if (burger.length && menu.length) {
+        for (var i = 0; i < burger.length; i++) {
+            burger[i].addEventListener('click', function() {
+                for (var j = 0; j < menu.length; j++) {
+                    menu[j].classList.toggle('hidden');
+                }
+            });
+        }
+    }
+
+    const close = document.querySelectorAll('.navbar-close');
+    const backdrop = document.querySelectorAll('.navbar-backdrop');
+
+    if (close.length) {
+        for (var k = 0; k < close.length; k++) {
+            close[k].addEventListener('click', function() {
+                for (var j = 0; j < menu.length; j++) {
+                    menu[j].classList.toggle('hidden');
+                }
+            });
+        }
+    }
+
+    if (backdrop.length) {
+        for (var l = 0; l < backdrop.length; l++) {
+            backdrop[l].addEventListener('click', function() {
+                for (var j = 0; j < menu.length; j++) {
+                    menu[j].classList.toggle('hidden');
+                }
+            });
+        }
+    }
+
+    const tooltipManager = TooltipManager.initialize();
+    tooltipManager.initializeTooltips();
+    setupShutdownModal();
+    setupDarkMode();
+    toggleImages();
+});
+
+function setupShutdownModal() {
+    const shutdownButtons = document.querySelectorAll('.shutdown-button');
+    const shutdownModal = document.getElementById('shutdownModal');
+    const closeModalButton = document.getElementById('closeShutdownModal');
+    const confirmShutdownButton = document.getElementById('confirmShutdown');
+    const shutdownWarning = document.getElementById('shutdownWarning');
+
+    function updateShutdownButtons() {
+        const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0');
+        shutdownButtons.forEach(button => {
+            if (activeSwaps > 0) {
+                button.classList.add('shutdown-disabled');
+                button.setAttribute('data-disabled', 'true');
+                button.setAttribute('title', 'Caution: Swaps in progress');
+            } else {
+                button.classList.remove('shutdown-disabled');
+                button.removeAttribute('data-disabled');
+                button.removeAttribute('title');
+            }
+        });
+    }
+
+    function closeAllDropdowns() {
+
+        const openDropdowns = document.querySelectorAll('.dropdown-menu:not(.hidden)');
+        openDropdowns.forEach(dropdown => {
+            if (dropdown.style.display !== 'none') {
+                dropdown.style.display = 'none';
+            }
+        });
+
+        if (window.Dropdown && window.Dropdown.instances) {
+            window.Dropdown.instances.forEach(instance => {
+                if (instance._visible) {
+                    instance.hide();
+                }
+            });
+        }
+    }
+
+    function showShutdownModal() {
+        closeAllDropdowns();
+        
+        const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0');
+        if (activeSwaps > 0) {
+            shutdownWarning.classList.remove('hidden');
+            confirmShutdownButton.textContent = 'Yes, Shut Down Anyway';
+        } else {
+            shutdownWarning.classList.add('hidden');
+            confirmShutdownButton.textContent = 'Yes, Shut Down';
+        }
+        shutdownModal.classList.remove('hidden');
+        document.body.style.overflow = 'hidden';
+    }
+
+    function hideShutdownModal() {
+        shutdownModal.classList.add('hidden');
+        document.body.style.overflow = '';
+    }
+
+    if (shutdownButtons.length) {
+        shutdownButtons.forEach(button => {
+            button.addEventListener('click', function(e) {
+                e.preventDefault();
+                showShutdownModal();
+            });
+        });
+    }
+
+    if (closeModalButton) {
+        closeModalButton.addEventListener('click', hideShutdownModal);
+    }
+
+    if (confirmShutdownButton) {
+        confirmShutdownButton.addEventListener('click', function() {
+            const shutdownToken = document.querySelector('.shutdown-button')
+                .getAttribute('href').split('/').pop();
+            window.location.href = '/shutdown/' + shutdownToken;
+        });
+    }
+
+    if (shutdownModal) {
+        shutdownModal.addEventListener('click', function(e) {
+            if (e.target === this) {
+                hideShutdownModal();
+            }
+        });
+    }
+
+    if (shutdownButtons.length) {
+        updateShutdownButtons();
+    }
+}
+
+function setupDarkMode() {
+    const themeToggle = document.getElementById('theme-toggle');
+    const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
+    const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
+
+    if (themeToggleDarkIcon && themeToggleLightIcon) {
+        if (localStorage.getItem('color-theme') === 'dark' || 
+            (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
+            themeToggleLightIcon.classList.remove('hidden');
+        } else {
+            themeToggleDarkIcon.classList.remove('hidden');
+        }
+    }
+
+    function setTheme(theme) {
+        if (theme === 'light') {
+            document.documentElement.classList.remove('dark');
+            localStorage.setItem('color-theme', 'light');
+        } else {
+            document.documentElement.classList.add('dark');
+            localStorage.setItem('color-theme', 'dark');
+        }
+    }
+
+    if (themeToggle) {
+        themeToggle.addEventListener('click', () => {
+            if (localStorage.getItem('color-theme') === 'dark') {
+                setTheme('light');
+            } else {
+                setTheme('dark');
+            }
+            
+            if (themeToggleDarkIcon && themeToggleLightIcon) {
+                themeToggleDarkIcon.classList.toggle('hidden');
+                themeToggleLightIcon.classList.toggle('hidden');
+            }
+            
+            toggleImages();
+        });
+    }
+}
+
+function toggleImages() {
+    var html = document.querySelector('html');
+    var darkImages = document.querySelectorAll('.dark-image');
+    var lightImages = document.querySelectorAll('.light-image');
+
+    if (html && html.classList.contains('dark')) {
+        toggleImageDisplay(darkImages, 'block');
+        toggleImageDisplay(lightImages, 'none');
+    } else {
+        toggleImageDisplay(darkImages, 'none');
+        toggleImageDisplay(lightImages, 'block');
+    }
+}
+
+function toggleImageDisplay(images, display) {
+    images.forEach(function(img) {
+        img.style.display = display;
+    });
+}
diff --git a/basicswap/static/js/main.js b/basicswap/static/js/main.js
deleted file mode 100644
index 38f6e4e..0000000
--- a/basicswap/static/js/main.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Burger menus
-document.addEventListener('DOMContentLoaded', function() {
-    // open
-    const burger = document.querySelectorAll('.navbar-burger');
-    const menu = document.querySelectorAll('.navbar-menu');
-
-    if (burger.length && menu.length) {
-        for (var i = 0; i < burger.length; i++) {
-            burger[i].addEventListener('click', function() {
-                for (var j = 0; j < menu.length; j++) {
-                    menu[j].classList.toggle('hidden');
-                }
-            });
-        }
-    }
-
-    // close
-    const close = document.querySelectorAll('.navbar-close');
-    const backdrop = document.querySelectorAll('.navbar-backdrop');
-
-    if (close.length) {
-        for (var k = 0; k < close.length; k++) {
-            close[k].addEventListener('click', function() {
-                for (var j = 0; j < menu.length; j++) {
-                    menu[j].classList.toggle('hidden');
-                }
-            });
-        }
-    }
-
-    if (backdrop.length) {
-        for (var l = 0; l < backdrop.length; l++) {
-            backdrop[l].addEventListener('click', function() {
-                for (var j = 0; j < menu.length; j++) {
-                    menu[j].classList.toggle('hidden');
-                }
-            });
-        }
-    }
-});
diff --git a/basicswap/static/js/modules/api-manager.js b/basicswap/static/js/modules/api-manager.js
new file mode 100644
index 0000000..2effc12
--- /dev/null
+++ b/basicswap/static/js/modules/api-manager.js
@@ -0,0 +1,389 @@
+const ApiManager = (function() {
+
+    const state = {
+        isInitialized: false
+    };
+
+    const config = {
+        requestTimeout: 60000,
+        retryDelays: [5000, 15000, 30000],
+        rateLimits: {
+            coingecko: {
+                requestsPerMinute: 50,
+                minInterval: 1200
+            },
+            cryptocompare: {
+                requestsPerMinute: 30,
+                minInterval: 2000
+            }
+        }
+    };
+
+    const rateLimiter = {
+        lastRequestTime: {},
+        minRequestInterval: {
+            coingecko: 1200,
+            cryptocompare: 2000
+        },
+        requestQueue: {},
+        retryDelays: [5000, 15000, 30000],
+
+        canMakeRequest: function(apiName) {
+            const now = Date.now();
+            const lastRequest = this.lastRequestTime[apiName] || 0;
+            return (now - lastRequest) >= this.minRequestInterval[apiName];
+        },
+
+        updateLastRequestTime: function(apiName) {
+            this.lastRequestTime[apiName] = Date.now();
+        },
+
+        getWaitTime: function(apiName) {
+            const now = Date.now();
+            const lastRequest = this.lastRequestTime[apiName] || 0;
+            return Math.max(0, this.minRequestInterval[apiName] - (now - lastRequest));
+        },
+
+        queueRequest: async function(apiName, requestFn, retryCount = 0) {
+            if (!this.requestQueue[apiName]) {
+                this.requestQueue[apiName] = Promise.resolve();
+            }
+
+            try {
+                await this.requestQueue[apiName];
+                
+                const executeRequest = async () => {
+                    const waitTime = this.getWaitTime(apiName);
+                    if (waitTime > 0) {
+                        await new Promise(resolve => setTimeout(resolve, waitTime));
+                    }
+
+                    try {
+                        this.updateLastRequestTime(apiName);
+                        return await requestFn();
+                    } catch (error) {
+                        if (error.message.includes('429') && retryCount < this.retryDelays.length) {
+                            const delay = this.retryDelays[retryCount];
+                            console.log(`Rate limit hit, retrying in ${delay/1000} seconds...`);
+                            await new Promise(resolve => setTimeout(resolve, delay));
+                            return publicAPI.rateLimiter.queueRequest(apiName, requestFn, retryCount + 1);
+                        }
+
+                        if ((error.message.includes('timeout') || error.name === 'NetworkError') && 
+                            retryCount < this.retryDelays.length) {
+                            const delay = this.retryDelays[retryCount];
+                            console.warn(`Request failed, retrying in ${delay/1000} seconds...`, {
+                                apiName,
+                                retryCount,
+                                error: error.message
+                            });
+                            await new Promise(resolve => setTimeout(resolve, delay));
+                            return publicAPI.rateLimiter.queueRequest(apiName, requestFn, retryCount + 1);
+                        }
+
+                        throw error;
+                    }
+                };
+
+                this.requestQueue[apiName] = executeRequest();
+                return await this.requestQueue[apiName];
+                
+            } catch (error) {
+                if (error.message.includes('429') || 
+                    error.message.includes('timeout') || 
+                    error.name === 'NetworkError') {
+                    const cacheKey = `coinData_${apiName}`;
+                    try {
+                        const cachedData = JSON.parse(localStorage.getItem(cacheKey));
+                        if (cachedData && cachedData.value) {
+                            return cachedData.value;
+                        }
+                    } catch (e) {
+                        console.warn('Error accessing cached data:', e);
+                    }
+                }
+                throw error;
+            }
+        }
+    };
+
+    const publicAPI = {
+        config,
+        rateLimiter,
+        
+        initialize: function(options = {}) {
+            if (state.isInitialized) {
+                console.warn('[ApiManager] Already initialized');
+                return this;
+            }
+
+            if (options.config) {
+                Object.assign(config, options.config);
+            }
+
+            if (config.rateLimits) {
+                Object.keys(config.rateLimits).forEach(api => {
+                    if (config.rateLimits[api].minInterval) {
+                        rateLimiter.minRequestInterval[api] = config.rateLimits[api].minInterval;
+                    }
+                });
+            }
+
+            if (config.retryDelays) {
+                rateLimiter.retryDelays = [...config.retryDelays];
+            }
+
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('apiManager', this, (mgr) => mgr.dispose());
+            }
+
+            state.isInitialized = true;
+            console.log('ApiManager initialized');
+            return this;
+        },
+
+        makeRequest: async function(url, method = 'GET', headers = {}, body = null) {
+            try {
+                const options = {
+                    method: method,
+                    headers: {
+                        'Content-Type': 'application/json',
+                        ...headers
+                    },
+                    signal: AbortSignal.timeout(config.requestTimeout)
+                };
+
+                if (body) {
+                    options.body = JSON.stringify(body);
+                }
+
+                const response = await fetch(url, options);
+                
+                if (!response.ok) {
+                    throw new Error(`HTTP error! status: ${response.status}`);
+                }
+
+                return await response.json();
+            } catch (error) {
+                console.error(`Request failed for ${url}:`, error);
+                throw error;
+            }
+        },
+
+        makePostRequest: async function(url, headers = {}) {
+            return new Promise((resolve, reject) => {
+                fetch('/json/readurl', {
+                    method: 'POST',
+                    headers: {'Content-Type': 'application/json'},
+                    body: JSON.stringify({
+                        url: url,
+                        headers: headers
+                    })
+                })
+                .then(response => {
+                    if (!response.ok) {
+                        throw new Error(`HTTP error! status: ${response.status}`);
+                    }
+                    return response.json();
+                })
+                .then(data => {
+                    if (data.Error) {
+                        reject(new Error(data.Error));
+                    } else {
+                        resolve(data);
+                    }
+                })
+                .catch(error => {
+                    console.error(`Request failed for ${url}:`, error);
+                    reject(error);
+                });
+            });
+        },
+
+        fetchCoinPrices: async function(coins, source = "coingecko.com", ttl = 300) {
+            if (!Array.isArray(coins)) {
+                coins = [coins];
+            }
+
+            return this.makeRequest('/json/coinprices', 'POST', {}, {
+                coins: Array.isArray(coins) ? coins.join(',') : coins,
+                source: source,
+                ttl: ttl
+            });
+        },
+
+        fetchCoinGeckoData: async function() {
+            return this.rateLimiter.queueRequest('coingecko', async () => {
+                try {
+                    const coins = (window.config && window.config.coins) ? 
+                        window.config.coins
+                            .filter(coin => coin.usesCoinGecko)
+                            .map(coin => coin.name)
+                            .join(',') : 
+                        'bitcoin,monero,particl,bitcoincash,pivx,firo,dash,litecoin,dogecoin,decred';
+
+                    //console.log('Fetching coin prices for:', coins);
+                    const response = await this.fetchCoinPrices(coins);
+                    
+                    //console.log('Full API response:', response);
+                    
+                    if (!response || typeof response !== 'object') {
+                        throw new Error('Invalid response type');
+                    }
+
+                    if (!response.rates || typeof response.rates !== 'object' || Object.keys(response.rates).length === 0) {
+                        throw new Error('No valid rates found in response');
+                    }
+
+                    return response;
+                } catch (error) {
+                    console.error('Error in fetchCoinGeckoData:', {
+                        message: error.message,
+                        stack: error.stack
+                    });
+                    throw error;
+                }
+            });
+        },
+
+        fetchVolumeData: async function() {
+            return this.rateLimiter.queueRequest('coingecko', async () => {
+                try {
+                    const coins = (window.config && window.config.coins) ? 
+                        window.config.coins
+                            .filter(coin => coin.usesCoinGecko)
+                            .map(coin => getCoinBackendId ? getCoinBackendId(coin.name) : coin.name)
+                            .join(',') : 
+                        'bitcoin,monero,particl,bitcoin-cash,pivx,firo,dash,litecoin,dogecoin,decred';
+
+                    const url = `https://api.coingecko.com/api/v3/simple/price?ids=${coins}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true`;
+
+                    const response = await this.makePostRequest(url, {
+                        'User-Agent': 'Mozilla/5.0',
+                        'Accept': 'application/json'
+                    });
+
+                    const volumeData = {};
+                    Object.entries(response).forEach(([coinId, data]) => {
+                        if (data && data.usd_24h_vol) {
+                            volumeData[coinId] = {
+                                total_volume: data.usd_24h_vol,
+                                price_change_percentage_24h: data.usd_24h_change || 0
+                            };
+                        }
+                    });
+
+                    return volumeData;
+                } catch (error) {
+                    console.error("Error fetching volume data:", error);
+                    throw error;
+                }
+            });
+        },
+        
+        fetchCryptoCompareData: function(coin) {
+            return this.rateLimiter.queueRequest('cryptocompare', async () => {
+                try {
+                    const apiKey = window.config?.apiKeys?.cryptoCompare || '';
+                    const url = `https://min-api.cryptocompare.com/data/pricemultifull?fsyms=${coin}&tsyms=USD,BTC&api_key=${apiKey}`;
+                    const headers = {
+                        'User-Agent': 'Mozilla/5.0',
+                        'Accept': 'application/json'
+                    };
+                    
+                    return await this.makePostRequest(url, headers);
+                } catch (error) {
+                    console.error(`CryptoCompare request failed for ${coin}:`, error);
+                    throw error;
+                }
+            });
+        },
+
+        fetchHistoricalData: async function(coinSymbols, resolution = 'day') {
+            if (!Array.isArray(coinSymbols)) {
+                coinSymbols = [coinSymbols];
+            }
+
+            const results = {};
+            const fetchPromises = coinSymbols.map(async coin => {
+                if (coin === 'WOW') {
+                    return this.rateLimiter.queueRequest('coingecko', async () => {
+                        const url = `https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=1`;
+                        try {
+                            const response = await this.makePostRequest(url);
+                            if (response && response.prices) {
+                                results[coin] = response.prices;
+                            }
+                        } catch (error) {
+                            console.error(`Error fetching CoinGecko data for WOW:`, error);
+                            throw error;
+                        }
+                    });
+                } else {
+                    return this.rateLimiter.queueRequest('cryptocompare', async () => {
+                        try {
+                            const apiKey = window.config?.apiKeys?.cryptoCompare || '';
+                            let url;
+                            
+                            if (resolution === 'day') {
+                                url = `https://min-api.cryptocompare.com/data/v2/histohour?fsym=${coin}&tsym=USD&limit=24&api_key=${apiKey}`;
+                            } else if (resolution === 'year') {
+                                url = `https://min-api.cryptocompare.com/data/v2/histoday?fsym=${coin}&tsym=USD&limit=365&api_key=${apiKey}`;
+                            } else {
+                                url = `https://min-api.cryptocompare.com/data/v2/histoday?fsym=${coin}&tsym=USD&limit=180&api_key=${apiKey}`;
+                            }
+
+                            const response = await this.makePostRequest(url);
+                            if (response.Response === "Error") {
+                                console.error(`API Error for ${coin}:`, response.Message);
+                                throw new Error(response.Message);
+                            } else if (response.Data && response.Data.Data) {
+                                results[coin] = response.Data;
+                            }
+                        } catch (error) {
+                            console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
+                            throw error;
+                        }
+                    });
+                }
+            });
+
+            await Promise.all(fetchPromises);
+            return results;
+        },
+        
+        dispose: function() {
+            // Clear any pending requests or resources
+            rateLimiter.requestQueue = {};
+            rateLimiter.lastRequestTime = {};
+            state.isInitialized = false;
+            console.log('ApiManager disposed');
+        }
+    };
+
+    return publicAPI;
+})();
+
+function getCoinBackendId(coinName) {
+    const nameMap = {
+        'bitcoin-cash': 'bitcoincash',
+        'bitcoin cash': 'bitcoincash',
+        'firo': 'zcoin',
+        'zcoin': 'zcoin',
+        'bitcoincash': 'bitcoin-cash'
+    };
+    return nameMap[coinName.toLowerCase()] || coinName.toLowerCase();
+}
+
+window.Api = ApiManager;
+window.ApiManager = ApiManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.apiManagerInitialized) {
+        ApiManager.initialize();
+        window.apiManagerInitialized = true;
+    }
+});
+
+//console.log('ApiManager initialized with methods:', Object.keys(ApiManager));
+console.log('ApiManager initialized');
diff --git a/basicswap/static/js/modules/cache-manager.js b/basicswap/static/js/modules/cache-manager.js
new file mode 100644
index 0000000..1dec899
--- /dev/null
+++ b/basicswap/static/js/modules/cache-manager.js
@@ -0,0 +1,535 @@
+const CacheManager = (function() {
+  const defaults = window.config?.cacheConfig?.storage || {
+    maxSizeBytes: 10 * 1024 * 1024,
+    maxItems: 200,
+    defaultTTL: 5 * 60 * 1000
+  };
+
+  const PRICES_CACHE_KEY = 'crypto_prices_unified';
+
+  const CACHE_KEY_PATTERNS = [
+    'coinData_',
+    'chartData_',
+    'historical_',
+    'rates_',
+    'prices_',
+    'offers_',
+    'fallback_',
+    'volumeData'
+  ];
+
+  const isCacheKey = (key) => {
+    return CACHE_KEY_PATTERNS.some(pattern => key.startsWith(pattern)) || 
+           key === 'coinGeckoOneLiner' ||
+           key === PRICES_CACHE_KEY;
+  };
+
+  const isLocalStorageAvailable = () => {
+    try {
+      const testKey = '__storage_test__';
+      localStorage.setItem(testKey, testKey);
+      localStorage.removeItem(testKey);
+      return true;
+    } catch (e) {
+      return false;
+    }
+  };
+
+  let storageAvailable = isLocalStorageAvailable();
+
+  const memoryCache = new Map();
+
+  if (!storageAvailable) {
+    console.warn('localStorage is not available. Using in-memory cache instead.');
+  }
+
+  const cacheAPI = {
+    getTTL: function(resourceType) {
+      const ttlConfig = window.config?.cacheConfig?.ttlSettings || {};
+      return ttlConfig[resourceType] || window.config?.cacheConfig?.defaultTTL || defaults.defaultTTL;
+    },
+    
+    set: function(key, value, resourceTypeOrCustomTtl = null) {
+      try {
+        this.cleanup();
+
+        if (!value) {
+          console.warn('Attempted to cache null/undefined value for key:', key);
+          return false;
+        }
+
+        let ttl;
+        if (typeof resourceTypeOrCustomTtl === 'string') {
+          ttl = this.getTTL(resourceTypeOrCustomTtl);
+        } else if (typeof resourceTypeOrCustomTtl === 'number') {
+          ttl = resourceTypeOrCustomTtl;
+        } else {
+          ttl = window.config?.cacheConfig?.defaultTTL || defaults.defaultTTL;
+        }
+
+        const item = {
+          value: value,
+          timestamp: Date.now(),
+          expiresAt: Date.now() + ttl
+        };
+
+        let serializedItem;
+        try {
+          serializedItem = JSON.stringify(item);
+        } catch (e) {
+          console.error('Failed to serialize cache item:', e);
+          return false;
+        }
+
+        const itemSize = new Blob([serializedItem]).size;
+        if (itemSize > defaults.maxSizeBytes) {
+          console.warn(`Cache item exceeds maximum size (${(itemSize/1024/1024).toFixed(2)}MB)`);
+          return false;
+        }
+
+        if (storageAvailable) {
+          try {
+            localStorage.setItem(key, serializedItem);
+            return true;
+          } catch (storageError) {
+            if (storageError.name === 'QuotaExceededError') {
+              this.cleanup(true);
+              try {
+                localStorage.setItem(key, serializedItem);
+                return true;
+              } catch (retryError) {
+                console.error('Storage quota exceeded even after cleanup:', retryError);
+                storageAvailable = false;
+                console.warn('Switching to in-memory cache due to quota issues');
+                memoryCache.set(key, item);
+                return true;
+              }
+            } else {
+              console.error('localStorage error:', storageError);
+              storageAvailable = false;
+              console.warn('Switching to in-memory cache due to localStorage error');
+              memoryCache.set(key, item);
+              return true;
+            }
+          }
+        } else {
+          memoryCache.set(key, item);
+          if (memoryCache.size > defaults.maxItems) {
+            const keysToDelete = Array.from(memoryCache.keys())
+              .filter(k => isCacheKey(k))
+              .sort((a, b) => memoryCache.get(a).timestamp - memoryCache.get(b).timestamp)
+              .slice(0, Math.floor(memoryCache.size * 0.2)); // Remove oldest 20%
+            
+            keysToDelete.forEach(k => memoryCache.delete(k));
+          }
+          
+          return true;
+        }
+      } catch (error) {
+        console.error('Cache set error:', error);
+        try {
+          memoryCache.set(key, {
+            value: value,
+            timestamp: Date.now(),
+            expiresAt: Date.now() + (window.config?.cacheConfig?.defaultTTL || defaults.defaultTTL)
+          });
+          return true;
+        } catch (e) {
+          console.error('Memory cache set error:', e);
+          return false;
+        }
+      }
+    },
+
+    get: function(key) {
+      try {
+        if (storageAvailable) {
+          try {
+            const itemStr = localStorage.getItem(key);
+            if (itemStr) {
+              let item;
+              try {
+                item = JSON.parse(itemStr);
+              } catch (parseError) {
+                console.error('Failed to parse cached item:', parseError);
+                localStorage.removeItem(key);
+                return null;
+              }
+
+              if (!item || typeof item.expiresAt !== 'number' || !Object.prototype.hasOwnProperty.call(item, 'value')) {
+                console.warn('Invalid cache item structure for key:', key);
+                localStorage.removeItem(key);
+                return null;
+              }
+
+              const now = Date.now();
+              if (now < item.expiresAt) {
+                return {
+                  value: item.value,
+                  remainingTime: item.expiresAt - now
+                };
+              }
+
+              localStorage.removeItem(key);
+              return null;
+            }
+          } catch (error) {
+            console.error("localStorage access error:", error);
+            storageAvailable = false;
+            console.warn('Switching to in-memory cache due to localStorage error');
+          }
+        }
+
+        if (memoryCache.has(key)) {
+          const item = memoryCache.get(key);
+          const now = Date.now();
+
+          if (now < item.expiresAt) {
+            return {
+              value: item.value,
+              remainingTime: item.expiresAt - now
+            };
+          } else {
+
+            memoryCache.delete(key);
+          }
+        }
+        
+        return null;
+      } catch (error) {
+        console.error("Cache retrieval error:", error);
+        try {
+          if (storageAvailable) {
+            localStorage.removeItem(key);
+          }
+          memoryCache.delete(key);
+        } catch (removeError) {
+          console.error("Failed to remove invalid cache entry:", removeError);
+        }
+        return null;
+      }
+    },
+
+    isValid: function(key) {
+      return this.get(key) !== null;
+    },
+
+    cleanup: function(aggressive = false) {
+      const now = Date.now();
+      let totalSize = 0;
+      let itemCount = 0;
+      const items = [];
+
+      if (storageAvailable) {
+        try {
+          for (let i = 0; i < localStorage.length; i++) {
+            const key = localStorage.key(i);
+            if (!isCacheKey(key)) continue;
+
+            try {
+              const itemStr = localStorage.getItem(key);
+              const size = new Blob([itemStr]).size;
+              const item = JSON.parse(itemStr);
+
+              if (now >= item.expiresAt) {
+                localStorage.removeItem(key);
+                continue;
+              }
+
+              items.push({
+                key,
+                size,
+                expiresAt: item.expiresAt,
+                timestamp: item.timestamp
+              });
+
+              totalSize += size;
+              itemCount++;
+            } catch (error) {
+              console.error("Error processing cache item:", error);
+              localStorage.removeItem(key);
+            }
+          }
+
+          if (aggressive || totalSize > defaults.maxSizeBytes || itemCount > defaults.maxItems) {
+            items.sort((a, b) => b.timestamp - a.timestamp);
+
+            while ((totalSize > defaults.maxSizeBytes || itemCount > defaults.maxItems) && items.length > 0) {
+              const item = items.pop();
+              try {
+                localStorage.removeItem(item.key);
+                totalSize -= item.size;
+                itemCount--;
+              } catch (error) {
+                console.error("Error removing cache item:", error);
+              }
+            }
+          }
+        } catch (error) {
+          console.error("Error during localStorage cleanup:", error);
+          storageAvailable = false;
+          console.warn('Switching to in-memory cache due to localStorage error');
+        }
+      }
+
+      const expiredKeys = [];
+      memoryCache.forEach((item, key) => {
+        if (now >= item.expiresAt) {
+          expiredKeys.push(key);
+        }
+      });
+
+      expiredKeys.forEach(key => memoryCache.delete(key));
+
+      if (aggressive && memoryCache.size > defaults.maxItems / 2) {
+        const keysToDelete = Array.from(memoryCache.keys())
+          .filter(key => isCacheKey(key))
+          .sort((a, b) => memoryCache.get(a).timestamp - memoryCache.get(b).timestamp)
+          .slice(0, Math.floor(memoryCache.size * 0.3)); // Remove oldest 30% during aggressive cleanup
+        
+        keysToDelete.forEach(key => memoryCache.delete(key));
+      }
+
+      return {
+        totalSize,
+        itemCount,
+        memoryCacheSize: memoryCache.size,
+        cleaned: items.length,
+        storageAvailable
+      };
+    },
+
+    clear: function() {
+
+      if (storageAvailable) {
+        try {
+          const keys = [];
+          for (let i = 0; i < localStorage.length; i++) {
+            const key = localStorage.key(i);
+            if (isCacheKey(key)) {
+              keys.push(key);
+            }
+          }
+
+          keys.forEach(key => {
+            try {
+              localStorage.removeItem(key);
+            } catch (error) {
+              console.error("Error clearing cache item:", error);
+            }
+          });
+        } catch (error) {
+          console.error("Error clearing localStorage cache:", error);
+          storageAvailable = false;
+        }
+      }
+
+      Array.from(memoryCache.keys())
+        .filter(key => isCacheKey(key))
+        .forEach(key => memoryCache.delete(key));
+      
+      console.log("Cache cleared successfully");
+      return true;
+    },
+
+    getStats: function() {
+      let totalSize = 0;
+      let itemCount = 0;
+      let expiredCount = 0;
+      const now = Date.now();
+
+      if (storageAvailable) {
+        try {
+          for (let i = 0; i < localStorage.length; i++) {
+            const key = localStorage.key(i);
+            if (!isCacheKey(key)) continue;
+
+            try {
+              const itemStr = localStorage.getItem(key);
+              const size = new Blob([itemStr]).size;
+              const item = JSON.parse(itemStr);
+
+              totalSize += size;
+              itemCount++;
+
+              if (now >= item.expiresAt) {
+                expiredCount++;
+              }
+            } catch (error) {
+              console.error("Error getting cache stats:", error);
+            }
+          }
+        } catch (error) {
+          console.error("Error getting localStorage stats:", error);
+          storageAvailable = false;
+        }
+      }
+
+      let memoryCacheSize = 0;
+      let memoryCacheItems = 0;
+      let memoryCacheExpired = 0;
+      
+      memoryCache.forEach((item, key) => {
+        if (isCacheKey(key)) {
+          memoryCacheItems++;
+          if (now >= item.expiresAt) {
+            memoryCacheExpired++;
+          }
+          try {
+            memoryCacheSize += new Blob([JSON.stringify(item)]).size;
+          } catch (e) {
+          }
+        }
+      });
+      
+      return {
+        totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
+        itemCount,
+        expiredCount,
+        utilization: ((totalSize / defaults.maxSizeBytes) * 100).toFixed(1) + '%',
+        memoryCacheItems,
+        memoryCacheExpired,
+        memoryCacheSizeKB: (memoryCacheSize / 1024).toFixed(2),
+        storageType: storageAvailable ? 'localStorage' : 'memory'
+      };
+    },
+
+    checkStorage: function() {
+      const wasAvailable = storageAvailable;
+      storageAvailable = isLocalStorageAvailable();
+
+      if (storageAvailable && !wasAvailable && memoryCache.size > 0) {
+        console.log('localStorage is now available. Migrating memory cache...');
+        let migratedCount = 0;
+        memoryCache.forEach((item, key) => {
+          if (isCacheKey(key)) {
+            try {
+              localStorage.setItem(key, JSON.stringify(item));
+              memoryCache.delete(key);
+              migratedCount++;
+            } catch (e) {
+              if (e.name === 'QuotaExceededError') {
+                console.warn('Storage quota exceeded during migration. Keeping items in memory cache.');
+                return false;
+              }
+            }
+          }
+        });
+        
+        console.log(`Migrated ${migratedCount} items from memory cache to localStorage.`);
+      }
+      
+      return {
+        available: storageAvailable,
+        type: storageAvailable ? 'localStorage' : 'memory'
+      };
+    }
+  };
+
+  const publicAPI = {
+    ...cacheAPI,
+
+    setPrices: function(priceData, customTtl = null) {
+      return this.set(PRICES_CACHE_KEY, priceData, 
+        customTtl || (typeof customTtl === 'undefined' ? 'prices' : null));
+    },
+
+    getPrices: function() {
+      return this.get(PRICES_CACHE_KEY);
+    },
+
+    getCoinPrice: function(symbol) {
+      const prices = this.getPrices();
+      if (!prices || !prices.value) {
+        return null;
+      }
+
+      const normalizedSymbol = symbol.toLowerCase();
+      return prices.value[normalizedSymbol] || null;
+    },
+    
+    getCompatiblePrices: function(format) {
+      const prices = this.getPrices();
+      if (!prices || !prices.value) {
+        return null;
+      }
+
+      switch(format) {
+        case 'rates':
+          const ratesFormat = {};
+          Object.entries(prices.value).forEach(([coin, data]) => {
+            const coinKey = coin.replace(/-/g, ' ')
+              .split(' ')
+              .map(word => word.charAt(0).toUpperCase() + word.slice(1))
+              .join(' ')
+              .toLowerCase()
+              .replace(' ', '-');
+            
+            ratesFormat[coinKey] = {
+              usd: data.price || data.usd,
+              btc: data.price_btc || data.btc
+            };
+          });
+          return {
+            value: ratesFormat,
+            remainingTime: prices.remainingTime
+          };
+
+        case 'coinGecko':
+          const geckoFormat = {};
+          Object.entries(prices.value).forEach(([coin, data]) => {
+            const symbol = this.getSymbolFromCoinId(coin);
+            if (symbol) {
+              geckoFormat[symbol.toLowerCase()] = {
+                current_price: data.price || data.usd,
+                price_btc: data.price_btc || data.btc,
+                total_volume: data.total_volume,
+                price_change_percentage_24h: data.price_change_percentage_24h,
+                displayName: symbol
+              };
+            }
+          });
+          return {
+            value: geckoFormat,
+            remainingTime: prices.remainingTime
+          };
+          
+        default:
+          return prices;
+      }
+    },
+
+    getSymbolFromCoinId: function(coinId) {
+      const symbolMap = {
+        'bitcoin': 'BTC',
+        'litecoin': 'LTC',
+        'monero': 'XMR',
+        'particl': 'PART',
+        'pivx': 'PIVX',
+        'firo': 'FIRO',
+        'zcoin': 'FIRO',
+        'dash': 'DASH',
+        'decred': 'DCR',
+        'wownero': 'WOW',
+        'bitcoin-cash': 'BCH',
+        'dogecoin': 'DOGE'
+      };
+
+      return symbolMap[coinId] || null;
+    }
+  };
+
+  if (window.CleanupManager) {
+    window.CleanupManager.registerResource('cacheManager', publicAPI, (cm) => {
+      cm.clear();
+    });
+  }
+
+  return publicAPI;
+})();
+
+window.CacheManager = CacheManager;
+
+
+//console.log('CacheManager initialized with methods:', Object.keys(CacheManager));
+console.log('CacheManager initialized');
diff --git a/basicswap/static/js/modules/cleanup-manager.js b/basicswap/static/js/modules/cleanup-manager.js
new file mode 100644
index 0000000..0615fca
--- /dev/null
+++ b/basicswap/static/js/modules/cleanup-manager.js
@@ -0,0 +1,270 @@
+const CleanupManager = (function() {
+
+    const state = {
+        eventListeners: [],
+        timeouts: [],
+        intervals: [],
+        animationFrames: [],
+        resources: new Map(),
+        debug: false
+    };
+
+    function log(message, ...args) {
+        if (state.debug) {
+            console.log(`[CleanupManager] ${message}`, ...args);
+        }
+    }
+
+    const publicAPI = {
+        addListener: function(element, type, handler, options = false) {
+            if (!element) {
+                log('Warning: Attempted to add listener to null/undefined element');
+                return handler;
+            }
+
+            element.addEventListener(type, handler, options);
+            state.eventListeners.push({ element, type, handler, options });
+            log(`Added ${type} listener to`, element);
+            return handler;
+        },
+
+        setTimeout: function(callback, delay) {
+            const id = window.setTimeout(callback, delay);
+            state.timeouts.push(id);
+            log(`Created timeout ${id} with ${delay}ms delay`);
+            return id;
+        },
+
+        setInterval: function(callback, delay) {
+            const id = window.setInterval(callback, delay);
+            state.intervals.push(id);
+            log(`Created interval ${id} with ${delay}ms delay`);
+            return id;
+        },
+
+        requestAnimationFrame: function(callback) {
+            const id = window.requestAnimationFrame(callback);
+            state.animationFrames.push(id);
+            log(`Requested animation frame ${id}`);
+            return id;
+        },
+
+        registerResource: function(type, resource, cleanupFn) {
+            const id = `${type}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
+            state.resources.set(id, { resource, cleanupFn });
+            log(`Registered custom resource ${id} of type ${type}`);
+            return id;
+        },
+
+        unregisterResource: function(id) {
+            const resourceInfo = state.resources.get(id);
+            if (resourceInfo) {
+                try {
+                    resourceInfo.cleanupFn(resourceInfo.resource);
+                    state.resources.delete(id);
+                    log(`Unregistered and cleaned up resource ${id}`);
+                    return true;
+                } catch (error) {
+                    console.error(`[CleanupManager] Error cleaning up resource ${id}:`, error);
+                    return false;
+                }
+            }
+            log(`Resource ${id} not found`);
+            return false;
+        },
+
+        clearTimeout: function(id) {
+            const index = state.timeouts.indexOf(id);
+            if (index !== -1) {
+                window.clearTimeout(id);
+                state.timeouts.splice(index, 1);
+                log(`Cleared timeout ${id}`);
+            }
+        },
+
+        clearInterval: function(id) {
+            const index = state.intervals.indexOf(id);
+            if (index !== -1) {
+                window.clearInterval(id);
+                state.intervals.splice(index, 1);
+                log(`Cleared interval ${id}`);
+            }
+        },
+
+        cancelAnimationFrame: function(id) {
+            const index = state.animationFrames.indexOf(id);
+            if (index !== -1) {
+                window.cancelAnimationFrame(id);
+                state.animationFrames.splice(index, 1);
+                log(`Cancelled animation frame ${id}`);
+            }
+        },
+
+        removeListener: function(element, type, handler, options = false) {
+            if (!element) return;
+
+            try {
+                element.removeEventListener(type, handler, options);
+                log(`Removed ${type} listener from`, element);
+            } catch (error) {
+                console.error(`[CleanupManager] Error removing event listener:`, error);
+            }
+
+            state.eventListeners = state.eventListeners.filter(
+                listener => !(listener.element === element && 
+                            listener.type === type && 
+                            listener.handler === handler)
+            );
+        },
+
+        removeListenersByElement: function(element) {
+            if (!element) return;
+
+            const listenersToRemove = state.eventListeners.filter(
+                listener => listener.element === element
+            );
+
+            listenersToRemove.forEach(({ element, type, handler, options }) => {
+                try {
+                    element.removeEventListener(type, handler, options);
+                    log(`Removed ${type} listener from`, element);
+                } catch (error) {
+                    console.error(`[CleanupManager] Error removing event listener:`, error);
+                }
+            });
+
+            state.eventListeners = state.eventListeners.filter(
+                listener => listener.element !== element
+            );
+        },
+
+        clearAllTimeouts: function() {
+            state.timeouts.forEach(id => {
+                window.clearTimeout(id);
+            });
+            const count = state.timeouts.length;
+            state.timeouts = [];
+            log(`Cleared all timeouts (${count})`);
+        },
+
+        clearAllIntervals: function() {
+            state.intervals.forEach(id => {
+                window.clearInterval(id);
+            });
+            const count = state.intervals.length;
+            state.intervals = [];
+            log(`Cleared all intervals (${count})`);
+        },
+
+        clearAllAnimationFrames: function() {
+            state.animationFrames.forEach(id => {
+                window.cancelAnimationFrame(id);
+            });
+            const count = state.animationFrames.length;
+            state.animationFrames = [];
+            log(`Cancelled all animation frames (${count})`);
+        },
+
+        clearAllResources: function() {
+            let successCount = 0;
+            let errorCount = 0;
+
+            state.resources.forEach((resourceInfo, id) => {
+                try {
+                    resourceInfo.cleanupFn(resourceInfo.resource);
+                    successCount++;
+                } catch (error) {
+                    console.error(`[CleanupManager] Error cleaning up resource ${id}:`, error);
+                    errorCount++;
+                }
+            });
+
+            state.resources.clear();
+            log(`Cleared all custom resources (${successCount} success, ${errorCount} errors)`);
+        },
+
+        clearAllListeners: function() {
+            state.eventListeners.forEach(({ element, type, handler, options }) => {
+                if (element) {
+                    try {
+                        element.removeEventListener(type, handler, options);
+                    } catch (error) {
+                        console.error(`[CleanupManager] Error removing event listener:`, error);
+                    }
+                }
+            });
+            const count = state.eventListeners.length;
+            state.eventListeners = [];
+            log(`Removed all event listeners (${count})`);
+        },
+
+        clearAll: function() {
+            const counts = {
+                listeners: state.eventListeners.length,
+                timeouts: state.timeouts.length,
+                intervals: state.intervals.length,
+                animationFrames: state.animationFrames.length,
+                resources: state.resources.size
+            };
+
+            this.clearAllListeners();
+            this.clearAllTimeouts();
+            this.clearAllIntervals();
+            this.clearAllAnimationFrames();
+            this.clearAllResources();
+
+            log(`All resources cleaned up:`, counts);
+            return counts;
+        },
+
+        getResourceCounts: function() {
+            return {
+                listeners: state.eventListeners.length,
+                timeouts: state.timeouts.length,
+                intervals: state.intervals.length,
+                animationFrames: state.animationFrames.length,
+                resources: state.resources.size,
+                total: state.eventListeners.length + 
+                      state.timeouts.length + 
+                      state.intervals.length + 
+                      state.animationFrames.length + 
+                      state.resources.size
+            };
+        },
+
+        setDebugMode: function(enabled) {
+            state.debug = Boolean(enabled);
+            log(`Debug mode ${state.debug ? 'enabled' : 'disabled'}`);
+            return state.debug;
+        },
+
+        dispose: function() {
+            this.clearAll();
+            log('CleanupManager disposed');
+        },
+
+        initialize: function(options = {}) {
+            if (options.debug !== undefined) {
+                this.setDebugMode(options.debug);
+            }
+            log('CleanupManager initialized');
+            return this;
+        }
+    };
+
+    return publicAPI;
+})();
+
+
+window.CleanupManager = CleanupManager;
+
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.cleanupManagerInitialized) {
+        CleanupManager.initialize();
+        window.cleanupManagerInitialized = true;
+    }
+});
+
+//console.log('CleanupManager initialized with methods:', Object.keys(CleanupManager));
+console.log('CleanupManager initialized');
diff --git a/basicswap/static/js/modules/config-manager.js b/basicswap/static/js/modules/config-manager.js
new file mode 100644
index 0000000..e77c55e
--- /dev/null
+++ b/basicswap/static/js/modules/config-manager.js
@@ -0,0 +1,414 @@
+const ConfigManager = (function() {
+    const state = {
+        isInitialized: false
+    };
+
+    function determineWebSocketPort() {
+        const wsPort = 
+            window.ws_port || 
+            (typeof getWebSocketConfig === 'function' ? getWebSocketConfig().port : null) || 
+            '11700';
+        return wsPort;
+    }
+
+    const selectedWsPort = determineWebSocketPort();
+
+    const defaultConfig = {
+        cacheDuration: 10 * 60 * 1000,
+        requestTimeout: 60000,
+        wsPort: selectedWsPort,
+        
+        cacheConfig: {
+            defaultTTL: 10 * 60 * 1000,
+            
+            ttlSettings: {
+                prices: 5 * 60 * 1000,
+                chart: 5 * 60 * 1000,
+                historical: 60 * 60 * 1000,
+                volume: 30 * 60 * 1000,
+                offers: 2 * 60 * 1000,
+                identity: 15 * 60 * 1000
+            },
+
+            storage: {
+                maxSizeBytes: 10 * 1024 * 1024,
+                maxItems: 200
+            },
+            
+            fallbackTTL: 24 * 60 * 60 * 1000
+        },
+
+        itemsPerPage: 50,
+
+        apiEndpoints: {
+            cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
+            coinGecko: 'https://api.coingecko.com/api/v3',
+            cryptoCompareHistorical: 'https://min-api.cryptocompare.com/data/v2/histoday',
+            cryptoCompareHourly: 'https://min-api.cryptocompare.com/data/v2/histohour',
+            volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
+        },
+
+        rateLimits: {
+            coingecko: {
+                requestsPerMinute: 50,
+                minInterval: 1200
+            },
+            cryptocompare: {
+                requestsPerMinute: 30,
+                minInterval: 2000
+            }
+        },
+
+        retryDelays: [5000, 15000, 30000],
+
+        coins: [
+            { symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'XMR', name: 'monero', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'PART', name: 'particl', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'BCH', name: 'bitcoincash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'PIVX', name: 'pivx', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'FIRO', name: 'firo', displayName: 'Firo', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'DASH', name: 'dash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'LTC', name: 'litecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'DOGE', name: 'dogecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'DCR', name: 'decred', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
+            { symbol: 'WOW', name: 'wownero', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 }
+        ],
+
+        coinMappings: {
+            nameToSymbol: {
+                'Bitcoin': 'BTC',
+                'Litecoin': 'LTC',
+                'Monero': 'XMR',
+                'Particl': 'PART',
+                'Particl Blind': 'PART',
+                'Particl Anon': 'PART',
+                'PIVX': 'PIVX',
+                'Firo': 'FIRO',
+                'Zcoin': 'FIRO',
+                'Dash': 'DASH',
+                'Decred': 'DCR',
+                'Wownero': 'WOW',
+                'Bitcoin Cash': 'BCH',
+                'Dogecoin': 'DOGE'
+            },
+            
+            nameToDisplayName: {
+                'Bitcoin': 'Bitcoin',
+                'Litecoin': 'Litecoin',
+                'Monero': 'Monero',
+                'Particl': 'Particl',
+                'Particl Blind': 'Particl Blind',
+                'Particl Anon': 'Particl Anon',
+                'PIVX': 'PIVX',
+                'Firo': 'Firo',
+                'Zcoin': 'Firo',
+                'Dash': 'Dash',
+                'Decred': 'Decred',
+                'Wownero': 'Wownero',
+                'Bitcoin Cash': 'Bitcoin Cash',
+                'Dogecoin': 'Dogecoin'
+            },
+
+            idToName: {
+                1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred',
+                6: 'monero', 7: 'particl blind', 8: 'particl anon',
+                9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash',
+                18: 'dogecoin'
+            },
+
+            nameToCoinGecko: {
+                'bitcoin': 'bitcoin',
+                'monero': 'monero',
+                'particl': 'particl',
+                'bitcoin cash': 'bitcoin-cash',
+                'bitcoincash': 'bitcoin-cash',
+                'pivx': 'pivx',
+                'firo': 'firo',
+                'zcoin': 'firo',
+                'dash': 'dash',
+                'litecoin': 'litecoin',
+                'dogecoin': 'dogecoin',
+                'decred': 'decred',
+                'wownero': 'wownero'
+            }
+        },
+
+        chartConfig: {
+            colors: {
+                default: {
+                    lineColor: 'rgba(77, 132, 240, 1)',
+                    backgroundColor: 'rgba(77, 132, 240, 0.1)'
+                }
+            },
+            showVolume: false,
+            specialCoins: [''],
+            resolutions: {
+                year: { days: 365, interval: 'month' },
+                sixMonths: { days: 180, interval: 'daily' },
+                day: { days: 1, interval: 'hourly' }
+            },
+            currentResolution: 'year'
+        }
+    };
+
+    const publicAPI = {
+        ...defaultConfig,
+
+        initialize: function(options = {}) {
+            if (state.isInitialized) {
+                console.warn('[ConfigManager] Already initialized');
+                return this;
+            }
+
+            if (options) {
+                Object.assign(this, options);
+            }
+            
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('configManager', this, (mgr) => mgr.dispose());
+            }
+
+            this.utils = utils;
+            
+            state.isInitialized = true;
+            console.log('ConfigManager initialized');
+            return this;
+        },
+
+        getAPIKeys: function() {
+            if (typeof window.getAPIKeys === 'function') {
+                const apiKeys = window.getAPIKeys();
+                return {
+                    cryptoCompare: apiKeys.cryptoCompare || '',
+                    coinGecko: apiKeys.coinGecko || ''
+                };
+            }
+
+            return {
+                cryptoCompare: '',
+                coinGecko: ''
+            };
+        },
+
+        getCoinBackendId: function(coinName) {
+            if (!coinName) return null;
+
+            const nameMap = {
+                'bitcoin-cash': 'bitcoincash',
+                'bitcoin cash': 'bitcoincash',
+                'firo': 'firo',
+                'zcoin': 'firo',
+                'bitcoincash': 'bitcoin-cash'
+            };
+
+            const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : '';
+            return nameMap[lowerCoinName] || lowerCoinName;
+        },
+        
+        coinMatches: function(offerCoin, filterCoin) {
+            if (!offerCoin || !filterCoin) return false;
+
+            offerCoin = offerCoin.toLowerCase();
+            filterCoin = filterCoin.toLowerCase();
+
+            if (offerCoin === filterCoin) return true;
+
+            if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
+                (filterCoin === 'firo' || filterCoin === 'zcoin')) {
+                return true;
+            }
+
+            if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
+                (offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
+                return true;
+            }
+
+            const particlVariants = ['particl', 'particl anon', 'particl blind'];
+            if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
+                return true;
+            }
+
+            if (particlVariants.includes(filterCoin)) {
+                return offerCoin === filterCoin;
+            }
+
+            return false;
+        },
+
+        update: function(path, value) {
+            const parts = path.split('.');
+            let current = this;
+
+            for (let i = 0; i < parts.length - 1; i++) {
+                if (!current[parts[i]]) {
+                    current[parts[i]] = {};
+                }
+                current = current[parts[i]];
+            }
+
+            current[parts[parts.length - 1]] = value;
+            return this;
+        },
+
+        get: function(path, defaultValue = null) {
+            const parts = path.split('.');
+            let current = this;
+            
+            for (let i = 0; i < parts.length; i++) {
+                if (current === undefined || current === null) {
+                    return defaultValue;
+                }
+                current = current[parts[i]];
+            }
+
+            return current !== undefined ? current : defaultValue;
+        },
+
+        dispose: function() {
+            state.isInitialized = false;
+            console.log('ConfigManager disposed');
+        }
+    };
+
+    const utils = {
+        formatNumber: function(number, decimals = 2) {
+            if (typeof number !== 'number' || isNaN(number)) {
+                console.warn('formatNumber received a non-number value:', number);
+                return '0';
+            }
+            try {
+                return new Intl.NumberFormat('en-US', {
+                    minimumFractionDigits: decimals,
+                    maximumFractionDigits: decimals
+                }).format(number);
+            } catch (e) {
+                return '0';
+            }
+        },
+
+        formatDate: function(timestamp, resolution) {
+            const date = new Date(timestamp);
+            const options = {
+                day: { hour: '2-digit', minute: '2-digit', hour12: true },
+                week: { month: 'short', day: 'numeric' },
+                month: { year: 'numeric', month: 'short', day: 'numeric' }
+            };
+            return date.toLocaleString('en-US', { ...options[resolution], timeZone: 'UTC' });
+        },
+
+        debounce: function(func, delay) {
+            let timeoutId;
+            return function(...args) {
+                clearTimeout(timeoutId);
+                timeoutId = setTimeout(() => func(...args), delay);
+            };
+        },
+
+        formatTimeLeft: function(timestamp) {
+            const now = Math.floor(Date.now() / 1000);
+            if (timestamp <= now) return "Expired";
+            return this.formatTime(timestamp);
+        },
+
+        formatTime: function(timestamp, addAgoSuffix = false) {
+            const now = Math.floor(Date.now() / 1000);
+            const diff = Math.abs(now - timestamp);
+
+            let timeString;
+            if (diff < 60) {
+                timeString = `${diff} seconds`;
+            } else if (diff < 3600) {
+                timeString = `${Math.floor(diff / 60)} minutes`;
+            } else if (diff < 86400) {
+                timeString = `${Math.floor(diff / 3600)} hours`;
+            } else if (diff < 2592000) {
+                timeString = `${Math.floor(diff / 86400)} days`;
+            } else if (diff < 31536000) {
+                timeString = `${Math.floor(diff / 2592000)} months`;
+            } else {
+                timeString = `${Math.floor(diff / 31536000)} years`;
+            }
+
+            return addAgoSuffix ? `${timeString} ago` : timeString;
+        },
+
+        escapeHtml: function(unsafe) {
+            if (typeof unsafe !== 'string') {
+                console.warn('escapeHtml received a non-string value:', unsafe);
+                return '';
+            }
+            return unsafe
+                .replace(/&/g, "&amp;")
+                .replace(/</g, "&lt;")
+                .replace(/>/g, "&gt;")
+                .replace(/"/g, "&quot;")
+                .replace(/'/g, "&#039;");
+        },
+
+        formatPrice: function(coin, price) {
+            if (typeof price !== 'number' || isNaN(price)) {
+                console.warn(`Invalid price for ${coin}:`, price);
+                return 'N/A';
+            }
+            if (price < 0.000001) return price.toExponential(2);
+            if (price < 0.001) return price.toFixed(8);
+            if (price < 1) return price.toFixed(4);
+            if (price < 10) return price.toFixed(3);
+            if (price < 1000) return price.toFixed(2);
+            if (price < 100000) return price.toFixed(1);
+            return price.toFixed(0);
+        },
+
+        getEmptyPriceData: function() {
+            return {
+                'bitcoin': { usd: null, btc: null },
+                'bitcoin-cash': { usd: null, btc: null },
+                'dash': { usd: null, btc: null },
+                'dogecoin': { usd: null, btc: null },
+                'decred': { usd: null, btc: null },
+                'litecoin': { usd: null, btc: null },
+                'particl': { usd: null, btc: null },
+                'pivx': { usd: null, btc: null },
+                'monero': { usd: null, btc: null },
+                'zano': { usd: null, btc: null },
+                'wownero': { usd: null, btc: null },
+                'firo': { usd: null, btc: null }
+            };
+        },
+        
+        getCoinSymbol: function(fullName) {
+            return publicAPI.coinMappings?.nameToSymbol[fullName] || fullName;
+        }
+    };
+    
+    return publicAPI;
+})();
+
+window.logger = {
+    log: function(message) {
+        console.log(`[AppLog] ${new Date().toISOString()}: ${message}`);
+    },
+    warn: function(message) {
+        console.warn(`[AppWarn] ${new Date().toISOString()}: ${message}`);
+    },
+    error: function(message) {
+        console.error(`[AppError] ${new Date().toISOString()}: ${message}`);
+    }
+};
+
+window.config = ConfigManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.configManagerInitialized) {
+        ConfigManager.initialize();
+        window.configManagerInitialized = true;
+    }
+});
+
+if (typeof module !== 'undefined') {
+    module.exports = ConfigManager;
+}
+
+//console.log('ConfigManager initialized with properties:', Object.keys(ConfigManager));
+console.log('ConfigManager initialized');
diff --git a/basicswap/static/js/modules/identity-manager.js b/basicswap/static/js/modules/identity-manager.js
new file mode 100644
index 0000000..9ac2859
--- /dev/null
+++ b/basicswap/static/js/modules/identity-manager.js
@@ -0,0 +1,192 @@
+const IdentityManager = (function() {
+    const state = {
+        cache: new Map(),
+        pendingRequests: new Map(),
+        config: {
+            retryDelay: 2000,
+            maxRetries: 3,
+            maxCacheSize: 100,
+            cacheTimeout: window.config?.cacheConfig?.ttlSettings?.identity || 15 * 60 * 1000,
+            debug: false
+        }
+    };
+
+    function log(message, ...args) {
+        if (state.config.debug) {
+            console.log(`[IdentityManager] ${message}`, ...args);
+        }
+    }
+
+    const publicAPI = {
+        getIdentityData: async function(address) {
+            if (!address) {
+                return null;
+            }
+
+            const cachedData = this.getCachedIdentity(address);
+            if (cachedData) {
+                log(`Cache hit for ${address}`);
+                return cachedData;
+            }
+
+            if (state.pendingRequests.has(address)) {
+                log(`Using pending request for ${address}`);
+                return state.pendingRequests.get(address);
+            }
+
+            log(`Fetching identity for ${address}`);
+            const request = fetchWithRetry(address);
+            state.pendingRequests.set(address, request);
+
+            try {
+                const data = await request;
+                this.setCachedIdentity(address, data);
+                return data;
+            } finally {
+                state.pendingRequests.delete(address);
+            }
+        },
+
+        getCachedIdentity: function(address) {
+            const cached = state.cache.get(address);
+            if (cached && (Date.now() - cached.timestamp) < state.config.cacheTimeout) {
+                return cached.data;
+            }
+            return null;
+        },
+
+        setCachedIdentity: function(address, data) {
+            if (state.cache.size >= state.config.maxCacheSize) {
+                const oldestEntries = [...state.cache.entries()]
+                    .sort((a, b) => a[1].timestamp - b[1].timestamp)
+                    .slice(0, Math.floor(state.config.maxCacheSize * 0.2));
+                    
+                oldestEntries.forEach(([key]) => {
+                    state.cache.delete(key);
+                    log(`Pruned cache entry for ${key}`);
+                });
+            }
+
+            state.cache.set(address, {
+                data,
+                timestamp: Date.now()
+            });
+            log(`Cached identity for ${address}`);
+        },
+
+        clearCache: function() {
+            log(`Clearing identity cache (${state.cache.size} entries)`);
+            state.cache.clear();
+            state.pendingRequests.clear();
+        },
+
+        limitCacheSize: function(maxSize = state.config.maxCacheSize) {
+            if (state.cache.size <= maxSize) {
+                return 0;
+            }
+
+            const entriesToRemove = [...state.cache.entries()]
+                .sort((a, b) => a[1].timestamp - b[1].timestamp)
+                .slice(0, state.cache.size - maxSize);
+            
+            entriesToRemove.forEach(([key]) => state.cache.delete(key));
+            log(`Limited cache size, removed ${entriesToRemove.length} entries`);
+            
+            return entriesToRemove.length;
+        },
+
+        getCacheSize: function() {
+            return state.cache.size;
+        },
+
+        configure: function(options = {}) {
+            Object.assign(state.config, options);
+            log(`Configuration updated:`, state.config);
+            return state.config;
+        },
+
+        getStats: function() {
+            const now = Date.now();
+            let expiredCount = 0;
+            let totalSize = 0;
+
+            state.cache.forEach((value, key) => {
+                if (now - value.timestamp > state.config.cacheTimeout) {
+                    expiredCount++;
+                }
+                const keySize = key.length * 2;
+                const dataSize = JSON.stringify(value.data).length * 2;
+                totalSize += keySize + dataSize;
+            });
+
+            return {
+                cacheEntries: state.cache.size,
+                pendingRequests: state.pendingRequests.size,
+                expiredEntries: expiredCount,
+                estimatedSizeKB: Math.round(totalSize / 1024),
+                config: { ...state.config }
+            };
+        },
+
+        setDebugMode: function(enabled) {
+            state.config.debug = Boolean(enabled);
+            return `Debug mode ${state.config.debug ? 'enabled' : 'disabled'}`;
+        },
+
+        initialize: function(options = {}) {
+
+            if (options) {
+                this.configure(options);
+            }
+            
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('identityManager', this, (mgr) => mgr.dispose());
+            }
+            
+            log('IdentityManager initialized');
+            return this;
+        },
+
+        dispose: function() {
+            this.clearCache();
+            log('IdentityManager disposed');
+        }
+    };
+
+    async function fetchWithRetry(address, attempt = 1) {
+        try {
+            const response = await fetch(`/json/identities/${address}`, {
+                signal: AbortSignal.timeout(5000)
+            });
+
+            if (!response.ok) {
+                throw new Error(`HTTP error! status: ${response.status}`);
+            }
+
+            return await response.json();
+        } catch (error) {
+            if (attempt >= state.config.maxRetries) {
+                console.error(`[IdentityManager] Error:`, error.message);
+                console.warn(`[IdentityManager] Failed to fetch identity for ${address} after ${attempt} attempts`);
+                return null;
+            }
+
+            await new Promise(resolve => setTimeout(resolve, state.config.retryDelay * attempt));
+            return fetchWithRetry(address, attempt + 1);
+        }
+    }
+
+    return publicAPI;
+})();
+
+window.IdentityManager = IdentityManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.identityManagerInitialized) {
+        IdentityManager.initialize();
+        window.identityManagerInitialized = true;
+    }
+});
+
+//console.log('IdentityManager initialized with methods:', Object.keys(IdentityManager));
+console.log('IdentityManager initialized');
diff --git a/basicswap/static/js/modules/memory-manager.js b/basicswap/static/js/modules/memory-manager.js
new file mode 100644
index 0000000..4c3cffd
--- /dev/null
+++ b/basicswap/static/js/modules/memory-manager.js
@@ -0,0 +1,219 @@
+const MemoryManager = (function() {
+
+    const state = {
+        isMonitoringEnabled: false,
+        monitorInterval: null,
+        cleanupInterval: null
+    };
+
+    const config = {
+        monitorInterval: 30000,
+        cleanupInterval: 60000,
+        debug: false
+    };
+
+    function log(message, ...args) {
+        if (config.debug) {
+            console.log(`[MemoryManager] ${message}`, ...args);
+        }
+    }
+
+    const publicAPI = {
+        enableMonitoring: function(interval = config.monitorInterval) {
+            if (state.monitorInterval) {
+                clearInterval(state.monitorInterval);
+            }
+
+            state.isMonitoringEnabled = true;
+            config.monitorInterval = interval;
+
+            this.logMemoryUsage();
+
+            state.monitorInterval = setInterval(() => {
+                this.logMemoryUsage();
+            }, interval);
+
+            console.log(`Memory monitoring enabled - reporting every ${interval/1000} seconds`);
+            return true;
+        },
+
+        disableMonitoring: function() {
+            if (state.monitorInterval) {
+                clearInterval(state.monitorInterval);
+                state.monitorInterval = null;
+            }
+
+            state.isMonitoringEnabled = false;
+            console.log('Memory monitoring disabled');
+            return true;
+        },
+
+        logMemoryUsage: function() {
+            const timestamp = new Date().toLocaleTimeString();
+            console.log(`=== Memory Monitor [${timestamp}] ===`);
+
+            if (window.performance && window.performance.memory) {
+                console.log('Memory usage:', {
+                    usedJSHeapSize: (window.performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
+                    totalJSHeapSize: (window.performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2) + ' MB'
+                });
+            }
+
+            if (navigator.deviceMemory) {
+                console.log('Device memory:', navigator.deviceMemory, 'GB');
+            }
+
+            const nodeCount = document.querySelectorAll('*').length;
+            console.log('DOM node count:', nodeCount);
+            
+            if (window.CleanupManager) {
+                const counts = CleanupManager.getResourceCounts();
+                console.log('Managed resources:', counts);
+            }
+
+            if (window.TooltipManager) {
+                const tooltipInstances = document.querySelectorAll('[data-tippy-root]').length;
+                const tooltipTriggers = document.querySelectorAll('[data-tooltip-trigger-id]').length;
+                console.log('Tooltip instances:', tooltipInstances, '- Tooltip triggers:', tooltipTriggers);
+            }
+
+            if (window.CacheManager && window.CacheManager.getStats) {
+                const cacheStats = CacheManager.getStats();
+                console.log('Cache stats:', cacheStats);
+            }
+
+            if (window.IdentityManager && window.IdentityManager.getStats) {
+                const identityStats = window.IdentityManager.getStats();
+                console.log('Identity cache stats:', identityStats);
+            }
+
+            console.log('==============================');
+        },
+
+        enableAutoCleanup: function(interval = config.cleanupInterval) {
+            if (state.cleanupInterval) {
+                clearInterval(state.cleanupInterval);
+            }
+
+            config.cleanupInterval = interval;
+
+            this.forceCleanup();
+
+            state.cleanupInterval = setInterval(() => {
+                this.forceCleanup();
+            }, interval);
+            
+            log('Auto-cleanup enabled every', interval/1000, 'seconds');
+            return true;
+        },
+        
+        disableAutoCleanup: function() {
+            if (state.cleanupInterval) {
+                clearInterval(state.cleanupInterval);
+                state.cleanupInterval = null;
+            }
+
+            console.log('Memory auto-cleanup disabled');
+            return true;
+        },
+
+        forceCleanup: function() {
+            if (config.debug) {
+                console.log('Running memory cleanup...', new Date().toLocaleTimeString());
+            }
+
+            if (window.CacheManager && CacheManager.cleanup) {
+                CacheManager.cleanup(true);
+            }
+
+            if (window.TooltipManager && TooltipManager.cleanup) {
+                window.TooltipManager.cleanup();
+            }
+
+            document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
+                if (window.TooltipManager && TooltipManager.destroy) {
+                    window.TooltipManager.destroy(element);
+                }
+            });
+
+            if (window.chartModule && chartModule.cleanup) {
+                chartModule.cleanup();
+            }
+
+            if (window.gc) {
+                window.gc();
+            } else {
+                const arr = new Array(1000);
+                for (let i = 0; i < 1000; i++) {
+                    arr[i] = new Array(10000).join('x');
+                }
+            }
+
+            if (config.debug) {
+                console.log('Memory cleanup completed');
+            }
+
+            return true;
+        },
+        
+        setDebugMode: function(enabled) {
+            config.debug = Boolean(enabled);
+            return `Debug mode ${config.debug ? 'enabled' : 'disabled'}`;
+        },
+
+        getStatus: function() {
+            return {
+                monitoring: {
+                    enabled: Boolean(state.monitorInterval),
+                    interval: config.monitorInterval
+                },
+                autoCleanup: {
+                    enabled: Boolean(state.cleanupInterval),
+                    interval: config.cleanupInterval
+                },
+                debug: config.debug
+            };
+        },
+
+        initialize: function(options = {}) {
+            if (options.debug !== undefined) {
+                this.setDebugMode(options.debug);
+            }
+
+            if (options.enableMonitoring) {
+                this.enableMonitoring(options.monitorInterval || config.monitorInterval);
+            }
+
+            if (options.enableAutoCleanup) {
+                this.enableAutoCleanup(options.cleanupInterval || config.cleanupInterval);
+            }
+
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('memoryManager', this, (mgr) => mgr.dispose());
+            }
+
+            log('MemoryManager initialized');
+            return this;
+        },
+
+        dispose: function() {
+            this.disableMonitoring();
+            this.disableAutoCleanup();
+            log('MemoryManager disposed');
+        }
+    };
+
+    return publicAPI;
+})();
+
+window.MemoryManager = MemoryManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.memoryManagerInitialized) {
+        MemoryManager.initialize();
+        window.memoryManagerInitialized = true;
+    }
+});
+
+//console.log('MemoryManager initialized with methods:', Object.keys(MemoryManager));
+console.log('MemoryManager initialized');
diff --git a/basicswap/static/js/modules/network-manager.js b/basicswap/static/js/modules/network-manager.js
new file mode 100644
index 0000000..c5a1dc4
--- /dev/null
+++ b/basicswap/static/js/modules/network-manager.js
@@ -0,0 +1,280 @@
+const NetworkManager = (function() {
+    const state = {
+        isOnline: navigator.onLine,
+        reconnectAttempts: 0,
+        reconnectTimer: null,
+        lastNetworkError: null,
+        eventHandlers: {},
+        connectionTestInProgress: false
+    };
+
+    const config = {
+        maxReconnectAttempts: 5,
+        reconnectDelay: 5000,
+        reconnectBackoff: 1.5,
+        connectionTestEndpoint: '/json',
+        connectionTestTimeout: 3000,
+        debug: false
+    };
+
+    function log(message, ...args) {
+        if (config.debug) {
+            console.log(`[NetworkManager] ${message}`, ...args);
+        }
+    }
+
+    function generateHandlerId() {
+        return `handler_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
+    }
+
+    const publicAPI = {
+        initialize: function(options = {}) {
+            Object.assign(config, options);
+            
+            window.addEventListener('online', this.handleOnlineStatus.bind(this));
+            window.addEventListener('offline', this.handleOfflineStatus.bind(this));
+            
+            state.isOnline = navigator.onLine;
+            log(`Network status initialized: ${state.isOnline ? 'online' : 'offline'}`);
+            
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('networkManager', this, (mgr) => mgr.dispose());
+            }
+
+            return this;
+        },
+
+        isOnline: function() {
+            return state.isOnline;
+        },
+
+        getReconnectAttempts: function() {
+            return state.reconnectAttempts;
+        },
+
+        resetReconnectAttempts: function() {
+            state.reconnectAttempts = 0;
+            return this;
+        },
+
+        handleOnlineStatus: function() {
+            log('Browser reports online status');
+            state.isOnline = true;
+            this.notifyHandlers('online');
+            
+            if (state.reconnectTimer) {
+                this.scheduleReconnectRefresh();
+            }
+        },
+
+        handleOfflineStatus: function() {
+            log('Browser reports offline status');
+            state.isOnline = false;
+            this.notifyHandlers('offline');
+        },
+
+        handleNetworkError: function(error) {
+            if (error && (
+                (error.name === 'TypeError' && error.message.includes('NetworkError')) ||
+                (error.name === 'AbortError') ||
+                (error.message && error.message.includes('network')) ||
+                (error.message && error.message.includes('timeout'))
+            )) {
+                log('Network error detected:', error.message);
+
+                if (state.isOnline) {
+                    state.isOnline = false;
+                    state.lastNetworkError = error;
+                    this.notifyHandlers('error', error);
+                }
+
+                if (!state.reconnectTimer) {
+                    this.scheduleReconnectRefresh();
+                }
+
+                return true;
+            }
+            return false;
+        },
+
+        scheduleReconnectRefresh: function() {
+            if (state.reconnectTimer) {
+                clearTimeout(state.reconnectTimer);
+                state.reconnectTimer = null;
+            }
+
+            const delay = config.reconnectDelay * Math.pow(config.reconnectBackoff, 
+                                                 Math.min(state.reconnectAttempts, 5));
+
+            log(`Scheduling reconnection attempt in ${delay/1000} seconds`);
+
+            state.reconnectTimer = setTimeout(() => {
+                state.reconnectTimer = null;
+                this.attemptReconnect();
+            }, delay);
+
+            return this;
+        },
+
+        attemptReconnect: function() {
+            if (!navigator.onLine) {
+                log('Browser still reports offline, delaying reconnection attempt');
+                this.scheduleReconnectRefresh();
+                return;
+            }
+
+            if (state.connectionTestInProgress) {
+                log('Connection test already in progress');
+                return;
+            }
+
+            state.reconnectAttempts++;
+            state.connectionTestInProgress = true;
+
+            log(`Attempting reconnect #${state.reconnectAttempts}`);
+
+            this.testBackendConnection()
+                .then(isAvailable => {
+                    state.connectionTestInProgress = false;
+
+                    if (isAvailable) {
+                        log('Backend connection confirmed');
+                        state.isOnline = true;
+                        state.reconnectAttempts = 0;
+                        state.lastNetworkError = null;
+                        this.notifyHandlers('reconnected');
+                    } else {
+                        log('Backend still unavailable');
+                        
+                        if (state.reconnectAttempts < config.maxReconnectAttempts) {
+                            this.scheduleReconnectRefresh();
+                        } else {
+                            log('Maximum reconnect attempts reached');
+                            this.notifyHandlers('maxAttemptsReached');
+                        }
+                    }
+                })
+                .catch(error => {
+                    state.connectionTestInProgress = false;
+                    log('Error during connection test:', error);
+                    
+                    if (state.reconnectAttempts < config.maxReconnectAttempts) {
+                        this.scheduleReconnectRefresh();
+                    } else {
+                        log('Maximum reconnect attempts reached');
+                        this.notifyHandlers('maxAttemptsReached');
+                    }
+                });
+        },
+
+        testBackendConnection: function() {
+            return fetch(config.connectionTestEndpoint, {
+                method: 'HEAD',
+                headers: {
+                    'Cache-Control': 'no-cache',
+                    'Pragma': 'no-cache'
+                },
+                timeout: config.connectionTestTimeout,
+                signal: AbortSignal.timeout(config.connectionTestTimeout)
+            })
+            .then(response => {
+                return response.ok;
+            })
+            .catch(error => {
+                log('Backend connection test failed:', error.message);
+                return false;
+            });
+        },
+
+        manualReconnect: function() {
+            log('Manual reconnection requested');
+
+            state.isOnline = navigator.onLine;
+            state.reconnectAttempts = 0;
+
+            this.notifyHandlers('manualReconnect');
+
+            if (state.isOnline) {
+                return this.attemptReconnect();
+            } else {
+                log('Cannot attempt manual reconnect while browser reports offline');
+                this.notifyHandlers('offlineWarning');
+                return false;
+            }
+        },
+
+        addHandler: function(event, handler) {
+            if (!state.eventHandlers[event]) {
+                state.eventHandlers[event] = {};
+            }
+
+            const handlerId = generateHandlerId();
+            state.eventHandlers[event][handlerId] = handler;
+            
+            return handlerId;
+        },
+
+        removeHandler: function(event, handlerId) {
+            if (state.eventHandlers[event] && state.eventHandlers[event][handlerId]) {
+                delete state.eventHandlers[event][handlerId];
+                return true;
+            }
+            return false;
+        },
+
+        notifyHandlers: function(event, data) {
+            if (state.eventHandlers[event]) {
+                Object.values(state.eventHandlers[event]).forEach(handler => {
+                    try {
+                        handler(data);
+                    } catch (error) {
+                        log(`Error in ${event} handler:`, error);
+                    }
+                });
+            }
+        },
+
+        setDebugMode: function(enabled) {
+            config.debug = Boolean(enabled);
+            return `Debug mode ${config.debug ? 'enabled' : 'disabled'}`;
+        },
+
+        getState: function() {
+            return {
+                isOnline: state.isOnline,
+                reconnectAttempts: state.reconnectAttempts,
+                hasReconnectTimer: Boolean(state.reconnectTimer),
+                connectionTestInProgress: state.connectionTestInProgress
+            };
+        },
+
+        dispose: function() {
+            if (state.reconnectTimer) {
+                clearTimeout(state.reconnectTimer);
+                state.reconnectTimer = null;
+            }
+
+            window.removeEventListener('online', this.handleOnlineStatus);
+            window.removeEventListener('offline', this.handleOfflineStatus);
+            
+            state.eventHandlers = {};
+            
+            log('NetworkManager disposed');
+        }
+    };
+
+    return publicAPI;
+})();
+
+window.NetworkManager = NetworkManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.networkManagerInitialized) {
+        NetworkManager.initialize();
+        window.networkManagerInitialized = true;
+    }
+});
+
+//console.log('NetworkManager initialized with methods:', Object.keys(NetworkManager));
+console.log('NetworkManager initialized');
+
diff --git a/basicswap/static/js/modules/notification-manager.js b/basicswap/static/js/modules/notification-manager.js
new file mode 100644
index 0000000..7265b15
--- /dev/null
+++ b/basicswap/static/js/modules/notification-manager.js
@@ -0,0 +1,126 @@
+const NotificationManager = (function() {
+
+  const config = {
+    showNewOffers: false,
+    showNewBids: true,
+    showBidAccepted: true
+  };
+
+  function ensureToastContainer() {
+    let container = document.getElementById('ul_updates');
+    if (!container) {
+      const floating_div = document.createElement('div');
+      floating_div.classList.add('floatright');
+      container = document.createElement('ul');
+      container.setAttribute('id', 'ul_updates');
+      floating_div.appendChild(container);
+      document.body.appendChild(floating_div);
+    }
+    return container;
+  }
+
+  const publicAPI = {
+    initialize: function(options = {}) {
+      Object.assign(config, options);
+
+      if (window.CleanupManager) {
+        window.CleanupManager.registerResource('notificationManager', this, (mgr) => {
+
+          console.log('NotificationManager disposed');
+        });
+      }
+
+      return this;
+    },
+
+    createToast: function(title, type = 'success') {
+      const messages = ensureToastContainer();
+      const message = document.createElement('li');
+      message.innerHTML = `
+        <div id="hide">
+          <div id="toast-${type}" class="flex items-center p-4 mb-4 w-full max-w-xs text-gray-500 
+            bg-white rounded-lg shadow" role="alert">
+            <div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10 
+              bg-blue-500 rounded-lg">
+              <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="18" width="18" 
+                viewBox="0 0 24 24">
+                <g fill="#ffffff">
+                  <path d="M8.5,20a1.5,1.5,0,0,1-1.061-.439L.379,12.5,2.5,10.379l6,6,13-13L23.621,
+                    5.5,9.561,19.561A1.5,1.5,0,0,1,8.5,20Z"></path>
+                </g>
+              </svg>
+            </div>
+            <div class="uppercase w-40 ml-3 text-sm font-semibold text-gray-900">${title}</div>
+            <button type="button" onclick="closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5 
+              bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-0 focus:outline-none 
+              focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8">
+              <span class="sr-only">Close</span>
+              <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" 
+                xmlns="http://www.w3.org/2000/svg">
+                <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 
+                  1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 
+                  4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" 
+                  clip-rule="evenodd"></path>
+              </svg>
+            </button>
+          </div>
+        </div>
+      `;
+      messages.appendChild(message);
+    },
+
+    handleWebSocketEvent: function(data) {
+      if (!data || !data.event) return;
+      let toastTitle;
+      let shouldShowToast = false;
+
+      switch (data.event) {
+        case 'new_offer':
+          toastTitle = `New network <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
+          shouldShowToast = config.showNewOffers;
+          break;
+        case 'new_bid':
+          toastTitle = `<a class="underline" href=/bid/${data.bid_id}>New bid</a> on 
+            <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
+          shouldShowToast = config.showNewBids;
+          break;
+        case 'bid_accepted':
+          toastTitle = `<a class="underline" href=/bid/${data.bid_id}>Bid</a> accepted`;
+          shouldShowToast = config.showBidAccepted;
+          break;
+      }
+
+      if (toastTitle && shouldShowToast) {
+        this.createToast(toastTitle);
+      }
+    },
+
+    updateConfig: function(newConfig) {
+      Object.assign(config, newConfig);
+      return this;
+    }
+  };
+
+  window.closeAlert = function(event) {
+    let element = event.target;
+    while (element.nodeName !== "BUTTON") {
+      element = element.parentNode;
+    }
+    element.parentNode.parentNode.removeChild(element.parentNode);
+  };
+
+  return publicAPI;
+})();
+
+window.NotificationManager = NotificationManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+
+  if (!window.notificationManagerInitialized) {
+    window.NotificationManager.initialize(window.notificationConfig || {});
+    window.notificationManagerInitialized = true;
+  }
+});
+
+//console.log('NotificationManager initialized with methods:', Object.keys(NotificationManager));
+console.log('NotificationManager initialized');
diff --git a/basicswap/static/js/modules/summary-manager.js b/basicswap/static/js/modules/summary-manager.js
new file mode 100644
index 0000000..d4f42b4
--- /dev/null
+++ b/basicswap/static/js/modules/summary-manager.js
@@ -0,0 +1,338 @@
+const SummaryManager = (function() {
+  const config = {
+    refreshInterval: window.config?.cacheDuration || 30000,
+    summaryEndpoint: '/json',
+    retryDelay: 5000,
+    maxRetries: 3,
+    requestTimeout: 15000
+  };
+
+  let refreshTimer = null;
+  let webSocket = null;
+  let fetchRetryCount = 0;
+  let lastSuccessfulData = null;
+
+  function updateElement(elementId, value) {
+    const element = document.getElementById(elementId);
+    if (!element) return false;
+    
+    const safeValue = (value !== undefined && value !== null) 
+      ? value 
+      : (element.dataset.lastValue || 0);
+
+    element.dataset.lastValue = safeValue;
+
+    if (elementId === 'sent-bids-counter' || elementId === 'recv-bids-counter') {
+      const svg = element.querySelector('svg');
+      element.textContent = safeValue;
+      if (svg) {
+        element.insertBefore(svg, element.firstChild);
+      }
+    } else {
+      element.textContent = safeValue;
+    }
+
+    if (['offers-counter', 'bid-requests-counter', 'sent-bids-counter', 
+         'recv-bids-counter', 'swaps-counter', 'network-offers-counter', 
+         'watched-outputs-counter'].includes(elementId)) {
+      element.classList.remove('bg-blue-500', 'bg-gray-400');
+      element.classList.add(safeValue > 0 ? 'bg-blue-500' : 'bg-gray-400');
+    }
+
+    if (elementId === 'swaps-counter') {
+      const swapContainer = document.getElementById('swapContainer');
+      if (swapContainer) {
+        const isSwapping = safeValue > 0;
+        if (isSwapping) {
+          swapContainer.innerHTML = document.querySelector('#swap-in-progress-green-template').innerHTML || '';
+          swapContainer.style.animation = 'spin 2s linear infinite';
+        } else {
+          swapContainer.innerHTML = document.querySelector('#swap-in-progress-template').innerHTML || '';
+          swapContainer.style.animation = 'none';
+        }
+      }
+    }
+    return true;
+  }
+
+  function updateUIFromData(data) {
+    if (!data) return;
+    
+    updateElement('network-offers-counter', data.num_network_offers);
+    updateElement('offers-counter', data.num_sent_active_offers);
+    updateElement('sent-bids-counter', data.num_sent_active_bids);
+    updateElement('recv-bids-counter', data.num_recv_active_bids);
+    updateElement('bid-requests-counter', data.num_available_bids);
+    updateElement('swaps-counter', data.num_swapping);
+    updateElement('watched-outputs-counter', data.num_watched_outputs);
+    
+    const shutdownButtons = document.querySelectorAll('.shutdown-button');
+    shutdownButtons.forEach(button => {
+      button.setAttribute('data-active-swaps', data.num_swapping);
+      if (data.num_swapping > 0) {
+        button.classList.add('shutdown-disabled');
+        button.setAttribute('data-disabled', 'true');
+        button.setAttribute('title', 'Caution: Swaps in progress');
+      } else {
+        button.classList.remove('shutdown-disabled');
+        button.removeAttribute('data-disabled');
+        button.removeAttribute('title');
+      }
+    });
+  }
+
+  function cacheSummaryData(data) {
+    if (!data) return;
+    
+    localStorage.setItem('summary_data_cache', JSON.stringify({
+      timestamp: Date.now(),
+      data: data
+    }));
+  }
+
+  function getCachedSummaryData() {
+    let cachedData = null;
+    
+    cachedData = localStorage.getItem('summary_data_cache');
+    if (!cachedData) return null;
+    
+    const parsedCache = JSON.parse(cachedData);
+    const maxAge = 24 * 60 * 60 * 1000;
+    
+    if (Date.now() - parsedCache.timestamp < maxAge) {
+      return parsedCache.data;
+    }
+    
+    return null;
+  }
+
+  function fetchSummaryDataWithTimeout() {
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), config.requestTimeout);
+    
+    return fetch(config.summaryEndpoint, {
+      signal: controller.signal,
+      headers: {
+        'Accept': 'application/json',
+        'Cache-Control': 'no-cache',
+        'Pragma': 'no-cache'
+      }
+    })
+    .then(response => {
+      clearTimeout(timeoutId);
+      
+      if (!response.ok) {
+        throw new Error(`HTTP error! Status: ${response.status}`);
+      }
+      
+      return response.json();
+    })
+    .catch(error => {
+      clearTimeout(timeoutId);
+      throw error;
+    });
+  }
+
+  function setupWebSocket() {
+    if (webSocket) {
+      webSocket.close();
+    }
+
+    const wsPort = window.config?.wsPort || 
+                   (typeof determineWebSocketPort === 'function' ? determineWebSocketPort() : '11700');
+                   
+    const wsUrl = "ws://" + window.location.hostname + ":" + wsPort;
+    webSocket = new WebSocket(wsUrl);
+    
+    webSocket.onopen = () => {
+      publicAPI.fetchSummaryData()
+        .then(() => {})
+        .catch(() => {});
+    };
+    
+    webSocket.onmessage = (event) => {
+      let data;
+      
+      try {
+        data = JSON.parse(event.data);
+      } catch (error) {
+        if (window.logger && window.logger.error) {
+          window.logger.error('WebSocket message processing error: ' + error.message);
+        }
+        return;
+      }
+      
+      if (data.event) {
+        publicAPI.fetchSummaryData()
+          .then(() => {})
+          .catch(() => {});
+        
+        if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
+          window.NotificationManager.handleWebSocketEvent(data);
+        }
+      }
+    };
+    
+    webSocket.onclose = () => {
+      setTimeout(setupWebSocket, 5000);
+    };
+  }
+
+  function ensureSwapTemplates() {
+    if (!document.getElementById('swap-in-progress-template')) {
+      const template = document.createElement('template');
+      template.id = 'swap-in-progress-template';
+      template.innerHTML = document.querySelector('[id^="swapContainer"]')?.innerHTML || '';
+      document.body.appendChild(template);
+    }
+    
+    if (!document.getElementById('swap-in-progress-green-template') && 
+        document.querySelector('[id^="swapContainer"]')?.innerHTML) {
+      const greenTemplate = document.createElement('template');
+      greenTemplate.id = 'swap-in-progress-green-template';
+      greenTemplate.innerHTML = document.querySelector('[id^="swapContainer"]')?.innerHTML || '';
+      document.body.appendChild(greenTemplate);
+    }
+  }
+
+  function startRefreshTimer() {
+    stopRefreshTimer();
+
+    publicAPI.fetchSummaryData()
+      .then(() => {})
+      .catch(() => {});
+
+    refreshTimer = setInterval(() => {
+      publicAPI.fetchSummaryData()
+        .then(() => {})
+        .catch(() => {});
+    }, config.refreshInterval);
+  }
+
+  function stopRefreshTimer() {
+    if (refreshTimer) {
+      clearInterval(refreshTimer);
+      refreshTimer = null;
+    }
+  }
+
+  const publicAPI = {
+    initialize: function(options = {}) {
+      Object.assign(config, options);
+
+      ensureSwapTemplates();
+
+      const cachedData = getCachedSummaryData();
+      if (cachedData) {
+        updateUIFromData(cachedData);
+      }
+
+      if (window.WebSocketManager && typeof window.WebSocketManager.initialize === 'function') {
+        const wsManager = window.WebSocketManager;
+        
+        if (!wsManager.isConnected()) {
+          wsManager.connect();
+        }
+
+        wsManager.addMessageHandler('message', (data) => {
+          if (data.event) {
+            this.fetchSummaryData()
+              .then(() => {})
+              .catch(() => {});
+            
+            if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
+              window.NotificationManager.handleWebSocketEvent(data);
+            }
+          }
+        });
+      } else {
+        setupWebSocket();
+      }
+
+      startRefreshTimer();
+
+      if (window.CleanupManager) {
+        window.CleanupManager.registerResource('summaryManager', this, (mgr) => mgr.dispose());
+      }
+
+      return this;
+    },
+
+    fetchSummaryData: function() {
+      return fetchSummaryDataWithTimeout()
+        .then(data => {
+          lastSuccessfulData = data;
+          cacheSummaryData(data);
+          fetchRetryCount = 0;
+
+          updateUIFromData(data);
+
+          return data;
+        })
+        .catch(error => {
+          if (window.logger && window.logger.error) {
+            window.logger.error('Summary data fetch error: ' + error.message);
+          }
+
+          if (fetchRetryCount < config.maxRetries) {
+            fetchRetryCount++;
+
+            if (window.logger && window.logger.warn) {
+              window.logger.warn(`Retrying summary data fetch (${fetchRetryCount}/${config.maxRetries}) in ${config.retryDelay/1000}s`);
+            }
+
+            return new Promise(resolve => {
+              setTimeout(() => {
+                resolve(this.fetchSummaryData());
+              }, config.retryDelay);
+            });
+          } else {
+            const cachedData = lastSuccessfulData || getCachedSummaryData();
+
+            if (cachedData) {
+              if (window.logger && window.logger.warn) {
+                window.logger.warn('Using cached summary data after fetch failures');
+              }
+              updateUIFromData(cachedData);
+            }
+
+            fetchRetryCount = 0;
+
+            throw error;
+          }
+        });
+    },
+    
+    startRefreshTimer: function() {
+      startRefreshTimer();
+    },
+
+    stopRefreshTimer: function() {
+      stopRefreshTimer();
+    },
+
+    dispose: function() {
+      stopRefreshTimer();
+
+      if (webSocket && webSocket.readyState === WebSocket.OPEN) {
+        webSocket.close();
+      }
+
+      webSocket = null;
+    }
+  };
+
+  return publicAPI;
+})();
+
+window.SummaryManager = SummaryManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+  if (!window.summaryManagerInitialized) {
+    window.SummaryManager = SummaryManager.initialize();
+    window.summaryManagerInitialized = true;
+  }
+});
+
+//console.log('SummaryManager initialized with methods:', Object.keys(SummaryManager));
+console.log('SummaryManager initialized');
diff --git a/basicswap/static/js/modules/tooltips-manager.js b/basicswap/static/js/modules/tooltips-manager.js
new file mode 100644
index 0000000..cb8018f
--- /dev/null
+++ b/basicswap/static/js/modules/tooltips-manager.js
@@ -0,0 +1,588 @@
+const TooltipManager = (function() {
+    let instance = null;
+
+    class TooltipManagerImpl {
+        constructor() {
+
+            if (instance) {
+                return instance;
+            }
+
+            this.activeTooltips = new WeakMap();
+            this.tooltipIdCounter = 0;
+            this.pendingAnimationFrames = new Set();
+            this.tooltipElementsMap = new Map();
+            this.maxTooltips = 300;
+            this.cleanupThreshold = 1.3;
+            this.disconnectedCheckInterval = null;
+
+            this.setupStyles();
+            this.setupCleanupEvents();
+            this.initializeMutationObserver();
+            this.startDisconnectedElementsCheck();
+
+            instance = this;
+        }
+
+        create(element, content, options = {}) {
+            if (!element) return null;
+            
+            this.destroy(element);
+
+            if (this.tooltipElementsMap.size > this.maxTooltips * this.cleanupThreshold) {
+                const oldestEntries = Array.from(this.tooltipElementsMap.entries())
+                    .sort((a, b) => a[1].timestamp - b[1].timestamp)
+                    .slice(0, 20);
+                
+                oldestEntries.forEach(([el]) => {
+                    this.destroy(el);
+                });
+            }
+
+            const originalContent = content;
+
+            const rafId = requestAnimationFrame(() => {
+                this.pendingAnimationFrames.delete(rafId);
+
+                if (!document.body.contains(element)) return;
+
+                const rect = element.getBoundingClientRect();
+                if (rect.width > 0 && rect.height > 0) {
+                    this.createTooltip(element, originalContent, options, rect);
+                } else {
+                    let retryCount = 0;
+                    const retryCreate = () => {
+                        const newRect = element.getBoundingClientRect();
+                        if ((newRect.width > 0 && newRect.height > 0) || retryCount >= 3) {
+                            if (newRect.width > 0 && newRect.height > 0) {
+                                this.createTooltip(element, originalContent, options, newRect);
+                            }
+                        } else {
+                            retryCount++;
+                            const newRafId = requestAnimationFrame(retryCreate);
+                            this.pendingAnimationFrames.add(newRafId);
+                        }
+                    };
+                                        const initialRetryId = requestAnimationFrame(retryCreate);
+                    this.pendingAnimationFrames.add(initialRetryId);
+                }
+            });
+
+            this.pendingAnimationFrames.add(rafId);
+            return null;
+        }
+
+        createTooltip(element, content, options, rect) {
+            const targetId = element.getAttribute('data-tooltip-target');
+            let bgClass = 'bg-gray-400';
+            let arrowColor = 'rgb(156 163 175)';
+
+            if (targetId?.includes('tooltip-offer-') && window.jsonData) {
+                try {
+                    const offerId = targetId.split('tooltip-offer-')[1];
+                    let actualOfferId = offerId;
+
+                    if (offerId.includes('_')) {
+                        [actualOfferId] = offerId.split('_');
+                    }
+
+                    let offer = null;
+                    if (Array.isArray(window.jsonData)) {
+                        for (let i = 0; i < window.jsonData.length; i++) {
+                            const o = window.jsonData[i];
+                            if (o && (o.unique_id === offerId || o.offer_id === actualOfferId)) {
+                                offer = o;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (offer) {
+                        if (offer.is_revoked) {
+                            bgClass = 'bg-red-500';
+                            arrowColor = 'rgb(239 68 68)';
+                        } else if (offer.is_own_offer) {
+                            bgClass = 'bg-gray-300';
+                            arrowColor = 'rgb(209 213 219)';
+                        } else {
+                            bgClass = 'bg-green-700';
+                            arrowColor = 'rgb(21 128 61)';
+                        }
+                    }
+                } catch (e) {
+                    console.warn('Error finding offer for tooltip:', e);
+                }
+            }
+
+            const tooltipId = `tooltip-${++this.tooltipIdCounter}`;
+
+            try {
+                if (typeof tippy !== 'function') {
+                    console.error('Tippy.js is not loaded. Cannot create tooltip.');
+                    return null;
+                }
+
+                const instance = tippy(element, {
+                    content: content,
+                    allowHTML: true,
+                    placement: options.placement || 'top',
+                    appendTo: document.body,
+                    animation: false,
+                    duration: 0,
+                    delay: 0,
+                    interactive: true,
+                    arrow: false,
+                    theme: '',
+                    moveTransition: 'none',
+                    offset: [0, 10],
+                    onShow(instance) {
+                        if (!document.body.contains(element)) {
+                            return false;
+                        }
+                        return true;
+                    },
+                    onMount(instance) {
+                        if (instance.popper && instance.popper.firstElementChild) {
+                            instance.popper.firstElementChild.classList.add(bgClass);
+                            instance.popper.setAttribute('data-for-tooltip-id', tooltipId);
+                        }
+                        const arrow = instance.popper.querySelector('.tippy-arrow');
+                        if (arrow) {
+                            arrow.style.setProperty('color', arrowColor, 'important');
+                        }
+                    },
+                    popperOptions: {
+                        strategy: 'fixed',
+                        modifiers: [
+                            {
+                                name: 'preventOverflow',
+                                options: {
+                                    boundary: 'viewport',
+                                    padding: 10
+                                }
+                            },
+                            {
+                                name: 'flip',
+                                options: {
+                                    padding: 10,
+                                    fallbackPlacements: ['top', 'bottom', 'right', 'left']
+                                }
+                            }
+                        ]
+                    }
+                });
+
+                element.setAttribute('data-tooltip-trigger-id', tooltipId);
+                this.activeTooltips.set(element, instance);
+
+                this.tooltipElementsMap.set(element, {
+                    timestamp: Date.now(),
+                    id: tooltipId
+                });
+
+                return instance;
+            } catch (e) {
+                console.error('Error creating tooltip:', e);
+                return null;
+            }
+        }
+
+        destroy(element) {
+            if (!element) return;
+
+            const id = element.getAttribute('data-tooltip-trigger-id');
+            if (!id) return;
+
+            const instance = this.activeTooltips.get(element);
+            if (instance?.[0]) {
+                try {
+                    instance[0].destroy();
+                } catch (e) {
+                    console.warn('Error destroying tooltip:', e);
+                    
+                    const tippyRoot = document.querySelector(`[data-for-tooltip-id="${id}"]`);
+                    if (tippyRoot && tippyRoot.parentNode) {
+                        tippyRoot.parentNode.removeChild(tippyRoot);
+                    }
+                }
+            }
+
+            this.activeTooltips.delete(element);
+            this.tooltipElementsMap.delete(element);
+            
+            element.removeAttribute('data-tooltip-trigger-id');
+        }
+
+        cleanup() {
+            this.pendingAnimationFrames.forEach(id => {
+                cancelAnimationFrame(id);
+            });
+            this.pendingAnimationFrames.clear();
+
+            const elements = document.querySelectorAll('[data-tooltip-trigger-id]');
+            const batchSize = 20;
+
+            const processElementsBatch = (startIdx) => {
+                const endIdx = Math.min(startIdx + batchSize, elements.length);
+
+                for (let i = startIdx; i < endIdx; i++) {
+                    this.destroy(elements[i]);
+                }
+
+                if (endIdx < elements.length) {
+                    const rafId = requestAnimationFrame(() => {
+                        this.pendingAnimationFrames.delete(rafId);
+                        processElementsBatch(endIdx);
+                    });
+                    this.pendingAnimationFrames.add(rafId);
+                } else {
+                    this.cleanupOrphanedTippyElements();
+                }
+            };
+
+            if (elements.length > 0) {
+                processElementsBatch(0);
+            } else {
+                this.cleanupOrphanedTippyElements();
+            }
+
+            this.tooltipElementsMap.clear();
+        }
+
+        cleanupOrphanedTippyElements() {
+            const tippyElements = document.querySelectorAll('[data-tippy-root]');
+            tippyElements.forEach(element => {
+                if (element.parentNode) {
+                    element.parentNode.removeChild(element);
+                }
+            });
+        }
+
+        setupStyles() {
+            if (document.getElementById('tooltip-styles')) return;
+
+            document.head.insertAdjacentHTML('beforeend', `
+                <style id="tooltip-styles">
+                    [data-tippy-root] {
+                        position: fixed !important;
+                        z-index: 9999 !important;
+                        pointer-events: none !important;
+                    }
+
+                    .tippy-box {
+                        font-size: 0.875rem;
+                        line-height: 1.25rem;
+                        font-weight: 500;
+                        border-radius: 0.5rem;
+                        color: white;
+                        position: relative !important;
+                        pointer-events: auto !important;
+                    }
+
+                    .tippy-content {
+                        padding: 0.5rem 0.75rem !important;
+                    }
+
+                    .tippy-box .bg-gray-400 {
+                        background-color: rgb(156 163 175);
+                        padding: 0.5rem 0.75rem;
+                    }
+                    .tippy-box:has(.bg-gray-400) .tippy-arrow {
+                        color: rgb(156 163 175);
+                    }
+
+                    .tippy-box .bg-red-500 {
+                        background-color: rgb(239 68 68);
+                        padding: 0.5rem 0.75rem;
+                    }
+                    .tippy-box:has(.bg-red-500) .tippy-arrow {
+                        color: rgb(239 68 68);
+                    }
+
+                    .tippy-box .bg-gray-300 {
+                        background-color: rgb(209 213 219);
+                        padding: 0.5rem 0.75rem;
+                    }
+                    .tippy-box:has(.bg-gray-300) .tippy-arrow {
+                        color: rgb(209 213 219);
+                    }
+
+                    .tippy-box .bg-green-700 {
+                        background-color: rgb(21 128 61);
+                        padding: 0.5rem 0.75rem;
+                    }
+                    .tippy-box:has(.bg-green-700) .tippy-arrow {
+                        color: rgb(21 128 61);
+                    }
+
+                    .tippy-box[data-placement^='top'] > .tippy-arrow::before {
+                        border-top-color: currentColor;
+                    }
+
+                    .tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
+                        border-bottom-color: currentColor;
+                    }
+
+                    .tippy-box[data-placement^='left'] > .tippy-arrow::before {
+                        border-left-color: currentColor;
+                    }
+
+                    .tippy-box[data-placement^='right'] > .tippy-arrow::before {
+                        border-right-color: currentColor;
+                    }
+
+                    .tippy-box[data-placement^='top'] > .tippy-arrow {
+                        bottom: 0;
+                    }
+
+                    .tippy-box[data-placement^='bottom'] > .tippy-arrow {
+                        top: 0;
+                    }
+
+                    .tippy-box[data-placement^='left'] > .tippy-arrow {
+                        right: 0;
+                    }
+
+                    .tippy-box[data-placement^='right'] > .tippy-arrow {
+                        left: 0;
+                    }
+                </style>
+            `);
+        }
+
+        setupCleanupEvents() {
+            this.boundCleanup = this.cleanup.bind(this);
+            this.handleVisibilityChange = () => {
+                if (document.hidden) {
+                    this.cleanup();
+                    
+                    if (window.MemoryManager) {
+                        window.MemoryManager.forceCleanup();
+                    }
+                }
+            };
+
+            window.addEventListener('beforeunload', this.boundCleanup);
+            window.addEventListener('unload', this.boundCleanup);
+            document.addEventListener('visibilitychange', this.handleVisibilityChange);
+            
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('tooltipManager', this, (tm) => tm.dispose());
+            }
+
+            this.cleanupInterval = setInterval(() => {
+                this.performPeriodicCleanup();
+            }, 120000);
+        }
+
+        startDisconnectedElementsCheck() {
+
+            if (this.disconnectedCheckInterval) {
+                clearInterval(this.disconnectedCheckInterval);
+            }
+
+            this.disconnectedCheckInterval = setInterval(() => {
+                this.checkForDisconnectedElements();
+            }, 60000);
+        }
+
+        checkForDisconnectedElements() {
+            if (this.tooltipElementsMap.size === 0) return;
+
+            const elementsToCheck = Array.from(this.tooltipElementsMap.keys());
+            let removedCount = 0;
+
+            elementsToCheck.forEach(element => {
+
+                if (!document.body.contains(element)) {
+                    this.destroy(element);
+                    removedCount++;
+                }
+            });
+
+            if (removedCount > 0) {
+                this.cleanupOrphanedTippyElements();
+            }
+        }
+
+        performPeriodicCleanup() {
+            this.cleanupOrphanedTippyElements();
+            this.checkForDisconnectedElements();
+
+            if (this.tooltipElementsMap.size > this.maxTooltips * this.cleanupThreshold) {
+                const sortedTooltips = Array.from(this.tooltipElementsMap.entries())
+                    .sort((a, b) => a[1].timestamp - b[1].timestamp);
+
+                const tooltipsToRemove = sortedTooltips.slice(0, sortedTooltips.length - this.maxTooltips);
+                tooltipsToRemove.forEach(([element]) => {
+                    this.destroy(element);
+                });
+            }
+        }
+
+        removeCleanupEvents() {
+            window.removeEventListener('beforeunload', this.boundCleanup);
+            window.removeEventListener('unload', this.boundCleanup);
+            document.removeEventListener('visibilitychange', this.handleVisibilityChange);
+
+            if (this.cleanupInterval) {
+                clearInterval(this.cleanupInterval);
+                this.cleanupInterval = null;
+            }
+
+            if (this.disconnectedCheckInterval) {
+                clearInterval(this.disconnectedCheckInterval);
+                this.disconnectedCheckInterval = null;
+            }
+        }
+
+        initializeMutationObserver() {
+            if (this.mutationObserver) return;
+
+            this.mutationObserver = new MutationObserver(mutations => {
+                let needsCleanup = false;
+
+                mutations.forEach(mutation => {
+                    if (mutation.removedNodes.length) {
+                        Array.from(mutation.removedNodes).forEach(node => {
+                            if (node.nodeType === 1) {
+
+                                if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
+                                    this.destroy(node);
+                                    needsCleanup = true;
+                                }
+
+                                if (node.querySelectorAll) {
+                                    const tooltipTriggers = node.querySelectorAll('[data-tooltip-trigger-id]');
+                                    if (tooltipTriggers.length > 0) {
+                                        tooltipTriggers.forEach(el => {
+                                            this.destroy(el);
+                                        });
+                                        needsCleanup = true;
+                                    }
+                                }
+                            }
+                        });
+                    }
+                });
+
+                if (needsCleanup) {
+                    this.cleanupOrphanedTippyElements();
+                }
+            });
+
+            this.mutationObserver.observe(document.body, { 
+                childList: true,
+                subtree: true
+            });
+        }
+
+        initializeTooltips(selector = '[data-tooltip-target]') {
+            document.querySelectorAll(selector).forEach(element => {
+                const targetId = element.getAttribute('data-tooltip-target');
+                const tooltipContent = document.getElementById(targetId);
+
+                if (tooltipContent) {
+                    this.create(element, tooltipContent.innerHTML, {
+                        placement: element.getAttribute('data-tooltip-placement') || 'top'
+                    });
+                }
+            });
+        }
+
+        dispose() {
+            this.cleanup();
+
+            this.pendingAnimationFrames.forEach(id => {
+                cancelAnimationFrame(id);
+            });
+            this.pendingAnimationFrames.clear();
+            
+            if (this.mutationObserver) {
+                this.mutationObserver.disconnect();
+                this.mutationObserver = null;
+            }
+
+            this.removeCleanupEvents();
+
+            const styleElement = document.getElementById('tooltip-styles');
+            if (styleElement && styleElement.parentNode) {
+                styleElement.parentNode.removeChild(styleElement);
+            }
+
+            this.activeTooltips = new WeakMap();
+            this.tooltipElementsMap.clear();
+
+            instance = null;
+        }
+
+        initialize(options = {}) {
+
+            if (options.maxTooltips) {
+                this.maxTooltips = options.maxTooltips;
+            }
+
+            console.log('TooltipManager initialized');
+            return this;
+        }
+    }
+
+    return {
+        initialize: function(options = {}) {
+            if (!instance) {
+                const manager = new TooltipManagerImpl();
+                manager.initialize(options);
+            }
+            return instance;
+        },
+
+        getInstance: function() {
+            if (!instance) {
+                const manager = new TooltipManagerImpl();
+            }
+            return instance;
+        },
+
+        create: function(...args) {
+            const manager = this.getInstance();
+            return manager.create(...args);
+        },
+
+        destroy: function(...args) {
+            const manager = this.getInstance();
+            return manager.destroy(...args);
+        },
+
+        cleanup: function(...args) {
+            const manager = this.getInstance();
+            return manager.cleanup(...args);
+        },
+
+        initializeTooltips: function(...args) {
+            const manager = this.getInstance();
+            return manager.initializeTooltips(...args);
+        },
+
+        dispose: function(...args) {
+            const manager = this.getInstance();
+            return manager.dispose(...args);
+        }
+    };
+})();
+
+window.TooltipManager = TooltipManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+    if (!window.tooltipManagerInitialized) {
+        TooltipManager.initialize();
+        TooltipManager.initializeTooltips();
+        window.tooltipManagerInitialized = true;
+    }
+});
+
+if (typeof module !== 'undefined' && module.exports) {
+    module.exports = TooltipManager;
+}
+
+//console.log('TooltipManager initialized with methods:', Object.keys(TooltipManager));
+console.log('TooltipManager initialized');
diff --git a/basicswap/static/js/modules/wallet-manager.js b/basicswap/static/js/modules/wallet-manager.js
new file mode 100644
index 0000000..ac81e0d
--- /dev/null
+++ b/basicswap/static/js/modules/wallet-manager.js
@@ -0,0 +1,655 @@
+const WalletManager = (function() {
+
+  const config = {
+    maxRetries: 5,
+    baseDelay: 500,
+    cacheExpiration: 5 * 60 * 1000,
+    priceUpdateInterval: 5 * 60 * 1000,
+    apiTimeout: 30000,
+    debounceDelay: 300,
+    cacheMinInterval: 60 * 1000,
+    defaultTTL: 300,
+    priceSource: {
+      primary: 'coingecko.com',
+      fallback: 'cryptocompare.com',
+      enabledSources: ['coingecko.com', 'cryptocompare.com']
+    }
+  };
+
+  const stateKeys = {
+    lastUpdate: 'last-update-time',
+    previousTotal: 'previous-total-usd',
+    currentTotal: 'current-total-usd',
+    balancesVisible: 'balancesVisible'
+  };
+
+  const coinData = {
+    symbols: {
+      'Bitcoin': 'BTC',
+      'Particl': 'PART',
+      'Monero': 'XMR',
+      'Wownero': 'WOW',
+      'Litecoin': 'LTC',
+      'Dogecoin': 'DOGE',
+      'Firo': 'FIRO',
+      'Dash': 'DASH',
+      'PIVX': 'PIVX',
+      'Decred': 'DCR',
+      'Bitcoin Cash': 'BCH'
+    },
+    
+    coingeckoIds: {
+      'BTC': 'btc',
+      'PART': 'part',
+      'XMR': 'xmr',
+      'WOW': 'wownero',
+      'LTC': 'ltc',
+      'DOGE': 'doge',
+      'FIRO': 'firo',
+      'DASH': 'dash',
+      'PIVX': 'pivx',
+      'DCR': 'dcr',
+      'BCH': 'bch'
+    },
+    
+    shortNames: {
+      'Bitcoin': 'BTC',
+      'Particl': 'PART',
+      'Monero': 'XMR',
+      'Wownero': 'WOW',
+      'Litecoin': 'LTC',
+      'Litecoin MWEB': 'LTC MWEB',
+      'Firo': 'FIRO',
+      'Dash': 'DASH',
+      'PIVX': 'PIVX',
+      'Decred': 'DCR',
+      'Bitcoin Cash': 'BCH',
+      'Dogecoin': 'DOGE'
+    }
+  };
+
+  const state = {
+    lastFetchTime: 0,
+    toggleInProgress: false,
+    toggleDebounceTimer: null,
+    priceUpdateInterval: null,
+    lastUpdateTime: 0,
+    isWalletsPage: false,
+    initialized: false,
+    cacheKey: 'rates_crypto_prices'
+  };
+
+  function getShortName(fullName) {
+    return coinData.shortNames[fullName] || fullName;
+  }
+
+  async function fetchPrices(forceUpdate = false) {
+    const now = Date.now();
+    const timeSinceLastFetch = now - state.lastFetchTime;
+
+    if (!forceUpdate && timeSinceLastFetch < config.cacheMinInterval) {
+      const cachedData = CacheManager.get(state.cacheKey);
+      if (cachedData) {
+        return cachedData.value;
+      }
+    }
+
+    let lastError = null;
+    for (let attempt = 0; attempt < config.maxRetries; attempt++) {
+      try {
+        const processedData = {};
+        const currentSource = config.priceSource.primary;
+        
+        const shouldIncludeWow = currentSource === 'coingecko.com';
+        
+        const coinsToFetch = Object.values(coinData.symbols)
+          .filter(symbol => shouldIncludeWow || symbol !== 'WOW')
+          .map(symbol => coinData.coingeckoIds[symbol] || symbol.toLowerCase())
+          .join(',');
+
+        const mainResponse = await fetch("/json/coinprices", {
+          method: "POST",
+          headers: {'Content-Type': 'application/json'},
+          body: JSON.stringify({
+            coins: coinsToFetch,
+            source: currentSource,
+            ttl: config.defaultTTL
+          })
+        });
+
+        if (!mainResponse.ok) {
+          throw new Error(`HTTP error: ${mainResponse.status}`);
+        }
+
+        const mainData = await mainResponse.json();
+
+        if (mainData && mainData.rates) {
+          Object.entries(mainData.rates).forEach(([coinId, price]) => {
+            const symbol = Object.entries(coinData.coingeckoIds).find(([sym, id]) => id.toLowerCase() === coinId.toLowerCase())?.[0];
+            if (symbol) {
+              const coinKey = Object.keys(coinData.symbols).find(key => coinData.symbols[key] === symbol);
+              if (coinKey) {
+                processedData[coinKey.toLowerCase().replace(' ', '-')] = {
+                  usd: price,
+                  btc: symbol === 'BTC' ? 1 : price / (mainData.rates.btc || 1)
+                };
+              }
+            }
+          });
+        }
+
+        if (!shouldIncludeWow && !processedData['wownero']) {
+          try {
+            const wowResponse = await fetch("/json/coinprices", {
+              method: "POST",
+              headers: {'Content-Type': 'application/json'},
+              body: JSON.stringify({
+                coins: "wownero",
+                source: "coingecko.com",
+                ttl: config.defaultTTL
+              })
+            });
+
+            if (wowResponse.ok) {
+              const wowData = await wowResponse.json();
+              if (wowData && wowData.rates && wowData.rates.wownero) {
+                processedData['wownero'] = {
+                  usd: wowData.rates.wownero,
+                  btc: processedData.bitcoin ? wowData.rates.wownero / processedData.bitcoin.usd : 0
+                };
+              }
+            }
+          } catch (wowError) {
+            console.error('Error fetching WOW price:', wowError);
+          }
+        }
+
+        CacheManager.set(state.cacheKey, processedData, config.cacheExpiration);
+        state.lastFetchTime = now;
+        return processedData;
+      } catch (error) {
+        lastError = error;
+        console.error(`Price fetch attempt ${attempt + 1} failed:`, error);
+
+        if (attempt === config.maxRetries - 1 && 
+            config.priceSource.fallback && 
+            config.priceSource.fallback !== config.priceSource.primary) {
+          const temp = config.priceSource.primary;
+          config.priceSource.primary = config.priceSource.fallback;
+          config.priceSource.fallback = temp;
+
+          console.warn(`Switching to fallback source: ${config.priceSource.primary}`);
+          attempt = -1;
+          continue;
+        }
+
+        if (attempt < config.maxRetries - 1) {
+          const delay = Math.min(config.baseDelay * Math.pow(2, attempt), 10000);
+          await new Promise(resolve => setTimeout(resolve, delay));
+        }
+      }
+    }
+
+    const cachedData = CacheManager.get(state.cacheKey);
+    if (cachedData) {
+      console.warn('Using cached data after fetch failures');
+      return cachedData.value;
+    }
+
+    throw lastError || new Error('Failed to fetch prices');
+  }
+
+  // UI Management functions
+  function storeOriginalValues() {
+    document.querySelectorAll('.coinname-value').forEach(el => {
+      const coinName = el.getAttribute('data-coinname');
+      const value = el.textContent?.trim() || '';
+
+      if (coinName) {
+        const amount = value ? parseFloat(value.replace(/[^0-9.-]+/g, '')) : 0;
+        const coinId = coinData.symbols[coinName];
+        const shortName = getShortName(coinName);
+
+        if (coinId) {
+          if (coinName === 'Particl') {
+            const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
+            const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
+            const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
+            localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
+          } else if (coinName === 'Litecoin') {
+            const isMWEB = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('MWEB');
+            const balanceType = isMWEB ? 'mweb' : 'public';
+            localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
+          } else {
+            localStorage.setItem(`${coinId.toLowerCase()}-amount`, amount.toString());
+          }
+
+          el.setAttribute('data-original-value', `${amount} ${shortName}`);
+        }
+      }
+    });
+
+    document.querySelectorAll('.usd-value').forEach(el => {
+      const text = el.textContent?.trim() || '';
+      if (text === 'Loading...') {
+        el.textContent = '';
+      }
+    });
+  }
+
+  async function updatePrices(forceUpdate = false) {
+    try {
+      const prices = await fetchPrices(forceUpdate);
+      let newTotal = 0;
+
+      const currentTime = Date.now();
+      localStorage.setItem(stateKeys.lastUpdate, currentTime.toString());
+      state.lastUpdateTime = currentTime;
+
+      if (prices) {
+        Object.entries(prices).forEach(([coinId, priceData]) => {
+          if (priceData?.usd) {
+            localStorage.setItem(`${coinId}-price`, priceData.usd.toString());
+          }
+        });
+      }
+
+      document.querySelectorAll('.coinname-value').forEach(el => {
+        const coinName = el.getAttribute('data-coinname');
+        const amountStr = el.getAttribute('data-original-value') || el.textContent?.trim() || '';
+
+        if (!coinName) return;
+
+        let amount = 0;
+        if (amountStr) {
+          const matches = amountStr.match(/([0-9]*[.])?[0-9]+/);
+          if (matches && matches.length > 0) {
+            amount = parseFloat(matches[0]);
+          }
+        }
+
+        const coinId = coinName.toLowerCase().replace(' ', '-');
+        
+        if (!prices[coinId]) {
+          return;
+        }
+
+        const price = prices[coinId]?.usd || parseFloat(localStorage.getItem(`${coinId}-price`) || '0');
+        if (!price) return;
+        
+        const usdValue = (amount * price).toFixed(2);
+
+        if (coinName === 'Particl') {
+          const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
+          const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
+          const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
+          localStorage.setItem(`particl-${balanceType}-last-value`, usdValue);
+          localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
+        } else if (coinName === 'Litecoin') {
+          const isMWEB = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('MWEB');
+          const balanceType = isMWEB ? 'mweb' : 'public';
+          localStorage.setItem(`litecoin-${balanceType}-last-value`, usdValue);
+          localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
+        } else {
+          localStorage.setItem(`${coinId}-last-value`, usdValue);
+          localStorage.setItem(`${coinId}-amount`, amount.toString());
+        }
+
+        if (amount > 0) {
+          newTotal += parseFloat(usdValue);
+        }
+
+        let usdEl = null;
+        
+        const flexContainer = el.closest('.flex');
+        if (flexContainer) {
+          const nextFlex = flexContainer.nextElementSibling;
+          if (nextFlex) {
+            const usdInNextFlex = nextFlex.querySelector('.usd-value');
+            if (usdInNextFlex) {
+              usdEl = usdInNextFlex;
+            }
+          }
+        }
+
+        if (!usdEl) {
+          const parentCell = el.closest('td');
+          if (parentCell) {
+            const usdInSameCell = parentCell.querySelector('.usd-value');
+            if (usdInSameCell) {
+              usdEl = usdInSameCell;
+            }
+          }
+        }
+
+        if (!usdEl) {
+          const sibling = el.nextElementSibling;
+          if (sibling && sibling.classList.contains('usd-value')) {
+            usdEl = sibling;
+          }
+        }
+
+        if (!usdEl) {
+          const parentElement = el.parentElement;
+          if (parentElement) {
+            const usdElNearby = parentElement.querySelector('.usd-value');
+            if (usdElNearby) {
+              usdEl = usdElNearby;
+            }
+          }
+        }
+
+        if (usdEl) {
+          usdEl.textContent = `$${usdValue}`;
+          usdEl.setAttribute('data-original-value', usdValue);
+        }
+      });
+
+      document.querySelectorAll('.usd-value').forEach(el => {
+        if (el.closest('tr')?.querySelector('td')?.textContent?.includes('Fee Estimate:')) {
+          const parentCell = el.closest('td');
+          if (!parentCell) return;
+
+          const coinValueEl = parentCell.querySelector('.coinname-value');
+          if (!coinValueEl) return;
+
+          const coinName = coinValueEl.getAttribute('data-coinname');
+          if (!coinName) return;
+
+          const amountStr = coinValueEl.textContent?.trim() || '0';
+          const amount = parseFloat(amountStr) || 0;
+
+          const coinId = coinName.toLowerCase().replace(' ', '-');
+          if (!prices[coinId]) return;
+
+          const price = prices[coinId]?.usd || parseFloat(localStorage.getItem(`${coinId}-price`) || '0');
+          if (!price) return;
+
+          const usdValue = (amount * price).toFixed(8);
+          el.textContent = `$${usdValue}`;
+          el.setAttribute('data-original-value', usdValue);
+        }
+      });
+
+      if (state.isWalletsPage) {
+        updateTotalValues(newTotal, prices?.bitcoin?.usd);
+      }
+
+      localStorage.setItem(stateKeys.previousTotal, localStorage.getItem(stateKeys.currentTotal) || '0');
+      localStorage.setItem(stateKeys.currentTotal, newTotal.toString());
+
+      return true;
+    } catch (error) {
+      console.error('Price update failed:', error);
+      return false;
+    }
+  }
+  
+  function updateTotalValues(totalUsd, btcPrice) {
+    const totalUsdEl = document.getElementById('total-usd-value');
+    if (totalUsdEl) {
+      totalUsdEl.textContent = `$${totalUsd.toFixed(2)}`;
+      totalUsdEl.setAttribute('data-original-value', totalUsd.toString());
+      localStorage.setItem('total-usd', totalUsd.toString());
+    }
+
+    if (btcPrice) {
+      const btcTotal = btcPrice ? totalUsd / btcPrice : 0;
+      const totalBtcEl = document.getElementById('total-btc-value');
+      if (totalBtcEl) {
+        totalBtcEl.textContent = `~ ${btcTotal.toFixed(8)} BTC`;
+        totalBtcEl.setAttribute('data-original-value', btcTotal.toString());
+      }
+    }
+  }
+
+  async function toggleBalances() {
+    if (state.toggleInProgress) return;
+
+    try {
+      state.toggleInProgress = true;
+      const balancesVisible = localStorage.getItem('balancesVisible') === 'true';
+      const newVisibility = !balancesVisible;
+
+      localStorage.setItem('balancesVisible', newVisibility.toString());
+      updateVisibility(newVisibility);
+
+      if (state.toggleDebounceTimer) {
+        clearTimeout(state.toggleDebounceTimer);
+      }
+
+      state.toggleDebounceTimer = window.setTimeout(async () => {
+        state.toggleInProgress = false;
+        if (newVisibility) {
+          await updatePrices(true);
+        }
+      }, config.debounceDelay);
+    } catch (error) {
+      console.error('Failed to toggle balances:', error);
+      state.toggleInProgress = false;
+    }
+  }
+
+  function updateVisibility(isVisible) {
+    if (isVisible) {
+      showBalances();
+    } else {
+      hideBalances();
+    }
+
+    const eyeIcon = document.querySelector("#hide-usd-amount-toggle svg");
+    if (eyeIcon) {
+      eyeIcon.innerHTML = isVisible ? 
+        '<path d="M23.444,10.239C21.905,8.062,17.708,3,12,3S2.1,8.062,.555,10.24a3.058,3.058,0,0,0,0,3.52h0C2.1,15.938,6.292,21,12,21s9.905-5.062,11.445-7.24A3.058,3.058,0,0,0,23.444,10.239ZM12,17a5,5,0,1,1,5-5A5,5,0,0,1,12,17Z"></path>' :
+        '<path d="M23.444,10.239a22.936,22.936,0,0,0-2.492-2.948l-4.021,4.021A5.026,5.026,0,0,1,17,12a5,5,0,0,1-5,5,5.026,5.026,0,0,1-.688-.069L8.055,20.188A10.286,10.286,0,0,0,12,21c5.708,0,9.905-5.062,11.445-7.24A3.058,3.058,0,0,0,23.444,10.239Z"></path><path d="M12,3C6.292,3,2.1,8.062,.555,10.24a3.058,3.058,0,0,0,0,3.52h0a21.272,21.272,0,0,0,4.784,4.9l3.124-3.124a5,5,0,0,1,7.071-7.072L8.464,15.536l10.2-10.2A11.484,11.484,0,0,0,12,3Z"></path><path data-color="color-2" d="M1,24a1,1,0,0,1-.707-1.707l22-22a1,1,0,0,1,1.414,1.414l-22,22A1,1,0,0,1,1,24Z"></path>';
+    }
+  }
+
+  function showBalances() {
+    const usdText = document.getElementById('usd-text');
+    if (usdText) {
+      usdText.style.display = 'inline';
+    }
+
+    document.querySelectorAll('.coinname-value').forEach(el => {
+      const originalValue = el.getAttribute('data-original-value');
+      if (originalValue) {
+        el.textContent = originalValue;
+      }
+    });
+
+    document.querySelectorAll('.usd-value').forEach(el => {
+      const storedValue = el.getAttribute('data-original-value');
+      if (storedValue !== null && storedValue !== undefined) {
+        if (el.closest('tr')?.querySelector('td')?.textContent?.includes('Fee Estimate:')) {
+          el.textContent = `$${parseFloat(storedValue).toFixed(8)}`;
+        } else {
+          el.textContent = `$${parseFloat(storedValue).toFixed(2)}`;
+        }
+      } else {
+        if (el.closest('tr')?.querySelector('td')?.textContent?.includes('Fee Estimate:')) {
+          el.textContent = '$0.00000000';
+        } else {
+          el.textContent = '$0.00';
+        }
+      }
+    });
+
+    if (state.isWalletsPage) {
+      ['total-usd-value', 'total-btc-value'].forEach(id => {
+        const el = document.getElementById(id);
+        const originalValue = el?.getAttribute('data-original-value');
+        if (el && originalValue) {
+          if (id === 'total-usd-value') {
+            el.textContent = `$${parseFloat(originalValue).toFixed(2)}`;
+            el.classList.add('font-extrabold');
+          } else {
+            el.textContent = `~ ${parseFloat(originalValue).toFixed(8)} BTC`;
+          }
+        }
+      });
+    }
+  }
+
+  function hideBalances() {
+    const usdText = document.getElementById('usd-text');
+    if (usdText) {
+      usdText.style.display = 'none';
+    }
+
+    document.querySelectorAll('.coinname-value').forEach(el => {
+      el.textContent = '****';
+    });
+    
+    document.querySelectorAll('.usd-value').forEach(el => {
+      el.textContent = '****';
+    });
+
+    if (state.isWalletsPage) {
+      ['total-usd-value', 'total-btc-value'].forEach(id => {
+        const el = document.getElementById(id);
+        if (el) {
+          el.textContent = '****';
+        }
+      });
+
+      const totalUsdEl = document.getElementById('total-usd-value');
+      if (totalUsdEl) {
+        totalUsdEl.classList.remove('font-extrabold');
+      }
+    }
+  }
+
+  async function loadBalanceVisibility() {
+    const balancesVisible = localStorage.getItem('balancesVisible') === 'true';
+    updateVisibility(balancesVisible);
+
+    if (balancesVisible) {
+      await updatePrices(true);
+    }
+  }
+
+  // Public API
+  const publicAPI = {
+    initialize: async function(options) {
+      if (state.initialized) {
+        console.warn('[WalletManager] Already initialized');
+        return this;
+      }
+
+      if (options) {
+        Object.assign(config, options);
+      }
+
+      state.lastUpdateTime = parseInt(localStorage.getItem(stateKeys.lastUpdate) || '0');
+      state.isWalletsPage = document.querySelector('.wallet-list') !== null || 
+        window.location.pathname.includes('/wallets');
+
+      document.querySelectorAll('.usd-value').forEach(el => {
+        const text = el.textContent?.trim() || '';
+        if (text === 'Loading...') {
+          el.textContent = '';
+        }
+      });
+
+      storeOriginalValues();
+      
+      if (localStorage.getItem('balancesVisible') === null) {
+        localStorage.setItem('balancesVisible', 'true');
+      }
+
+      const hideBalancesToggle = document.getElementById('hide-usd-amount-toggle');
+      if (hideBalancesToggle) {
+        hideBalancesToggle.addEventListener('click', toggleBalances);
+      }
+
+      await loadBalanceVisibility();
+
+      if (state.priceUpdateInterval) {
+        clearInterval(state.priceUpdateInterval);
+      }
+
+      state.priceUpdateInterval = setInterval(() => {
+        if (localStorage.getItem('balancesVisible') === 'true' && !state.toggleInProgress) {
+          updatePrices(false);
+        }
+      }, config.priceUpdateInterval);
+
+      if (window.CleanupManager) {
+        window.CleanupManager.registerResource('walletManager', this, (mgr) => mgr.dispose());
+      }
+
+      state.initialized = true;
+      console.log('WalletManager initialized');
+      
+      return this;
+    },
+
+    updatePrices: function(forceUpdate = false) {
+      return updatePrices(forceUpdate);
+    },
+
+    toggleBalances: function() {
+      return toggleBalances();
+    },
+
+    setPriceSource: function(primarySource, fallbackSource = null) {
+      if (!config.priceSource.enabledSources.includes(primarySource)) {
+        throw new Error(`Invalid primary source: ${primarySource}`);
+      }
+
+      if (fallbackSource && !config.priceSource.enabledSources.includes(fallbackSource)) {
+        throw new Error(`Invalid fallback source: ${fallbackSource}`);
+      }
+
+      config.priceSource.primary = primarySource;
+      if (fallbackSource) {
+        config.priceSource.fallback = fallbackSource;
+      }
+      
+      return this;
+    },
+
+    getConfig: function() {
+      return { ...config };
+    },
+
+    getState: function() {
+      return {
+        initialized: state.initialized,
+        lastUpdateTime: state.lastUpdateTime,
+        isWalletsPage: state.isWalletsPage,
+        balancesVisible: localStorage.getItem('balancesVisible') === 'true'
+      };
+    },
+
+    dispose: function() {
+      if (state.priceUpdateInterval) {
+        clearInterval(state.priceUpdateInterval);
+        state.priceUpdateInterval = null;
+      }
+
+      if (state.toggleDebounceTimer) {
+        clearTimeout(state.toggleDebounceTimer);
+        state.toggleDebounceTimer = null;
+      }
+
+      state.initialized = false;
+      console.log('WalletManager disposed');
+    }
+  };
+
+  return publicAPI;
+})();
+
+window.WalletManager = WalletManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+  if (!window.walletManagerInitialized) {
+    WalletManager.initialize();
+    window.walletManagerInitialized = true;
+  }
+});
+
+//console.log('WalletManager initialized with methods:', Object.keys(WalletManager));
+console.log('WalletManager initialized');
diff --git a/basicswap/static/js/modules/websocket-manager.js b/basicswap/static/js/modules/websocket-manager.js
new file mode 100644
index 0000000..1aae881
--- /dev/null
+++ b/basicswap/static/js/modules/websocket-manager.js
@@ -0,0 +1,444 @@
+const WebSocketManager = (function() {
+    let ws = null;
+
+    const config = {
+        reconnectAttempts: 0,
+        maxReconnectAttempts: 5,
+        reconnectDelay: 5000,
+        debug: false
+    };
+
+    const state = {
+        isConnecting: false,
+        isIntentionallyClosed: false,
+        lastConnectAttempt: null,
+        connectTimeout: null,
+        lastHealthCheck: null,
+        healthCheckInterval: null,
+        isPageHidden: document.hidden,
+        messageHandlers: {},
+        listeners: {},
+        reconnectTimeout: null
+    };
+
+    function log(message, ...args) {
+        if (config.debug) {
+            console.log(`[WebSocketManager] ${message}`, ...args);
+        }
+    }
+
+    function generateHandlerId() {
+        return `handler_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
+    }
+
+    function determineWebSocketPort() {
+        let wsPort;
+
+        if (window.config && window.config.wsPort) {
+            wsPort = window.config.wsPort;
+            return wsPort;
+        }
+
+        if (window.ws_port) {
+            wsPort = window.ws_port.toString();
+            return wsPort;
+        }
+        
+        if (typeof getWebSocketConfig === 'function') {
+            const wsConfig = getWebSocketConfig();
+            wsPort = (wsConfig.port || wsConfig.fallbackPort || '11700').toString();
+            return wsPort;
+        } 
+
+        wsPort = '11700';
+        return wsPort;
+    }
+
+    const publicAPI = {
+        initialize: function(options = {}) {
+            Object.assign(config, options);
+            setupPageVisibilityHandler();
+            this.connect();
+            startHealthCheck();
+
+            log('WebSocketManager initialized with options:', options);
+            
+            if (window.CleanupManager) {
+                window.CleanupManager.registerResource('webSocketManager', this, (mgr) => mgr.dispose());
+            }
+
+            return this;
+        },
+        
+        connect: function() {
+            if (state.isConnecting || state.isIntentionallyClosed) {
+                log('Connection attempt blocked - already connecting or intentionally closed');
+                return false;
+            }
+
+            if (state.reconnectTimeout) {
+                clearTimeout(state.reconnectTimeout);
+                state.reconnectTimeout = null;
+            }
+
+            cleanup();
+            state.isConnecting = true;
+            state.lastConnectAttempt = Date.now();
+
+            try {
+                const wsPort = determineWebSocketPort();
+                
+                if (!wsPort) {
+                    state.isConnecting = false;
+                    return false;
+                }
+
+                ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
+                setupEventHandlers();
+
+                state.connectTimeout = setTimeout(() => {
+                    if (state.isConnecting) {
+                        log('Connection timeout, cleaning up');
+                        cleanup();
+                        handleReconnect();
+                    }
+                }, 5000);
+
+                return true;
+            } catch (error) {
+                log('Error during connection attempt:', error);
+                state.isConnecting = false;
+                handleReconnect();
+                return false;
+            }
+        },
+
+        disconnect: function() {
+            log('Disconnecting WebSocket');
+            state.isIntentionallyClosed = true;
+            cleanup();
+            stopHealthCheck();
+        },
+
+        isConnected: function() {
+            return ws && ws.readyState === WebSocket.OPEN;
+        },
+
+        sendMessage: function(message) {
+            if (!this.isConnected()) {
+                log('Cannot send message - not connected');
+                return false;
+            }
+            
+            try {
+                ws.send(JSON.stringify(message));
+                return true;
+            } catch (error) {
+                log('Error sending message:', error);
+                return false;
+            }
+        },
+
+        addMessageHandler: function(type, handler) {
+            if (!state.messageHandlers[type]) {
+                state.messageHandlers[type] = {};
+            }
+            
+            const handlerId = generateHandlerId();
+            state.messageHandlers[type][handlerId] = handler;
+            
+            return handlerId;
+        },
+        
+        removeMessageHandler: function(type, handlerId) {
+            if (state.messageHandlers[type] && state.messageHandlers[type][handlerId]) {
+                delete state.messageHandlers[type][handlerId];
+            }
+        },
+
+        cleanup: function() {
+            log('Cleaning up WebSocket resources');
+            
+            clearTimeout(state.connectTimeout);
+            stopHealthCheck();
+
+            if (state.reconnectTimeout) {
+                clearTimeout(state.reconnectTimeout);
+                state.reconnectTimeout = null;
+            }
+
+            state.isConnecting = false;
+
+            if (ws) {
+                ws.onopen = null;
+                ws.onmessage = null;
+                ws.onerror = null;
+                ws.onclose = null;
+
+                if (ws.readyState === WebSocket.OPEN) {
+                    ws.close(1000, 'Cleanup');
+                }
+
+                ws = null;
+                window.ws = null;
+            }
+        },
+
+        dispose: function() {
+            log('Disposing WebSocketManager');
+
+            this.disconnect();
+
+            if (state.listeners.visibilityChange) {
+                document.removeEventListener('visibilitychange', state.listeners.visibilityChange);
+            }
+
+            state.messageHandlers = {};
+            state.listeners = {};
+        },
+
+        pause: function() {
+            log('WebSocketManager paused');
+            state.isIntentionallyClosed = true;
+
+            if (ws && ws.readyState === WebSocket.OPEN) {
+                ws.close(1000, 'WebSocketManager paused');
+            }
+
+            stopHealthCheck();
+        },
+
+        resume: function() {
+            log('WebSocketManager resumed');
+            state.isIntentionallyClosed = false;
+            
+            if (!this.isConnected()) {
+                this.connect();
+            }
+            
+            startHealthCheck();
+        }
+    };
+
+    function setupEventHandlers() {
+        if (!ws) return;
+
+        ws.onopen = () => {
+            state.isConnecting = false;
+            config.reconnectAttempts = 0;
+            clearTimeout(state.connectTimeout);
+            state.lastHealthCheck = Date.now();
+            window.ws = ws;
+
+            log('WebSocket connection established');
+
+            notifyHandlers('connect', { isConnected: true });
+
+            if (typeof updateConnectionStatus === 'function') {
+                updateConnectionStatus('connected');
+            }
+        };
+
+        ws.onmessage = (event) => {
+            try {
+                const message = JSON.parse(event.data);
+                log('WebSocket message received:', message);
+                notifyHandlers('message', message);
+            } catch (error) {
+                log('Error processing message:', error);
+                if (typeof updateConnectionStatus === 'function') {
+                    updateConnectionStatus('error');
+                }
+            }
+        };
+
+        ws.onerror = (error) => {
+            log('WebSocket error:', error);
+            if (typeof updateConnectionStatus === 'function') {
+                updateConnectionStatus('error');
+            }
+            notifyHandlers('error', error);
+        };
+
+        ws.onclose = (event) => {
+            log('WebSocket closed:', event);
+            state.isConnecting = false;
+            window.ws = null;
+            
+            if (typeof updateConnectionStatus === 'function') {
+                updateConnectionStatus('disconnected');
+            }
+
+            notifyHandlers('disconnect', { 
+                code: event.code, 
+                reason: event.reason 
+            });
+            
+            if (!state.isIntentionallyClosed) {
+                handleReconnect();
+            }
+        };
+    }
+
+    function setupPageVisibilityHandler() {
+        const visibilityChangeHandler = () => {
+            if (document.hidden) {
+                handlePageHidden();
+            } else {
+                handlePageVisible();
+            }
+        };
+
+        document.addEventListener('visibilitychange', visibilityChangeHandler);
+        state.listeners.visibilityChange = visibilityChangeHandler;
+    }
+    
+    function handlePageHidden() {
+        log('Page hidden');
+        state.isPageHidden = true;
+        stopHealthCheck();
+        
+        if (ws && ws.readyState === WebSocket.OPEN) {
+            state.isIntentionallyClosed = true;
+            ws.close(1000, 'Page hidden');
+        }
+    }
+
+    function handlePageVisible() {
+        log('Page visible');
+        state.isPageHidden = false;
+        state.isIntentionallyClosed = false;
+        
+        setTimeout(() => {
+            if (!publicAPI.isConnected()) {
+                publicAPI.connect();
+            }
+            startHealthCheck();
+        }, 0);
+    }
+
+    function startHealthCheck() {
+        stopHealthCheck();
+        state.healthCheckInterval = setInterval(() => {
+            performHealthCheck();
+        }, 30000);
+    }
+    
+    function stopHealthCheck() {
+        if (state.healthCheckInterval) {
+            clearInterval(state.healthCheckInterval);
+            state.healthCheckInterval = null;
+        }
+    }
+
+    function performHealthCheck() {
+        if (!publicAPI.isConnected()) {
+            log('Health check failed - not connected');
+            handleReconnect();
+            return;
+        }
+
+        const now = Date.now();
+        const lastCheck = state.lastHealthCheck;
+        
+        if (lastCheck && (now - lastCheck) > 60000) {
+            log('Health check failed - too long since last check');
+            handleReconnect();
+            return;
+        }
+
+        state.lastHealthCheck = now;
+        log('Health check passed');
+    }
+
+    function handleReconnect() {
+
+        if (state.reconnectTimeout) {
+            clearTimeout(state.reconnectTimeout);
+            state.reconnectTimeout = null;
+        }
+
+        config.reconnectAttempts++;
+        if (config.reconnectAttempts <= config.maxReconnectAttempts) {
+            const delay = Math.min(
+                config.reconnectDelay * Math.pow(1.5, config.reconnectAttempts - 1),
+                30000
+            );
+
+            log(`Scheduling reconnect in ${delay}ms (attempt ${config.reconnectAttempts})`);
+
+            state.reconnectTimeout = setTimeout(() => {
+                state.reconnectTimeout = null;
+                if (!state.isIntentionallyClosed) {
+                    publicAPI.connect();
+                }
+            }, delay);
+        } else {
+            log('Max reconnect attempts reached');
+            if (typeof updateConnectionStatus === 'function') {
+                updateConnectionStatus('error');
+            }
+
+            state.reconnectTimeout = setTimeout(() => {
+                state.reconnectTimeout = null;
+                config.reconnectAttempts = 0;
+                publicAPI.connect();
+            }, 60000);
+        }
+    }
+
+    function notifyHandlers(type, data) {
+        if (state.messageHandlers[type]) {
+            Object.values(state.messageHandlers[type]).forEach(handler => {
+                try {
+                    handler(data);
+                } catch (error) {
+                    log(`Error in ${type} handler:`, error);
+                }
+            });
+        }
+    }
+
+    function cleanup() {
+        log('Cleaning up WebSocket resources');
+        
+        clearTimeout(state.connectTimeout);
+        stopHealthCheck();
+        
+        if (state.reconnectTimeout) {
+            clearTimeout(state.reconnectTimeout);
+            state.reconnectTimeout = null;
+        }
+
+        state.isConnecting = false;
+
+        if (ws) {
+            ws.onopen = null;
+            ws.onmessage = null;
+            ws.onerror = null;
+            ws.onclose = null;
+
+            if (ws.readyState === WebSocket.OPEN) {
+                ws.close(1000, 'Cleanup');
+            }
+
+            ws = null;
+            window.ws = null;
+        }
+    }
+
+    return publicAPI;
+})();
+
+window.WebSocketManager = WebSocketManager;
+
+document.addEventListener('DOMContentLoaded', function() {
+
+  if (!window.webSocketManagerInitialized) {
+    window.WebSocketManager.initialize();
+    window.webSocketManagerInitialized = true;
+  }
+});
+
+//console.log('WebSocketManager initialized with methods:', Object.keys(WebSocketManager));
+console.log('WebSocketManager initialized');
diff --git a/basicswap/static/js/new_offer.js b/basicswap/static/js/new_offer.js
index 058e8dc..c88df43 100644
--- a/basicswap/static/js/new_offer.js
+++ b/basicswap/static/js/new_offer.js
@@ -1,59 +1,548 @@
-window.addEventListener('DOMContentLoaded', () => {
-  const err_msgs = document.querySelectorAll('p.error_msg');
-  for (let i = 0; i < err_msgs.length; i++) {
-    err_msg = err_msgs[i].innerText;
-    if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) {
-      e = document.getElementById('coin_to');
-      e.classList.add('error');
-    }
-    if (err_msg.indexOf('Coin From') >= 0) {
-      e = document.getElementById('coin_from');
-      e.classList.add('error');
-    }
-    if (err_msg.indexOf('Amount From') >= 0) {
-      e = document.getElementById('amt_from');
-      e.classList.add('error');
-    }
-    if (err_msg.indexOf('Amount To') >= 0) {
-      e = document.getElementById('amt_to');
-      e.classList.add('error');
-    }
-    if (err_msg.indexOf('Minimum Bid Amount') >= 0) {
-      e = document.getElementById('amt_bid_min');
-      e.classList.add('error');
-    }
-    if (err_msg.indexOf('Select coin you send') >= 0) {
-      e = document.getElementById('coin_from').parentNode;
-      e.classList.add('error');
-    }
-  }
+const DOM = {
+    get: (id) => document.getElementById(id),
+    getValue: (id) => {
+        const el = document.getElementById(id);
+        return el ? el.value : '';
+    },
+    setValue: (id, value) => {
+        const el = document.getElementById(id);
+        if (el) el.value = value;
+    },
+    addEvent: (id, event, handler) => {
+        const el = document.getElementById(id);
+        if (el) el.addEventListener(event, handler);
+    },
+    query: (selector) => document.querySelector(selector),
+    queryAll: (selector) => document.querySelectorAll(selector)
+};
 
-  // remove error class on input or select focus
-  const inputs = document.querySelectorAll('input.error');
-  const selects = document.querySelectorAll('select.error');
-  const elements = [...inputs, ...selects];
-  elements.forEach((element) => {
-    element.addEventListener('focus', (event) => {
-      event.target.classList.remove('error');
+const Storage = {
+    get: (key) => {
+        try {
+            return JSON.parse(localStorage.getItem(key));
+        } catch(e) {
+            console.warn(`Failed to retrieve item from storage: ${key}`, e);
+            return null;
+        }
+    },
+    set: (key, value) => {
+        try {
+            localStorage.setItem(key, JSON.stringify(value));
+            return true;
+        } catch(e) {
+            console.error(`Failed to save item to storage: ${key}`, e);
+            return false;
+        }
+    },
+    setRaw: (key, value) => {
+        try {
+            localStorage.setItem(key, value);
+            return true;
+        } catch(e) {
+            console.error(`Failed to save raw item to storage: ${key}`, e);
+            return false;
+        }
+    },
+    getRaw: (key) => {
+        try {
+            return localStorage.getItem(key);
+        } catch(e) {
+            console.warn(`Failed to retrieve raw item from storage: ${key}`, e);
+            return null;
+        }
+    }
+};
+
+const Ajax = {
+    post: (url, data, onSuccess, onError) => {
+        const xhr = new XMLHttpRequest();
+        xhr.onreadystatechange = function() {
+            if (xhr.readyState !== XMLHttpRequest.DONE) return;
+            if (xhr.status === 200) {
+                if (onSuccess) {
+                    try {
+                        const response = xhr.responseText.startsWith('{') ? 
+                            JSON.parse(xhr.responseText) : xhr.responseText;
+                        onSuccess(response);
+                    } catch (e) {
+                        console.error('Failed to parse response:', e);
+                        if (onError) onError('Invalid response format');
+                    }
+                }
+            } else {
+                console.error('Request failed:', xhr.statusText);
+                if (onError) onError(xhr.statusText);
+            }
+        };
+        xhr.open('POST', url);
+        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+        xhr.send(data);
+        return xhr;
+    }
+};
+
+function handleNewOfferAddress() {
+    const STORAGE_KEY = 'lastUsedAddressNewOffer';
+    const selectElement = DOM.query('select[name="addr_from"]');
+    const form = selectElement?.closest('form');
+
+    if (!selectElement || !form) return;
+
+    function loadInitialAddress() {
+        const savedAddress = Storage.get(STORAGE_KEY);
+        if (savedAddress) {
+            try {
+                selectElement.value = savedAddress.value;
+            } catch (e) {
+                selectFirstAddress();
+            }
+        } else {
+            selectFirstAddress();
+        }
+    }
+
+    function selectFirstAddress() {
+        if (selectElement.options.length > 1) {
+            const firstOption = selectElement.options[1];
+            if (firstOption) {
+                selectElement.value = firstOption.value;
+                saveAddress(firstOption.value, firstOption.text);
+            }
+        }
+    }
+
+    function saveAddress(value, text) {
+        Storage.set(STORAGE_KEY, { value, text });
+    }
+
+    form.addEventListener('submit', () => {
+        saveAddress(selectElement.value, selectElement.selectedOptions[0].text);
     });
-  });
-});
 
-const selects = document.querySelectorAll('select.disabled-select');
-for (const select of selects) {
-    if (select.disabled) {
-        select.classList.add('disabled-select-enabled');
-    } else {
-        select.classList.remove('disabled-select-enabled');
-    }
+    selectElement.addEventListener('change', (event) => {
+        saveAddress(event.target.value, event.target.selectedOptions[0].text);
+    });
+
+    loadInitialAddress();
 }
 
+const RateManager = {
+    lookupRates: () => {
+        const coinFrom = DOM.getValue('coin_from');
+        const coinTo = DOM.getValue('coin_to');
+        const ratesDisplay = DOM.get('rates_display');
 
-const inputs = document.querySelectorAll('input.disabled-input, input[type="checkbox"].disabled-input');
-for (const input of inputs) {
-    if (input.readOnly) {
-        input.classList.add('disabled-input-enabled');
-    } else {
-        input.classList.remove('disabled-input-enabled');
+        if (!coinFrom || !coinTo || !ratesDisplay) {
+            console.log('Required elements for lookup_rates not found');
+            return;
+        }
+
+        if (coinFrom === '-1' || coinTo === '-1') {
+            alert('Coins from and to must be set first.');
+            return;
+        }
+
+        const selectedCoin = (coinFrom === '15') ? '3' : coinFrom;
+
+        ratesDisplay.innerHTML = '<p>Updating...</p>';
+
+        const priceJsonElement = DOM.query(".pricejsonhidden");
+        if (priceJsonElement) {
+            priceJsonElement.classList.remove("hidden");
+        }
+
+        const params = 'coin_from=' + selectedCoin + '&coin_to=' + coinTo;
+
+        Ajax.post('/json/rates', params, 
+            (response) => {
+                if (ratesDisplay) {
+                    ratesDisplay.innerHTML = typeof response === 'string' ? 
+                        response : '<pre><code>' + JSON.stringify(response, null, '  ') + '</code></pre>';
+                }
+            },
+            (error) => {
+                if (ratesDisplay) {
+                    ratesDisplay.innerHTML = '<p>Error loading rates: ' + error + '</p>';
+                }
+            }
+        );
+    },
+    
+    getRateInferred: (event) => {
+        if (event) event.preventDefault();
+
+        const coinFrom = DOM.getValue('coin_from');
+        const coinTo = DOM.getValue('coin_to');
+        const rateElement = DOM.get('rate');
+
+        if (!coinFrom || !coinTo || !rateElement) {
+            console.log('Required elements for getRateInferred not found');
+            return;
+        }
+
+        const params = 'coin_from=' + encodeURIComponent(coinFrom) + 
+                      '&coin_to=' + encodeURIComponent(coinTo);
+
+        DOM.setValue('rate', 'Loading...');
+
+        Ajax.post('/json/rates', params, 
+            (response) => {
+                if (response.coingecko && response.coingecko.rate_inferred) {
+                    DOM.setValue('rate', response.coingecko.rate_inferred);
+                    RateManager.setRate('rate');
+                } else {
+                    DOM.setValue('rate', 'Error: No rate available');
+                    console.error('Rate not available in response');
+                }
+            },
+            (error) => {
+                DOM.setValue('rate', 'Error: Rate lookup failed');
+                console.error('Error fetching rate data:', error);
+            }
+        );
+    },
+
+    setRate: (valueChanged) => {
+        const elements = {
+            coinFrom: DOM.get('coin_from'),
+            coinTo: DOM.get('coin_to'),
+            amtFrom: DOM.get('amt_from'),
+            amtTo: DOM.get('amt_to'),
+            rate: DOM.get('rate'),
+            rateLock: DOM.get('rate_lock'),
+            swapType: DOM.get('swap_type')
+        };
+
+        if (!elements.coinFrom || !elements.coinTo || 
+            !elements.amtFrom || !elements.amtTo || !elements.rate) {
+            console.log('Required elements for setRate not found');
+            return;
+        }
+
+        const values = {
+            coinFrom: elements.coinFrom.value,
+            coinTo: elements.coinTo.value,
+            amtFrom: elements.amtFrom.value,
+            amtTo: elements.amtTo.value,
+            rate: elements.rate.value,
+            lockRate: elements.rate.value == '' ? false : 
+                     (elements.rateLock ? elements.rateLock.checked : false)
+        };
+
+        if (valueChanged === 'coin_from' || valueChanged === 'coin_to') {
+            DOM.setValue('rate', '');
+            return;
+        }
+
+        if (elements.swapType) {
+            SwapTypeManager.setSwapTypeEnabled(
+                values.coinFrom, 
+                values.coinTo, 
+                elements.swapType
+            );
+        }
+
+        if (values.coinFrom == '-1' || values.coinTo == '-1') {
+            return;
+        }
+
+        let params = 'coin_from=' + values.coinFrom + '&coin_to=' + values.coinTo;
+
+        if (valueChanged == 'rate' || 
+            (values.lockRate && valueChanged == 'amt_from') || 
+            (values.amtTo == '' && valueChanged == 'amt_from')) {
+
+            if (values.rate == '' || (values.amtFrom == '' && values.amtTo == '')) {
+                return;
+            } else if (values.amtFrom == '' && values.amtTo != '') {
+                if (valueChanged == 'amt_from') {
+                    return;
+                }
+                params += '&rate=' + values.rate + '&amt_to=' + values.amtTo;
+            } else {
+                params += '&rate=' + values.rate + '&amt_from=' + values.amtFrom;
+            }
+        } else if (values.lockRate && valueChanged == 'amt_to') {
+            if (values.amtTo == '' || values.rate == '') {
+                return;
+            }
+            params += '&amt_to=' + values.amtTo + '&rate=' + values.rate;
+        } else {
+            if (values.amtFrom == '' || values.amtTo == '') {
+                return;
+            }
+            params += '&amt_from=' + values.amtFrom + '&amt_to=' + values.amtTo;
+        }
+
+        Ajax.post('/json/rate', params, 
+            (response) => {
+                if (response.hasOwnProperty('rate')) {
+                    DOM.setValue('rate', response.rate);
+                } else if (response.hasOwnProperty('amount_to')) {
+                    DOM.setValue('amt_to', response.amount_to);
+                } else if (response.hasOwnProperty('amount_from')) {
+                    DOM.setValue('amt_from', response.amount_from);
+                }
+            },
+            (error) => {
+                console.error('Rate calculation failed:', error);
+            }
+        );
     }
+};
+
+function set_rate(valueChanged) {
+    RateManager.setRate(valueChanged);
+}
+
+function lookup_rates() {
+    RateManager.lookupRates();
+}
+
+function getRateInferred(event) {
+    RateManager.getRateInferred(event);
+}
+
+const SwapTypeManager = {
+    adaptor_sig_only_coins: ['6', '9', '8', '7', '13', '18', '17'],
+    secret_hash_only_coins: ['11', '12'],
+
+    setSwapTypeEnabled: (coinFrom, coinTo, swapTypeElement) => {
+        if (!swapTypeElement) return;
+
+        let makeHidden = false;
+        coinFrom = String(coinFrom);
+        coinTo = String(coinTo);
+
+        if (SwapTypeManager.adaptor_sig_only_coins.includes(coinFrom) || 
+            SwapTypeManager.adaptor_sig_only_coins.includes(coinTo)) {
+            swapTypeElement.disabled = true;
+            swapTypeElement.value = 'xmr_swap';
+            makeHidden = true;
+            swapTypeElement.classList.add('select-disabled');
+        } else if (SwapTypeManager.secret_hash_only_coins.includes(coinFrom) || 
+                  SwapTypeManager.secret_hash_only_coins.includes(coinTo)) {
+            swapTypeElement.disabled = true;
+            swapTypeElement.value = 'seller_first';
+            makeHidden = true;
+            swapTypeElement.classList.add('select-disabled');
+        } else {
+            swapTypeElement.disabled = false;
+            swapTypeElement.classList.remove('select-disabled');
+            swapTypeElement.value = 'xmr_swap';
+        }
+
+        let swapTypeHidden = DOM.get('swap_type_hidden');
+        if (makeHidden) {
+            if (!swapTypeHidden) {
+                const form = DOM.get('form');
+                if (form) {
+                    swapTypeHidden = document.createElement('input');
+                    swapTypeHidden.setAttribute('id', 'swap_type_hidden');
+                    swapTypeHidden.setAttribute('type', 'hidden');
+                    swapTypeHidden.setAttribute('name', 'swap_type');
+                    form.appendChild(swapTypeHidden);
+                }
+            }
+            if (swapTypeHidden) {
+                swapTypeHidden.setAttribute('value', swapTypeElement.value);
+            }
+        } else if (swapTypeHidden) {
+            swapTypeHidden.parentNode.removeChild(swapTypeHidden);
+        }
+    }
+};
+
+function set_swap_type_enabled(coinFrom, coinTo, swapTypeElement) {
+    SwapTypeManager.setSwapTypeEnabled(coinFrom, coinTo, swapTypeElement);
+}
+
+const UIEnhancer = {
+    handleErrorHighlighting: () => {
+        const errMsgs = document.querySelectorAll('p.error_msg');
+
+        const errorFieldMap = {
+            'coin_to': ['coin_to', 'Coin To'],
+            'coin_from': ['Coin From'],
+            'amt_from': ['Amount From'],
+            'amt_to': ['Amount To'],
+            'amt_bid_min': ['Minimum Bid Amount'],
+            'Select coin you send': ['coin_from', 'parentNode']
+        };
+
+        errMsgs.forEach(errMsg => {
+            const text = errMsg.innerText;
+
+            Object.entries(errorFieldMap).forEach(([field, keywords]) => {
+                if (keywords.some(keyword => text.includes(keyword))) {
+                    let element = DOM.get(field);
+
+                    if (field === 'Select coin you send' && element) {
+                        element = element.parentNode;
+                    }
+
+                    if (element) {
+                        element.classList.add('error');
+                    }
+                }
+            });
+        });
+
+        document.querySelectorAll('input.error, select.error').forEach(element => {
+            element.addEventListener('focus', event => {
+                event.target.classList.remove('error');
+            });
+        });
+    },
+
+    updateDisabledStyles: () => {
+        document.querySelectorAll('select.disabled-select').forEach(select => {
+            if (select.disabled) {
+                select.classList.add('disabled-select-enabled');
+            } else {
+                select.classList.remove('disabled-select-enabled');
+            }
+        });
+
+        document.querySelectorAll('input.disabled-input, input[type="checkbox"].disabled-input').forEach(input => {
+            if (input.readOnly) {
+                input.classList.add('disabled-input-enabled');
+            } else {
+                input.classList.remove('disabled-input-enabled');
+            }
+        });
+    },
+
+    setupCustomSelects: () => {
+        const selectCache = {};
+
+        function updateSelectCache(select) {
+            if (!select || !select.options || select.selectedIndex === undefined) return;
+
+            const selectedOption = select.options[select.selectedIndex];
+            if (!selectedOption) return;
+
+            const image = selectedOption.getAttribute('data-image');
+            const name = selectedOption.textContent.trim();
+            selectCache[select.id] = { image, name };
+        }
+        
+        function setSelectData(select) {
+            if (!select || !select.options || select.selectedIndex === undefined) return;
+
+            const selectedOption = select.options[select.selectedIndex];
+            if (!selectedOption) return;
+
+            const image = selectedOption.getAttribute('data-image') || '';
+            const name = selectedOption.textContent.trim();
+
+            select.style.backgroundImage = image ? `url(${image}?${new Date().getTime()})` : '';
+
+            const selectImage = select.nextElementSibling?.querySelector('.select-image');
+            if (selectImage) {
+                selectImage.src = image;
+            }
+
+            const selectNameElement = select.nextElementSibling?.querySelector('.select-name');
+            if (selectNameElement) {
+                selectNameElement.textContent = name;
+            }
+
+            updateSelectCache(select);
+        }
+
+        function setupCustomSelect(select) {
+            if (!select) return;
+
+            const options = select.querySelectorAll('option');
+            const selectIcon = select.parentElement?.querySelector('.select-icon');
+            const selectImage = select.parentElement?.querySelector('.select-image');
+            
+            if (!options || !selectIcon || !selectImage) return;
+            
+            options.forEach(option => {
+                const image = option.getAttribute('data-image');
+                if (image) {
+                    option.style.backgroundImage = `url(${image})`;
+                }
+            });
+
+            const storedValue = Storage.getRaw(select.name);
+            if (storedValue && select.value == '-1') {
+                select.value = storedValue;
+            }
+
+            select.addEventListener('change', () => {
+                setSelectData(select);
+                Storage.setRaw(select.name, select.value);
+            });
+            
+            setSelectData(select);
+            selectIcon.style.display = 'none';
+            selectImage.style.display = 'none';
+        }
+
+        const selectIcons = document.querySelectorAll('.custom-select .select-icon');
+        const selectImages = document.querySelectorAll('.custom-select .select-image');
+        const selectNames = document.querySelectorAll('.custom-select .select-name');
+
+        selectIcons.forEach(icon => icon.style.display = 'none');
+        selectImages.forEach(image => image.style.display = 'none');
+        selectNames.forEach(name => name.style.display = 'none');
+
+        const customSelects = document.querySelectorAll('.custom-select select');
+        customSelects.forEach(setupCustomSelect);
+    }
+};
+
+function initializeApp() {
+    handleNewOfferAddress();
+
+    DOM.addEvent('get_rate_inferred_button', 'click', RateManager.getRateInferred);
+
+    const coinFrom = DOM.get('coin_from');
+    const coinTo = DOM.get('coin_to');
+    const swapType = DOM.get('swap_type');
+
+    if (coinFrom && coinTo && swapType) {
+        SwapTypeManager.setSwapTypeEnabled(coinFrom.value, coinTo.value, swapType);
+
+        coinFrom.addEventListener('change', function() {
+            SwapTypeManager.setSwapTypeEnabled(this.value, coinTo.value, swapType);
+            RateManager.setRate('coin_from');
+        });
+
+        coinTo.addEventListener('change', function() {
+            SwapTypeManager.setSwapTypeEnabled(coinFrom.value, this.value, swapType);
+            RateManager.setRate('coin_to');
+        });
+    }
+
+    ['amt_from', 'amt_to', 'rate'].forEach(id => {
+        DOM.addEvent(id, 'change', function() {
+            RateManager.setRate(id);
+        });
+
+        DOM.addEvent(id, 'input', function() {
+            RateManager.setRate(id);
+        });
+    });
+
+    DOM.addEvent('rate_lock', 'change', function() {
+        if (DOM.getValue('rate')) {
+            RateManager.setRate('rate');
+        }
+    });
+
+    DOM.addEvent('lookup_rates_button', 'click', RateManager.lookupRates);
+
+    UIEnhancer.handleErrorHighlighting();
+    UIEnhancer.updateDisabledStyles();
+    UIEnhancer.setupCustomSelects();
+}
+
+if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', initializeApp);
+} else {
+    initializeApp();
 }
diff --git a/basicswap/static/js/offers.js b/basicswap/static/js/offers.js
index 83b5864..1d534cf 100644
--- a/basicswap/static/js/offers.js
+++ b/basicswap/static/js/offers.js
@@ -1,68 +1,3 @@
-// EVENT MANAGER
-const EventManager = {
-    listeners: new Map(),
-
-    add(element, type, handler, options = false) {
-        if (!this.listeners.has(element)) {
-            this.listeners.set(element, new Map());
-        }
-
-        const elementListeners = this.listeners.get(element);
-        if (!elementListeners.has(type)) {
-            elementListeners.set(type, new Set());
-        }
-
-        const handlerInfo = { handler, options };
-        elementListeners.get(type).add(handlerInfo);
-        element.addEventListener(type, handler, options);
-
-        return handlerInfo;
-    },
-
-    remove(element, type, handler, options = false) {
-        const elementListeners = this.listeners.get(element);
-        if (!elementListeners) return;
-
-        const typeListeners = elementListeners.get(type);
-        if (!typeListeners) return;
-
-        typeListeners.forEach(info => {
-            if (info.handler === handler) {
-                element.removeEventListener(type, handler, options);
-                typeListeners.delete(info);
-            }
-        });
-
-        if (typeListeners.size === 0) {
-            elementListeners.delete(type);
-        }
-        if (elementListeners.size === 0) {
-            this.listeners.delete(element);
-        }
-    },
-
-    removeAll(element) {
-        const elementListeners = this.listeners.get(element);
-        if (!elementListeners) return;
-
-        elementListeners.forEach((typeListeners, type) => {
-            typeListeners.forEach(info => {
-                element.removeEventListener(type, info.handler, info.options);
-            });
-        });
-
-        this.listeners.delete(element);
-    },
-
-    clearAll() {
-        this.listeners.forEach((elementListeners, element) => {
-            this.removeAll(element);
-        });
-        this.listeners.clear();
-    }
-};
-
-// GLOBAL STATE VARIABLES
 let latestPrices = null;
 let lastRefreshTime = null;
 let currentPage = 1;
@@ -72,68 +7,11 @@ let currentSortColumn = 0;
 let currentSortDirection = 'desc';
 let filterTimeout = null;
 
-// CONFIGURATION CONSTANTS
-// TIME CONSTANTS
-const CACHE_DURATION = 10 * 60 * 1000;
-const wsPort = config.port || window.ws_port || '11700';
-
-//  APP CONSTANTS
-const itemsPerPage = 50;
 const isSentOffers = window.offersTableConfig.isSentOffers;
+const CACHE_DURATION = window.config.cacheConfig.defaultTTL;
+const wsPort = window.config.wsPort;
+const itemsPerPage = window.config.itemsPerPage;
 
-const offersConfig = {
-  apiEndpoints: {
-    coinGecko: 'https://api.coingecko.com/api/v3',
-    cryptoCompare: 'https://min-api.cryptocompare.com/data'
-  },
-  apiKeys: getAPIKeys()
-};
-
-// MAPPING OBJECTS
-const coinNameToSymbol = {
-    'Bitcoin': 'bitcoin',
-    'Particl': 'particl',
-    'Particl Blind': 'particl',
-    'Particl Anon': 'particl',
-    'Monero': 'monero',
-    'Wownero': 'wownero',
-    'Litecoin': 'litecoin',
-    'Firo': 'firo',
-    'Zcoin': 'firo',
-    'Dash': 'dash',
-    'PIVX': 'pivx',
-    'Decred': 'decred',
-    'Zano': 'zano',
-    'Dogecoin': 'dogecoin',
-    'Bitcoin Cash': 'bitcoin-cash'
-};
-
-const coinNameToDisplayName = {
-    'Bitcoin': 'Bitcoin',
-    'Litecoin': 'Litecoin',
-    'Monero': 'Monero',
-    'Particl': 'Particl',
-    'Particl Blind': 'Particl Blind',
-    'Particl Anon': 'Particl Anon',
-    'PIVX': 'PIVX',
-    'Firo': 'Firo',
-    'Zcoin': 'Firo',
-    'Dash': 'Dash',
-    'Decred': 'Decred',
-    'Wownero': 'Wownero',
-    'Bitcoin Cash': 'Bitcoin Cash',
-    'Dogecoin': 'Dogecoin',
-    'Zano': 'Zano'
-};
-
-const coinIdToName = {
-    1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred',
-    6: 'monero', 7: 'particl blind', 8: 'particl anon',
-    9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash',
-    18: 'dogecoin'
-};
-
-// DOM ELEMENT REFERENCES
 const offersBody = document.getElementById('offers-body');
 const filterForm = document.getElementById('filterForm');
 const prevPageButton = document.getElementById('prevPage');
@@ -143,582 +21,6 @@ const totalPagesSpan = document.getElementById('totalPages');
 const lastRefreshTimeSpan = document.getElementById('lastRefreshTime');
 const newEntriesCountSpan = document.getElementById('newEntriesCount');
 
-
-// MANAGER OBJECTS
-const WebSocketManager = {
-    ws: null,
-    messageQueue: [],
-    processingQueue: false,
-    debounceTimeout: null,
-    reconnectTimeout: null,
-    maxReconnectAttempts: 5,
-    reconnectAttempts: 0,
-    reconnectDelay: 5000,
-    maxQueueSize: 1000,
-    isIntentionallyClosed: false,
-    handlers: {},
-    isPageHidden: document.hidden,
-    priceUpdatePaused: false,
-    lastVisibilityChange: Date.now(),
-
-    connectionState: {
-        isConnecting: false,
-        lastConnectAttempt: null,
-        connectTimeout: null,
-        lastHealthCheck: null,
-        healthCheckInterval: null
-    },
-
-    initialize() {
-        this.setupPageVisibilityHandler();
-        this.connect();
-        this.startHealthCheck();
-    },
-
-    setupPageVisibilityHandler() {
-        this.handlers.visibilityChange = () => {
-            if (document.hidden) {
-                this.handlePageHidden();
-            } else {
-                this.handlePageVisible();
-            }
-        };
-        document.addEventListener('visibilitychange', this.handlers.visibilityChange);
-    },
-
-    handlePageHidden() {
-        this.isPageHidden = true;
-        this.priceUpdatePaused = true;
-        this.stopHealthCheck();
-        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
-            this.isIntentionallyClosed = true;
-            this.ws.close(1000, 'Page hidden');
-        }
-    },
-
-    handlePageVisible() {
-        this.isPageHidden = false;
-        this.lastVisibilityChange = Date.now();
-        this.isIntentionallyClosed = false;
-
-        setTimeout(() => {
-            this.priceUpdatePaused = false;
-            if (!this.isConnected()) {
-                this.connect();
-            }
-            this.startHealthCheck();
-        }, 0);
-    },
-
-    startHealthCheck() {
-        this.stopHealthCheck();
-        this.connectionState.healthCheckInterval = setInterval(() => {
-            this.performHealthCheck();
-        }, 30000);
-    },
-
-    stopHealthCheck() {
-        if (this.connectionState.healthCheckInterval) {
-            clearInterval(this.connectionState.healthCheckInterval);
-            this.connectionState.healthCheckInterval = null;
-        }
-    },
-
-    performHealthCheck() {
-        if (!this.isConnected()) {
-            this.handleReconnect();
-            return;
-        }
-
-        const now = Date.now();
-        const lastCheck = this.connectionState.lastHealthCheck;
-        if (lastCheck && (now - lastCheck) > 60000) {
-            this.handleReconnect();
-            return;
-        }
-
-        this.connectionState.lastHealthCheck = now;
-    },
-
-    connect() {
-    if (this.connectionState.isConnecting || this.isIntentionallyClosed) {
-        return false;
-    }
-
-    this.cleanup();
-    this.connectionState.isConnecting = true;
-    this.connectionState.lastConnectAttempt = Date.now();
-
-    try {
-        let wsPort;
-        
-        if (typeof getWebSocketConfig === 'function') {
-            const wsConfig = getWebSocketConfig();
-            wsPort = wsConfig.port || wsConfig.fallbackPort;
-            console.log("Using WebSocket port:", wsPort);
-        } else {
-            wsPort = config?.port || window.ws_port || '11700';
-        }
-
-        if (!wsPort) {
-            this.connectionState.isConnecting = false;
-            return false;
-        }
-
-        this.ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
-        this.setupEventHandlers();
-
-        this.connectionState.connectTimeout = setTimeout(() => {
-            if (this.connectionState.isConnecting) {
-                this.cleanup();
-                this.handleReconnect();
-            }
-        }, 5000);
-
-        return true;
-    } catch (error) {
-        this.connectionState.isConnecting = false;
-        this.handleReconnect();
-        return false;
-    }
-},
-    setupEventHandlers() {
-        if (!this.ws) return;
-
-        this.handlers.open = () => {
-            this.connectionState.isConnecting = false;
-            this.reconnectAttempts = 0;
-            clearTimeout(this.connectionState.connectTimeout);
-            this.connectionState.lastHealthCheck = Date.now();
-            window.ws = this.ws;
-            console.log('🟢  WebSocket connection established for Offers');
-            updateConnectionStatus('connected');
-        };
-
-        this.handlers.message = (event) => {
-            try {
-                const message = JSON.parse(event.data);
-                this.handleMessage(message);
-            } catch (error) {
-                updateConnectionStatus('error');
-            }
-        };
-
-        this.handlers.error = (error) => {
-            updateConnectionStatus('error');
-        };
-
-        this.handlers.close = (event) => {
-            this.connectionState.isConnecting = false;
-            window.ws = null;
-            updateConnectionStatus('disconnected');
-
-            if (!this.isIntentionallyClosed) {
-                this.handleReconnect();
-            }
-        };
-
-        this.ws.onopen = this.handlers.open;
-        this.ws.onmessage = this.handlers.message;
-        this.ws.onerror = this.handlers.error;
-        this.ws.onclose = this.handlers.close;
-    },
-
-    handleMessage(message) {
-        if (this.messageQueue.length >= this.maxQueueSize) {
-            this.messageQueue.shift();
-        }
-
-        if (this.debounceTimeout) {
-            clearTimeout(this.debounceTimeout);
-        }
-
-        this.messageQueue.push(message);
-
-        this.debounceTimeout = setTimeout(() => {
-            this.processMessageQueue();
-        }, 200);
-    },
-
-    async processMessageQueue() {
-        if (this.processingQueue || this.messageQueue.length === 0) return;
-
-        this.processingQueue = true;
-        const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
-
-        try {
-            const response = await fetch(endpoint);
-            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
-
-            const newData = await response.json();
-            const fetchedOffers = Array.isArray(newData) ? newData : Object.values(newData);
-
-            jsonData = formatInitialData(fetchedOffers);
-            originalJsonData = [...jsonData];
-
-            requestAnimationFrame(() => {
-                updateOffersTable();
-                updatePaginationInfo();
-            });
-
-            this.messageQueue = [];
-        } catch (error) {
-        } finally {
-            this.processingQueue = false;
-        }
-    },
-
-    handleReconnect() {
-        if (this.reconnectTimeout) {
-            clearTimeout(this.reconnectTimeout);
-        }
-
-        this.reconnectAttempts++;
-        if (this.reconnectAttempts <= this.maxReconnectAttempts) {
-            const delay = Math.min(
-                this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1),
-                30000
-            );
-
-            this.reconnectTimeout = setTimeout(() => {
-                if (!this.isIntentionallyClosed) {
-                    this.connect();
-                }
-            }, delay);
-        } else {
-            updateConnectionStatus('error');
-            setTimeout(() => {
-                this.reconnectAttempts = 0;
-                this.connect();
-            }, 60000);
-        }
-    },
-
-    cleanup() {
-        clearTimeout(this.debounceTimeout);
-        clearTimeout(this.reconnectTimeout);
-        clearTimeout(this.connectionState.connectTimeout);
-        this.stopHealthCheck();
-
-        this.messageQueue = [];
-        this.processingQueue = false;
-        this.connectionState.isConnecting = false;
-
-        if (this.ws) {
-            this.ws.onopen = null;
-            this.ws.onmessage = null;
-            this.ws.onerror = null;
-            this.ws.onclose = null;
-
-            if (this.ws.readyState === WebSocket.OPEN) {
-                this.ws.close(1000, 'Cleanup');
-            }
-
-            this.ws = null;
-            window.ws = null;
-        }
-
-        if (this.handlers.visibilityChange) {
-            document.removeEventListener('visibilitychange', this.handlers.visibilityChange);
-        }
-
-        this.handlers = {};
-    },
-
-    disconnect() {
-        this.isIntentionallyClosed = true;
-        this.cleanup();
-        this.stopHealthCheck();
-    },
-
-    isConnected() {
-        return this.ws && this.ws.readyState === WebSocket.OPEN;
-    }
-};
-
-window.WebSocketManager = WebSocketManager;
-
-const CacheManager = {
-    maxItems: 100,
-    maxSize: 5 * 1024 * 1024, // 5MB
-
-    set: function(key, value, customTtl = null) {
-        try {
-            this.cleanup();
-
-            if (!value) {
-                console.warn('Attempted to cache null/undefined value for key:', key);
-                return false;
-            }
-
-            const item = {
-                value: value,
-                timestamp: Date.now(),
-                expiresAt: Date.now() + (customTtl || CACHE_DURATION)
-            };
-
-            try {
-                JSON.stringify(item);
-            } catch (e) {
-                console.error('Failed to serialize cache item:', e);
-                return false;
-            }
-
-            const itemSize = new Blob([JSON.stringify(item)]).size;
-            if (itemSize > this.maxSize) {
-                console.warn(`Cache item exceeds maximum size (${(itemSize/1024/1024).toFixed(2)}MB)`);
-                return false;
-            }
-
-            try {
-                localStorage.setItem(key, JSON.stringify(item));
-                return true;
-            } catch (storageError) {
-                if (storageError.name === 'QuotaExceededError') {
-                    this.cleanup(true);
-                    try {
-                        localStorage.setItem(key, JSON.stringify(item));
-                        return true;
-                    } catch (retryError) {
-                        console.error('Storage quota exceeded even after cleanup:', retryError);
-                        return false;
-                    }
-                }
-                throw storageError;
-            }
-
-        } catch (error) {
-            console.error('Cache set error:', error);
-            return false;
-        }
-    },
-
-    get: function(key) {
-    try {
-        const itemStr = localStorage.getItem(key);
-        if (!itemStr) {
-            return null;
-        }
-
-        let item;
-        try {
-            item = JSON.parse(itemStr);
-        } catch (parseError) {
-            console.error('Failed to parse cached item:', parseError);
-            localStorage.removeItem(key);
-            return null;
-        }
-
-        if (!item || typeof item.expiresAt !== 'number' || !Object.prototype.hasOwnProperty.call(item, 'value')) {
-            console.warn('Invalid cache item structure for key:', key);
-            localStorage.removeItem(key);
-            return null;
-        }
-
-        const now = Date.now();
-        if (now < item.expiresAt) {
-            return {
-                value: item.value,
-                remainingTime: item.expiresAt - now
-            };
-        }
-
-        localStorage.removeItem(key);
-        return null;
-
-    } catch (error) {
-        console.error("Cache retrieval error:", error);
-        try {
-            localStorage.removeItem(key);
-        } catch (removeError) {
-            console.error("Failed to remove invalid cache entry:", removeError);
-        }
-        return null;
-    }
-},
-
-    cleanup: function(aggressive = false) {
-        const now = Date.now();
-        let totalSize = 0;
-        let itemCount = 0;
-        const items = [];
-
-        for (let i = 0; i < localStorage.length; i++) {
-            const key = localStorage.key(i);
-            if (!key.startsWith('offers_') && !key.startsWith('prices_')) continue;
-
-            try {
-                const itemStr = localStorage.getItem(key);
-                const size = new Blob([itemStr]).size;
-                const item = JSON.parse(itemStr);
-
-                if (now >= item.expiresAt) {
-                    localStorage.removeItem(key);
-                    continue;
-                }
-
-                items.push({
-                    key,
-                    size,
-                    expiresAt: item.expiresAt,
-                    timestamp: item.timestamp
-                });
-
-                totalSize += size;
-                itemCount++;
-            } catch (error) {
-                console.error("Error processing cache item:", error);
-                localStorage.removeItem(key);
-            }
-        }
-
-        if (aggressive || totalSize > this.maxSize || itemCount > this.maxItems) {
-            items.sort((a, b) => b.timestamp - a.timestamp);
-
-            while ((totalSize > this.maxSize || itemCount > this.maxItems) && items.length > 0) {
-                const item = items.pop();
-                try {
-                    localStorage.removeItem(item.key);
-                    totalSize -= item.size;
-                    itemCount--;
-                } catch (error) {
-                    console.error("Error removing cache item:", error);
-                }
-            }
-        }
-
-        return {
-            totalSize,
-            itemCount,
-            cleaned: items.length
-        };
-    },
-
-    clear: function() {
-        const keys = [];
-        for (let i = 0; i < localStorage.length; i++) {
-            const key = localStorage.key(i);
-            if (key.startsWith('offers_') || key.startsWith('prices_')) {
-                keys.push(key);
-            }
-        }
-
-        keys.forEach(key => {
-            try {
-                localStorage.removeItem(key);
-            } catch (error) {
-                console.error("Error clearing cache item:", error);
-            }
-        });
-    },
-
-    getStats: function() {
-        let totalSize = 0;
-        let itemCount = 0;
-        let expiredCount = 0;
-        const now = Date.now();
-
-        for (let i = 0; i < localStorage.length; i++) {
-            const key = localStorage.key(i);
-            if (!key.startsWith('offers_') && !key.startsWith('prices_')) continue;
-
-            try {
-                const itemStr = localStorage.getItem(key);
-                const size = new Blob([itemStr]).size;
-                const item = JSON.parse(itemStr);
-
-                totalSize += size;
-                itemCount++;
-
-                if (now >= item.expiresAt) {
-                    expiredCount++;
-                }
-            } catch (error) {
-                console.error("Error getting cache stats:", error);
-            }
-        }
-
-        return {
-            totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
-            itemCount,
-            expiredCount,
-            utilization: ((totalSize / this.maxSize) * 100).toFixed(1) + '%'
-        };
-    }
-};
-
-window.CacheManager = CacheManager;
-
-// IDENTITY CACHE MANAGEMENT
-const IdentityManager = {
-    cache: new Map(),
-    pendingRequests: new Map(),
-    retryDelay: 2000,
-    maxRetries: 3,
-    cacheTimeout: 5 * 60 * 1000,
-    async getIdentityData(address) {
-
-        const cachedData = this.getCachedIdentity(address);
-        if (cachedData) {
-            return cachedData;
-        }
-
-        if (this.pendingRequests.has(address)) {
-            return this.pendingRequests.get(address);
-        }
-
-        const request = this.fetchWithRetry(address);
-        this.pendingRequests.set(address, request);
-
-        try {
-            const data = await request;
-            this.cache.set(address, {
-                data,
-                timestamp: Date.now()
-            });
-            return data;
-        } finally {
-            this.pendingRequests.delete(address);
-        }
-    },
-
-    getCachedIdentity(address) {
-        const cached = this.cache.get(address);
-        if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
-            return cached.data;
-        }
-        return null;
-    },
-
-    async fetchWithRetry(address, attempt = 1) {
-        try {
-            const response = await fetch(`/json/identities/${address}`, {
-                signal: AbortSignal.timeout(5000)
-            });
-
-            if (!response.ok) {
-                throw new Error(`HTTP error! status: ${response.status}`);
-            }
-
-            return await response.json();
-        } catch (error) {
-            if (attempt >= this.maxRetries) {
-                console.error("An error occured:", error.message);
-                console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
-                return null;
-            }
-
-            await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
-            return this.fetchWithRetry(address, attempt + 1);
-        }
-    },
-
-    clearCache() {
-        this.cache.clear();
-        this.pendingRequests.clear();
-    }
-};
-
 window.tableRateModule = {
     coinNameToSymbol: {
         'Bitcoin': 'BTC',
@@ -753,7 +55,12 @@ window.tableRateModule = {
         return null;
     },
 
-    setCachedValue(key, value, ttl = 900000) {
+    setCachedValue(key, value, resourceType = null) {
+        const ttl = resourceType ? 
+            window.config.cacheConfig.ttlSettings[resourceType] || 
+            window.config.cacheConfig.defaultTTL : 
+            900000;
+
         const item = {
             value: value,
             expiry: Date.now() + ttl,
@@ -762,7 +69,7 @@ window.tableRateModule = {
     },
 
     setFallbackValue(coinSymbol, value) {
-        this.setCachedValue(`fallback_${coinSymbol}_usd`, value, 24 * 60 * 60 * 1000);
+        this.setCachedValue(`fallback_${coinSymbol}_usd`, value, 'fallback');
     },
 
     isNewOffer(offerId) {
@@ -802,7 +109,6 @@ window.tableRateModule = {
         document.querySelectorAll('.coinname-value').forEach(coinNameValue => {
             const coinFullNameOrSymbol = coinNameValue.getAttribute('data-coinname');
             if (!coinFullNameOrSymbol || coinFullNameOrSymbol === 'Unknown') {
-                //console.warn('Missing or unknown coin name/symbol in data-coinname attribute');
                 return;
             }
             coinNameValue.classList.remove('hidden');
@@ -825,19 +131,15 @@ window.tableRateModule = {
     },
 
     init() {
-        //console.log('Initializing TableRateModule');
         this.initializeTable();
     }
 };
 
-// CORE SYSTEM FUNCTIONS
 function initializeTableRateModule() {
     if (typeof window.tableRateModule !== 'undefined') {
         tableRateModule = window.tableRateModule;
-        //console.log('tableRateModule loaded successfully');
         return true;
     } else {
-        //console.warn('tableRateModule not found. Waiting for it to load...');
         return false;
     }
 }
@@ -855,8 +157,6 @@ function continueInitialization() {
     if (listingLabel) {
         listingLabel.textContent = isSentOffers ? 'Total Listings: ' : 'Network Listings: ';
     }
-    //console.log('Initialization completed');
-
 }
 
 function initializeTooltips() {
@@ -865,36 +165,42 @@ function initializeTooltips() {
     }
 }
 
-// DATA PROCESSING FUNCTIONS
 function getValidOffers() {
     if (!jsonData) {
-        //console.warn('jsonData is undefined or null');
         return [];
     }
 
     const filteredData = filterAndSortData();
-    //console.log(`getValidOffers: Found ${filteredData.length} valid offers`);
     return filteredData;
 }
 
+function saveFilterSettings() {
+    const formData = new FormData(filterForm);
+    const filters = Object.fromEntries(formData);
+
+    const storageKey = isSentOffers ? 'sentOffersTableSettings' : 'networkOffersTableSettings';
+
+    localStorage.setItem(storageKey, JSON.stringify({
+        coin_to: filters.coin_to,
+        coin_from: filters.coin_from,
+        status: filters.status,
+        sent_from: filters.sent_from,
+        sortColumn: currentSortColumn,
+        sortDirection: currentSortDirection
+    }));
+}
+
 function filterAndSortData() {
    const formData = new FormData(filterForm);
    const filters = Object.fromEntries(formData);
 
-   localStorage.setItem('offersTableSettings', JSON.stringify({
-       coin_to: filters.coin_to,
-       coin_from: filters.coin_from,
-       status: filters.status,
-       sent_from: filters.sent_from,
-       sortColumn: currentSortColumn,
-       sortDirection: currentSortDirection
-   }));
+   saveFilterSettings();
 
    if (filters.coin_to !== 'any') {
-       filters.coin_to = coinIdToName[filters.coin_to] || filters.coin_to;
+       filters.coin_to = window.config.coinMappings.idToName[filters.coin_to] || filters.coin_to;
    }
    if (filters.coin_from !== 'any') {
-       filters.coin_from = coinIdToName[filters.coin_from] || filters.coin_from;
+       filters.coin_from = window.config.coinMappings.idToName[filters.coin_from] || filters.coin_from;
    }
 
    let filteredData = [...originalJsonData];
@@ -959,14 +265,14 @@ function filterAndSortData() {
        filteredData.sort((a, b) => {
            let comparison;
            switch(currentSortColumn) {
-               case 0: // Time
+               case 0:
                    comparison = a.created_at - b.created_at;
                    break;
-               case 5: // Rate
-               case 6: // Market +/-
+               case 5:
+               case 6:
                    comparison = sortValues.get(a.offer_id) - sortValues.get(b.offer_id);
                    break;
-               case 7: // Trade
+               case 7:
                    comparison = a.offer_id.localeCompare(b.offer_id);
                    break;
                default:
@@ -989,69 +295,77 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwn
 
         const getPriceKey = (coin) => {
             const lowerCoin = coin.toLowerCase();
-            if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
-                return 'zcoin';
-            }
-            if (lowerCoin === 'bitcoin cash') {
-                return 'bitcoin-cash';
-            }
-            if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') {
-                return 'particl';
-            }
-            return coinNameToSymbol[coin] || lowerCoin;
+            const symbolToName = {
+                'btc': 'bitcoin',
+                'xmr': 'monero',
+                'part': 'particl',
+                'bch': 'bitcoin-cash',
+                'pivx': 'pivx',
+                'firo': 'firo',
+                'dash': 'dash',
+                'ltc': 'litecoin',
+                'doge': 'dogecoin',
+                'dcr': 'decred',
+                'wow': 'wownero'
+            };
+
+            if (lowerCoin === 'zcoin') return 'firo';
+            if (lowerCoin === 'bitcoin cash') return 'bitcoin-cash';
+            if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') return 'particl';
+            
+            return symbolToName[lowerCoin] || lowerCoin;
         };
 
         const fromSymbol = getPriceKey(fromCoin);
         const toSymbol = getPriceKey(toCoin);
-        let fromPriceUSD = latestPrices[fromSymbol]?.usd;
-        let toPriceUSD = latestPrices[toSymbol]?.usd;
+
+        let fromPriceUSD = latestPrices && latestPrices[fromSymbol] ? latestPrices[fromSymbol].usd : null;
+        let toPriceUSD = latestPrices && latestPrices[toSymbol] ? latestPrices[toSymbol].usd : null;
 
         if (!fromPriceUSD || !toPriceUSD) {
             fromPriceUSD = tableRateModule.getFallbackValue(fromSymbol);
             toPriceUSD = tableRateModule.getFallbackValue(toSymbol);
         }
+
         if (!fromPriceUSD || !toPriceUSD || isNaN(fromPriceUSD) || isNaN(toPriceUSD)) {
             resolve(null);
             return;
         }
+
         const fromValueUSD = fromAmount * fromPriceUSD;
         const toValueUSD = toAmount * toPriceUSD;
+
         if (isNaN(fromValueUSD) || isNaN(toValueUSD) || fromValueUSD === 0 || toValueUSD === 0) {
             resolve(null);
             return;
         }
+
         let percentDiff;
         if (isOwnOffer) {
             percentDiff = ((toValueUSD / fromValueUSD) - 1) * 100;
         } else {
             percentDiff = ((fromValueUSD / toValueUSD) - 1) * 100;
         }
+
         if (isNaN(percentDiff)) {
             resolve(null);
             return;
         }
+
         resolve(percentDiff);
     });
 }
 
 function getEmptyPriceData() {
-    return {
-        'bitcoin': { usd: null, btc: null },
-        'bitcoin-cash': { usd: null, btc: null },
-        'dash': { usd: null, btc: null },
-        'dogecoin': { usd: null, btc: null },
-        'decred': { usd: null, btc: null },
-        'litecoin': { usd: null, btc: null },
-        'particl': { usd: null, btc: null },
-        'pivx': { usd: null, btc: null },
-        'monero': { usd: null, btc: null },
-        'zano': { usd: null, btc: null },
-        'wownero': { usd: null, btc: null },
-        'zcoin': { usd: null, btc: null }
-    };
+    return window.config.utils.getEmptyPriceData();
 }
 
 async function fetchLatestPrices() {
+    if (!NetworkManager.isOnline()) {
+        const cachedData = CacheManager.get('prices_coingecko');
+        return cachedData?.value || getEmptyPriceData();
+    }
+
     if (WebSocketManager.isPageHidden || WebSocketManager.priceUpdatePaused) {
         const cachedData = CacheManager.get('prices_coingecko');
         return cachedData?.value || getEmptyPriceData();
@@ -1086,48 +400,65 @@ async function fetchLatestPrices() {
     }
 
     try {
-        const existingCache = CacheManager.get(PRICES_CACHE_KEY, true);
+        const existingCache = CacheManager.get(PRICES_CACHE_KEY);
         const fallbackData = existingCache ? existingCache.value : null;
-        const url = `${offersConfig.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${offersConfig.apiKeys.coinGecko}`;
 
-        const response = await fetch('/json/readurl', {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            },
-            body: JSON.stringify({
-                url: url,
-                headers: {
-                    'User-Agent': 'Mozilla/5.0',
-                    'Accept': 'application/json',
-                    'Accept-Language': 'en-US,en;q=0.5'
+        const coinIds = [
+            'bitcoin', 'particl', 'monero', 'litecoin',
+            'dogecoin', 'firo', 'dash', 'pivx',
+            'decred', 'bitcoincash'
+        ];
+
+        let processedData = {};
+        const MAX_RETRIES = 3;
+
+        for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
+            try {
+                const mainResponse = await Api.fetchCoinPrices(coinIds);
+
+                if (mainResponse && mainResponse.rates) {
+                    Object.entries(mainResponse.rates).forEach(([coinId, price]) => {
+                        const normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
+                        
+                        processedData[normalizedCoinId] = {
+                            usd: price,
+                            btc: normalizedCoinId === 'bitcoin' ? 1 : price / (mainResponse.rates.bitcoin || 1)
+                        };
+                    });
                 }
-            })
-        });
 
-        if (!response.ok) {
-            throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
-        }
+                try {
+                    const wowResponse = await Api.fetchCoinPrices("wownero");
 
-        const data = await response.json();
+                    if (wowResponse && wowResponse.rates && wowResponse.rates.wownero) {
+                        processedData['wownero'] = {
+                            usd: wowResponse.rates.wownero,
+                            btc: processedData.bitcoin ? wowResponse.rates.wownero / processedData.bitcoin.usd : 0
+                        };
+                    }
+                } catch (wowError) {
+                    console.error('Error fetching WOW price:', wowError);
+                }
 
-        if (data.Error) {
-            if (fallbackData) {
-                return fallbackData;
+                latestPrices = processedData;
+                CacheManager.set(PRICES_CACHE_KEY, processedData, 'prices');
+
+                Object.entries(processedData).forEach(([coin, prices]) => {
+                    if (prices.usd) {
+                        tableRateModule.setFallbackValue(coin, prices.usd);
+                    }
+                });
+
+                return processedData;
+            } catch (error) {
+                console.error(`Price fetch attempt ${attempt + 1} failed:`, error);
+                NetworkManager.handleNetworkError(error);
+                
+                if (attempt < MAX_RETRIES - 1) {
+                    const delay = Math.min(500 * Math.pow(2, attempt), 5000);
+                    await new Promise(resolve => setTimeout(resolve, delay));
+                }
             }
-            throw new Error(data.Error);
-        }
-
-        if (data && Object.keys(data).length > 0) {
-            latestPrices = data;
-            CacheManager.set(PRICES_CACHE_KEY, data, CACHE_DURATION);
-
-            Object.entries(data).forEach(([coin, prices]) => {
-                if (prices.usd) {
-                    tableRateModule.setFallbackValue(coin, prices.usd);
-                }
-            });
-            return data;
         }
 
         if (fallbackData) {
@@ -1146,16 +477,11 @@ async function fetchLatestPrices() {
             return fallbackPrices;
         }
 
-        return null;
+        return getEmptyPriceData();
     } catch (error) {
-        const fallbackPrices = {};
-        Object.keys(getEmptyPriceData()).forEach(coin => {
-            const fallbackValue = tableRateModule.getFallbackValue(coin);
-            if (fallbackValue !== null) {
-                fallbackPrices[coin] = { usd: fallbackValue, btc: null };
-            }
-        });
-        return Object.keys(fallbackPrices).length > 0 ? fallbackPrices : null;
+        console.error('Unexpected error in fetchLatestPrices:', error);
+        NetworkManager.handleNetworkError(error);
+        return getEmptyPriceData();
     } finally {
         window.isManualRefresh = false;
     }
@@ -1167,6 +493,10 @@ async function fetchOffers() {
     const refreshText = document.getElementById('refreshText');
 
     try {
+        if (!NetworkManager.isOnline()) {
+            throw new Error('Network is offline');
+        }
+
         if (refreshButton) {
             refreshButton.disabled = true;
             refreshIcon.classList.add('animate-spin');
@@ -1190,12 +520,15 @@ async function fetchOffers() {
         originalJsonData = [...jsonData];
 
         latestPrices = pricesData || getEmptyPriceData();
+        
+        CacheManager.set('offers_cached', jsonData, 'offers');
 
         await updateOffersTable();
         updatePaginationInfo();
 
     } catch (error) {
         console.error('[Debug] Error fetching offers:', error);
+        NetworkManager.handleNetworkError(error);
 
         const cachedOffers = CacheManager.get('offers_cached');
         if (cachedOffers?.value) {
@@ -1235,13 +568,11 @@ function formatInitialData(data) {
     }));
 }
 
-// UI COMPONENT FUNCTIONS
 function updateConnectionStatus(status) {
     const dot = document.getElementById('status-dot');
     const text = document.getElementById('status-text');
 
     if (!dot || !text) {
-        //console.warn('Status indicators not found in DOM');
         return;
     }
 
@@ -1375,7 +706,180 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
             profitLossElement.textContent = `${percentDiffDisplay}%`;
             profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`;
 
-            // Update tooltip if it exists
+            const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`;
+            const tooltipElement = document.getElementById(tooltipId);
+            if (tooltipElement) {
+                const tooltipContent = createTooltipContent(isSentOffers || isOwnOffer, fromCoin, toCoin, fromAmount, toAmount);
+                tooltipElement.innerHTML = `
+                    <div class="tooltip-content">
+                        ${tooltipContent}
+                    </div>
+                    <div class="tooltip-arrow" data-popper-arrow></div>
+                `;
+            }
+        })
+        .catch(error => {
+            console.error('Error in updateProfitLoss:', error);
+            profitLossElement.textContent = 'N/A';
+            profitLossElement.className = 'profit-loss text-lg font-bold text-gray-300';
+        });
+}
+
+function updateClearFiltersButton() {
+    const clearButton = document.getElementById('clearFilters');
+    if (clearButton) {
+        const hasFilters = hasActiveFilters();
+        clearButton.classList.toggle('opacity-50', !hasFilters);
+        clearButton.disabled = !hasFilters;
+
+        if (hasFilters) {
+            clearButton.classList.add('hover:bg-green-600', 'hover:text-white');
+            clearButton.classList.remove('cursor-not-allowed');
+        } else {
+            clearButton.classList.remove('hover:bg-green-600', 'hover:text-white');
+            clearButton.classList.add('cursor-not-allowed');
+        }
+    }
+}
+
+function updateConnectionStatus(status) {
+    const dot = document.getElementById('status-dot');
+    const text = document.getElementById('status-text');
+
+    if (!dot || !text) {
+        return;
+    }
+
+    switch(status) {
+        case 'connected':
+            dot.className = 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2';
+            text.textContent = 'Connected';
+            text.className = 'text-sm text-green-500';
+            break;
+        case 'disconnected':
+            dot.className = 'w-2.5 h-2.5 rounded-full bg-red-500 mr-2';
+            text.textContent = 'Disconnected - Reconnecting...';
+            text.className = 'text-sm text-red-500';
+            break;
+        case 'error':
+            dot.className = 'w-2.5 h-2.5 rounded-full bg-yellow-500 mr-2';
+            text.textContent = 'Connection Error';
+            text.className = 'text-sm text-yellow-500';
+            break;
+        default:
+            dot.className = 'w-2.5 h-2.5 rounded-full bg-gray-500 mr-2';
+            text.textContent = 'Connecting...';
+            text.className = 'text-sm text-gray-500';
+    }
+}
+
+function updateRowTimes() {
+    requestAnimationFrame(() => {
+        const rows = document.querySelectorAll('[data-offer-id]');
+        rows.forEach(row => {
+            const offerId = row.getAttribute('data-offer-id');
+            const offer = jsonData.find(o => o.offer_id === offerId);
+            if (!offer) return;
+
+            const newPostedTime = formatTime(offer.created_at, true);
+            const newExpiresIn = formatTimeLeft(offer.expire_at);
+
+            const postedElement = row.querySelector('.text-xs:first-child');
+            const expiresElement = row.querySelector('.text-xs:last-child');
+
+            if (postedElement && postedElement.textContent !== `Posted: ${newPostedTime}`) {
+                postedElement.textContent = `Posted: ${newPostedTime}`;
+            }
+            if (expiresElement && expiresElement.textContent !== `Expires in: ${newExpiresIn}`) {
+                expiresElement.textContent = `Expires in: ${newExpiresIn}`;
+            }
+        });
+    });
+}
+
+function updateLastRefreshTime() {
+    if (lastRefreshTimeSpan) {
+        lastRefreshTimeSpan.textContent = lastRefreshTime ? new Date(lastRefreshTime).toLocaleTimeString() : 'Never';
+    }
+}
+
+function stopRefreshAnimation() {
+    const refreshButton = document.getElementById('refreshOffers');
+    const refreshIcon = document.getElementById('refreshIcon');
+    const refreshText = document.getElementById('refreshText');
+
+    if (refreshButton) {
+        refreshButton.disabled = false;
+        refreshButton.classList.remove('opacity-75', 'cursor-wait');
+    }
+    if (refreshIcon) {
+        refreshIcon.classList.remove('animate-spin');
+    }
+    if (refreshText) {
+        refreshText.textContent = 'Refresh';
+    }
+}
+
+function updatePaginationInfo() {
+    const validOffers = getValidOffers();
+    const totalItems = validOffers.length;
+    const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
+
+    currentPage = Math.max(1, Math.min(currentPage, totalPages));
+
+    currentPageSpan.textContent = currentPage;
+    totalPagesSpan.textContent = totalPages;
+
+    const showPrev = currentPage > 1;
+    const showNext = currentPage < totalPages && totalItems > 0;
+
+    prevPageButton.style.display = showPrev ? 'inline-flex' : 'none';
+    nextPageButton.style.display = showNext ? 'inline-flex' : 'none';
+
+    if (lastRefreshTime) {
+        lastRefreshTimeSpan.textContent = new Date(lastRefreshTime).toLocaleTimeString();
+    }
+
+    if (newEntriesCountSpan) {
+        newEntriesCountSpan.textContent = totalItems;
+    }
+}
+
+function updatePaginationControls(totalPages) {
+    prevPageButton.style.display = currentPage > 1 ? 'inline-flex' : 'none';
+    nextPageButton.style.display = currentPage < totalPages ? 'inline-flex' : 'none';
+    currentPageSpan.textContent = currentPage;
+    totalPagesSpan.textContent = totalPages;
+}
+
+function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) {
+    const profitLossElement = row.querySelector('.profit-loss');
+    if (!profitLossElement) {
+        return;
+    }
+
+    if (!fromCoin || !toCoin) {
+        profitLossElement.textContent = 'N/A';
+        profitLossElement.className = 'profit-loss text-lg font-bold text-gray-300';
+        return;
+    }
+
+    calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer)
+        .then(percentDiff => {
+            if (percentDiff === null || isNaN(percentDiff)) {
+                profitLossElement.textContent = 'N/A';
+                profitLossElement.className = 'profit-loss text-lg font-bold text-gray-300';
+                return;
+            }
+
+            const formattedPercentDiff = percentDiff.toFixed(2);
+            const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
+                                     (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
+
+            const colorClass = getProfitColorClass(percentDiff);
+            profitLossElement.textContent = `${percentDiffDisplay}%`;
+            profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`;
+
             const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`;
             const tooltipElement = document.getElementById(tooltipId);
             if (tooltipElement) {
@@ -1406,12 +910,14 @@ function updateCoinFilterImages() {
         const imagePath = selectedOption.getAttribute('data-image');
         if (imagePath && select.value !== 'any') {
             button.style.backgroundImage = `url(${imagePath})`;
-            button.style.backgroundSize = 'contain';
-            button.style.backgroundRepeat = 'no-repeat';
+            button.style.backgroundSize = '25px 25px';
             button.style.backgroundPosition = 'center';
+            button.style.backgroundRepeat = 'no-repeat';
         } else {
             button.style.backgroundImage = 'none';
         }
+        button.style.minWidth = '25px';
+        button.style.minHeight = '25px';
     }
 
     updateButtonImage(coinToSelect, coinToButton);
@@ -1436,23 +942,33 @@ function updateClearFiltersButton() {
 }
 
 function cleanupRow(row) {
+    if (!row) return;
+
     const tooltipTriggers = row.querySelectorAll('[data-tooltip-trigger-id]');
     tooltipTriggers.forEach(trigger => {
         if (window.TooltipManager) {
             window.TooltipManager.destroy(trigger);
         }
     });
-    EventManager.removeAll(row);
+
+    CleanupManager.removeListenersByElement(row);
+
+    row.removeAttribute('data-offer-id');
+
+    while (row.firstChild) {
+        const child = row.firstChild;
+        row.removeChild(child);
+    }
 }
 
-
 function cleanupTable() {
-    EventManager.clearAll();
-    if (offersBody) {
-        const existingRows = offersBody.querySelectorAll('tr');
-        existingRows.forEach(row => cleanupRow(row));
-        offersBody.innerHTML = '';
-    }
+    if (!offersBody) return;
+
+    const existingRows = offersBody.querySelectorAll('tr');
+    existingRows.forEach(row => cleanupRow(row));
+
+    offersBody.innerHTML = '';
+
     if (window.TooltipManager) {
         window.TooltipManager.cleanup();
     }
@@ -1645,8 +1161,8 @@ function createTableRow(offer, identity = null) {
        is_public: isPublic
    } = offer;
 
-   const coinFromSymbol = coinNameToSymbol[coinFrom] || coinFrom.toLowerCase();
-   const coinToSymbol = coinNameToSymbol[coinTo] || coinTo.toLowerCase();
+   const coinFromSymbol = window.config.coinMappings.nameToSymbol[coinFrom] || coinFrom.toLowerCase();
+   const coinToSymbol = window.config.coinMappings.nameToSymbol[coinTo] || coinTo.toLowerCase();
    const coinFromDisplay = getDisplayName(coinFrom);
    const coinToDisplay = getDisplayName(coinTo);
    const postedTime = formatTime(createdAt, true);
@@ -1663,7 +1179,7 @@ function createTableRow(offer, identity = null) {
        ${createDetailsColumn(offer, identity)}
        ${createTakerAmountColumn(offer, coinTo, coinFrom)}
        ${createSwapColumn(offer, coinFromDisplay, coinToDisplay, coinFromSymbol, coinToSymbol)}
-       ${createOrderbookColumn(offer, coinFrom, coinTo)}
+       ${createOrderbookColumn(offer, coinFrom)}
        ${createRateColumn(offer, coinFrom, coinTo)}
        ${createPercentageColumn(offer)}
        ${createActionColumn(offer, isActuallyExpired)}
@@ -1698,11 +1214,11 @@ function createTimeColumn(offer, postedTime, expiresIn) {
     const now = Math.floor(Date.now() / 1000);
     const timeLeft = offer.expire_at - now;
 
-    let strokeColor = '#10B981'; // Default green for > 30 min
+    let strokeColor = '#10B981';
     if (timeLeft <= 300) {
-        strokeColor = '#9CA3AF'; // Grey for 5 min or less
+        strokeColor = '#9CA3AF';
     } else if (timeLeft <= 1800) {
-        strokeColor = '#3B82F6'; // Blue for 5-30 min
+        strokeColor = '#3B82F6';
     }
 
     return `
@@ -1836,22 +1352,31 @@ function createRateColumn(offer, coinFrom, coinTo) {
     const inverseRate = rate ? (1 / rate) : 0;
 
     const getPriceKey = (coin) => {
-        if (!coin) return null;
         const lowerCoin = coin.toLowerCase();
-        if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
-            return 'zcoin';
-        }
-        if (lowerCoin === 'bitcoin cash') {
-            return 'bitcoin-cash';
-        }
-        if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') {
-            return 'particl';
-        }
-        return coinNameToSymbol[coin] || lowerCoin;
+        
+        const symbolToName = {
+            'btc': 'bitcoin',
+            'xmr': 'monero',
+            'part': 'particl',
+            'bch': 'bitcoin-cash',
+            'pivx': 'pivx',
+            'firo': 'firo',
+            'dash': 'dash',
+            'ltc': 'litecoin',
+            'doge': 'dogecoin',
+            'dcr': 'decred',
+            'wow': 'wownero'
+        };
+        
+        if (lowerCoin === 'zcoin') return 'firo';
+        if (lowerCoin === 'bitcoin cash') return 'bitcoin-cash';
+        if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') return 'particl';
+        
+        return symbolToName[lowerCoin] || lowerCoin;
     };
 
     const toSymbolKey = getPriceKey(coinTo);
-    let toPriceUSD = latestPrices && toSymbolKey ? latestPrices[toSymbolKey]?.usd : null;
+    let toPriceUSD = latestPrices && latestPrices[toSymbolKey] ? latestPrices[toSymbolKey].usd : null;
 
     if (!toPriceUSD || isNaN(toPriceUSD)) {
         toPriceUSD = tableRateModule.getFallbackValue(toSymbolKey);
@@ -1927,7 +1452,6 @@ function createActionColumn(offer, isActuallyExpired = false) {
     `;
 }
 
-// TOOLTIP FUNCTIONS
 function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount, postedTime, expiresIn, isActuallyExpired, isRevoked, identity = null) {
     const uniqueId = `${offer.offer_id}_${offer.created_at}`;
 
@@ -2119,19 +1643,46 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
 
     const getPriceKey = (coin) => {
         const lowerCoin = coin.toLowerCase();
-        return lowerCoin === 'firo' || lowerCoin === 'zcoin' ? 'zcoin' :
-               lowerCoin === 'bitcoin cash' ? 'bitcoin-cash' :
-               lowerCoin === 'particl anon' || lowerCoin === 'particl blind' ? 'particl' :
-               coinNameToSymbol[coin] || lowerCoin;
+
+        const symbolToName = {
+            'btc': 'bitcoin',
+            'xmr': 'monero',
+            'part': 'particl',
+            'bch': 'bitcoin-cash',
+            'pivx': 'pivx',
+            'firo': 'firo',
+            'dash': 'dash',
+            'ltc': 'litecoin',
+            'doge': 'dogecoin',
+            'dcr': 'decred',
+            'wow': 'wownero'
+        };
+
+        if (lowerCoin === 'zcoin') return 'firo';
+        if (lowerCoin === 'bitcoin cash') return 'bitcoin-cash';
+        if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') return 'particl';
+        
+        return symbolToName[lowerCoin] || lowerCoin;
     };
+    
+    if (latestPrices && latestPrices['firo'] && !latestPrices['zcoin']) {
+        latestPrices['zcoin'] = JSON.parse(JSON.stringify(latestPrices['firo']));
+    }
 
     const fromSymbol = getPriceKey(coinFrom);
     const toSymbol = getPriceKey(coinTo);
-    const fromPriceUSD = latestPrices[fromSymbol]?.usd;
-    const toPriceUSD = latestPrices[toSymbol]?.usd;
+    
+    let fromPriceUSD = latestPrices && latestPrices[fromSymbol] ? latestPrices[fromSymbol].usd : null;
+    let toPriceUSD = latestPrices && latestPrices[toSymbol] ? latestPrices[toSymbol].usd : null;
+
+    if (!fromPriceUSD || !toPriceUSD) {
+        fromPriceUSD = tableRateModule.getFallbackValue(fromSymbol);
+        toPriceUSD = tableRateModule.getFallbackValue(toSymbol);
+    }
 
     if (fromPriceUSD === null || toPriceUSD === null ||
-        fromPriceUSD === undefined || toPriceUSD === undefined) {
+        fromPriceUSD === undefined || toPriceUSD === undefined ||
+        isNaN(fromPriceUSD) || isNaN(toPriceUSD)) {
         return `<p class="font-bold mb-1">Price Information Unavailable</p>
                 <p>Current market prices are temporarily unavailable.</p>
                 <p class="mt-2">You are ${isSentOffers ? 'selling' : 'buying'} ${fromAmount.toFixed(8)} ${coinFrom} 
@@ -2194,25 +1745,46 @@ function createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer) {
 
     const getPriceKey = (coin) => {
         const lowerCoin = coin.toLowerCase();
-        if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
-            return 'zcoin';
-        }
-        if (lowerCoin === 'bitcoin cash') {
-            return 'bitcoin-cash';
-        }
-        if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') {
-            return 'particl';
-        }
-        return coinNameToSymbol[coin] || lowerCoin;
+
+        const symbolToName = {
+            'btc': 'bitcoin',
+            'xmr': 'monero',
+            'part': 'particl',
+            'bch': 'bitcoin-cash',
+            'pivx': 'pivx',
+            'firo': 'firo',
+            'dash': 'dash',
+            'ltc': 'litecoin',
+            'doge': 'dogecoin',
+            'dcr': 'decred',
+            'wow': 'wownero'
+        };
+
+        if (lowerCoin === 'zcoin') return 'firo';
+        if (lowerCoin === 'bitcoin cash') return 'bitcoin-cash';
+        if (lowerCoin === 'particl anon' || lowerCoin === 'particl blind') return 'particl';
+        
+        return symbolToName[lowerCoin] || lowerCoin;
     };
 
+    if (latestPrices && latestPrices['firo'] && !latestPrices['zcoin']) {
+        latestPrices['zcoin'] = JSON.parse(JSON.stringify(latestPrices['firo']));
+    }
+
     const fromSymbol = getPriceKey(coinFrom);
     const toSymbol = getPriceKey(coinTo);
-    const fromPriceUSD = latestPrices[fromSymbol]?.usd;
-    const toPriceUSD = latestPrices[toSymbol]?.usd;
+
+    let fromPriceUSD = latestPrices && latestPrices[fromSymbol] ? latestPrices[fromSymbol].usd : null;
+    let toPriceUSD = latestPrices && latestPrices[toSymbol] ? latestPrices[toSymbol].usd : null;
+
+    if (!fromPriceUSD || !toPriceUSD) {
+        fromPriceUSD = tableRateModule.getFallbackValue(fromSymbol);
+        toPriceUSD = tableRateModule.getFallbackValue(toSymbol);
+    }
 
     if (fromPriceUSD === null || toPriceUSD === null ||
-        fromPriceUSD === undefined || toPriceUSD === undefined) {
+        fromPriceUSD === undefined || toPriceUSD === undefined ||
+        isNaN(fromPriceUSD) || isNaN(toPriceUSD)) {
         return `
             <p class="font-bold mb-1">Exchange Rate Information</p>
             <p>Market price data is temporarily unavailable.</p>
@@ -2267,7 +1839,6 @@ function updateTooltipTargets(row, uniqueId) {
     });
 }
 
-// FILTER FUNCTIONS
 function applyFilters() {
     if (filterTimeout) {
         clearTimeout(filterTimeout);
@@ -2289,7 +1860,6 @@ function applyFilters() {
 }
 
 function clearFilters() {
-
     filterForm.reset();
 
     const selectElements = filterForm.querySelectorAll('select');
@@ -2306,6 +1876,9 @@ function clearFilters() {
 
     jsonData = [...originalJsonData];
     currentPage = 1;
+    
+    const storageKey = isSentOffers ? 'sentOffersTableSettings' : 'networkOffersTableSettings';
+    localStorage.removeItem(storageKey);
 
     updateOffersTable();
     updateCoinFilterImages();
@@ -2324,18 +1897,16 @@ function hasActiveFilters() {
 
     return hasChangedFilters;
 }
-// UTILITY FUNCTIONS
+
 function formatTimeLeft(timestamp) {
-    const now = Math.floor(Date.now() / 1000);
-    if (timestamp <= now) return "Expired";
-    return formatTime(timestamp);
+    return window.config.utils.formatTimeLeft(timestamp);
 }
 
 function getDisplayName(coinName) {
     if (coinName.toLowerCase() === 'zcoin') {
         return 'Firo';
     }
-    return coinNameToDisplayName[coinName] || coinName;
+    return window.config.coinMappings.nameToDisplayName[coinName] || coinName;
 }
 
 function getCoinSymbolLowercase(coin) {
@@ -2343,43 +1914,16 @@ function getCoinSymbolLowercase(coin) {
         if (coin.toLowerCase() === 'bitcoin cash') {
             return 'bitcoin-cash';
         }
-        return (coinNameToSymbol[coin] || coin).toLowerCase();
+        return (window.config.coinMappings.nameToSymbol[coin] || coin).toLowerCase();
     } else if (coin && typeof coin === 'object' && coin.symbol) {
         return coin.symbol.toLowerCase();
     } else {
-        //console.warn('Invalid coin input:', coin);
         return 'unknown';
     }
 }
 
 function coinMatches(offerCoin, filterCoin) {
-    if (!offerCoin || !filterCoin) return false;
-
-    offerCoin = offerCoin.toLowerCase();
-    filterCoin = filterCoin.toLowerCase();
-
-    if (offerCoin === filterCoin) return true;
-
-    if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
-        (filterCoin === 'firo' || filterCoin === 'zcoin')) {
-        return true;
-    }
-
-    if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
-        (offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
-        return true;
-    }
-
-    const particlVariants = ['particl', 'particl anon', 'particl blind'];
-    if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
-        return true;
-    }
-
-    if (particlVariants.includes(filterCoin)) {
-        return offerCoin === filterCoin;
-    }
-
-    return false;
+    return window.config.coinMatches(offerCoin, filterCoin);
 }
 
 function getProfitColorClass(percentage) {
@@ -2396,68 +1940,30 @@ function isOfferExpired(offer) {
     }
     const currentTime = Math.floor(Date.now() / 1000);
     const isExpired = offer.expire_at <= currentTime;
-    if (isExpired) {
-       // console.log(`Offer ${offer.offer_id} is expired. Expire time: ${offer.expire_at}, Current time: ${currentTime}`);
-    }
     return isExpired;
 }
 
 function formatTime(timestamp, addAgoSuffix = false) {
-    const now = Math.floor(Date.now() / 1000);
-    const diff = Math.abs(now - timestamp);
-
-    let timeString;
-    if (diff < 60) {
-        timeString = `${diff} seconds`;
-    } else if (diff < 3600) {
-        timeString = `${Math.floor(diff / 60)} minutes`;
-    } else if (diff < 86400) {
-        timeString = `${Math.floor(diff / 3600)} hours`;
-    } else if (diff < 2592000) {
-        timeString = `${Math.floor(diff / 86400)} days`;
-    } else if (diff < 31536000) {
-        timeString = `${Math.floor(diff / 2592000)} months`;
-    } else {
-        timeString = `${Math.floor(diff / 31536000)} years`;
-    }
-
-    return addAgoSuffix ? `${timeString} ago` : timeString;
+    return window.config.utils.formatTime(timestamp, addAgoSuffix);
 }
 
 function escapeHtml(unsafe) {
-    if (typeof unsafe !== 'string') {
-        //console.warn('escapeHtml received a non-string value:', unsafe);
-        return '';
-    }
-    return unsafe
-        .replace(/&/g, "&amp;")
-        .replace(/</g, "&lt;")
-        .replace(/>/g, "&gt;")
-        .replace(/"/g, "&quot;")
-        .replace(/'/g, "&#039;");
+    return window.config.utils.escapeHtml(unsafe);
 }
 
 function getCoinSymbol(fullName) {
-    const symbolMap = {
-        'Bitcoin': 'BTC', 'Litecoin': 'LTC', 'Monero': 'XMR',
-        'Particl': 'PART', 'Particl Blind': 'PART', 'Particl Anon': 'PART',
-        'PIVX': 'PIVX', 'Firo': 'FIRO', 'Zcoin': 'FIRO',
-        'Dash': 'DASH', 'Decred': 'DCR', 'Wownero': 'WOW',
-        'Bitcoin Cash': 'BCH', 'Dogecoin': 'DOGE'
-    };
-    return symbolMap[fullName] || fullName;
+    return window.config.coinMappings.nameToSymbol[fullName] || fullName;
 }
 
-// EVENT LISTENERS
 function initializeTableEvents() {
     const filterForm = document.getElementById('filterForm');
     if (filterForm) {
-        EventManager.add(filterForm, 'submit', (e) => {
+        CleanupManager.addListener(filterForm, 'submit', (e) => {
             e.preventDefault();
             applyFilters();
         });
 
-        EventManager.add(filterForm, 'change', () => {
+        CleanupManager.addListener(filterForm, 'change', () => {
             applyFilters();
             updateClearFiltersButton();
         });
@@ -2467,14 +1973,14 @@ function initializeTableEvents() {
     const coinFromSelect = document.getElementById('coin_from');
 
     if (coinToSelect) {
-        EventManager.add(coinToSelect, 'change', () => {
+        CleanupManager.addListener(coinToSelect, 'change', () => {
             applyFilters();
             updateCoinFilterImages();
         });
     }
 
     if (coinFromSelect) {
-        EventManager.add(coinFromSelect, 'change', () => {
+        CleanupManager.addListener(coinFromSelect, 'change', () => {
             applyFilters();
             updateCoinFilterImages();
         });
@@ -2482,116 +1988,117 @@ function initializeTableEvents() {
 
     const clearFiltersBtn = document.getElementById('clearFilters');
     if (clearFiltersBtn) {
-        EventManager.add(clearFiltersBtn, 'click', () => {
+        CleanupManager.addListener(clearFiltersBtn, 'click', () => {
             clearFilters();
             updateCoinFilterImages();
         });
     }
 
-const refreshButton = document.getElementById('refreshOffers');
-if (refreshButton) {
-    let lastRefreshTime = 0;
-    const REFRESH_COOLDOWN = 6000;
-    let countdownInterval;
+    const refreshButton = document.getElementById('refreshOffers');
+    if (refreshButton) {
+        let lastRefreshTime = 0;
+        const REFRESH_COOLDOWN = 6000;
+        let countdownInterval;
 
-    EventManager.add(refreshButton, 'click', async () => {
-        const now = Date.now();
-        if (now - lastRefreshTime < REFRESH_COOLDOWN) {
-            console.log('Refresh rate limited. Please wait before refreshing again.');
-            const startTime = now;
-            const refreshText = document.getElementById('refreshText');
+        CleanupManager.addListener(refreshButton, 'click', async () => {
+            const now = Date.now();
+            if (now - lastRefreshTime < REFRESH_COOLDOWN) {
+                console.log('Refresh rate limited. Please wait before refreshing again.');
+                const startTime = now;
+                const refreshText = document.getElementById('refreshText');
 
-            refreshButton.classList.remove('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
-            refreshButton.classList.add('bg-red-600', 'border-red-500', 'cursor-not-allowed');
+                refreshButton.classList.remove('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
+                refreshButton.classList.add('bg-red-600', 'border-red-500', 'cursor-not-allowed');
 
-            if (countdownInterval) clearInterval(countdownInterval);
+                if (countdownInterval) clearInterval(countdownInterval);
 
-            countdownInterval = setInterval(() => {
-                const currentTime = Date.now();
-                const elapsedTime = currentTime - startTime;
-                const remainingTime = Math.ceil((REFRESH_COOLDOWN - elapsedTime) / 1000);
+                countdownInterval = setInterval(() => {
+                    const currentTime = Date.now();
+                    const elapsedTime = currentTime - startTime;
+                    const remainingTime = Math.ceil((REFRESH_COOLDOWN - elapsedTime) / 1000);
 
-                if (remainingTime <= 0) {
-                    clearInterval(countdownInterval);
-                    refreshText.textContent = 'Refresh';
+                    if (remainingTime <= 0) {
+                        clearInterval(countdownInterval);
+                        refreshText.textContent = 'Refresh';
 
-                    refreshButton.classList.remove('bg-red-600', 'border-red-500', 'cursor-not-allowed');
-                    refreshButton.classList.add('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
-                } else {
-                    refreshText.textContent = `Refresh (${remainingTime}s)`;
-                }
-            }, 100);
-            return;
-        }
-
-        console.log('Manual refresh initiated');
-        lastRefreshTime = now;
-        const refreshIcon = document.getElementById('refreshIcon');
-        const refreshText = document.getElementById('refreshText');
-        refreshButton.disabled = true;
-        refreshIcon.classList.add('animate-spin');
-        refreshText.textContent = 'Refreshing...';
-        refreshButton.classList.add('opacity-75', 'cursor-wait');
-
-        try {
-            const cachedPrices = CacheManager.get('prices_coingecko');
-            const previousPrices = cachedPrices ? cachedPrices.value : null;
-            CacheManager.clear();
-            window.isManualRefresh = true;
-            const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
-            const response = await fetch(endpoint);
-            if (!response.ok) {
-                throw new Error(`HTTP error! status: ${response.status}`);
+                        refreshButton.classList.remove('bg-red-600', 'border-red-500', 'cursor-not-allowed');
+                        refreshButton.classList.add('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
+                    } else {
+                        refreshText.textContent = `Refresh (${remainingTime}s)`;
+                    }
+                }, 100);
+                return;
             }
-            const newData = await response.json();
-            const processedNewData = Array.isArray(newData) ? newData : Object.values(newData);
-            jsonData = formatInitialData(processedNewData);
-            originalJsonData = [...jsonData];
-            const priceData = await fetchLatestPrices();
-            if (!priceData && previousPrices) {
-                console.log('Using previous price data after failed refresh');
-                latestPrices = previousPrices;
-                await updateOffersTable();
-            } else if (priceData) {
-                latestPrices = priceData;
-                await updateOffersTable();
-            } else {
-                throw new Error('Unable to fetch price data');
-            }
-            updatePaginationInfo();
+
+            console.log('Manual refresh initiated');
             lastRefreshTime = now;
-            updateLastRefreshTime();
+            const refreshIcon = document.getElementById('refreshIcon');
+            const refreshText = document.getElementById('refreshText');
+            refreshButton.disabled = true;
+            refreshIcon.classList.add('animate-spin');
+            refreshText.textContent = 'Refreshing...';
+            refreshButton.classList.add('opacity-75', 'cursor-wait');
 
-            console.log('Manual refresh completed successfully');
+            try {
+                const cachedPrices = CacheManager.get('prices_coingecko');
+                const previousPrices = cachedPrices ? cachedPrices.value : null;
+                CacheManager.clear();
+                window.isManualRefresh = true;
+                const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
+                const response = await fetch(endpoint);
+                if (!response.ok) {
+                    throw new Error(`HTTP error! status: ${response.status}`);
+                }
+                const newData = await response.json();
+                const processedNewData = Array.isArray(newData) ? newData : Object.values(newData);
+                jsonData = formatInitialData(processedNewData);
+                originalJsonData = [...jsonData];
+                const priceData = await fetchLatestPrices();
+                if (!priceData && previousPrices) {
+                    console.log('Using previous price data after failed refresh');
+                    latestPrices = previousPrices;
+                    await updateOffersTable();
+                } else if (priceData) {
+                    latestPrices = priceData;
+                    await updateOffersTable();
+                } else {
+                    throw new Error('Unable to fetch price data');
+                }
+                updatePaginationInfo();
+                lastRefreshTime = now;
+                updateLastRefreshTime();
 
-        } catch (error) {
-            console.error('Error during manual refresh:', error);
-            ui.displayErrorMessage('Unable to refresh data. Previous data will be preserved.');
+                console.log('Manual refresh completed successfully');
 
-            const cachedData = CacheManager.get('prices_coingecko');
-            if (cachedData?.value) {
-                latestPrices = cachedData.value;
-                await updateOffersTable();
+            } catch (error) {
+                console.error('Error during manual refresh:', error);
+                NetworkManager.handleNetworkError(error);
+                ui.displayErrorMessage('Unable to refresh data. Previous data will be preserved.');
+
+                const cachedData = CacheManager.get('prices_coingecko');
+                if (cachedData?.value) {
+                    latestPrices = cachedData.value;
+                    await updateOffersTable();
+                }
+            } finally {
+                window.isManualRefresh = false;
+                refreshButton.disabled = false;
+                refreshIcon.classList.remove('animate-spin');
+                refreshText.textContent = 'Refresh';
+                refreshButton.classList.remove('opacity-75', 'cursor-wait');
+
+                refreshButton.classList.remove('bg-red-600', 'border-red-500', 'cursor-not-allowed');
+                refreshButton.classList.add('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
+
+                if (countdownInterval) {
+                    clearInterval(countdownInterval);
+                }
             }
-        } finally {
-            window.isManualRefresh = false;
-            refreshButton.disabled = false;
-            refreshIcon.classList.remove('animate-spin');
-            refreshText.textContent = 'Refresh';
-            refreshButton.classList.remove('opacity-75', 'cursor-wait');
-
-            refreshButton.classList.remove('bg-red-600', 'border-red-500', 'cursor-not-allowed');
-            refreshButton.classList.add('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
-
-            if (countdownInterval) {
-                clearInterval(countdownInterval);
-            }
-        }
-    });
-}
+        });
+    }
 
     document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
-        EventManager.add(header, 'click', () => {
+        CleanupManager.addListener(header, 'click', async () => {
             const columnIndex = parseInt(header.getAttribute('data-column-index'));
             handleTableSort(columnIndex, header);
         });
@@ -2601,7 +2108,7 @@ if (refreshButton) {
     const nextPageButton = document.getElementById('nextPage');
 
     if (prevPageButton) {
-        EventManager.add(prevPageButton, 'click', () => {
+        CleanupManager.addListener(prevPageButton, 'click', () => {
             if (currentPage > 1) {
                 currentPage--;
                 updateOffersTable();
@@ -2610,7 +2117,7 @@ if (refreshButton) {
     }
 
     if (nextPageButton) {
-        EventManager.add(nextPageButton, 'click', () => {
+        CleanupManager.addListener(nextPageButton, 'click', () => {
             const totalPages = Math.ceil(jsonData.length / itemsPerPage);
             if (currentPage < totalPages) {
                 currentPage++;
@@ -2628,14 +2135,7 @@ function handleTableSort(columnIndex, header) {
         currentSortDirection = 'desc';
     }
 
-    localStorage.setItem('offersTableSettings', JSON.stringify({
-        coin_to: document.getElementById('coin_to').value,
-        coin_from: document.getElementById('coin_from').value,
-        status: document.getElementById('status')?.value || 'any',
-        sent_from: document.getElementById('sent_from').value,
-        sortColumn: currentSortColumn,
-        sortDirection: currentSortDirection
-    }));
+    saveFilterSettings();
 
     document.querySelectorAll('th[data-sortable="true"]').forEach(th => {
         const columnSpan = th.querySelector('span:not(.sort-icon)');
@@ -2674,39 +2174,6 @@ function handleTableSort(columnIndex, header) {
     }, 100);
 }
 
-// TIMER MANAGEMENT
-const timerManager = {
-    intervals: [],
-    timeouts: [],
-
-    addInterval(callback, delay) {
-        const intervalId = setInterval(callback, delay);
-        this.intervals.push(intervalId);
-        return intervalId;
-    },
-
-    addTimeout(callback, delay) {
-        const timeoutId = setTimeout(callback, delay);
-        this.timeouts.push(timeoutId);
-        return timeoutId;
-    },
-
-    clearAllIntervals() {
-        this.intervals.forEach(clearInterval);
-        this.intervals = [];
-    },
-
-    clearAllTimeouts() {
-        this.timeouts.forEach(clearTimeout);
-        this.timeouts = [];
-    },
-
-    clearAll() {
-        this.clearAllIntervals();
-        this.clearAllTimeouts();
-    }
-};
-
 async function initializeTableAndData() {
     loadSavedSettings();
     updateClearFiltersButton();
@@ -2719,12 +2186,15 @@ async function initializeTableAndData() {
         applyFilters();
     } catch (error) {
         console.error('Error loading initial data:', error);
+        NetworkManager.handleNetworkError(error);
         ui.displayErrorMessage('Error loading data. Retrying in background...');
     }
 }
 
 function loadSavedSettings() {
-    const saved = localStorage.getItem('offersTableSettings');
+    const storageKey = isSentOffers ? 'sentOffersTableSettings' : 'networkOffersTableSettings';
+    const saved = localStorage.getItem(storageKey);
+    
     if (saved) {
         const settings = JSON.parse(saved);
 
@@ -2755,174 +2225,162 @@ function updateSortIndicators() {
 }
 
 document.addEventListener('DOMContentLoaded', async () => {
-    const tableLoadPromise = initializeTableAndData();
+    if (window.NetworkManager && !window.networkManagerInitialized) {
+        NetworkManager.initialize({
+            connectionTestEndpoint: '/json',
+            connectionTestTimeout: 3000,
+            reconnectDelay: 5000, 
+            maxReconnectAttempts: 5
+        });
+        window.networkManagerInitialized = true;
+    }
 
-    WebSocketManager.initialize();
+    NetworkManager.addHandler('offline', () => {
+        ui.displayErrorMessage("Network connection lost. Will automatically retry when connection is restored.");
+        updateConnectionStatus('disconnected');
+    });
+
+    NetworkManager.addHandler('reconnected', () => {
+        ui.hideErrorMessage();
+        updateConnectionStatus('connected');
+        fetchOffers();
+    });
+
+    NetworkManager.addHandler('maxAttemptsReached', () => {
+        ui.displayErrorMessage("Server connection lost. Please check your internet connection and try refreshing the page.");
+        updateConnectionStatus('error');
+    });
+
+    const tableLoadPromise = initializeTableAndData();
+    
+    WebSocketManager.initialize({
+        debug: false
+    });
+
+    WebSocketManager.addMessageHandler('message', async (message) => {
+        try {
+            if (!NetworkManager.isOnline()) {
+                return;
+            }
+            
+            const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
+            const response = await fetch(endpoint);
+            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
+
+            const newData = await response.json();
+            const fetchedOffers = Array.isArray(newData) ? newData : Object.values(newData);
+
+            jsonData = formatInitialData(fetchedOffers);
+            originalJsonData = [...jsonData];
+
+            CacheManager.set('offers_cached', jsonData, 'offers');
+
+            requestAnimationFrame(() => {
+                updateOffersTable();
+                updatePaginationInfo();
+            });
+        } catch (error) {
+            console.error('[Debug] Error processing WebSocket message:', error);
+            NetworkManager.handleNetworkError(error);
+        }
+    });
 
     await tableLoadPromise;
 
-    timerManager.addInterval(() => {
-        if (WebSocketManager.isConnected()) {
-            console.log('🟢  WebSocket connection established for Offers');
-        }
-    }, 30000);
-
-    timerManager.addInterval(() => {
+    CleanupManager.setInterval(() => {
         CacheManager.cleanup();
     }, 300000);
 
-    timerManager.addInterval(updateRowTimes, 900000);
+    CleanupManager.setInterval(updateRowTimes, 900000);
 
-    EventManager.add(document, 'visibilitychange', () => {
+    if (window.MemoryManager) {
+        MemoryManager.enableAutoCleanup();
+    }
+
+    CleanupManager.addListener(document, 'visibilitychange', () => {
         if (!document.hidden) {
             if (!WebSocketManager.isConnected()) {
                 WebSocketManager.connect();
             }
+
+            if (NetworkManager.isOnline()) {
+                fetchLatestPrices().then(priceData => {
+                    if (priceData) {
+                        latestPrices = priceData;
+                        updateProfitLossDisplays();
+                    }
+                });
+            }
         }
     });
 
-    EventManager.add(window, 'beforeunload', () => {
+    CleanupManager.addListener(window, 'beforeunload', () => {
         cleanup();
     });
 });
 
 async function cleanup() {
-    const debug = {
-        startTime: Date.now(),
-        steps: [],
-        errors: [],
-        addStep: function(step, details = null) {
-            const timeFromStart = Date.now() - this.startTime;
-            console.log(`[Cleanup ${timeFromStart}ms] ${step}`, details || '');
-            this.steps.push({ step, time: timeFromStart, details });
-        },
-        addError: function(step, error) {
-            const timeFromStart = Date.now() - this.startTime;
-            console.error(`[Cleanup Error ${timeFromStart}ms] ${step}:`, error);
-            this.errors.push({ step, error, time: timeFromStart });
-        },
-        summarizeLogs: function() {
-            console.log('Cleanup Summary:');
-            console.log(`Total cleanup time: ${Date.now() - this.startTime}ms`);
-            console.log(`Steps completed: ${this.steps.length}`);
-            console.log(`Errors encountered: ${this.errors.length}`);
-        }
-    };
+    console.log('Starting cleanup process');
 
     try {
-        debug.addStep('Starting cleanup process');
 
-        debug.addStep('Starting tooltip cleanup');
+        if (filterTimeout) {
+            clearTimeout(filterTimeout);
+            filterTimeout = null;
+        }
+
+        if (window.WebSocketManager) {
+            WebSocketManager.disconnect();
+            WebSocketManager.dispose();
+        }
+
         if (window.TooltipManager) {
             window.TooltipManager.cleanup();
+            window.TooltipManager.dispose();
         }
-        debug.addStep('Tooltip cleanup completed');
 
-        debug.addStep('Clearing timers');
-        const timerCount = timerManager.intervals.length + timerManager.timeouts.length;
-        timerManager.clearAll();
-        debug.addStep('Timers cleared', `Cleaned up ${timerCount} timers`);
-
-        debug.addStep('Starting WebSocket cleanup');
-        await Promise.resolve(WebSocketManager.cleanup()).catch(error => {
-            debug.addError('WebSocket cleanup', error);
-        });
-        debug.addStep('WebSocket cleanup completed');
-
-        debug.addStep('Clearing event listeners');
-        const listenerCount = EventManager.listeners.size;
-        EventManager.clearAll();
-        debug.addStep('Event listeners cleared', `Cleaned up ${listenerCount} listeners`);
-
-        debug.addStep('Starting table cleanup');
-        const rowCount = offersBody ? offersBody.querySelectorAll('tr').length : 0;
         cleanupTable();
-        debug.addStep('Table cleanup completed', `Cleaned up ${rowCount} rows`);
 
-        debug.addStep('Resetting global state');
-        const globals = {
-            currentPage: currentPage,
-            dataLength: jsonData.length,
-            originalDataLength: originalJsonData.length
-        };
-        currentPage = 1;
+        CleanupManager.clearAll();
+
+        latestPrices = null;
         jsonData = [];
         originalJsonData = [];
+        lastRefreshTime = null;
+
+        const domRefs = [
+            'offersBody', 'filterForm', 'prevPageButton', 'nextPageButton', 
+            'currentPageSpan', 'totalPagesSpan', 'lastRefreshTimeSpan', 'newEntriesCountSpan'
+        ];
+
+        domRefs.forEach(ref => {
+            if (window[ref]) window[ref] = null;
+        });
+
+        if (window.tableRateModule) {
+            window.tableRateModule.cache = {};
+            window.tableRateModule.processedOffers.clear();
+        }
+
+        currentPage = 1;
         currentSortColumn = 0;
         currentSortDirection = 'desc';
-        filterTimeout = null;
-        latestPrices = null;
-        lastRefreshTime = null;
-        debug.addStep('Global state reset', globals);
 
-        debug.addStep('Clearing global references');
-        [
-            'WebSocketManager',
-            'tableRateModule',
-            'offersBody',
-            'filterForm',
-            'prevPageButton',
-            'nextPageButton',
-            'currentPageSpan',
-            'totalPagesSpan',
-            'lastRefreshTimeSpan',
-            'newEntriesCountSpan'
-        ].forEach(ref => {
-            if (window[ref]) {
-                window[ref] = null;
-            }
-        });
-        debug.addStep('Global references cleared');
-
-        debug.addStep('Cleaning up tooltip containers');
-        const tooltipContainers = document.querySelectorAll('.tooltip-container');
-        tooltipContainers.forEach(container => {
-            if (container && container.parentNode) {
-                container.parentNode.removeChild(container);
-            }
-        });
-        debug.addStep('Tooltip containers cleaned up');
-
-        debug.addStep('Clearing document/window events');
-        ['visibilitychange', 'beforeunload', 'scroll'].forEach(event => {
-            document.removeEventListener(event, null);
-            window.removeEventListener(event, null);
-        });
-        debug.addStep('Document/window events cleared');
-
-        debug.addStep('Clearing localStorage items');
-        try {
-            localStorage.removeItem('tableSortColumn');
-            localStorage.removeItem('tableSortDirection');
-            debug.addStep('localStorage items cleared');
-        } catch (e) {
-            debug.addError('localStorage cleanup', e);
+        if (window.MemoryManager) {
+            MemoryManager.forceCleanup();
         }
 
+        console.log('Offers table cleanup completed');
     } catch (error) {
-        debug.addError('Main cleanup process', error);
+        console.error('Error during offers cleanup:', error);
 
-        debug.addStep('Starting failsafe cleanup');
         try {
-            if (window.TooltipManager) {
-                window.TooltipManager.cleanup();
-            }
-            WebSocketManager.cleanup();
-            EventManager.clearAll();
-            timerManager.clearAll();
-            if (window.ws) {
-                window.ws.close();
-                window.ws = null;
-            }
-            debug.addStep('Failsafe cleanup completed');
-        } catch (criticalError) {
-            debug.addError('Critical failsafe cleanup', criticalError);
+            CleanupManager.clearAll();
+            cleanupTable();
+        } catch (e) {
+            console.error('Failsafe cleanup failed:', e);
         }
-    } finally {
-        debug.summarizeLogs();
     }
 }
 
 window.cleanup = cleanup;
-
-//console.log('Offers Table Module fully initialized');
diff --git a/basicswap/static/js/pricechart.js b/basicswap/static/js/pricechart.js
index 010ab5e..05e7147 100644
--- a/basicswap/static/js/pricechart.js
+++ b/basicswap/static/js/pricechart.js
@@ -1,184 +1,22 @@
-// CLEANUP
-const cleanupManager = {
-  eventListeners: [],
-  timeouts: [],
-  intervals: [],
-  animationFrames: [],
+const chartConfig = window.config.chartConfig;
+const coins = window.config.coins;
+const apiKeys = window.config.getAPIKeys();
 
-  addListener: function(element, type, handler, options) {
-    if (!element) return null;
-    element.addEventListener(type, handler, options);
-    this.eventListeners.push({ element, type, handler, options });
-    return handler;
-  },
-
-  setTimeout: function(callback, delay) {
-    const id = setTimeout(callback, delay);
-    this.timeouts.push(id);
-    return id;
-  },
-
-  setInterval: function(callback, delay) {
-    const id = setInterval(callback, delay);
-    this.intervals.push(id);
-    return id;
-  },
-
-  requestAnimationFrame: function(callback) {
-    const id = requestAnimationFrame(callback);
-    this.animationFrames.push(id);
-    return id;
-  },
-
-  clearAll: function() {
-    this.eventListeners.forEach(({ element, type, handler, options }) => {
-      if (element) {
-        try {
-          element.removeEventListener(type, handler, options);
-        } catch (e) {
-          console.warn('Error removing event listener:', e);
-        }
-      }
-    });
-    this.eventListeners = [];
-
-    this.timeouts.forEach(id => clearTimeout(id));
-    this.timeouts = [];
-
-    this.intervals.forEach(id => clearInterval(id));
-    this.intervals = [];
-
-    this.animationFrames.forEach(id => cancelAnimationFrame(id));
-    this.animationFrames = [];
-
-    console.log('All resources cleaned up');
-  },
-  
-  clearTimeouts: function() {
-    this.timeouts.forEach(id => clearTimeout(id));
-    this.timeouts = [];
-  },
-  
-  clearIntervals: function() {
-    this.intervals.forEach(id => clearInterval(id));
-    this.intervals = [];
-  },
-
-  removeListenersByElement: function(element) {
-    if (!element) return;
-
-    const listenersToRemove = this.eventListeners.filter(
-      listener => listener.element === element
-    );
-
-    listenersToRemove.forEach(({ element, type, handler, options }) => {
-      try {
-        element.removeEventListener(type, handler, options);
-      } catch (e) {
-        console.warn('Error removing event listener:', e);
-      }
-    });
-
-    this.eventListeners = this.eventListeners.filter(
-      listener => listener.element !== element
-    );
-  }
-};
-
-// MEMORY
-const memoryMonitor = {
-  isEnabled: true,
-  lastLogTime: 0,
-  logInterval: 5 * 60 * 1000,
-  monitorInterval: null,
-
-  startMonitoring: function() {
-    console.log('Starting memory monitoring');
-    if (!this.isEnabled) return;
-
-    if (this.monitorInterval) {
-      clearInterval(this.monitorInterval);
-    }
-
-    this.monitorInterval = setInterval(() => {
-      this.logMemoryUsage();
-    }, this.logInterval);
-
-    this.logMemoryUsage();
-  },
-  
-  logMemoryUsage: function() {
-    console.log('Logging memory usage');
-    if (window.performance && window.performance.memory) {
-      const memory = window.performance.memory;
-      console.log(`Memory Usage: ${Math.round(memory.usedJSHeapSize / (1024 * 1024))}MB / ${Math.round(memory.jsHeapSizeLimit / (1024 * 1024))}MB`);
-    }
-  },
-
-  stopMonitoring: function() {
-    if (this.monitorInterval) {
-      clearInterval(this.monitorInterval);
-      this.monitorInterval = null;
-    }
-  }
-};
-
-// CONFIG
-const config = {
-  apiKeys: getAPIKeys(),
-  coins: [
-    { symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'XMR', name: 'monero', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'PART', name: 'particl', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'BCH', name: 'bitcoin-cash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'PIVX', name: 'pivx', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'FIRO', name: 'zcoin', displayName: 'Firo', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'DASH', name: 'dash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'LTC', name: 'litecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'DOGE', name: 'dogecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'ETH', name: 'ethereum', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'DCR', name: 'decred', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'ZANO', name: 'zano', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
-    { symbol: 'WOW', name: 'wownero', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 }
-  ],
-  apiEndpoints: {
-    cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
-    coinGecko: 'https://api.coingecko.com/api/v3',
-    cryptoCompareHistorical: 'https://min-api.cryptocompare.com/data/v2/histoday'
-  },
-  chartColors: {
-    default: {
-      lineColor: 'rgba(77, 132, 240, 1)',
-      backgroundColor: 'rgba(77, 132, 240, 0.1)'
-    }
-  },
-  showVolume: false,
-  cacheTTL: 10 * 60 * 1000,
-  specialCoins: [''],
-  resolutions: {
-    year: { days: 365, interval: 'month' },
-    sixMonths: { days: 180, interval: 'daily' },
-    day: { days: 1, interval: 'hourly' }
-  },
-  currentResolution: 'year',
-  requestTimeout: 60000,  // 60 sec
-  retryDelays: [5000, 15000, 30000],
-  rateLimits: {
-    coingecko: {
-      requestsPerMinute: 50,
-      minInterval: 1200  // 1.2 sec
-    },
-    cryptocompare: {
-      requestsPerMinute: 30,
-      minInterval: 2000  // 2 sec
-    }
-  }
-};
-
-// UTILS
 const utils = {
-  formatNumber: (number, decimals = 2) =>
-    number.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ','),
+  formatNumber: (number, decimals = 2) => {
+    if (typeof number !== 'number' || isNaN(number)) {
+      return '0';
+    }
+
+    try {
+      return new Intl.NumberFormat('en-US', {
+        minimumFractionDigits: decimals,
+        maximumFractionDigits: decimals
+      }).format(number);
+    } catch (e) {
+      return '0';
+    }
+  },
   formatDate: (timestamp, resolution) => {
     const date = new Date(timestamp);
     const options = {
@@ -188,7 +26,6 @@ const utils = {
     };
     return date.toLocaleString('en-US', { ...options[resolution], timeZone: 'UTC' });
   },
-
   debounce: (func, delay) => {
     let timeoutId;
     return (...args) => {
@@ -198,7 +35,6 @@ const utils = {
   }
 };
 
-// ERROR
 class AppError extends Error {
   constructor(message, type = 'AppError') {
     super(message);
@@ -206,7 +42,6 @@ class AppError extends Error {
   }
 }
 
-// LOG
 const logger = {
   log: (message) => console.log(`[AppLog] ${new Date().toISOString()}: ${message}`),
   warn: (message) => console.warn(`[AppWarn] ${new Date().toISOString()}: ${message}`),
@@ -214,153 +49,192 @@ const logger = {
 };
 
 const api = {
-    makePostRequest: (url, headers = {}) => {
-        return new Promise((resolve, reject) => {
-            const xhr = new XMLHttpRequest();
-            xhr.open('POST', '/json/readurl');
-            xhr.setRequestHeader('Content-Type', 'application/json');
-            xhr.timeout = config.requestTimeout;
-            
-            xhr.ontimeout = () => {
-                logger.warn(`Request timed out for ${url}`);
-                reject(new AppError('Request timed out'));
-            };
-            
-            xhr.onload = () => {
-                logger.log(`Response for ${url}:`, xhr.responseText);
-                if (xhr.status === 200) {
-                    try {
-                        const response = JSON.parse(xhr.responseText);
-                        if (response.Error) {
-                            logger.error(`API Error for ${url}:`, response.Error);
-                            reject(new AppError(response.Error, 'APIError'));
-                        } else {
-                            resolve(response);
-                        }
-                    } catch (error) {
-                        logger.error(`Invalid JSON response for ${url}:`, xhr.responseText);
-                        reject(new AppError(`Invalid JSON response: ${error.message}`, 'ParseError'));
+    fetchVolumeDataXHR: async () => {
+        const cacheKey = 'volumeData';
+        const cachedData = CacheManager.get(cacheKey);
+
+        if (cachedData) {
+            console.log("Using cached volume data");
+            return cachedData.value;
+        }
+
+        try {
+            if (!NetworkManager.isOnline()) {
+                throw new Error('Network is offline');
+            }
+
+            const volumeData = await Api.fetchVolumeData({
+                cryptoCompare: apiKeys.cryptoCompare,
+                coinGecko: apiKeys.coinGecko
+            });
+
+            if (Object.keys(volumeData).length > 0) {
+                CacheManager.set(cacheKey, volumeData, 'volume');
+                return volumeData;
+            }
+
+            throw new Error("No volume data found in the response");
+        } catch (error) {
+            console.error("Error fetching volume data:", error);
+
+            NetworkManager.handleNetworkError(error);
+
+            try {
+                const existingCache = localStorage.getItem(cacheKey);
+                if (existingCache) {
+                    const fallbackData = JSON.parse(existingCache).value;
+                    if (fallbackData && Object.keys(fallbackData).length > 0) {
+                        return fallbackData;
                     }
-                } else {
-                    logger.error(`HTTP Error for ${url}: ${xhr.status} ${xhr.statusText}`);
-                    reject(new AppError(`HTTP Error: ${xhr.status} ${xhr.statusText}`, 'HTTPError'));
                 }
-            };
-            
-            xhr.onerror = () => {
-                logger.error(`Network error occurred for ${url}`);
-                reject(new AppError('Network error occurred', 'NetworkError'));
-            };
-            
-            xhr.send(JSON.stringify({
-                url: url,
-                headers: headers
-            }));
-        });
+            } catch (e) {
+                console.warn("Error accessing cached volume data:", e);
+            }
+            return {};
+        }
     },
 
     fetchCryptoCompareDataXHR: (coin) => {
-        return rateLimiter.queueRequest('cryptocompare', async () => {
-            const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`;
-            const headers = {
-                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
-                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
-                'Accept-Language': 'en-US,en;q=0.5',
-            };
-            try {
-                return await api.makePostRequest(url, headers);
-            } catch (error) {
-                logger.error(`CryptoCompare request failed for ${coin}:`, error);
-                const cachedData = cache.get(`coinData_${coin}`);
-                if (cachedData) {
-                    logger.info(`Using cached data for ${coin}`);
-                    return cachedData.value;
-                }
-                return { error: error.message };
+        try {
+            if (!NetworkManager.isOnline()) {
+                throw new Error('Network is offline');
             }
-        });
+
+            return Api.fetchCryptoCompareData(coin, {
+                cryptoCompare: apiKeys.cryptoCompare
+            });
+        } catch (error) {
+            logger.error(`CryptoCompare request failed for ${coin}:`, error);
+
+            NetworkManager.handleNetworkError(error);
+
+            const cachedData = CacheManager.get(`coinData_${coin}`);
+            if (cachedData) {
+                logger.info(`Using cached data for ${coin}`);
+                return cachedData.value;
+            }
+            return { error: error.message };
+        }
     },
 
     fetchCoinGeckoDataXHR: async () => {
         const cacheKey = 'coinGeckoOneLiner';
-        const cachedData = cache.get(cacheKey);
+        const cachedData = CacheManager.get(cacheKey);
 
         if (cachedData) {
-            //console.log('Using cached CoinGecko data');
             return cachedData.value;
         }
 
-        return rateLimiter.queueRequest('coingecko', async () => {
+        try {
+            if (!NetworkManager.isOnline()) {
+                throw new Error('Network is offline');
+            }
+
+            const existingCache = localStorage.getItem(cacheKey);
+            let fallbackData = null;
+
+            if (existingCache) {
+                try {
+                    const parsed = JSON.parse(existingCache);
+                    fallbackData = parsed.value;
+                } catch (e) {
+                    console.warn('Failed to parse existing cache:', e);
+                }
+            }
+
+            const apiResponse = await Api.fetchCoinGeckoData({
+                coinGecko: window.config.getAPIKeys().coinGecko
+            });
+            
+            if (!apiResponse || !apiResponse.rates) {
+                if (fallbackData) {
+                    return fallbackData;
+                }
+                throw new Error('Invalid data structure received from API');
+            }
+
+            const transformedData = {};
+            window.config.coins.forEach(coin => {
+                const coinName = coin.name;
+                const coinRate = apiResponse.rates[coinName];
+                if (coinRate) {
+                    const symbol = coin.symbol.toLowerCase();
+                    transformedData[symbol] = {
+                        current_price: coinRate,
+                        price_btc: coinName === 'bitcoin' ? 1 : coinRate / (apiResponse.rates.bitcoin || 1),
+                        total_volume: fallbackData && fallbackData[symbol] ? fallbackData[symbol].total_volume : null,
+                        price_change_percentage_24h: fallbackData && fallbackData[symbol] ? fallbackData[symbol].price_change_percentage_24h : null,
+                        displayName: coin.displayName || coin.symbol || coinName
+                    };
+                }
+            });
+
+            try {
+                if (!transformedData['wow'] && config.coins.some(c => c.symbol === 'WOW')) {
+                    const wowResponse = await Api.fetchCoinPrices("wownero", {
+                        coinGecko: window.config.getAPIKeys().coinGecko
+                    });
+                    
+                    if (wowResponse && wowResponse.rates && wowResponse.rates.wownero) {
+                        transformedData['wow'] = {
+                            current_price: wowResponse.rates.wownero,
+                            price_btc: transformedData.btc ? wowResponse.rates.wownero / transformedData.btc.current_price : 0,
+                            total_volume: fallbackData && fallbackData['wow'] ? fallbackData['wow'].total_volume : null,
+                            price_change_percentage_24h: fallbackData && fallbackData['wow'] ? fallbackData['wow'].price_change_percentage_24h : null,
+                            displayName: 'Wownero'
+                        };
+                    }
+                }
+            } catch (wowError) {
+                console.error('Error fetching WOW price:', wowError);
+            }
+
+            const missingCoins = window.config.coins.filter(coin => 
+                !transformedData[coin.symbol.toLowerCase()] && 
+                fallbackData && 
+                fallbackData[coin.symbol.toLowerCase()]
+            );
+
+            missingCoins.forEach(coin => {
+                const symbol = coin.symbol.toLowerCase();
+                if (fallbackData && fallbackData[symbol]) {
+                    transformedData[symbol] = fallbackData[symbol];
+                }
+            });
+
+            CacheManager.set(cacheKey, transformedData, 'prices');
+            
+            if (NetworkManager.getReconnectAttempts() > 0) {
+                NetworkManager.resetReconnectAttempts();
+            }
+            
+            return transformedData;
+        } catch (error) {
+            console.error('Error fetching coin data:', error);
+
+            NetworkManager.handleNetworkError(error);
+
+            const cachedData = CacheManager.get(cacheKey);
+            if (cachedData) {
+                console.log('Using cached data due to error');
+                return cachedData.value;
+            }
+
             try {
                 const existingCache = localStorage.getItem(cacheKey);
-                let fallbackData = null;
-
                 if (existingCache) {
-                    try {
-                        const parsed = JSON.parse(existingCache);
-                        fallbackData = parsed.value;
-                    } catch (e) {
-                        console.warn('Failed to parse existing cache:', e);
+                    const parsed = JSON.parse(existingCache);
+                    if (parsed.value) {
+                        console.log('Using expired cache as last resort');
+                        return parsed.value;
                     }
                 }
-
-                const coinIds = config.coins
-                    .filter(coin => coin.usesCoinGecko)
-                    .map(coin => coin.name)
-                    .join(',');
-
-                const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
-
-                const response = await api.makePostRequest(url, {
-                    'User-Agent': 'Mozilla/5.0',
-                    'Accept': 'application/json',
-                    'Accept-Language': 'en-US,en;q=0.5'
-                });
-
-                if (typeof response !== 'object' || response === null) {
-                    if (fallbackData) {
-                        //console.log('Using fallback data due to invalid response');
-                        return fallbackData;
-                    }
-                    throw new AppError('Invalid data structure received from CoinGecko');
-                }
-
-                if (response.error || response.Error) {
-                    if (fallbackData) {
-                        //console.log('Using fallback data due to API error');
-                        return fallbackData;
-                    }
-                    throw new AppError(response.error || response.Error);
-                }
-
-                const transformedData = {};
-                Object.entries(response).forEach(([id, values]) => {
-                    const coinConfig = config.coins.find(coin => coin.name === id);
-                    const symbol = coinConfig?.symbol.toLowerCase() || id;
-                    transformedData[symbol] = {
-                        current_price: values.usd,
-                        price_btc: values.btc,
-                        total_volume: values.usd_24h_vol,
-                        price_change_percentage_24h: values.usd_24h_change,
-                        displayName: coinConfig?.displayName || coinConfig?.symbol || id
-                    };
-                });
-
-                cache.set(cacheKey, transformedData);
-                return transformedData;
-
-            } catch (error) {
-                console.error('Error fetching CoinGecko data:', error);
-
-                const cachedData = cache.get(cacheKey);
-                if (cachedData) {
-                    //console.log('Using expired cache data due to error');
-                    return cachedData.value;
-                }
-
-                throw error;
+            } catch (e) {
+                console.warn('Failed to parse expired cache:', e);
             }
-        });
+
+            throw error;
+        }
     },
 
     fetchHistoricalDataXHR: async (coinSymbols) => {
@@ -369,78 +243,57 @@ const api = {
         }
 
         const results = {};
-        const fetchPromises = coinSymbols.map(async coin => {
-            const coinConfig = config.coins.find(c => c.symbol === coin);
-            if (!coinConfig) return;
 
-            const cacheKey = `historical_${coin}_${config.currentResolution}`;
-            const cachedData = cache.get(cacheKey);
-            if (cachedData) {
-                results[coin] = cachedData.value;
-                return;
+        try {
+            if (!NetworkManager.isOnline()) {
+                throw new Error('Network is offline');
             }
 
-            if (coin === 'WOW') {
-                return rateLimiter.queueRequest('coingecko', async () => {
-                    const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`;
-                    try {
-                        const response = await api.makePostRequest(url);
-                        if (response && response.prices) {
-                            results[coin] = response.prices;
-                            cache.set(cacheKey, response.prices);
-                        }
-                    } catch (error) {
-                        console.error(`Error fetching CoinGecko data for WOW:`, error);
-                        if (cachedData) {
-                            results[coin] = cachedData.value;
-                        }
-                    }
-                });
-            } else {
-                return rateLimiter.queueRequest('cryptocompare', async () => {
-                    const resolution = config.resolutions[config.currentResolution];
-                    let url;
-                    if (resolution.interval === 'hourly') {
-                        url = `https://min-api.cryptocompare.com/data/v2/histohour?fsym=${coin}&tsym=USD&limit=${resolution.days * 24}&api_key=${config.apiKeys.cryptoCompare}`;
-                    } else {
-                        url = `${config.apiEndpoints.cryptoCompareHistorical}?fsym=${coin}&tsym=USD&limit=${resolution.days}&api_key=${config.apiKeys.cryptoCompare}`;
-                    }
+            const historicalData = await Api.fetchHistoricalData(
+                coinSymbols, 
+                window.config.currentResolution, 
+                {
+                    cryptoCompare: window.config.getAPIKeys().cryptoCompare
+                }
+            );
 
-                    try {
-                        const response = await api.makePostRequest(url);
-                        if (response.Response === "Error") {
-                            console.error(`API Error for ${coin}:`, response.Message);
-                            if (cachedData) {
-                                results[coin] = cachedData.value;
-                            }
-                        } else if (response.Data && response.Data.Data) {
-                            results[coin] = response.Data;
-                            cache.set(cacheKey, response.Data);
-                        }
-                    } catch (error) {
-                        console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
-                        if (cachedData) {
-                            results[coin] = cachedData.value;
-                        }
-                    }
-                });
+            Object.keys(historicalData).forEach(coin => {
+                if (historicalData[coin]) {
+                    results[coin] = historicalData[coin];
+                    
+                    const cacheKey = `historical_${coin}_${window.config.currentResolution}`;
+                    CacheManager.set(cacheKey, historicalData[coin], 'historical');
+                }
+            });
+
+            return results;
+        } catch (error) {
+            console.error('Error fetching historical data:', error);
+
+            NetworkManager.handleNetworkError(error);
+
+            for (const coin of coinSymbols) {
+                const cacheKey = `historical_${coin}_${window.config.currentResolution}`;
+                const cachedData = CacheManager.get(cacheKey);
+                if (cachedData) {
+                    results[coin] = cachedData.value;
+                }
             }
-        });
-
-        await Promise.all(fetchPromises);
-        return results;
-    }
+            
+            return results;
+        }
+    },
 };
 
 const rateLimiter = {
     lastRequestTime: {},
     minRequestInterval: {
-        coingecko: config.rateLimits.coingecko.minInterval,
-        cryptocompare: config.rateLimits.cryptocompare.minInterval
+        coingecko: window.config.rateLimits.coingecko.minInterval,
+        cryptocompare: window.config.rateLimits.cryptocompare.minInterval
     },
     requestQueue: {},
-    retryDelays: config.retryDelays,
-    
+    retryDelays: window.config.retryDelays,
+
     canMakeRequest: function(apiName) {
         const now = Date.now();
         const lastRequest = this.lastRequestTime[apiName] || 0;
@@ -485,11 +338,7 @@ const rateLimiter = {
                     if ((error.message.includes('timeout') || error.name === 'NetworkError') && 
                         retryCount < this.retryDelays.length) {
                         const delay = this.retryDelays[retryCount];
-                        logger.warn(`Request failed, retrying in ${delay/1000} seconds...`, {
-                            apiName,
-                            retryCount,
-                            error: error.message
-                        });
+                        logger.warn(`Request failed, retrying in ${delay/1000} seconds...`);
                         await new Promise(resolve => setTimeout(resolve, delay));
                         return this.queueRequest(apiName, requestFn, retryCount + 1);
                     }
@@ -500,14 +349,15 @@ const rateLimiter = {
 
             this.requestQueue[apiName] = executeRequest();
             return await this.requestQueue[apiName];
-            
         } catch (error) {
             if (error.message.includes('429') || 
                 error.message.includes('timeout') || 
                 error.name === 'NetworkError') {
-                const cachedData = cache.get(`coinData_${apiName}`);
+                
+                NetworkManager.handleNetworkError(error);
+                
+                const cachedData = CacheManager.get(`coinData_${apiName}`);
                 if (cachedData) {
-                    //console.log('Using cached data due to request failure');
                     return cachedData.value;
                 }
             }
@@ -516,190 +366,65 @@ const rateLimiter = {
     }
 };
 
-// CACHE
-const cache = {
-  maxSizeBytes: 10 * 1024 * 1024,
-  maxItems: 200,
-  cacheTTL: 5 * 60 * 1000,
-
-  set: function(key, value, customTtl = null) {
-    this.cleanup();
-
-    const item = {
-      value: value,
-      timestamp: Date.now(),
-      expiresAt: Date.now() + (customTtl || this.cacheTTL)
-    };
-
-    try {
-      const serialized = JSON.stringify(item);
-      localStorage.setItem(key, serialized);
-    } catch (e) {
-      console.warn('Cache set error:', e);
-      this.clear();
-      try {
-        const serialized = JSON.stringify(item);
-        localStorage.setItem(key, serialized);
-      } catch (e2) {
-        console.error('Failed to store in cache even after cleanup:', e2);
-      }
-    }
-  },
-
-  get: function(key) {
-    const itemStr = localStorage.getItem(key);
-    if (!itemStr) {
-      return null;
-    }
-
-    try {
-      const item = JSON.parse(itemStr);
-      const now = Date.now();
-      
-      if (now < item.expiresAt) {
-        return {
-          value: item.value,
-          remainingTime: item.expiresAt - now
-        };
-      } else {
-        localStorage.removeItem(key);
-      }
-    } catch (error) {
-      console.error('Error parsing cache item:', error.message);
-      localStorage.removeItem(key);
-    }
-
-    return null;
-  },
-
-  isValid: function(key) {
-    return this.get(key) !== null;
-  },
-
-  clear: function() {
-    const keysToRemove = [];
-
-    for (let i = 0; i < localStorage.length; i++) {
-      const key = localStorage.key(i);
-      if (key.startsWith('coinData_') || key.startsWith('chartData_') || key === 'coinGeckoOneLiner') {
-        keysToRemove.push(key);
-      }
-    }
-
-    keysToRemove.forEach(key => {
-      localStorage.removeItem(key);
-    });
-
-    console.log(`Cache cleared: removed ${keysToRemove.length} items`);
-  },
-
-  cleanup: function() {
-    let totalSize = 0;
-    const items = [];
-    const keysToRemove = [];
-    const now = Date.now();
-
-    for (let i = 0; i < localStorage.length; i++) {
-      const key = localStorage.key(i);
-      if (key.startsWith('coinData_') || key.startsWith('chartData_') || key === 'coinGeckoOneLiner') {
-        try {
-          const value = localStorage.getItem(key);
-          const size = new Blob([value]).size;
-
-          const item = JSON.parse(value);
-
-          if (item.expiresAt && item.expiresAt < now) {
-            keysToRemove.push(key);
-            continue;
-          }
-
-          totalSize += size;
-          items.push({
-            key,
-            size,
-            timestamp: item.timestamp || 0,
-            expiresAt: item.expiresAt || 0
-          });
-        } catch (e) {
-          keysToRemove.push(key);
-        }
-      }
-    }
-
-    keysToRemove.forEach(key => {
-      localStorage.removeItem(key);
-    });
-
-    if (totalSize > this.maxSizeBytes || items.length > this.maxItems) {
-      items.sort((a, b) => a.timestamp - b.timestamp);
-
-      const itemsToRemove = Math.max(
-        Math.ceil(items.length * 0.2),
-        items.length - this.maxItems
-      );
-
-      items.slice(0, itemsToRemove).forEach(item => {
-        localStorage.removeItem(item.key);
-      });
- 
-      console.log(`Cache cleanup: removed ${itemsToRemove} items, freed ${Math.round((totalSize - this.maxSizeBytes) / 1024)}KB`);
-    }
-
-    return {
-      totalSize,
-      itemCount: items.length,
-      removedCount: keysToRemove.length
-    };
-  }
-};
-
-// UI
 const ui = {
-displayCoinData: (coin, data) => {
+  displayCoinData: (coin, data) => {
     let priceUSD, priceBTC, priceChange1d, volume24h;
     const updateUI = (isError = false) => {
-        const priceUsdElement = document.querySelector(`#${coin.toLowerCase()}-price-usd`);
-        const volumeDiv = document.querySelector(`#${coin.toLowerCase()}-volume-div`);
-        const volumeElement = document.querySelector(`#${coin.toLowerCase()}-volume-24h`);
-        const btcPriceDiv = document.querySelector(`#${coin.toLowerCase()}-btc-price-div`);
-        const priceBtcElement = document.querySelector(`#${coin.toLowerCase()}-price-btc`);
-        if (priceUsdElement) {
-            priceUsdElement.textContent = isError ? 'N/A' : `$ ${ui.formatPrice(coin, priceUSD)}`;
+      const priceUsdElement = document.querySelector(`#${coin.toLowerCase()}-price-usd`);
+      const volumeDiv = document.querySelector(`#${coin.toLowerCase()}-volume-div`);
+      const volumeElement = document.querySelector(`#${coin.toLowerCase()}-volume-24h`);
+      const btcPriceDiv = document.querySelector(`#${coin.toLowerCase()}-btc-price-div`);
+      const priceBtcElement = document.querySelector(`#${coin.toLowerCase()}-price-btc`);
+      
+      if (priceUsdElement) {
+        priceUsdElement.textContent = isError ? 'N/A' : `$ ${ui.formatPrice(coin, priceUSD)}`;
+      }
+
+      if (volumeDiv && volumeElement) {
+        if (isError || volume24h === null || volume24h === undefined) {
+          volumeElement.textContent = 'N/A';
+        } else {
+          volumeElement.textContent = `${utils.formatNumber(volume24h, 0)} USD`;
         }
-        if (volumeDiv && volumeElement) {
-            volumeElement.textContent = isError ? 'N/A' : `${utils.formatNumber(volume24h, 0)} USD`;
-            volumeDiv.style.display = volumeToggle.isVisible ? 'flex' : 'none';
+        volumeDiv.style.display = volumeToggle.isVisible ? 'flex' : 'none';
+      }
+
+      if (btcPriceDiv && priceBtcElement) {
+        if (coin === 'BTC') {
+          btcPriceDiv.style.display = 'none';
+        } else {
+          priceBtcElement.textContent = isError ? 'N/A' : `${priceBTC.toFixed(8)}`;
+          btcPriceDiv.style.display = 'flex';
         }
-        if (btcPriceDiv && priceBtcElement) {
-            if (coin === 'BTC') {
-                btcPriceDiv.style.display = 'none';
-            } else {
-                priceBtcElement.textContent = isError ? 'N/A' : `${priceBTC.toFixed(8)}`;
-                btcPriceDiv.style.display = 'flex';
-            }
-        }
-        ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d);
+      }
+
+      ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d);
     };
+
     try {
-        if (data.error) {
-            throw new Error(data.error);
-        }
-        if (!data || !data.current_price) {
-            throw new Error(`Invalid CoinGecko data structure for ${coin}`);
-        }
-        priceUSD = data.current_price;
-        priceBTC = coin === 'BTC' ? 1 : data.price_btc || (data.current_price / app.btcPriceUSD);
-        priceChange1d = data.price_change_percentage_24h;
-        volume24h = data.total_volume;
-        if (isNaN(priceUSD) || isNaN(priceBTC) || isNaN(volume24h)) {
-            throw new Error(`Invalid numeric values in data for ${coin}`);
-        }
-        updateUI(false);
+      if (data.error) {
+        throw new Error(data.error);
+      }
+
+      if (!data || !data.current_price) {
+        throw new Error(`Invalid data structure for ${coin}`);
+      }
+
+      priceUSD = data.current_price;
+      priceBTC = coin === 'BTC' ? 1 : data.price_btc || (data.current_price / app.btcPriceUSD);
+      priceChange1d = data.price_change_percentage_24h || 0;
+      volume24h = data.total_volume || 0;
+
+      if (isNaN(priceUSD) || isNaN(priceBTC)) {
+        throw new Error(`Invalid numeric values in data for ${coin}`);
+      }
+
+      updateUI(false);
     } catch (error) {
-    logger.error(`Failed to display data for ${coin}:`, error.message);
-    updateUI(true); // Show error state in UI
-}
-},
+      logger.error(`Failed to display data for ${coin}:`, error.message);
+      updateUI(true);
+    }
+  },
 
   showLoader: () => {
     const loader = document.getElementById('loader');
@@ -762,9 +487,13 @@ displayCoinData: (coin, data) => {
   updatePriceChangeContainer: (coin, priceChange) => {
     const container = document.querySelector(`#${coin.toLowerCase()}-price-change-container`);
     if (container) {
-      container.innerHTML = priceChange !== null ?
-        (priceChange >= 0 ? ui.positivePriceChangeHTML(priceChange) : ui.negativePriceChangeHTML(priceChange)) :
-        'N/A';
+      if (priceChange === null || priceChange === undefined) {
+        container.innerHTML = 'N/A';
+      } else {
+        container.innerHTML = priceChange >= 0 ? 
+          ui.positivePriceChangeHTML(priceChange) : 
+          ui.negativePriceChangeHTML(priceChange);
+      }
     }
   },
 
@@ -775,6 +504,16 @@ displayCoinData: (coin, data) => {
       lastRefreshedElement.textContent = `Last Refreshed: ${formattedTime}`;
     }
   },
+  
+  updateConnectionStatus: () => {
+    const statusElement = document.getElementById('connection-status');
+    if (statusElement) {
+      const online = NetworkManager.isOnline();
+      statusElement.textContent = online ? 'Connected' : 'Disconnected';
+      statusElement.classList.toggle('text-green-500', online);
+      statusElement.classList.toggle('text-red-500', !online);
+    }
+  },
 
   positivePriceChangeHTML: (value) => `
     <div class="flex flex-wrap items-center py-px px-1 border border-green-500 rounded-full">
@@ -819,7 +558,7 @@ displayCoinData: (coin, data) => {
     });
   },
 
-  displayErrorMessage: (message) => {
+  displayErrorMessage: (message, duration = 0) => {
     const errorOverlay = document.getElementById('error-overlay');
     const errorMessage = document.getElementById('error-message');
     const chartContainer = document.querySelector('.container-to-blur');
@@ -827,6 +566,12 @@ displayCoinData: (coin, data) => {
       errorOverlay.classList.remove('hidden');
       errorMessage.textContent = message;
       chartContainer.classList.add('blurred');
+
+      if (duration > 0) {
+        setTimeout(() => {
+          ui.hideErrorMessage();
+        }, duration);
+      }
     }
   },
 
@@ -837,16 +582,42 @@ displayCoinData: (coin, data) => {
       errorOverlay.classList.add('hidden');
       containersToBlur.forEach(container => container.classList.remove('blurred'));
     }
+  },
+
+  showNetworkErrorMessage: () => {
+    ui.displayErrorMessage(
+      "Network connection lost. Data shown may be outdated. We'll automatically refresh once connection is restored.",
+      0
+    );
+
+    const errorOverlay = document.getElementById('error-overlay');
+    if (errorOverlay) {
+      const reconnectBtn = document.createElement('button');
+      reconnectBtn.className = "mt-4 bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded";
+      reconnectBtn.textContent = "Try to Reconnect";
+      reconnectBtn.onclick = () => {
+        NetworkManager.manualReconnect();
+      };
+
+      const buttonContainer = errorOverlay.querySelector('.button-container') || 
+                              document.createElement('div');
+      buttonContainer.className = "button-container mt-4";
+      buttonContainer.innerHTML = '';
+      buttonContainer.appendChild(reconnectBtn);
+      
+      if (!errorOverlay.querySelector('.button-container')) {
+        errorOverlay.querySelector('div').appendChild(buttonContainer);
+      }
+    }
   }
 };
 
-// CHART
 const chartModule = {
   chart: null,
   currentCoin: 'BTC',
   loadStartTime: 0,
   chartRefs: new WeakMap(),
-  
+
   verticalLinePlugin: {
     id: 'verticalLine',
     beforeDraw: (chart, args, options) => {
@@ -879,29 +650,54 @@ const chartModule = {
   destroyChart: function() {
     if (chartModule.chart) {
       try {
+        const canvas = document.getElementById('coin-chart');
+        if (canvas) {
+          const events = ['click', 'mousemove', 'mouseout', 'mouseover', 'mousedown', 'mouseup'];
+          events.forEach(eventType => {
+            canvas.removeEventListener(eventType, null);
+          });
+        }
+
         chartModule.chart.destroy();
+        chartModule.chart = null;
+
+        if (canvas) {
+          chartModule.chartRefs.delete(canvas);
+        }
       } catch (e) {
-        console.error('Error destroying chart:', e);
+        try {
+          if (chartModule.chart) {
+            if (chartModule.chart.destroy && typeof chartModule.chart.destroy === 'function') {
+              chartModule.chart.destroy();
+            }
+            chartModule.chart = null;
+          }
+        } catch (finalError) {}
       }
-      chartModule.chart = null;
     }
   },
 
   initChart: function() {
     this.destroyChart();
-    
+
     const canvas = document.getElementById('coin-chart');
     if (!canvas) {
-      logger.error('Chart canvas element not found');
+      console.error('Chart canvas element not found');
       return;
     }
-    
+
+    canvas.style.display = 'block';
+    if (canvas.style.width === '1px' || canvas.style.height === '1px') {
+      canvas.style.width = '100%';
+      canvas.style.height = '100%';
+    }
+
     const ctx = canvas.getContext('2d');
     if (!ctx) {
-      logger.error('Failed to get chart context. Make sure the canvas element exists.');
+      console.error('Failed to get chart context. Make sure the canvas element exists.');
       return;
     }
-    
+
     const gradient = ctx.createLinearGradient(0, 0, 0, 400);
     gradient.addColorStop(0, 'rgba(77, 132, 240, 0.2)');
     gradient.addColorStop(1, 'rgba(77, 132, 240, 0)');
@@ -923,6 +719,9 @@ const chartModule = {
       options: {
         responsive: true,
         maintainAspectRatio: false,
+        animation: {
+          duration: 750
+        },
         interaction: {
           intersect: false,
           mode: 'index'
@@ -945,7 +744,7 @@ const chartModule = {
               }
             },
             ticks: {
-              source: 'data',
+              source: 'auto',
               maxTicksLimit: 12,
               font: {
                 size: 12,
@@ -956,14 +755,14 @@ const chartModule = {
               minRotation: 0,
               callback: function(value) {
                 const date = new Date(value);
-                if (config.currentResolution === 'day') {
+                if (window.config.currentResolution === 'day') {
                   return date.toLocaleTimeString('en-US', {
                     hour: 'numeric',
                     minute: '2-digit',
                     hour12: true,
                     timeZone: 'UTC'
                   });
-                } else if (config.currentResolution === 'year') {
+                } else if (window.config.currentResolution === 'year') {
                   return date.toLocaleDateString('en-US', {
                     month: 'short',
                     year: 'numeric',
@@ -1015,7 +814,7 @@ const chartModule = {
             callbacks: {
               title: (tooltipItems) => {
                 const date = new Date(tooltipItems[0].parsed.x);
-                if (config.currentResolution === 'day') {
+                if (window.config.currentResolution === 'day') {
                   return date.toLocaleString('en-US', {
                     month: 'short',
                     day: 'numeric',
@@ -1024,7 +823,7 @@ const chartModule = {
                     hour12: true,
                     timeZone: 'UTC'
                   });
-                } else if (config.currentResolution === 'year') {
+                } else if (window.config.currentResolution === 'year') {
                   return date.toLocaleString('en-US', {
                     year: 'numeric',
                     month: 'short',
@@ -1058,6 +857,12 @@ const chartModule = {
     });
 
     this.setChartReference(canvas, chartModule.chart);
+
+    if (window.CleanupManager) {
+      window.CleanupManager.registerResource('chart', chartModule.chart, () => {
+        chartModule.destroyChart();
+      });
+    }
   },
 
   prepareChartData: function(coinSymbol, data) {
@@ -1066,68 +871,85 @@ const chartModule = {
     }
 
     try {
-      let preparedData;
-
-      if (coinSymbol === 'WOW' && Array.isArray(data)) {
-        const endTime = new Date(data[data.length - 1][0]);
-        endTime.setUTCMinutes(0, 0, 0);
-        const endUnix = endTime.getTime();
-        const startUnix = endUnix - (24 * 3600000);
-        const hourlyPoints = [];
-
-        for (let hourUnix = startUnix; hourUnix <= endUnix; hourUnix += 3600000) {
-          const targetHour = new Date(hourUnix);
-          targetHour.setUTCMinutes(0, 0, 0);
-
-          const closestPoint = data.reduce((prev, curr) => {
-            const prevTime = new Date(prev[0]);
-            const currTime = new Date(curr[0]);
-            const prevDiff = Math.abs(prevTime - targetHour);
-            const currDiff = Math.abs(currTime - targetHour);
-            return currDiff < prevDiff ? curr : prev;
-          });
-
-          hourlyPoints.push({
-            x: targetHour,
-            y: closestPoint[1]
-          });
-        }
-
-        const lastTime = new Date(data[data.length - 1][0]);
-        if (lastTime.getUTCMinutes() !== 0) {
-          hourlyPoints.push({
-            x: lastTime,
-            y: data[data.length - 1][1]
-          });
-        }
-
-        preparedData = hourlyPoints;
+      let rawDataPoints = [];
 
+      if (Array.isArray(data)) {
+        rawDataPoints = data.map(([timestamp, price]) => ({
+          time: new Date(timestamp).getTime(),
+          close: price
+        }));
       } else if (data.Data && Array.isArray(data.Data)) {
-        preparedData = data.Data.map(d => ({
-          x: new Date(d.time * 1000),
-          y: d.close
+        rawDataPoints = data.Data.map(d => ({
+          time: d.time * 1000,
+          close: d.close
         }));
       } else if (data.Data && data.Data.Data && Array.isArray(data.Data.Data)) {
-        preparedData = data.Data.Data.map(d => ({
-          x: new Date(d.time * 1000),
-          y: d.close
-        }));
-      } else if (Array.isArray(data)) {
-        preparedData = data.map(([timestamp, price]) => ({
-          x: new Date(timestamp),
-          y: price
+        rawDataPoints = data.Data.Data.map(d => ({
+          time: d.time * 1000,
+          close: d.close
         }));
       } else {
-        console.warn('Unknown data format for chartData:', data);
         return [];
       }
-      return preparedData.map(point => ({
-        x: new Date(point.x).getTime(),
-        y: point.y
-      }));
+
+      if (rawDataPoints.length === 0) {
+        return [];
+      }
+
+      rawDataPoints.sort((a, b) => a.time - b.time);
+      
+      let preparedData = [];
+
+      if (window.config.currentResolution === 'day') {
+        const endTime = new Date(rawDataPoints[rawDataPoints.length - 1].time);
+        endTime.setUTCMinutes(0, 0, 0);
+
+        const endUnix = endTime.getTime();
+        const startUnix = endUnix - (24 * 3600000);
+
+        for (let hourUnix = startUnix; hourUnix <= endUnix; hourUnix += 3600000) {
+          let closestPoint = null;
+          let closestDiff = Infinity;
+
+          for (const point of rawDataPoints) {
+            const diff = Math.abs(point.time - hourUnix);
+            if (diff < closestDiff) {
+              closestDiff = diff;
+              closestPoint = point;
+            }
+          }
+          
+          if (closestPoint) {
+            preparedData.push({
+              x: hourUnix,
+              y: closestPoint.close
+            });
+          }
+        }
+
+        const lastTime = rawDataPoints[rawDataPoints.length - 1].time;
+        if (lastTime > endUnix) {
+          preparedData.push({
+            x: lastTime,
+            y: rawDataPoints[rawDataPoints.length - 1].close
+          });
+        }
+      } else {
+        preparedData = rawDataPoints.map(point => ({
+          x: point.time,
+          y: point.close
+        }));
+      }
+      
+      if (preparedData.length === 0 && rawDataPoints.length > 0) {
+        preparedData = rawDataPoints.map(point => ({
+          x: point.time,
+          y: point.close
+        }));
+      }
+
+      return preparedData;
     } catch (error) {
-      console.error(`Error preparing chart data for ${coinSymbol}:`, error);
       return [];
     }
   },
@@ -1142,10 +964,10 @@ const chartModule = {
       const targetTime = new Date(twentyFourHoursAgo.getTime() + i * 60 * 60 * 1000);
 
       if (data.length > 0) {
-       const closestDataPoint = data.reduce((prev, curr) =>
-  Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) < 
-  Math.abs(new Date(prev.x).getTime() - targetTime.getTime()) ? curr : prev
-);
+        const closestDataPoint = data.reduce((prev, curr) =>
+          Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) < 
+          Math.abs(new Date(prev.x).getTime() - targetTime.getTime()) ? curr : prev
+        , data[0]);
         hourlyData.push({
           x: targetTime.getTime(),
           y: closestDataPoint.y
@@ -1166,13 +988,17 @@ const chartModule = {
         chartModule.showChartLoader();
       }
       chartModule.loadStartTime = Date.now();
-      const cacheKey = `chartData_${coinSymbol}_${config.currentResolution}`;
-      let cachedData = !forceRefresh ? cache.get(cacheKey) : null;
+      const cacheKey = `chartData_${coinSymbol}_${window.config.currentResolution}`;
+      let cachedData = !forceRefresh ? CacheManager.get(cacheKey) : null;
       let data;
       if (cachedData && Object.keys(cachedData.value).length > 0) {
         data = cachedData.value;
       } else {
         try {
+          if (!NetworkManager.isOnline()) {
+            throw new Error('Network is offline');
+          }
+
           const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
           data = allData[coinSymbol];
           
@@ -1180,8 +1006,10 @@ const chartModule = {
             throw new Error(`No data returned for ${coinSymbol}`);
           }
 
-          cache.set(cacheKey, data, config.cacheTTL);
+          CacheManager.set(cacheKey, data, 'chart');
         } catch (error) {
+          NetworkManager.handleNetworkError(error);
+          
           if (error.message.includes('429') && currentChartData.length > 0) {
             console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`);
             chartModule.hideChartLoader();
@@ -1212,10 +1040,10 @@ const chartModule = {
         if (coinSymbol === 'WOW') {
           chartModule.chart.options.scales.x.time.unit = 'hour';
         } else {
-          const resolution = config.resolutions[config.currentResolution];
+          const resolution = window.config.chartConfig.resolutions[window.config.currentResolution];
           chartModule.chart.options.scales.x.time.unit = 
-            resolution.interval === 'hourly' ? 'hour' : 
-            config.currentResolution === 'year' ? 'month' : 'day';
+            resolution && resolution.interval === 'hourly' ? 'hour' :
+            window.config.currentResolution === 'year' ? 'month' : 'day';
         }
         chartModule.chart.update('active');
         chartModule.currentCoin = coinSymbol;
@@ -1224,8 +1052,7 @@ const chartModule = {
       }
     } catch (error) {
       console.error(`Error updating chart for ${coinSymbol}:`, error);
-      
-      // Keep existing chart data if possible /todo
+
       if (!(chartModule.chart?.data?.datasets[0]?.data?.length > 0)) {
         if (!chartModule.chart) {
           chartModule.initChart();
@@ -1259,11 +1086,12 @@ const chartModule = {
     loader.classList.add('hidden');
     chart.classList.remove('hidden');
   },
+  
   cleanup: function() {
     this.destroyChart();
     this.currentCoin = null;
     this.loadStartTime = 0;
-    console.log('Chart module cleaned up');
+    this.chartRefs = new WeakMap();
   }
 };
 
@@ -1274,8 +1102,8 @@ const volumeToggle = {
   init: function() {
     const toggleButton = document.getElementById('toggle-volume');
     if (toggleButton) {
-      if (typeof cleanupManager !== 'undefined') {
-        cleanupManager.addListener(toggleButton, 'click', volumeToggle.toggle);
+      if (typeof CleanupManager !== 'undefined') {
+        CleanupManager.addListener(toggleButton, 'click', volumeToggle.toggle);
       } else {
         toggleButton.addEventListener('click', volumeToggle.toggle);
       }
@@ -1311,12 +1139,12 @@ const volumeToggle = {
   }
 };
 
-  function updateButtonStyles(button, isActive, color) {
-    button.classList.toggle('text-' + color + '-500', isActive);
-    button.classList.toggle('text-gray-600', !isActive);
-    button.classList.toggle('dark:text-' + color + '-400', isActive);
-    button.classList.toggle('dark:text-gray-400', !isActive);
-  }
+function updateButtonStyles(button, isActive, color) {
+  button.classList.toggle('text-' + color + '-500', isActive);
+  button.classList.toggle('text-gray-600', !isActive);
+  button.classList.toggle('dark:text-' + color + '-400', isActive);
+  button.classList.toggle('dark:text-gray-400', !isActive);
+}
 
 const app = {
   btcPriceUSD: 0,
@@ -1330,92 +1158,122 @@ const app = {
     disabled: 'Auto-refresh: disabled',
     justRefreshed: 'Just refreshed',
   },
-  cacheTTL: 5 * 60 * 1000, // 5 min
-  minimumRefreshInterval: 60 * 1000, // 1 min
+  cacheTTL: window.config.cacheConfig.ttlSettings.prices,
+  minimumRefreshInterval: 60 * 1000,
 
-  init: () => {
-    console.log('Init');
+  init: function() {
     window.addEventListener('load', app.onLoad);
     app.loadLastRefreshedTime();
     app.updateAutoRefreshButton();
-    //console.log('App initialized');
+
+    NetworkManager.addHandler('offline', () => {
+      ui.showNetworkErrorMessage();
+    });
+
+    NetworkManager.addHandler('reconnected', () => {
+      ui.hideErrorMessage();
+      app.refreshAllData();
+    });
+
+    NetworkManager.addHandler('maxAttemptsReached', () => {
+      ui.displayErrorMessage(
+        "Server connection lost. Please check your internet connection and try refreshing the page.",
+        0
+      );
+    });
+    
+    return app;
   },
 
-  onLoad: async () => {
-    //console.log('App onLoad event triggered');
+  onLoad: async function() {
     ui.showLoader();
     try {
-        volumeToggle.init();
-        await app.updateBTCPrice();
-        const chartContainer = document.getElementById('coin-chart');
-        if (chartContainer) {
-            chartModule.initChart();
-            chartModule.showChartLoader();
+      volumeToggle.init();
+      await app.updateBTCPrice();
+      const chartContainer = document.getElementById('coin-chart');
+      if (chartContainer) {
+        chartModule.initChart();
+        chartModule.showChartLoader();
+      }
+
+      await app.loadAllCoinData();
+
+      if (chartModule.chart) {
+        window.config.currentResolution = 'day';
+        await chartModule.updateChart('BTC');
+        app.updateResolutionButtons('BTC');
+
+        const chartTitle = document.getElementById('chart-title');
+        if (chartTitle) {
+          chartTitle.textContent = 'Price Chart (BTC)';
         }
+      }
+      ui.setActiveContainer('btc-container');
 
-        //console.log('Loading all coin data...');
-        await app.loadAllCoinData();
-
-        if (chartModule.chart) {
-            config.currentResolution = 'day';
-            await chartModule.updateChart('BTC');
-            app.updateResolutionButtons('BTC');
-
-            const chartTitle = document.getElementById('chart-title');
-            if (chartTitle) {
-                chartTitle.textContent = 'Price Chart (BTC)';
-            }
-        }
-        ui.setActiveContainer('btc-container');
-
-        app.setupEventListeners();
-        app.initializeSelectImages();
-        app.initAutoRefresh();
+      app.setupEventListeners();
+      app.initAutoRefresh();
 
     } catch (error) {
-        ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
+      ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
+      NetworkManager.handleNetworkError(error);
     } finally {
-        ui.hideLoader();
-        if (chartModule.chart) {
-            chartModule.hideChartLoader();
-        }
-        //console.log('App onLoad completed');
+      ui.hideLoader();
+      if (chartModule.chart) {
+        chartModule.hideChartLoader();
+      }
     }
-},
-    loadAllCoinData: async () => {
-        //console.log('Loading data for all coins...');
-        try {
-            const allCoinData = await api.fetchCoinGeckoDataXHR();
-            if (allCoinData.error) {
-                throw new Error(allCoinData.error);
-            }
+  },
+  
+  loadAllCoinData: async function() {
+    try {
+      if (!NetworkManager.isOnline()) {
+        throw new Error('Network is offline');
+      }
+      
+      const allCoinData = await api.fetchCoinGeckoDataXHR();
+      if (allCoinData.error) {
+        throw new Error(allCoinData.error);
+      }
 
-            for (const coin of config.coins) {
-                const coinData = allCoinData[coin.symbol.toLowerCase()];
-                if (coinData) {
-                    coinData.displayName = coin.displayName || coin.symbol;
-                    ui.displayCoinData(coin.symbol, coinData);
-                    const cacheKey = `coinData_${coin.symbol}`;
-                    cache.set(cacheKey, coinData);
-                } else {
-                    //console.warn(`No data found for ${coin.symbol}`);
-                }
+      let volumeData = {};
+      try {
+        volumeData = await api.fetchVolumeDataXHR();
+      } catch (volumeError) {}
+
+      for (const coin of window.config.coins) {
+        const coinData = allCoinData[coin.symbol.toLowerCase()];
+        
+        if (coinData) {
+          coinData.displayName = coin.displayName || coin.symbol;
+
+          const backendId = getCoinBackendId ? getCoinBackendId(coin.name) : coin.name;
+          if (volumeData[backendId]) {
+            coinData.total_volume = volumeData[backendId].total_volume;
+            if (!coinData.price_change_percentage_24h && volumeData[backendId].price_change_percentage_24h) {
+              coinData.price_change_percentage_24h = volumeData[backendId].price_change_percentage_24h;
             }
-        } catch (error) {
-            //console.error('Error loading all coin data:', error);
-            ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
-        } finally {
-            //console.log('All coin data loaded');
+          }
+
+          ui.displayCoinData(coin.symbol, coinData);
+
+          const cacheKey = `coinData_${coin.symbol}`;
+          CacheManager.set(cacheKey, coinData);
+        } else {
+          console.warn(`No data found for ${coin.symbol}`);
         }
-    },
+      }
+    } catch (error) {
+      console.error('Error loading all coin data:', error);
+      NetworkManager.handleNetworkError(error);
+      ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
+    }
+  },
 
-  loadCoinData: async (coin) => {
-    //console.log(`Loading data for ${coin.symbol}...`);
+  loadCoinData: async function(coin) {
     const cacheKey = `coinData_${coin.symbol}`;
-    let cachedData = cache.get(cacheKey);
+    let cachedData = CacheManager.get(cacheKey);
     let data;
     if (cachedData) {
-      //console.log(`Using cached data for ${coin.symbol}`);
       data = cachedData.value;
     } else {
       try {
@@ -1428,11 +1286,10 @@ const app = {
         if (data.error) {
           throw new Error(data.error);
         }
-        //console.log(`Caching new data for ${coin.symbol}`);
-        cache.set(cacheKey, data);
+        CacheManager.set(cacheKey, data, 'prices');
         cachedData = null;
       } catch (error) {
-        //console.error(`Error fetching ${coin.symbol} data:`, error.message);
+        NetworkManager.handleNetworkError(error);
         data = {
           error: error.message
         };
@@ -1442,49 +1299,51 @@ const app = {
     }
     ui.displayCoinData(coin.symbol, data);
     ui.updateLoadTimeAndCache(0, cachedData);
-    //console.log(`Data loaded for ${coin.symbol}`);
   },
 
-setupEventListeners: () => {
-    config.coins.forEach(coin => {
-        const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`);
-        if (container) {
-            container.addEventListener('click', () => {
-                const chartTitle = document.getElementById('chart-title');
-                if (chartTitle) {
-                    chartTitle.textContent = `Price Chart (${coin.symbol})`;
-                }
-                ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
-                if (chartModule.chart) {
-                    if (coin.symbol === 'WOW') {
-                        config.currentResolution = 'day';
-                    }
-                    chartModule.updateChart(coin.symbol);
-                    app.updateResolutionButtons(coin.symbol);
-                }
-            });
-        }
+  setupEventListeners: function() {
+    window.config.coins.forEach(coin => {
+      const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`);
+      if (container) {
+        CleanupManager.addListener(container, 'click', () => {
+          const chartTitle = document.getElementById('chart-title');
+          if (chartTitle) {
+            chartTitle.textContent = `Price Chart (${coin.symbol})`;
+          }
+          ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
+          if (chartModule.chart) {
+            if (coin.symbol === 'WOW') {
+              window.config.currentResolution = 'day';
+            }
+            chartModule.updateChart(coin.symbol);
+            app.updateResolutionButtons(coin.symbol);
+          }
+        });
+      }
     });
 
     const refreshAllButton = document.getElementById('refresh-all');
     if (refreshAllButton) {
-      refreshAllButton.addEventListener('click', app.refreshAllData);
+      CleanupManager.addListener(refreshAllButton, 'click', app.refreshAllData);
     }
 
     const headers = document.querySelectorAll('th');
     headers.forEach((header, index) => {
-      header.addEventListener('click', () => app.sortTable(index, header.classList.contains('disabled')));
+      CleanupManager.addListener(header, 'click', () => app.sortTable(index, header.classList.contains('disabled')));
     });
 
     const closeErrorButton = document.getElementById('close-error');
     if (closeErrorButton) {
-      closeErrorButton.addEventListener('click', ui.hideErrorMessage);
+      CleanupManager.addListener(closeErrorButton, 'click', ui.hideErrorMessage);
+    }
+
+    const reconnectButton = document.getElementById('network-reconnect');
+    if (reconnectButton) {
+      CleanupManager.addListener(reconnectButton, 'click', NetworkManager.manualReconnect);
     }
-    //console.log('Event listeners set up');
   },
 
-  initAutoRefresh: () => {
-   //console.log('Initializing auto-refresh...');
+  initAutoRefresh: function() {
     const toggleAutoRefreshButton = document.getElementById('toggle-auto-refresh');
     if (toggleAutoRefreshButton) {
       toggleAutoRefreshButton.addEventListener('click', app.toggleAutoRefresh);
@@ -1492,15 +1351,11 @@ setupEventListeners: () => {
     }
 
     if (app.isAutoRefreshEnabled) {
-      console.log('Auto-refresh is enabled, scheduling next refresh');
       app.scheduleNextRefresh();
-    } else {
-      console.log('Auto-refresh is disabled');
     }
   },
 
-  scheduleNextRefresh: () => {
-    //console.log('Scheduling next refresh...');
+  scheduleNextRefresh: function() {
     if (app.autoRefreshInterval) {
       clearTimeout(app.autoRefreshInterval);
     }
@@ -1516,7 +1371,6 @@ setupEventListeners: () => {
             earliestExpiration = Math.min(earliestExpiration, cachedItem.expiresAt);
           }
         } catch (error) {
-          //console.error(`Error parsing cached item ${key}:`, error);
           localStorage.removeItem(key);
         }
       }
@@ -1526,22 +1380,30 @@ setupEventListeners: () => {
     if (earliestExpiration !== Infinity) {
       nextRefreshTime = Math.max(earliestExpiration, now + app.minimumRefreshInterval);
     } else {
-      nextRefreshTime = now + config.cacheTTL;
+      nextRefreshTime = now + window.config.cacheTTL;
     }
     const timeUntilRefresh = nextRefreshTime - now;
-    console.log(`Next refresh scheduled in ${timeUntilRefresh / 1000} seconds`);
     app.nextRefreshTime = nextRefreshTime;
     app.autoRefreshInterval = setTimeout(() => {
-      console.log('Auto-refresh triggered');
-      app.refreshAllData();
+      if (NetworkManager.isOnline()) {
+        app.refreshAllData();
+      } else {
+        app.scheduleNextRefresh();
+      }
     }, timeUntilRefresh);
     localStorage.setItem('nextRefreshTime', app.nextRefreshTime.toString());
     app.updateNextRefreshTime();
   },
-      refreshAllData: async () => {
+
+  refreshAllData: async function() {
     if (app.isRefreshing) {
-        console.log('Refresh already in progress, skipping...');
-        return;
+      console.log('Refresh already in progress, skipping...');
+      return;
+    }
+
+    if (!NetworkManager.isOnline()) {
+      ui.displayErrorMessage("Network connection unavailable. Please check your connection.");
+      return;
     }
 
     const lastGeckoRequest = rateLimiter.lastRequestTime['coingecko'] || 0;
@@ -1549,131 +1411,159 @@ setupEventListeners: () => {
     const waitTime = Math.max(0, rateLimiter.minRequestInterval.coingecko - timeSinceLastRequest);
     
     if (waitTime > 0) {
-        const seconds = Math.ceil(waitTime / 1000);
-        ui.displayErrorMessage(`Rate limit: Please wait ${seconds} seconds before refreshing`);
+      const seconds = Math.ceil(waitTime / 1000);
+      ui.displayErrorMessage(`Rate limit: Please wait ${seconds} seconds before refreshing`);
 
-        let remainingTime = seconds;
-        const countdownInterval = setInterval(() => {
-            remainingTime--;
-            if (remainingTime > 0) {
-                ui.displayErrorMessage(`Rate limit: Please wait ${remainingTime} seconds before refreshing`);
-            } else {
-                clearInterval(countdownInterval);
-                ui.hideErrorMessage();
-            }
-        }, 1000);
+      let remainingTime = seconds;
+      const countdownInterval = setInterval(() => {
+        remainingTime--;
+        if (remainingTime > 0) {
+          ui.displayErrorMessage(`Rate limit: Please wait ${remainingTime} seconds before refreshing`);
+        } else {
+          clearInterval(countdownInterval);
+          ui.hideErrorMessage();
+        }
+      }, 1000);
 
-        return;
+      return;
     }
 
-    //console.log('Starting refresh of all data...');
+    console.log('Starting refresh of all data...');
     app.isRefreshing = true;
     ui.showLoader();
     chartModule.showChartLoader();
 
     try {
-        ui.hideErrorMessage();
-        cache.clear();
+      ui.hideErrorMessage();
+      CacheManager.clear();
 
-        const btcUpdateSuccess = await app.updateBTCPrice();
-        if (!btcUpdateSuccess) {
-            console.warn('BTC price update failed, continuing with cached or default value');
-        }
+      const btcUpdateSuccess = await app.updateBTCPrice();
+      if (!btcUpdateSuccess) {
+        console.warn('BTC price update failed, continuing with cached or default value');
+      }
 
-        await new Promise(resolve => setTimeout(resolve, 1000));
+      await new Promise(resolve => setTimeout(resolve, 1000));
 
-        const allCoinData = await api.fetchCoinGeckoDataXHR();
-        if (allCoinData.error) {
-            throw new Error(`CoinGecko API Error: ${allCoinData.error}`);
-        }
+      const allCoinData = await api.fetchCoinGeckoDataXHR();
+      if (allCoinData.error) {
+        throw new Error(`CoinGecko API Error: ${allCoinData.error}`);
+      }
 
-        const failedCoins = [];
+      let volumeData = {};
+      try {
+        volumeData = await api.fetchVolumeDataXHR();
+      } catch (volumeError) {}
 
-        for (const coin of config.coins) {
-            const symbol = coin.symbol.toLowerCase();
-            const coinData = allCoinData[symbol];
+      const failedCoins = [];
 
-            try {
-                if (!coinData) {
-                    throw new Error(`No data received`);
-                }
+      for (const coin of window.config.coins) {
+        const symbol = coin.symbol.toLowerCase();
+        const coinData = allCoinData[symbol];
 
-                coinData.displayName = coin.displayName || coin.symbol;
-                ui.displayCoinData(coin.symbol, coinData);
+        try {
+          if (!coinData) {
+            throw new Error(`No data received`);
+          }
 
-                const cacheKey = `coinData_${coin.symbol}`;
-                cache.set(cacheKey, coinData);
+          coinData.displayName = coin.displayName || coin.symbol;
 
-            } catch (coinError) {
-                console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
-                failedCoins.push(coin.symbol);
+          const backendId = getCoinBackendId ? getCoinBackendId(coin.name) : coin.name;
+          if (volumeData[backendId]) {
+            coinData.total_volume = volumeData[backendId].total_volume;
+            if (!coinData.price_change_percentage_24h && volumeData[backendId].price_change_percentage_24h) {
+              coinData.price_change_percentage_24h = volumeData[backendId].price_change_percentage_24h;
             }
-        }
-
-        await new Promise(resolve => setTimeout(resolve, 1000));
-
-        if (chartModule.currentCoin) {
+          } else {
             try {
-                await chartModule.updateChart(chartModule.currentCoin, true);
-            } catch (chartError) {
-                console.error('Chart update failed:', chartError);
+              const cacheKey = `coinData_${coin.symbol}`;
+              const cachedData = CacheManager.get(cacheKey);
+              if (cachedData && cachedData.value && cachedData.value.total_volume) {
+                coinData.total_volume = cachedData.value.total_volume;
+              }
+              if (cachedData && cachedData.value && cachedData.value.price_change_percentage_24h && 
+                  !coinData.price_change_percentage_24h) {
+                coinData.price_change_percentage_24h = cachedData.value.price_change_percentage_24h;
+              }
+            } catch (e) {
+              console.warn(`Failed to retrieve cached volume data for ${coin.symbol}:`, e);
             }
+          }
+
+          ui.displayCoinData(coin.symbol, coinData);
+
+          const cacheKey = `coinData_${coin.symbol}`;
+          CacheManager.set(cacheKey, coinData, 'prices');
+
+        } catch (coinError) {
+          console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
+          failedCoins.push(coin.symbol);
         }
+      }
 
-        app.lastRefreshedTime = new Date();
-        localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
-        ui.updateLastRefreshedTime();
+      await new Promise(resolve => setTimeout(resolve, 1000));
 
-        if (failedCoins.length > 0) {
-            const failureMessage = failedCoins.length === config.coins.length
-                ? 'Failed to update any coin data'
-                : `Failed to update some coins: ${failedCoins.join(', ')}`;
+      if (chartModule.currentCoin) {
+        try {
+          await chartModule.updateChart(chartModule.currentCoin, true);
+        } catch (chartError) {
+          console.error('Chart update failed:', chartError);
+        }
+      }
 
-            let countdown = 5;
+      app.lastRefreshedTime = new Date();
+      localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
+      ui.updateLastRefreshedTime();
+
+      if (failedCoins.length > 0) {
+        const failureMessage = failedCoins.length === window.config.coins.length
+            ? 'Failed to update any coin data'
+            : `Failed to update some coins: ${failedCoins.join(', ')}`;
+
+        let countdown = 5;
+        ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
+
+        const countdownInterval = setInterval(() => {
+          countdown--;
+          if (countdown > 0) {
             ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
+          } else {
+            clearInterval(countdownInterval);
+            ui.hideErrorMessage();
+          }
+        }, 1000);
+      }
 
-            const countdownInterval = setInterval(() => {
-                countdown--;
-                if (countdown > 0) {
-                    ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
-                } else {
-                    clearInterval(countdownInterval);
-                    ui.hideErrorMessage();
-                }
-            }, 1000);
-        }
-
-        console.log(`Refresh completed. Failed coins: ${failedCoins.length}`);
+      console.log(`Refresh completed. Failed coins: ${failedCoins.length}`);
 
     } catch (error) {
-        console.error('Critical error during refresh:', error);
+      console.error('Critical error during refresh:', error);
+      NetworkManager.handleNetworkError(error);
 
-        let countdown = 10;
-        ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
-        
-        const countdownInterval = setInterval(() => {
-            countdown--;
-            if (countdown > 0) {
-                ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
-            } else {
-                clearInterval(countdownInterval);
-                ui.hideErrorMessage();
-            }
-        }, 1000);
+      let countdown = 10;
+      ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
+      
+      const countdownInterval = setInterval(() => {
+        countdown--;
+        if (countdown > 0) {
+          ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
+        } else {
+          clearInterval(countdownInterval);
+          ui.hideErrorMessage();
+        }
+      }, 1000);
 
     } finally {
-        ui.hideLoader();
-        chartModule.hideChartLoader();
-        app.isRefreshing = false;
+      ui.hideLoader();
+      chartModule.hideChartLoader();
+      app.isRefreshing = false;
 
-        if (app.isAutoRefreshEnabled) {
-            app.scheduleNextRefresh();
-        }
+      if (app.isAutoRefreshEnabled) {
+        app.scheduleNextRefresh();
+      }
     }
-},
+  },
 
-  updateNextRefreshTime: () => {
-    //console.log('Updating next refresh time display');
+  updateNextRefreshTime: function() {
     const nextRefreshSpan = document.getElementById('next-refresh-time');
     const labelElement = document.getElementById('next-refresh-label');
     const valueElement = document.getElementById('next-refresh-value');
@@ -1708,8 +1598,7 @@ setupEventListeners: () => {
     }
   },
 
-  updateAutoRefreshButton: () => {
-    //console.log('Updating auto-refresh button state');
+  updateAutoRefreshButton: function() {
     const button = document.getElementById('toggle-auto-refresh');
     if (button) {
       if (app.isAutoRefreshEnabled) {
@@ -1725,8 +1614,7 @@ setupEventListeners: () => {
     }
   },
 
-  startSpinAnimation: () => {
-    //console.log('Starting spin animation on auto-refresh button');
+  startSpinAnimation: function() {
     const svg = document.querySelector('#toggle-auto-refresh svg');
     if (svg) {
       svg.classList.add('animate-spin');
@@ -1736,16 +1624,14 @@ setupEventListeners: () => {
     }
   },
 
-  stopSpinAnimation: () => {
-    //console.log('Stopping spin animation on auto-refresh button');
+  stopSpinAnimation: function() {
     const svg = document.querySelector('#toggle-auto-refresh svg');
     if (svg) {
       svg.classList.remove('animate-spin');
     }
   },
 
-  updateLastRefreshedTime: () => {
-    //console.log('Updating last refreshed time');
+  updateLastRefreshedTime: function() {
     const lastRefreshedElement = document.getElementById('last-refreshed-time');
     if (lastRefreshedElement && app.lastRefreshedTime) {
       const formattedTime = app.lastRefreshedTime.toLocaleTimeString();
@@ -1753,8 +1639,7 @@ setupEventListeners: () => {
     }
   },
 
-  loadLastRefreshedTime: () => {
-    //console.log('Loading last refreshed time from storage');
+  loadLastRefreshedTime: function() {
     const storedTime = localStorage.getItem('lastRefreshedTime');
     if (storedTime) {
       app.lastRefreshedTime = new Date(parseInt(storedTime));
@@ -1762,199 +1647,57 @@ setupEventListeners: () => {
     }
   },
 
-updateBTCPrice: async () => {
-    //console.log('Updating BTC price...');
+  updateBTCPrice: async function() {
     try {
-        const priceData = await api.fetchCoinGeckoDataXHR();
+      if (!NetworkManager.isOnline()) {
+        throw new Error('Network is offline');
+      }
+      
+      const response = await Api.fetchCoinPrices("bitcoin");
 
-        if (priceData.error) {
-            console.warn('API error when fetching BTC price:', priceData.error);
-            return false;
-        }
+      if (response && response.rates && response.rates.bitcoin) {
+        app.btcPriceUSD = response.rates.bitcoin;
+        return true;
+      }
 
-        if (priceData.btc && typeof priceData.btc.current_price === 'number') {
-            app.btcPriceUSD = priceData.btc.current_price;
-            return true;
-        } else if (priceData.bitcoin && typeof priceData.bitcoin.usd === 'number') {
-            app.btcPriceUSD = priceData.bitcoin.usd;
-            return true;
-        }
-
-        console.warn('Unexpected BTC price data structure:', priceData);
-        return false;
+      console.warn('Unexpected BTC price data structure:', response);
+      return false;
 
     } catch (error) {
-        console.error('Error fetching BTC price:', error);
-        return false;
+      console.error('Error fetching BTC price:', error);
+      NetworkManager.handleNetworkError(error);
+      return false;
     }
-},
+  },
 
-sortTable: (columnIndex) => {
-  //console.log(`Sorting column: ${columnIndex}`);
-  const sortableColumns = [0, 5, 6, 7]; // 0: Time, 5: Rate, 6: Market +/-, 7: Trade
-  if (!sortableColumns.includes(columnIndex)) {
-    //console.log(`Column ${columnIndex} is not sortable`);
-    return;
-  }
-  const table = document.querySelector('table');
-  if (!table) {
-    //console.error("Table not found for sorting.");
-    return;
-  }
-  const rows = Array.from(table.querySelectorAll('tbody tr'));
-  console.log(`Found ${rows.length} rows to sort`);
-  const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
-  if (!sortIcon) {
-    //console.error("Sort icon not found.");
-    return;
-  }
-  const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
-  sortIcon.textContent = sortOrder === 1 ? '↑' : '↓';
-
-  const getSafeTextContent = (element) => element ? element.textContent.trim() : '';
-
-  rows.sort((a, b) => {
-    let aValue, bValue;
-    switch (columnIndex) {
-      case 1: // Time column
-        aValue = getSafeTextContent(a.querySelector('td:first-child .text-xs:first-child'));
-        bValue = getSafeTextContent(b.querySelector('td:first-child .text-xs:first-child'));
-        //console.log(`Comparing times: "${aValue}" vs "${bValue}"`);
-
-        const parseTime = (timeStr) => {
-          const [value, unit] = timeStr.split(' ');
-          const numValue = parseFloat(value);
-          switch(unit) {
-            case 'seconds': return numValue;
-            case 'minutes': return numValue * 60;
-            case 'hours': return numValue * 3600;
-            case 'days': return numValue * 86400;
-            default: return 0;
-          }
-        };
-        return (parseTime(bValue) - parseTime(aValue)) * sortOrder;
-
-      case 5: // Rate
-      case 6: // Market +/-
-        aValue = getSafeTextContent(a.cells[columnIndex]);
-        bValue = getSafeTextContent(b.cells[columnIndex]);
-        //console.log(`Comparing values: "${aValue}" vs "${bValue}"`);
-
-        aValue = parseFloat(aValue.replace(/[^\d.-]/g, '') || '0');
-        bValue = parseFloat(bValue.replace(/[^\d.-]/g, '') || '0');
-        return (aValue - bValue) * sortOrder;
-
-      case 7: // Trade
-        const aCell = a.cells[columnIndex];
-        const bCell = b.cells[columnIndex];
-        //console.log('aCell:', aCell ? aCell.outerHTML : 'null');
-        //console.log('bCell:', bCell ? bCell.outerHTML : 'null');
-
-        aValue = getSafeTextContent(aCell.querySelector('a')) || 
-                 getSafeTextContent(aCell.querySelector('button')) || 
-                 getSafeTextContent(aCell);
-        bValue = getSafeTextContent(bCell.querySelector('a')) || 
-                 getSafeTextContent(bCell.querySelector('button')) || 
-                 getSafeTextContent(bCell);
-        
-        aValue = aValue.toLowerCase();
-        bValue = bValue.toLowerCase();
-
-        //console.log(`Comparing trade actions: "${aValue}" vs "${bValue}"`);
-
-        if (aValue === bValue) return 0;
-        if (aValue === "swap") return -1 * sortOrder;
-        if (bValue === "swap") return 1 * sortOrder;
-        return aValue.localeCompare(bValue) * sortOrder;
-
-      default:
-        aValue = getSafeTextContent(a.cells[columnIndex]);
-        bValue = getSafeTextContent(b.cells[columnIndex]);
-        //console.log(`Comparing default values: "${aValue}" vs "${bValue}"`);
-        return aValue.localeCompare(bValue, undefined, {
-          numeric: true,
-          sensitivity: 'base'
-        }) * sortOrder;
-    }
-  });
-
-  const tbody = table.querySelector('tbody');
-  if (tbody) {
-    rows.forEach(row => tbody.appendChild(row));
-  } else {
-    //console.error("Table body not found.");
-  }
-  //console.log('Sorting completed');
-},
-  
-  initializeSelectImages: () => {
-    const updateSelectedImage = (selectId) => {
-      const select = document.getElementById(selectId);
-      const button = document.getElementById(`${selectId}_button`);
-      if (!select || !button) {
-        //console.error(`Elements not found for ${selectId}`);
-        return;
-      }
-      const selectedOption = select.options[select.selectedIndex];
-      const imageURL = selectedOption?.getAttribute('data-image');
-      requestAnimationFrame(() => {
-        if (imageURL) {
-          button.style.backgroundImage = `url('${imageURL}')`;
-          button.style.backgroundSize = '25px 25px';
-          button.style.backgroundPosition = 'center';
-          button.style.backgroundRepeat = 'no-repeat';
+  updateResolutionButtons: function(coinSymbol) {
+    const resolutionButtons = document.querySelectorAll('.resolution-button');
+    resolutionButtons.forEach(button => {
+      const resolution = button.id.split('-')[1];
+      if (coinSymbol === 'WOW') {
+        if (resolution === 'day') {
+          button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
+          button.classList.add('active');
+          button.disabled = false;
         } else {
-          button.style.backgroundImage = 'none';
+          button.classList.add('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
+          button.classList.remove('active');
+          button.disabled = true;
         }
-        button.style.minWidth = '25px';
-        button.style.minHeight = '25px';
-      });
-    };
-    const handleSelectChange = (event) => {
-      updateSelectedImage(event.target.id);
-    };
-    ['coin_to', 'coin_from'].forEach(selectId => {
-      const select = document.getElementById(selectId);
-      if (select) {
-        select.addEventListener('change', handleSelectChange);
-        updateSelectedImage(selectId);
       } else {
-        //console.error(`Select element not found for ${selectId}`);
+        button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
+        button.classList.toggle('active', resolution === window.config.currentResolution);
+        button.disabled = false;
       }
     });
   },
 
-updateResolutionButtons: (coinSymbol) => {
-  const resolutionButtons = document.querySelectorAll('.resolution-button');
-  resolutionButtons.forEach(button => {
-    const resolution = button.id.split('-')[1];
-    if (coinSymbol === 'WOW') {
-      if (resolution === 'day') {
-        button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
-        button.classList.add('active');
-        button.disabled = false;
-      } else {
-        button.classList.add('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
-        button.classList.remove('active');
-        button.disabled = true;
-      }
-    } else {
-      button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
-      button.classList.toggle('active', resolution === config.currentResolution);
-      button.disabled = false;
-    }
-  });
-},
-  
- toggleAutoRefresh: () => {
-    console.log('Toggling auto-refresh');
+  toggleAutoRefresh: function() {
     app.isAutoRefreshEnabled = !app.isAutoRefreshEnabled;
     localStorage.setItem('autoRefreshEnabled', app.isAutoRefreshEnabled.toString());
     if (app.isAutoRefreshEnabled) {
-      console.log('Auto-refresh enabled, scheduling next refresh');
       app.scheduleNextRefresh();
     } else {
-      console.log('Auto-refresh disabled, clearing interval');
       if (app.autoRefreshInterval) {
         clearTimeout(app.autoRefreshInterval);
         app.autoRefreshInterval = null;
@@ -1974,55 +1717,158 @@ resolutionButtons.forEach(button => {
     const currentCoin = chartModule.currentCoin;
     
     if (currentCoin !== 'WOW' || resolution === 'day') {
-      config.currentResolution = resolution;
+      window.config.currentResolution = resolution;
       chartModule.updateChart(currentCoin, true);
       app.updateResolutionButtons(currentCoin);
     }
   });
 });
 
-// LOAD
+function cleanup() {
+  console.log('Starting cleanup process');
+  
+  try {
+    if (window.MemoryManager) {
+      MemoryManager.forceCleanup();
+    }
+    
+    if (chartModule) {
+      CleanupManager.registerResource('chartModule', chartModule, (cm) => {
+        cm.cleanup();
+      });
+    }
+
+    if (volumeToggle) {
+      CleanupManager.registerResource('volumeToggle', volumeToggle, (vt) => {
+        vt.cleanup();
+      });
+    }
+
+    ['chartModule', 'volumeToggle', 'app'].forEach(ref => {
+      if (window[ref]) {
+        window[ref] = null;
+      }
+    });
+
+    const cleanupCounts = CleanupManager.clearAll();
+    console.log('All resources cleaned up:', cleanupCounts);
+    
+  } catch (error) {
+    console.error('Error during cleanup:', error);
+    CleanupManager.clearAll();
+  }
+}
+
+window.cleanup = cleanup;
+
 const appCleanup = {
   init: function() {
-    memoryMonitor.startMonitoring();
     window.addEventListener('beforeunload', this.globalCleanup);
   },
 
   globalCleanup: function() {
     try {
+      if (window.MemoryManager) {
+        MemoryManager.forceCleanup();
+      }
+
       if (app.autoRefreshInterval) {
-        clearTimeout(app.autoRefreshInterval);
+        CleanupManager.clearTimeout(app.autoRefreshInterval);
       }
       if (chartModule) {
-        chartModule.cleanup();
+        CleanupManager.registerResource('chartModule', chartModule, (cm) => {
+          cm.cleanup();
+        });
       }
       if (volumeToggle) {
-        volumeToggle.cleanup();
+        CleanupManager.registerResource('volumeToggle', volumeToggle, (vt) => {
+          vt.cleanup();
+        });
       }
-      cleanupManager.clearAll();
-      memoryMonitor.stopMonitoring();
-      cache.clear();
-
-      console.log('Global application cleanup completed');
-    } catch (error) {
-      console.error('Error during global cleanup:', error);
-    }
+      CleanupManager.clearAll();
+      CacheManager.clear();
+    } catch (error) {}
   },
+
   manualCleanup: function() {
     this.globalCleanup();
     window.location.reload();
   }
 };
 
-app.init = () => {
-  //console.log('Init');
+document.addEventListener('DOMContentLoaded', () => {
+    if (window.NetworkManager && !window.networkManagerInitialized) {
+        NetworkManager.initialize({
+            connectionTestEndpoint: '/json',
+            connectionTestTimeout: 3000,
+            reconnectDelay: 5000, 
+            maxReconnectAttempts: 5
+        });
+        window.networkManagerInitialized = true;
+    }
+
+    app.init();
+
+    if (window.MemoryManager) {
+        MemoryManager.enableAutoCleanup();
+    }
+
+    CleanupManager.setInterval(() => {
+        CacheManager.cleanup();
+    }, 300000);  // Every 5 minutes
+
+    CleanupManager.setInterval(() => {
+        if (chartModule && chartModule.currentCoin && NetworkManager.isOnline()) {
+            chartModule.updateChart(chartModule.currentCoin);
+        }
+    }, 900000);  // Every 15 minutes
+
+    CleanupManager.addListener(document, 'visibilitychange', () => {
+        if (!document.hidden) {
+            console.log('Page is now visible');
+
+            if (NetworkManager.isOnline()) {
+                if (chartModule && chartModule.currentCoin) {
+                    chartModule.updateChart(chartModule.currentCoin);
+                }
+            } else {
+
+                NetworkManager.attemptReconnect();
+            }
+        }
+    });
+
+    CleanupManager.addListener(window, 'beforeunload', () => {
+        cleanup();
+    });
+
+    appCleanup.init();
+});
+
+app.init = function() {
   window.addEventListener('load', app.onLoad);
-  appCleanup.init();
   app.loadLastRefreshedTime();
   app.updateAutoRefreshButton();
-  memoryMonitor.startMonitoring();
-  //console.log('App initialized');
+
+  if (window.NetworkManager) {
+    NetworkManager.addHandler('offline', () => {
+      ui.showNetworkErrorMessage();
+    });
+
+    NetworkManager.addHandler('reconnected', () => {
+      ui.hideErrorMessage();
+      app.refreshAllData();
+    });
+
+    NetworkManager.addHandler('maxAttemptsReached', () => {
+      ui.displayErrorMessage(
+        "Server connection lost. Please check your internet connection and try refreshing the page.",
+        0
+      );
+    });
+  }
+
+  return app;
 };
 
-// LOAD
 app.init();
diff --git a/basicswap/static/js/active.js b/basicswap/static/js/swaps_in_progress.js
similarity index 78%
rename from basicswap/static/js/active.js
rename to basicswap/static/js/swaps_in_progress.js
index 2189979..6efe3d7 100644
--- a/basicswap/static/js/active.js
+++ b/basicswap/static/js/swaps_in_progress.js
@@ -1,4 +1,3 @@
-// Constants and State
 const PAGE_SIZE = 50;
 const COIN_NAME_TO_SYMBOL = {
     'Bitcoin': 'BTC',
@@ -16,7 +15,6 @@ const COIN_NAME_TO_SYMBOL = {
     'Dogecoin': 'DOGE'
 };
 
-// Global state
 const state = {
     identities: new Map(),
     currentPage: 1,
@@ -27,7 +25,6 @@ const state = {
     refreshPromise: null
 };
 
-// DOM
 const elements = {
     swapsBody: document.getElementById('active-swaps-body'),
     prevPageButton: document.getElementById('prevPage'),
@@ -40,105 +37,6 @@ const elements = {
     statusText: document.getElementById('status-text')
 };
 
-// Identity Manager
-const IdentityManager = {
-    cache: new Map(),
-    pendingRequests: new Map(),
-    retryDelay: 2000,
-    maxRetries: 3,
-    cacheTimeout: 5 * 60 * 1000, // 5 minutes
-
-    async getIdentityData(address) {
-        if (!address) {
-            return { address: '' };
-        }
-
-        const cachedData = this.getCachedIdentity(address);
-        if (cachedData) {
-            return { ...cachedData, address };
-        }
-
-        if (this.pendingRequests.has(address)) {
-            const pendingData = await this.pendingRequests.get(address);
-            return { ...pendingData, address };
-        }
-
-        const request = this.fetchWithRetry(address);
-        this.pendingRequests.set(address, request);
-
-        try {
-            const data = await request;
-            this.cache.set(address, {
-                data,
-                timestamp: Date.now()
-            });
-            return { ...data, address };
-        } catch (error) {
-            console.warn(`Error fetching identity for ${address}:`, error);
-            return { address };
-        } finally {
-            this.pendingRequests.delete(address);
-        }
-    },
-
-    getCachedIdentity(address) {
-        const cached = this.cache.get(address);
-        if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
-            return cached.data;
-        }
-        if (cached) {
-            this.cache.delete(address);
-        }
-        return null;
-    },
-
-    async fetchWithRetry(address, attempt = 1) {
-        try {
-            const response = await fetch(`/json/identities/${address}`, {
-                signal: AbortSignal.timeout(5000)
-            });
-
-            if (!response.ok) {
-                throw new Error(`HTTP error! status: ${response.status}`);
-            }
-
-            const data = await response.json();
-            return {
-                ...data,
-                address,
-                num_sent_bids_successful: safeParseInt(data.num_sent_bids_successful),
-                num_recv_bids_successful: safeParseInt(data.num_recv_bids_successful),
-                num_sent_bids_failed: safeParseInt(data.num_sent_bids_failed),
-                num_recv_bids_failed: safeParseInt(data.num_recv_bids_failed),
-                num_sent_bids_rejected: safeParseInt(data.num_sent_bids_rejected),
-                num_recv_bids_rejected: safeParseInt(data.num_recv_bids_rejected),
-                label: data.label || '',
-                note: data.note || '',
-                automation_override: safeParseInt(data.automation_override)
-            };
-        } catch (error) {
-            if (attempt >= this.maxRetries) {
-                console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
-                return {
-                    address,
-                    num_sent_bids_successful: 0,
-                    num_recv_bids_successful: 0,
-                    num_sent_bids_failed: 0,
-                    num_recv_bids_failed: 0,
-                    num_sent_bids_rejected: 0,
-                    num_recv_bids_rejected: 0,
-                    label: '',
-                    note: '',
-                    automation_override: 0
-                };
-            }
-
-            await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
-            return this.fetchWithRetry(address, attempt + 1);
-        }
-    }
-};
-
 const safeParseInt = (value) => {
     const parsed = parseInt(value);
     return isNaN(parsed) ? 0 : parsed;
@@ -200,7 +98,6 @@ const getTxStatusClass = (status) => {
     return 'text-blue-500';
 };
 
-// Util
 const formatTimeAgo = (timestamp) => {
     const now = Math.floor(Date.now() / 1000);
     const diff = now - timestamp;
@@ -211,7 +108,6 @@ const formatTimeAgo = (timestamp) => {
     return `${Math.floor(diff / 86400)} days ago`;
 };
 
-
 const formatTime = (timestamp) => {
     if (!timestamp) return '';
     const date = new Date(timestamp * 1000);
@@ -251,111 +147,6 @@ const getTimeStrokeColor = (expireTime) => {
     return '#10B981'; // More than 30 minutes
 };
 
-// WebSocket Manager
-const WebSocketManager = {
-    ws: null,
-    processingQueue: false,
-    reconnectTimeout: null,
-    maxReconnectAttempts: 5,
-    reconnectAttempts: 0,
-    reconnectDelay: 5000,
-
-    initialize() {
-        this.connect();
-        this.startHealthCheck();
-    },
-
-    connect() {
-    if (this.ws?.readyState === WebSocket.OPEN) return;
-
-    try {
-
-        let wsPort;
-        
-        if (typeof getWebSocketConfig === 'function') {
-            const wsConfig = getWebSocketConfig();
-            wsPort = wsConfig?.port || wsConfig?.fallbackPort;
-        }
-
-        if (!wsPort && window.config?.port) {
-            wsPort = window.config.port;
-        }
-
-        if (!wsPort) {
-            wsPort = window.ws_port || '11700';
-        }
-
-        console.log("Using WebSocket port:", wsPort);
-        this.ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
-        this.setupEventHandlers();
-    } catch (error) {
-        console.error('WebSocket connection error:', error);
-        this.handleReconnect();
-    }
-},
-    setupEventHandlers() {
-        this.ws.onopen = () => {
-            state.wsConnected = true;
-            this.reconnectAttempts = 0;
-            updateConnectionStatus('connected');
-            console.log('🟢  WebSocket connection established for Swaps in Progress');
-            updateSwapsTable({ resetPage: true, refreshData: true });
-        };
-
-        this.ws.onmessage = () => {
-            if (!this.processingQueue) {
-                this.processingQueue = true;
-                setTimeout(async () => {
-                    try {
-                        if (!state.isRefreshing) {
-                            await updateSwapsTable({ resetPage: false, refreshData: true });
-                        }
-                    } finally {
-                        this.processingQueue = false;
-                    }
-                }, 200);
-            }
-        };
-
-        this.ws.onclose = () => {
-            state.wsConnected = false;
-            updateConnectionStatus('disconnected');
-            this.handleReconnect();
-        };
-
-        this.ws.onerror = () => {
-            updateConnectionStatus('error');
-        };
-    },
-
-    startHealthCheck() {
-        setInterval(() => {
-            if (this.ws?.readyState !== WebSocket.OPEN) {
-                this.handleReconnect();
-            }
-        }, 30000);
-    },
-
-    handleReconnect() {
-        if (this.reconnectTimeout) {
-            clearTimeout(this.reconnectTimeout);
-        }
-
-        this.reconnectAttempts++;
-        if (this.reconnectAttempts <= this.maxReconnectAttempts) {
-            const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
-            this.reconnectTimeout = setTimeout(() => this.connect(), delay);
-        } else {
-            updateConnectionStatus('error');
-            setTimeout(() => {
-                this.reconnectAttempts = 0;
-                this.connect();
-            }, 60000);
-        }
-    }
-};
-
-// UI
 const updateConnectionStatus = (status) => {
     const { statusDot, statusText } = elements;
     if (!statusDot || !statusText) return;
@@ -528,7 +319,7 @@ const createSwapTableRow = async (swap) => {
             <td class="relative w-0 p-0 m-0">
                 <div class="absolute top-0 bottom-0 left-0 w-1"></div>
             </td>
-            
+
             <!-- Time Column -->
             <td class="py-3 pl-1 pr-2 text-xs whitespace-nowrap">
                 <div class="flex items-center">
@@ -575,13 +366,14 @@ const createSwapTableRow = async (swap) => {
              </div>
             </div>
            </td>
-
-            <!-- You Receive Column -->
+            <!-- You Send Column -->
             <td class="py-0">
                 <div class="py-3 px-4 text-left">
                     <div class="items-center monospace">
-                        <div class="text-sm font-semibold">${toAmount.toFixed(8)}</div>
-                        <div class="text-sm text-gray-500 dark:text-gray-400">${toSymbol}</div>
+                        <div class="pr-2">
+                            <div class="text-sm font-semibold">${fromAmount.toFixed(8)}</div>
+                            <div class="text-sm text-gray-500 dark:text-gray-400">${fromSymbol}</div>
+                        </div>
                     </div>
                 </div>
             </td>
@@ -592,8 +384,8 @@ const createSwapTableRow = async (swap) => {
                     <div class="flex items-center justify-center">
                         <span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
                             <img class="h-12" 
-                                 src="/static/images/coins/${swap.coin_to.replace(' ', '-')}.png" 
-                                 alt="${swap.coin_to}"
+                                 src="/static/images/coins/${swap.coin_from.replace(' ', '-')}.png" 
+                                 alt="${swap.coin_from}"
                                  onerror="this.src='/static/images/coins/default.png'">
                         </span>
                         <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
@@ -601,30 +393,27 @@ const createSwapTableRow = async (swap) => {
                         </svg>
                         <span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
                             <img class="h-12" 
-                                 src="/static/images/coins/${swap.coin_from.replace(' ', '-')}.png" 
-                                 alt="${swap.coin_from}"
+                                 src="/static/images/coins/${swap.coin_to.replace(' ', '-')}.png" 
+                                 alt="${swap.coin_to}"
                                  onerror="this.src='/static/images/coins/default.png'">
                         </span>
                     </div>
                 </div>
             </td>
 
-            <!-- You Send Column -->
+            <!-- You Receive Column -->
             <td class="py-0">
                 <div class="py-3 px-4 text-right">
                     <div class="items-center monospace">
-                        <div>
-                            <div class="text-sm font-semibold">${fromAmount.toFixed(8)}</div>
-                            <div class="text-sm text-gray-500 dark:text-gray-400">${fromSymbol}</div>
-                        </div>
+                        <div class="text-sm font-semibold">${toAmount.toFixed(8)}</div>
+                        <div class="text-sm text-gray-500 dark:text-gray-400">${toSymbol}</div>
                     </div>
                 </div>
             </td>
-
             <!-- Status Column -->
             <td class="py-3 px-4 text-center">
                 <div data-tooltip-target="tooltip-status-${uniqueId}" class="flex justify-center">
-                    <span class="px-2.5 py-1 text-xs font-medium rounded-full ${getStatusClass(swap.bid_state, swap.tx_state_a, swap.tx_state_b)}">
+                    <span class="w-full lg:w-7/8 xl:w-2/3 px-2.5 py-1 inline-flex items-center justify-center text-center rounded-full text-xs font-medium bold ${getStatusClass(swap.bid_state, swap.tx_state_a, swap.tx_state_b)}">
                         ${swap.bid_state}
                     </span>
                 </div>
@@ -727,6 +516,8 @@ const createSwapTableRow = async (swap) => {
 async function updateSwapsTable(options = {}) {
     const { resetPage = false, refreshData = true } = options;
 
+    //console.log('Updating swaps table:', { resetPage, refreshData });
+
     if (state.refreshPromise) {
         await state.refreshPromise;
         return;
@@ -752,9 +543,19 @@ async function updateSwapsTable(options = {}) {
                     }
 
                     const data = await response.json();
-                    state.swapsData = Array.isArray(data) ? data : [];
+                    //console.log('Received swap data:', data);
+
+                    state.swapsData = Array.isArray(data) 
+                        ? data.filter(swap => {
+                            const isActive = isActiveSwap(swap);
+                            //console.log(`Swap ${swap.bid_id}: ${isActive ? 'Active' : 'Inactive'}`, swap.bid_state);
+                            return isActive;
+                        }) 
+                        : [];
+
+                    //console.log('Filtered active swaps:', state.swapsData);
                 } catch (error) {
-                    console.error('Error fetching swap data:', error);
+                    //console.error('Error fetching swap data:', error);
                     state.swapsData = [];
                 } finally {
                     state.refreshPromise = null;
@@ -780,13 +581,14 @@ async function updateSwapsTable(options = {}) {
         const endIndex = startIndex + PAGE_SIZE;
         const currentPageSwaps = state.swapsData.slice(startIndex, endIndex);
 
+        //console.log('Current page swaps:', currentPageSwaps);
+
         if (elements.swapsBody) {
             if (currentPageSwaps.length > 0) {
                 const rowPromises = currentPageSwaps.map(swap => createSwapTableRow(swap));
                 const rows = await Promise.all(rowPromises);
                 elements.swapsBody.innerHTML = rows.join('');
 
-                // Initialize tooltips
                 if (window.TooltipManager) {
                     window.TooltipManager.cleanup();
                     const tooltipTriggers = document.querySelectorAll('[data-tooltip-target]');
@@ -801,6 +603,7 @@ async function updateSwapsTable(options = {}) {
                     });
                 }
             } else {
+                //console.log('No active swaps found, displaying empty state');
                 elements.swapsBody.innerHTML = `
                     <tr>
                         <td colspan="8" class="text-center py-4 text-gray-500 dark:text-white">
@@ -810,22 +613,6 @@ async function updateSwapsTable(options = {}) {
             }
         }
 
-        if (elements.paginationControls) {
-            elements.paginationControls.style.display = totalPages > 1 ? 'flex' : 'none';
-        }
-
-        if (elements.currentPageSpan) {
-            elements.currentPageSpan.textContent = state.currentPage;
-        }
-
-        if (elements.prevPageButton) {
-            elements.prevPageButton.style.display = state.currentPage > 1 ? 'inline-flex' : 'none';
-        }
-
-        if (elements.nextPageButton) {
-            elements.nextPageButton.style.display = state.currentPage < totalPages ? 'inline-flex' : 'none';
-        }
-
     } catch (error) {
         console.error('Error updating swaps table:', error);
         if (elements.swapsBody) {
@@ -841,7 +628,34 @@ async function updateSwapsTable(options = {}) {
     }
 }
 
-// Event
+function isActiveSwap(swap) {
+    const activeStates = [
+
+        'InProgress', 
+        'Accepted', 
+        'Delaying', 
+        'Auto accept delay',
+        'Request accepted',
+        //'Received',
+
+        'Script coin locked', 
+        'Scriptless coin locked',
+        'Script coin lock released',
+ 
+        'SendingInitialTx', 
+        'SendingPaymentTx',
+
+        'Exchanged script lock tx sigs msg',
+        'Exchanged script lock spend tx msg',
+
+        'Script tx redeemed',
+        'Scriptless tx redeemed',
+        'Scriptless tx recovered'
+    ];
+
+    return activeStates.includes(swap.bid_state);
+}
+
 const setupEventListeners = () => {
     if (elements.refreshSwapsButton) {
         elements.refreshSwapsButton.addEventListener('click', async (e) => {
@@ -881,8 +695,11 @@ const setupEventListeners = () => {
     }
 };
 
-// Init
-document.addEventListener('DOMContentLoaded', () => {
+document.addEventListener('DOMContentLoaded', async () => {
     WebSocketManager.initialize();
     setupEventListeners();
+    await updateSwapsTable({ resetPage: true, refreshData: true });
+    const autoRefreshInterval = setInterval(async () => {
+        await updateSwapsTable({ resetPage: false, refreshData: true });
+    }, 10000);  // 30 seconds
 });
diff --git a/basicswap/static/js/tooltips.js b/basicswap/static/js/tooltips.js
deleted file mode 100644
index 9249c56..0000000
--- a/basicswap/static/js/tooltips.js
+++ /dev/null
@@ -1,387 +0,0 @@
-class TooltipManager {
-    constructor() {
-        this.activeTooltips = new WeakMap();
-        this.sizeCheckIntervals = new WeakMap();
-        this.tooltipIdCounter = 0;
-        this.setupStyles();
-        this.setupCleanupEvents();
-        this.initializeMutationObserver();
-    }
-
-    static initialize() {
-        if (!window.TooltipManager) {
-            window.TooltipManager = new TooltipManager();
-        }
-        return window.TooltipManager;
-    }
-
-    create(element, content, options = {}) {
-        if (!element) return null;
-        
-        this.destroy(element);
-
-        const checkSize = () => {
-            if (!document.body.contains(element)) {
-                return;
-            }
-            
-            const rect = element.getBoundingClientRect();
-            if (rect.width && rect.height) {
-                delete element._tooltipRetryCount;
-                this.createTooltip(element, content, options, rect);
-            } else {
-                const retryCount = element._tooltipRetryCount || 0;
-                if (retryCount < 5) {
-                    element._tooltipRetryCount = retryCount + 1;
-                    requestAnimationFrame(checkSize);
-                } else {
-                    delete element._tooltipRetryCount;
-                }
-            }
-        };
-
-        requestAnimationFrame(checkSize);
-        return null;
-    }
-
-    createTooltip(element, content, options, rect) {
-        const targetId = element.getAttribute('data-tooltip-target');
-        let bgClass = 'bg-gray-400';
-        let arrowColor = 'rgb(156 163 175)';
-
-        if (targetId?.includes('tooltip-offer-')) {
-            const offerId = targetId.split('tooltip-offer-')[1];
-            const [actualOfferId] = offerId.split('_');
-            
-            if (window.jsonData) {
-                const offer = window.jsonData.find(o => 
-                    o.unique_id === offerId || 
-                    o.offer_id === actualOfferId
-                );
-
-                if (offer) {
-                    if (offer.is_revoked) {
-                        bgClass = 'bg-red-500';
-                        arrowColor = 'rgb(239 68 68)';
-                    } else if (offer.is_own_offer) {
-                        bgClass = 'bg-gray-300';
-                        arrowColor = 'rgb(209 213 219)';
-                    } else {
-                        bgClass = 'bg-green-700';
-                        arrowColor = 'rgb(21 128 61)';
-                    }
-                }
-            }
-        }
-
-        const tooltipId = `tooltip-${++this.tooltipIdCounter}`;
-
-        const instance = tippy(element, {
-            content,
-            allowHTML: true,
-            placement: options.placement || 'top',
-            appendTo: document.body,
-            animation: false,
-            duration: 0,
-            delay: 0,
-            interactive: true,
-            arrow: false,
-            theme: '',
-            moveTransition: 'none',
-            offset: [0, 10],
-            onShow(instance) {
-                if (!document.body.contains(element)) {
-                    return false;
-                }
-
-                const rect = element.getBoundingClientRect();
-                if (!rect.width || !rect.height) {
-                    return false;
-                }
-
-                return true;
-            },
-            onMount(instance) {
-                if (instance.popper.firstElementChild) {
-                    instance.popper.firstElementChild.classList.add(bgClass);
-                    instance.popper.setAttribute('data-for-tooltip-id', tooltipId);
-                }
-                const arrow = instance.popper.querySelector('.tippy-arrow');
-                if (arrow) {
-                    arrow.style.setProperty('color', arrowColor, 'important');
-                }
-            },
-            popperOptions: {
-                strategy: 'fixed',
-                modifiers: [
-                    {
-                        name: 'preventOverflow',
-                        options: {
-                            boundary: 'viewport',
-                            padding: 10
-                        }
-                    },
-                    {
-                        name: 'flip',
-                        options: {
-                            padding: 10,
-                            fallbackPlacements: ['top', 'bottom', 'right', 'left']
-                        }
-                    }
-                ]
-            }
-        });
-
-        element.setAttribute('data-tooltip-trigger-id', tooltipId);
-        this.activeTooltips.set(element, instance);
-        
-        return instance;
-    }
-
-    destroy(element) {
-        if (!element) return;
-
-        delete element._tooltipRetryCount;
-        
-        const id = element.getAttribute('data-tooltip-trigger-id');
-        if (!id) return;
-
-        const instance = this.activeTooltips.get(element);
-        if (instance?.[0]) {
-            try {
-                instance[0].destroy();
-            } catch (e) {
-                console.warn('Error destroying tooltip:', e);
-                
-                const tippyRoot = document.querySelector(`[data-for-tooltip-id="${id}"]`);
-                if (tippyRoot && tippyRoot.parentNode) {
-                    tippyRoot.parentNode.removeChild(tippyRoot);
-                }
-            }
-        }
-        
-        this.activeTooltips.delete(element);
-        element.removeAttribute('data-tooltip-trigger-id');
-    }
-
-    cleanup() {
-        document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
-            this.destroy(element);
-        });
-
-        document.querySelectorAll('[data-tippy-root]').forEach(element => {
-            if (element.parentNode) {
-                element.parentNode.removeChild(element);
-            }
-        });
-    }
-
-    getActiveTooltipInstances() {
-        const result = [];
-        
-        document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
-            const instance = this.activeTooltips.get(element);
-            if (instance) {
-                result.push([element, instance]);
-            }
-        });
-        
-        return result;
-    }
-
-    initializeMutationObserver() {
-        if (this.mutationObserver) return;
-        
-        this.mutationObserver = new MutationObserver(mutations => {
-            let needsCleanup = false;
-            
-            mutations.forEach(mutation => {
-                if (mutation.removedNodes.length) {
-                    Array.from(mutation.removedNodes).forEach(node => {
-                        if (node.nodeType === 1) {
-                            if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
-                                this.destroy(node);
-                                needsCleanup = true;
-                            }
-                            
-                            if (node.querySelectorAll) {
-                                node.querySelectorAll('[data-tooltip-trigger-id]').forEach(el => {
-                                    this.destroy(el);
-                                    needsCleanup = true;
-                                });
-                            }
-                        }
-                    });
-                }
-            });
-            
-            if (needsCleanup) {
-                document.querySelectorAll('[data-tippy-root]').forEach(element => {
-                    const id = element.getAttribute('data-for-tooltip-id');
-                    if (id && !document.querySelector(`[data-tooltip-trigger-id="${id}"]`)) {
-                        if (element.parentNode) {
-                            element.parentNode.removeChild(element);
-                        }
-                    }
-                });
-            }
-        });
-        
-        this.mutationObserver.observe(document.body, { 
-            childList: true,
-            subtree: true
-        });
-    }
-
-    setupStyles() {
-        if (document.getElementById('tooltip-styles')) return;
-
-        document.head.insertAdjacentHTML('beforeend', `
-            <style id="tooltip-styles">
-                [data-tippy-root] {
-                    position: fixed !important;
-                    z-index: 9999 !important;
-                    pointer-events: none !important;
-                }
-
-                .tippy-box {
-                    font-size: 0.875rem;
-                    line-height: 1.25rem;
-                    font-weight: 500;
-                    border-radius: 0.5rem;
-                    color: white;
-                    position: relative !important;
-                    pointer-events: auto !important;
-                }
-
-                .tippy-content {
-                    padding: 0.5rem 0.75rem !important;
-                }
-
-                .tippy-box .bg-gray-400 {
-                    background-color: rgb(156 163 175);
-                    padding: 0.5rem 0.75rem;
-                }
-                .tippy-box:has(.bg-gray-400) .tippy-arrow {
-                    color: rgb(156 163 175);
-                }
-                
-                .tippy-box .bg-red-500 {
-                    background-color: rgb(239 68 68);
-                    padding: 0.5rem 0.75rem;
-                }
-                .tippy-box:has(.bg-red-500) .tippy-arrow {
-                    color: rgb(239 68 68);
-                }
-                
-                .tippy-box .bg-gray-300 {
-                    background-color: rgb(209 213 219);
-                    padding: 0.5rem 0.75rem;
-                }
-                .tippy-box:has(.bg-gray-300) .tippy-arrow {
-                    color: rgb(209 213 219);
-                }
-                
-                .tippy-box .bg-green-700 {
-                    background-color: rgb(21 128 61);
-                    padding: 0.5rem 0.75rem;
-                }
-                .tippy-box:has(.bg-green-700) .tippy-arrow {
-                    color: rgb(21 128 61);
-                }
-
-                .tippy-box[data-placement^='top'] > .tippy-arrow::before {
-                    border-top-color: currentColor;
-                }
-
-                .tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
-                    border-bottom-color: currentColor;
-                }
-
-                .tippy-box[data-placement^='left'] > .tippy-arrow::before {
-                    border-left-color: currentColor;
-                }
-
-                .tippy-box[data-placement^='right'] > .tippy-arrow::before {
-                    border-right-color: currentColor;
-                }
-
-                .tippy-box[data-placement^='top'] > .tippy-arrow {
-                    bottom: 0;
-                }
-
-                .tippy-box[data-placement^='bottom'] > .tippy-arrow {
-                    top: 0;
-                }
-
-                .tippy-box[data-placement^='left'] > .tippy-arrow {
-                    right: 0;
-                }
-
-                .tippy-box[data-placement^='right'] > .tippy-arrow {
-                    left: 0;
-                }
-            </style>
-        `);
-    }
-
-    setupCleanupEvents() {
-        this.boundCleanup = this.cleanup.bind(this);
-        this.handleVisibilityChange = () => {
-            if (document.hidden) {
-                this.cleanup();
-            }
-        };
-        
-        window.addEventListener('beforeunload', this.boundCleanup);
-        window.addEventListener('unload', this.boundCleanup);
-        document.addEventListener('visibilitychange', this.handleVisibilityChange);
-    }
-
-    removeCleanupEvents() {
-        window.removeEventListener('beforeunload', this.boundCleanup);
-        window.removeEventListener('unload', this.boundCleanup);
-        document.removeEventListener('visibilitychange', this.handleVisibilityChange);
-    }
-
-    initializeTooltips(selector = '[data-tooltip-target]') {
-        document.querySelectorAll(selector).forEach(element => {
-            const targetId = element.getAttribute('data-tooltip-target');
-            const tooltipContent = document.getElementById(targetId);
-            
-            if (tooltipContent) {
-                this.create(element, tooltipContent.innerHTML, {
-                    placement: element.getAttribute('data-tooltip-placement') || 'top'
-                });
-            }
-        });
-    }
-
-    dispose() {
-        this.cleanup();
-        
-        if (this.mutationObserver) {
-            this.mutationObserver.disconnect();
-            this.mutationObserver = null;
-        }
-        
-        this.removeCleanupEvents();
-        
-        const styleElement = document.getElementById('tooltip-styles');
-        if (styleElement && styleElement.parentNode) {
-            styleElement.parentNode.removeChild(styleElement);
-        }
-        
-        if (window.TooltipManager === this) {
-            window.TooltipManager = null;
-        }
-    }
-}
-
-if (typeof module !== 'undefined' && module.exports) {
-    module.exports = TooltipManager;
-}
-
-document.addEventListener('DOMContentLoaded', () => {
-    TooltipManager.initialize();
-});
diff --git a/basicswap/static/js/dropdown.js b/basicswap/static/js/ui/dropdown.js
similarity index 91%
rename from basicswap/static/js/dropdown.js
rename to basicswap/static/js/ui/dropdown.js
index 6fdf12a..788382d 100644
--- a/basicswap/static/js/dropdown.js
+++ b/basicswap/static/js/ui/dropdown.js
@@ -1,15 +1,17 @@
 (function(window) {
     'use strict';
 
+    const dropdownInstances = [];
+
     function positionElement(targetEl, triggerEl, placement = 'bottom', offsetDistance = 8) {
         targetEl.style.visibility = 'hidden';
         targetEl.style.display = 'block';
-        
+
         const triggerRect = triggerEl.getBoundingClientRect();
         const targetRect = targetEl.getBoundingClientRect();
         const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
         const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
-        
+
         let top, left;
 
         top = triggerRect.bottom + offsetDistance;
@@ -58,6 +60,9 @@
             this._handleScroll = this._handleScroll.bind(this);
             this._handleResize = this._handleResize.bind(this);
             this._handleOutsideClick = this._handleOutsideClick.bind(this);
+
+            dropdownInstances.push(this);
+            
             this.init();
         }
 
@@ -66,7 +71,8 @@
                 this._targetEl.style.margin = '0';
                 this._targetEl.style.display = 'none';
                 this._targetEl.style.position = 'fixed';
-                this._targetEl.style.zIndex = '50';
+                this._targetEl.style.zIndex = '40';
+                this._targetEl.classList.add('dropdown-menu');
                 
                 this._setupEventListeners();
                 this._initialized = true;
@@ -123,6 +129,12 @@
 
         show() {
             if (!this._visible) {
+                dropdownInstances.forEach(instance => {
+                    if (instance !== this && instance._visible) {
+                        instance.hide();
+                    }
+                });
+
                 this._targetEl.style.display = 'block';
                 this._targetEl.style.visibility = 'hidden';
 
@@ -133,7 +145,7 @@
                         this._options.placement,
                         this._options.offset
                     );
-                    
+
                     this._visible = true;
                     this._options.onShow();
                 });
@@ -160,6 +172,12 @@
             document.removeEventListener('click', this._handleOutsideClick);
             window.removeEventListener('scroll', this._handleScroll, true);
             window.removeEventListener('resize', this._handleResize);
+
+            const index = dropdownInstances.indexOf(this);
+            if (index > -1) {
+                dropdownInstances.splice(index, 1);
+            }
+
             this._initialized = false;
         }
     }
@@ -168,7 +186,7 @@
         document.querySelectorAll('[data-dropdown-toggle]').forEach(triggerEl => {
             const targetId = triggerEl.getAttribute('data-dropdown-toggle');
             const targetEl = document.getElementById(targetId);
-            
+
             if (targetEl) {
                 const placement = triggerEl.getAttribute('data-dropdown-placement');
                 new Dropdown(targetEl, triggerEl, {
@@ -184,6 +202,8 @@
         initDropdowns();
     }
 
+    Dropdown.instances = dropdownInstances;
+
     window.Dropdown = Dropdown;
     window.initDropdowns = initDropdowns;
 
diff --git a/basicswap/static/js/tabs.js b/basicswap/static/js/ui/tabs.js
similarity index 99%
rename from basicswap/static/js/tabs.js
rename to basicswap/static/js/ui/tabs.js
index a8e9c76..5ca6339 100644
--- a/basicswap/static/js/tabs.js
+++ b/basicswap/static/js/ui/tabs.js
@@ -36,7 +36,7 @@
 
         show(tabId, force = false) {
             const tab = this.getTab(tabId);
-            
+
             if ((tab !== this._activeTab) || force) {
                 this._items.forEach(t => {
                     if (t !== tab) {
diff --git a/basicswap/static/js/wallets.js b/basicswap/static/js/wallets.js
deleted file mode 100644
index 4f5f70c..0000000
--- a/basicswap/static/js/wallets.js
+++ /dev/null
@@ -1,654 +0,0 @@
-const Wallets = (function() {
-  const CONFIG = {
-    MAX_RETRIES: 5,
-    BASE_DELAY: 500,
-    CACHE_EXPIRATION: 5 * 60 * 1000,
-    PRICE_UPDATE_INTERVAL: 5 * 60 * 1000,
-    API_TIMEOUT: 30000,
-    DEBOUNCE_DELAY: 300,
-    CACHE_MIN_INTERVAL: 60 * 1000,
-    DEFAULT_TTL: 300,
-    PRICE_SOURCE: {
-      PRIMARY: 'coingecko.com',
-      FALLBACK: 'cryptocompare.com',
-      ENABLED_SOURCES: ['coingecko.com', 'cryptocompare.com']
-    }
-  };
-
-  const COIN_SYMBOLS = {
-    'Bitcoin': 'BTC',
-    'Particl': 'PART',
-    'Monero': 'XMR',
-    'Wownero': 'WOW',
-    'Litecoin': 'LTC',
-    'Dogecoin': 'DOGE',
-    'Firo': 'FIRO',
-    'Dash': 'DASH',
-    'PIVX': 'PIVX',
-    'Decred': 'DCR',
-    'Bitcoin Cash': 'BCH'
-  };
-
-  const COINGECKO_IDS = {
-    'BTC': 'btc',
-    'PART': 'part',
-    'XMR': 'xmr',
-    'WOW': 'wownero',
-    'LTC': 'ltc',
-    'DOGE': 'doge',
-    'FIRO': 'firo',
-    'DASH': 'dash',
-    'PIVX': 'pivx',
-    'DCR': 'dcr',
-    'BCH': 'bch'
-  };
-
-  const SHORT_NAMES = {
-    'Bitcoin': 'BTC',
-    'Particl': 'PART',
-    'Monero': 'XMR',
-    'Wownero': 'WOW',
-    'Litecoin': 'LTC',
-    'Litecoin MWEB': 'LTC MWEB',
-    'Firo': 'FIRO',
-    'Dash': 'DASH',
-    'PIVX': 'PIVX',
-    'Decred': 'DCR',
-    'Bitcoin Cash': 'BCH',
-    'Dogecoin': 'DOGE'
-  };
-
-  class Cache {
-    constructor(expirationTime) {
-      this.data = null;
-      this.timestamp = null;
-      this.expirationTime = expirationTime;
-    }
-
-    isValid() {
-      return Boolean(
-        this.data && 
-        this.timestamp &&
-        (Date.now() - this.timestamp < this.expirationTime)
-      );
-    }
-
-    set(data) {
-      this.data = data;
-      this.timestamp = Date.now();
-    }
-
-    get() {
-      if (this.isValid()) {
-        return this.data;
-      }
-      return null;
-    }
-
-    clear() {
-      this.data = null;
-      this.timestamp = null;
-    }
-  }
-
-  class ApiClient {
-    constructor() {
-      this.cache = new Cache(CONFIG.CACHE_EXPIRATION);
-      this.lastFetchTime = 0;
-    }
-
-    async fetchPrices(forceUpdate = false) {
-      const now = Date.now();
-      const timeSinceLastFetch = now - this.lastFetchTime;
-
-      if (!forceUpdate && timeSinceLastFetch < CONFIG.CACHE_MIN_INTERVAL) {
-        const cachedData = this.cache.get();
-        if (cachedData) {
-          return cachedData;
-        }
-      }
-
-      let lastError = null;
-      for (let attempt = 0; attempt < CONFIG.MAX_RETRIES; attempt++) {
-        try {
-          const processedData = {};
-          const currentSource = CONFIG.PRICE_SOURCE.PRIMARY;
-          
-          const shouldIncludeWow = currentSource === 'coingecko.com';
-          
-          const coinsToFetch = Object.values(COIN_SYMBOLS)
-            .filter(symbol => shouldIncludeWow || symbol !== 'WOW')
-            .map(symbol => COINGECKO_IDS[symbol] || symbol.toLowerCase())
-            .join(',');
-
-          const mainResponse = await fetch("/json/coinprices", {
-            method: "POST",
-            headers: {'Content-Type': 'application/json'},
-            body: JSON.stringify({
-              coins: coinsToFetch,
-              source: currentSource,
-              ttl: CONFIG.DEFAULT_TTL
-            })
-          });
-
-          if (!mainResponse.ok) {
-            throw new Error(`HTTP error: ${mainResponse.status}`);
-          }
-
-          const mainData = await mainResponse.json();
-
-          if (mainData && mainData.rates) {
-            Object.entries(mainData.rates).forEach(([coinId, price]) => {
-              const symbol = Object.entries(COINGECKO_IDS).find(([sym, id]) => id.toLowerCase() === coinId.toLowerCase())?.[0];
-              if (symbol) {
-                const coinKey = Object.keys(COIN_SYMBOLS).find(key => COIN_SYMBOLS[key] === symbol);
-                if (coinKey) {
-                  processedData[coinKey.toLowerCase().replace(' ', '-')] = {
-                    usd: price,
-                    btc: symbol === 'BTC' ? 1 : price / (mainData.rates.btc || 1)
-                  };
-                }
-              }
-            });
-          }
-
-          if (!shouldIncludeWow && !processedData['wownero']) {
-            try {
-              const wowResponse = await fetch("/json/coinprices", {
-                method: "POST",
-                headers: {'Content-Type': 'application/json'},
-                body: JSON.stringify({
-                  coins: "wownero",
-                  source: "coingecko.com",
-                  ttl: CONFIG.DEFAULT_TTL
-                })
-              });
-
-              if (wowResponse.ok) {
-                const wowData = await wowResponse.json();
-                if (wowData && wowData.rates && wowData.rates.wownero) {
-                  processedData['wownero'] = {
-                    usd: wowData.rates.wownero,
-                    btc: processedData.bitcoin ? wowData.rates.wownero / processedData.bitcoin.usd : 0
-                  };
-                }
-              }
-            } catch (wowError) {
-              console.error('Error fetching WOW price:', wowError);
-            }
-          }
-
-          this.cache.set(processedData);
-          this.lastFetchTime = now;
-          return processedData;
-        } catch (error) {
-          lastError = error;
-          console.error(`Price fetch attempt ${attempt + 1} failed:`, error);
-
-          if (attempt === CONFIG.MAX_RETRIES - 1 && 
-              CONFIG.PRICE_SOURCE.FALLBACK && 
-              CONFIG.PRICE_SOURCE.FALLBACK !== CONFIG.PRICE_SOURCE.PRIMARY) {
-            const temp = CONFIG.PRICE_SOURCE.PRIMARY;
-            CONFIG.PRICE_SOURCE.PRIMARY = CONFIG.PRICE_SOURCE.FALLBACK;
-            CONFIG.PRICE_SOURCE.FALLBACK = temp;
-
-            console.warn(`Switching to fallback source: ${CONFIG.PRICE_SOURCE.PRIMARY}`);
-            attempt = -1;
-            continue;
-          }
-
-          if (attempt < CONFIG.MAX_RETRIES - 1) {
-            const delay = Math.min(CONFIG.BASE_DELAY * Math.pow(2, attempt), 10000);
-            await new Promise(resolve => setTimeout(resolve, delay));
-          }
-        }
-      }
-
-      const cachedData = this.cache.get();
-      if (cachedData) {
-        console.warn('Using cached data after fetch failures');
-        return cachedData;
-      }
-
-      throw lastError || new Error('Failed to fetch prices');
-    }
-
-    setPriceSource(primarySource, fallbackSource = null) {
-      if (!CONFIG.PRICE_SOURCE.ENABLED_SOURCES.includes(primarySource)) {
-        throw new Error(`Invalid primary source: ${primarySource}`);
-      }
-
-      if (fallbackSource && !CONFIG.PRICE_SOURCE.ENABLED_SOURCES.includes(fallbackSource)) {
-        throw new Error(`Invalid fallback source: ${fallbackSource}`);
-      }
-
-      CONFIG.PRICE_SOURCE.PRIMARY = primarySource;
-      if (fallbackSource) {
-        CONFIG.PRICE_SOURCE.FALLBACK = fallbackSource;
-      }
-    }
-  }
-
-  class UiManager {
-    constructor() {
-      this.api = new ApiClient();
-      this.toggleInProgress = false;
-      this.toggleDebounceTimer = null;
-      this.priceUpdateInterval = null;
-      this.lastUpdateTime = parseInt(localStorage.getItem(STATE_KEYS.LAST_UPDATE) || '0');
-      this.isWalletsPage = document.querySelector('.wallet-list') !== null || 
-      window.location.pathname.includes('/wallets');
-    }
-
-    getShortName(fullName) {
-      return SHORT_NAMES[fullName] || fullName;
-    }
-
-    storeOriginalValues() {
-      document.querySelectorAll('.coinname-value').forEach(el => {
-        const coinName = el.getAttribute('data-coinname');
-        const value = el.textContent?.trim() || '';
-
-        if (coinName) {
-          const amount = value ? parseFloat(value.replace(/[^0-9.-]+/g, '')) : 0;
-          const coinId = COIN_SYMBOLS[coinName];
-          const shortName = this.getShortName(coinName);
-
-          if (coinId) {
-            if (coinName === 'Particl') {
-              const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
-              const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
-              const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
-              localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
-            } else if (coinName === 'Litecoin') {
-              const isMWEB = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('MWEB');
-              const balanceType = isMWEB ? 'mweb' : 'public';
-              localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
-            } else {
-              localStorage.setItem(`${coinId.toLowerCase()}-amount`, amount.toString());
-            }
-
-            el.setAttribute('data-original-value', `${amount} ${shortName}`);
-          }
-        }
-      });
-
-      document.querySelectorAll('.usd-value').forEach(el => {
-        const text = el.textContent?.trim() || '';
-        if (text === 'Loading...') {
-          el.textContent = '';
-        }
-      });
-    }
-
-    async updatePrices(forceUpdate = false) {
-      try {
-        const prices = await this.api.fetchPrices(forceUpdate);
-        let newTotal = 0;
-
-        const currentTime = Date.now();
-        localStorage.setItem(STATE_KEYS.LAST_UPDATE, currentTime.toString());
-        this.lastUpdateTime = currentTime;
-
-        if (prices) {
-          Object.entries(prices).forEach(([coinId, priceData]) => {
-            if (priceData?.usd) {
-              localStorage.setItem(`${coinId}-price`, priceData.usd.toString());
-            }
-          });
-        }
-
-        document.querySelectorAll('.coinname-value').forEach(el => {
-          const coinName = el.getAttribute('data-coinname');
-          const amountStr = el.getAttribute('data-original-value') || el.textContent?.trim() || '';
-
-          if (!coinName) return;
-
-          let amount = 0;
-          if (amountStr) {
-            const matches = amountStr.match(/([0-9]*[.])?[0-9]+/);
-            if (matches && matches.length > 0) {
-              amount = parseFloat(matches[0]);
-            }
-          }
-
-          const coinId = coinName.toLowerCase().replace(' ', '-');
-          
-          if (!prices[coinId]) {
-            return;
-          }
-
-          const price = prices[coinId]?.usd || parseFloat(localStorage.getItem(`${coinId}-price`) || '0');
-          if (!price) return;
-          
-          const usdValue = (amount * price).toFixed(2);
-
-          if (coinName === 'Particl') {
-            const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
-            const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
-            const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
-            localStorage.setItem(`particl-${balanceType}-last-value`, usdValue);
-            localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
-          } else if (coinName === 'Litecoin') {
-            const isMWEB = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('MWEB');
-            const balanceType = isMWEB ? 'mweb' : 'public';
-            localStorage.setItem(`litecoin-${balanceType}-last-value`, usdValue);
-            localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
-          } else {
-            localStorage.setItem(`${coinId}-last-value`, usdValue);
-            localStorage.setItem(`${coinId}-amount`, amount.toString());
-          }
-
-          if (amount > 0) {
-            newTotal += parseFloat(usdValue);
-          }
-
-          let usdEl = null;
-          
-          const flexContainer = el.closest('.flex');
-          if (flexContainer) {
-            const nextFlex = flexContainer.nextElementSibling;
-            if (nextFlex) {
-              const usdInNextFlex = nextFlex.querySelector('.usd-value');
-              if (usdInNextFlex) {
-                usdEl = usdInNextFlex;
-              }
-            }
-          }
-
-          if (!usdEl) {
-            const parentCell = el.closest('td');
-            if (parentCell) {
-              const usdInSameCell = parentCell.querySelector('.usd-value');
-              if (usdInSameCell) {
-                usdEl = usdInSameCell;
-              }
-            }
-          }
-
-          if (!usdEl) {
-            const sibling = el.nextElementSibling;
-            if (sibling && sibling.classList.contains('usd-value')) {
-              usdEl = sibling;
-            }
-          }
-
-          if (!usdEl) {
-            const parentElement = el.parentElement;
-            if (parentElement) {
-              const usdElNearby = parentElement.querySelector('.usd-value');
-              if (usdElNearby) {
-                usdEl = usdElNearby;
-              }
-            }
-          }
-
-          if (usdEl) {
-            usdEl.textContent = `$${usdValue}`;
-            usdEl.setAttribute('data-original-value', usdValue);
-          }
-        });
-
-        document.querySelectorAll('.usd-value').forEach(el => {
-          if (el.closest('tr')?.querySelector('td')?.textContent?.includes('Fee Estimate:')) {
-            const parentCell = el.closest('td');
-            if (!parentCell) return;
-
-            const coinValueEl = parentCell.querySelector('.coinname-value');
-            if (!coinValueEl) return;
-
-            const coinName = coinValueEl.getAttribute('data-coinname');
-            if (!coinName) return;
-
-            const amountStr = coinValueEl.textContent?.trim() || '0';
-            const amount = parseFloat(amountStr) || 0;
-
-            const coinId = coinName.toLowerCase().replace(' ', '-');
-            if (!prices[coinId]) return;
-
-            const price = prices[coinId]?.usd || parseFloat(localStorage.getItem(`${coinId}-price`) || '0');
-            if (!price) return;
-
-            const usdValue = (amount * price).toFixed(8);
-            el.textContent = `$${usdValue}`;
-            el.setAttribute('data-original-value', usdValue);
-          }
-        });
-
-        if (this.isWalletsPage) {
-          this.updateTotalValues(newTotal, prices?.bitcoin?.usd);
-        }
-
-        localStorage.setItem(STATE_KEYS.PREVIOUS_TOTAL, localStorage.getItem(STATE_KEYS.CURRENT_TOTAL) || '0');
-        localStorage.setItem(STATE_KEYS.CURRENT_TOTAL, newTotal.toString());
-
-        return true;
-      } catch (error) {
-        console.error('Price update failed:', error);
-        return false;
-      }
-    }
-    
-    updateTotalValues(totalUsd, btcPrice) {
-      const totalUsdEl = document.getElementById('total-usd-value');
-      if (totalUsdEl) {
-        totalUsdEl.textContent = `$${totalUsd.toFixed(2)}`;
-        totalUsdEl.setAttribute('data-original-value', totalUsd.toString());
-        localStorage.setItem('total-usd', totalUsd.toString());
-      }
-
-      if (btcPrice) {
-        const btcTotal = btcPrice ? totalUsd / btcPrice : 0;
-        const totalBtcEl = document.getElementById('total-btc-value');
-        if (totalBtcEl) {
-          totalBtcEl.textContent = `~ ${btcTotal.toFixed(8)} BTC`;
-          totalBtcEl.setAttribute('data-original-value', btcTotal.toString());
-        }
-      }
-    }
-
-    async toggleBalances() {
-      if (this.toggleInProgress) return;
-
-      try {
-        this.toggleInProgress = true;
-        const balancesVisible = localStorage.getItem('balancesVisible') === 'true';
-        const newVisibility = !balancesVisible;
-
-        localStorage.setItem('balancesVisible', newVisibility.toString());
-        this.updateVisibility(newVisibility);
-
-        if (this.toggleDebounceTimer) {
-          clearTimeout(this.toggleDebounceTimer);
-        }
-
-        this.toggleDebounceTimer = window.setTimeout(async () => {
-          this.toggleInProgress = false;
-          if (newVisibility) {
-            await this.updatePrices(true);
-          }
-        }, CONFIG.DEBOUNCE_DELAY);
-      } catch (error) {
-        console.error('Failed to toggle balances:', error);
-        this.toggleInProgress = false;
-      }
-    }
-
-    updateVisibility(isVisible) {
-      if (isVisible) {
-        this.showBalances();
-      } else {
-        this.hideBalances();
-      }
-
-      const eyeIcon = document.querySelector("#hide-usd-amount-toggle svg");
-      if (eyeIcon) {
-        eyeIcon.innerHTML = isVisible ? 
-          '<path d="M23.444,10.239C21.905,8.062,17.708,3,12,3S2.1,8.062,.555,10.24a3.058,3.058,0,0,0,0,3.52h0C2.1,15.938,6.292,21,12,21s9.905-5.062,11.445-7.24A3.058,3.058,0,0,0,23.444,10.239ZM12,17a5,5,0,1,1,5-5A5,5,0,0,1,12,17Z"></path>' :
-          '<path d="M23.444,10.239a22.936,22.936,0,0,0-2.492-2.948l-4.021,4.021A5.026,5.026,0,0,1,17,12a5,5,0,0,1-5,5,5.026,5.026,0,0,1-.688-.069L8.055,20.188A10.286,10.286,0,0,0,12,21c5.708,0,9.905-5.062,11.445-7.24A3.058,3.058,0,0,0,23.444,10.239Z"></path><path d="M12,3C6.292,3,2.1,8.062,.555,10.24a3.058,3.058,0,0,0,0,3.52h0a21.272,21.272,0,0,0,4.784,4.9l3.124-3.124a5,5,0,0,1,7.071-7.072L8.464,15.536l10.2-10.2A11.484,11.484,0,0,0,12,3Z"></path><path data-color="color-2" d="M1,24a1,1,0,0,1-.707-1.707l22-22a1,1,0,0,1,1.414,1.414l-22,22A1,1,0,0,1,1,24Z"></path>';
-      }
-    }
-
-    showBalances() {
-      const usdText = document.getElementById('usd-text');
-      if (usdText) {
-        usdText.style.display = 'inline';
-      }
-
-      document.querySelectorAll('.coinname-value').forEach(el => {
-        const originalValue = el.getAttribute('data-original-value');
-        if (originalValue) {
-          el.textContent = originalValue;
-        }
-      });
-
-      document.querySelectorAll('.usd-value').forEach(el => {
-        const storedValue = el.getAttribute('data-original-value');
-        if (storedValue !== null && storedValue !== undefined) {
-          if (el.closest('tr')?.querySelector('td')?.textContent?.includes('Fee Estimate:')) {
-            el.textContent = `$${parseFloat(storedValue).toFixed(8)}`;
-          } else {
-            el.textContent = `$${parseFloat(storedValue).toFixed(2)}`;
-          }
-        } else {
-          if (el.closest('tr')?.querySelector('td')?.textContent?.includes('Fee Estimate:')) {
-            el.textContent = '$0.00000000';
-          } else {
-            el.textContent = '$0.00';
-          }
-        }
-      });
-
-      if (this.isWalletsPage) {
-        ['total-usd-value', 'total-btc-value'].forEach(id => {
-          const el = document.getElementById(id);
-          const originalValue = el?.getAttribute('data-original-value');
-          if (el && originalValue) {
-            if (id === 'total-usd-value') {
-              el.textContent = `$${parseFloat(originalValue).toFixed(2)}`;
-              el.classList.add('font-extrabold');
-            } else {
-              el.textContent = `~ ${parseFloat(originalValue).toFixed(8)} BTC`;
-            }
-          }
-        });
-      }
-    }
-
-    hideBalances() {
-      const usdText = document.getElementById('usd-text');
-      if (usdText) {
-        usdText.style.display = 'none';
-      }
-
-      document.querySelectorAll('.coinname-value').forEach(el => {
-        el.textContent = '****';
-      });
-      
-      document.querySelectorAll('.usd-value').forEach(el => {
-        el.textContent = '****';
-      });
-
-      if (this.isWalletsPage) {
-        ['total-usd-value', 'total-btc-value'].forEach(id => {
-          const el = document.getElementById(id);
-          if (el) {
-            el.textContent = '****';
-          }
-        });
-
-        const totalUsdEl = document.getElementById('total-usd-value');
-        if (totalUsdEl) {
-          totalUsdEl.classList.remove('font-extrabold');
-        }
-      }
-    }
-
-    async initialize() {
-      document.querySelectorAll('.usd-value').forEach(el => {
-        const text = el.textContent?.trim() || '';
-        if (text === 'Loading...') {
-          el.textContent = '';
-        }
-      });
-
-      this.storeOriginalValues();
-      
-      if (localStorage.getItem('balancesVisible') === null) {
-        localStorage.setItem('balancesVisible', 'true');
-      }
-
-      const hideBalancesToggle = document.getElementById('hide-usd-amount-toggle');
-      if (hideBalancesToggle) {
-        hideBalancesToggle.addEventListener('click', () => this.toggleBalances());
-      }
-
-      await this.loadBalanceVisibility();
-
-      if (this.priceUpdateInterval) {
-        clearInterval(this.priceUpdateInterval);
-      }
-
-      this.priceUpdateInterval = setInterval(() => {
-        if (localStorage.getItem('balancesVisible') === 'true' && !this.toggleInProgress) {
-          this.updatePrices(false);
-        }
-      }, CONFIG.PRICE_UPDATE_INTERVAL);
-    }
-
-    async loadBalanceVisibility() {
-      const balancesVisible = localStorage.getItem('balancesVisible') === 'true';
-      this.updateVisibility(balancesVisible);
-
-      if (balancesVisible) {
-        await this.updatePrices(true);
-      }
-    }
-
-    cleanup() {
-      if (this.priceUpdateInterval) {
-        clearInterval(this.priceUpdateInterval);
-      }
-    }
-  }
-
-  const STATE_KEYS = {
-    LAST_UPDATE: 'last-update-time',
-    PREVIOUS_TOTAL: 'previous-total-usd',
-    CURRENT_TOTAL: 'current-total-usd',
-    BALANCES_VISIBLE: 'balancesVisible'
-  };
-
-  return {
-    initialize: function() {
-      const uiManager = new UiManager();
-
-      window.cryptoPricingManager = uiManager;
-
-      window.addEventListener('beforeunload', () => {
-        uiManager.cleanup();
-      });
-
-      uiManager.initialize().catch(error => {
-        console.error('Failed to initialize crypto pricing:', error);
-      });
-
-      return uiManager;
-    },
-
-    getUiManager: function() {
-      return window.cryptoPricingManager;
-    },
-
-    setPriceSource: function(primarySource, fallbackSource = null) {
-      const uiManager = this.getUiManager();
-      if (uiManager && uiManager.api) {
-        uiManager.api.setPriceSource(primarySource, fallbackSource);
-      }
-    }
-  };
-})();
-
-document.addEventListener('DOMContentLoaded', function() {
-  Wallets.initialize();
-});
diff --git a/basicswap/templates/active.html b/basicswap/templates/active.html
index 8630183..72fbad5 100644
--- a/basicswap/templates/active.html
+++ b/basicswap/templates/active.html
@@ -113,6 +113,6 @@
  </div>
 </section>
 
-<script src="/static/js/active.js"></script>
+<script src="/static/js/swaps_in_progress.js"></script>
 
 {% include 'footer.html' %}
diff --git a/basicswap/templates/bid.html b/basicswap/templates/bid.html
index d96d504..e916a60 100644
--- a/basicswap/templates/bid.html
+++ b/basicswap/templates/bid.html
@@ -557,6 +557,27 @@
              </div>
             </div>
            </section>
+<div id="confirmModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
+  <div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-out"></div>
+  <div class="relative z-50 min-h-screen px-4 flex items-center justify-center">
+    <div class="bg-white dark:bg-gray-500 rounded-lg max-w-md w-full p-6 shadow-lg transition-opacity duration-300 ease-out">
+      <div class="text-center">
+        <h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4" id="confirmTitle">Confirm Action</h2>
+        <p class="text-gray-600 dark:text-gray-200 mb-6 whitespace-pre-line" id="confirmMessage">Are you sure?</p>
+        <div class="flex justify-center gap-4">
+          <button type="button" id="confirmYes" 
+                  class="px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
+            Confirm
+          </button>
+          <button type="button" id="confirmNo"
+                  class="px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
+            Cancel
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
            <input type="hidden" name="formid" value="{{ form_id }}">
           </div>
          </div>
@@ -564,9 +585,74 @@
        </div>
  </form>
  <script>
-  function confirmPopup(name) {
-   return confirm(name + " Bid - Are you sure?");
+document.addEventListener('DOMContentLoaded', function() {
+  let confirmCallback = null;
+  let triggerElement = null;
+  
+  document.getElementById('confirmYes').addEventListener('click', function() {
+    if (typeof confirmCallback === 'function') {
+      confirmCallback();
+    }
+    hideConfirmDialog();
+  });
+  
+  document.getElementById('confirmNo').addEventListener('click', hideConfirmDialog);
+  
+  function showConfirmDialog(title, message, callback) {
+    confirmCallback = callback;
+    document.getElementById('confirmTitle').textContent = title;
+    document.getElementById('confirmMessage').textContent = message;
+    const modal = document.getElementById('confirmModal');
+    if (modal) {
+      modal.classList.remove('hidden');
+    }
+    return false;
   }
+  
+  function hideConfirmDialog() {
+    const modal = document.getElementById('confirmModal');
+    if (modal) {
+      modal.classList.add('hidden');
+    }
+    confirmCallback = null;
+    return false;
+  }
+  
+  window.confirmPopup = function(action = 'Abandon') {
+    triggerElement = document.activeElement;
+    const title = `Confirm ${action} Bid`;
+    const message = `Are you sure you want to ${action.toLowerCase()} this bid?`;
+    
+    return showConfirmDialog(title, message, function() {
+      if (triggerElement) {
+        const form = triggerElement.form;
+        const hiddenInput = document.createElement('input');
+        hiddenInput.type = 'hidden';
+        hiddenInput.name = triggerElement.name;
+        hiddenInput.value = triggerElement.value;
+        form.appendChild(hiddenInput);
+        form.submit();
+      }
+    });
+  };
+  
+  const overrideButtonConfirm = function(button, action) {
+    if (button) {
+      button.removeAttribute('onclick');
+      button.addEventListener('click', function(e) {
+        e.preventDefault();
+        triggerElement = this;
+        return confirmPopup(action);
+      });
+    }
+  };
+  
+  const abandonBidBtn = document.querySelector('button[name="abandon_bid"]');
+  overrideButtonConfirm(abandonBidBtn, 'Abandon');
+  
+  const acceptBidBtn = document.querySelector('button[name="accept_bid"]');
+  overrideButtonConfirm(acceptBidBtn, 'Accept');
+});
  </script>
 </div>
 {% include 'footer.html' %}
diff --git a/basicswap/templates/bids.html b/basicswap/templates/bids.html
index 1bf3d5d..e2ec345 100644
--- a/basicswap/templates/bids.html
+++ b/basicswap/templates/bids.html
@@ -363,6 +363,6 @@
  </div>
 
 <script src="/static/js/bids_sentreceived.js"></script>
-<script src="/static/js/bids_export.js"></script>
+<script src="/static/js/bids_sentreceived_export.js"></script>
 
 {% include 'footer.html' %}
diff --git a/basicswap/templates/footer.html b/basicswap/templates/footer.html
index bdfd99e..0042412 100644
--- a/basicswap/templates/footer.html
+++ b/basicswap/templates/footer.html
@@ -43,68 +43,3 @@
     </div>
   </div>
 </section>
-<script>
-  var toggleImages = function() {
-    var html = document.querySelector('html');
-    var darkImages = document.querySelectorAll('.dark-image');
-    var lightImages = document.querySelectorAll('.light-image');
-
-    if (html && html.classList.contains('dark')) {
-      toggleImageDisplay(darkImages, 'block');
-      toggleImageDisplay(lightImages, 'none');
-    } else {
-      toggleImageDisplay(darkImages, 'none');
-      toggleImageDisplay(lightImages, 'block');
-    }
-  };
-
-  var toggleImageDisplay = function(images, display) {
-    images.forEach(function(img) {
-      img.style.display = display;
-    });
-  };
-
-  document.addEventListener('DOMContentLoaded', function() {
-    var themeToggle = document.getElementById('theme-toggle');
-
-    if (themeToggle) {
-      themeToggle.addEventListener('click', function() {
-        toggleImages();
-      });
-    }
-
-    toggleImages();
-  });
-</script>
-<script>
-  var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
-  var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
-
-  if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
-    themeToggleLightIcon.classList.remove('hidden');
-  } else {
-    themeToggleDarkIcon.classList.remove('hidden');
-  }
-
-  function setTheme(theme) {
-    if (theme === 'light') {
-      document.documentElement.classList.remove('dark');
-      localStorage.setItem('color-theme', 'light');
-    } else {
-      document.documentElement.classList.add('dark');
-      localStorage.setItem('color-theme', 'dark');
-    }
-  }
-
-  document.getElementById('theme-toggle').addEventListener('click', () => {
-    if (localStorage.getItem('color-theme') === 'dark') {
-      setTheme('light');
-    } else {
-      setTheme('dark');
-    }
-    themeToggleDarkIcon.classList.toggle('hidden');
-    themeToggleLightIcon.classList.toggle('hidden');
-    toggleImages();
-  });
-
-</script>
diff --git a/basicswap/templates/header.html b/basicswap/templates/header.html
index 0e8b867..f57f5ab 100644
--- a/basicswap/templates/header.html
+++ b/basicswap/templates/header.html
@@ -9,153 +9,95 @@
   swap_in_progress_green_svg, available_bids_svg, your_offers_svg, bids_received_svg, 
   bids_sent_svg, header_arrow_down_svg, love_svg %}
 
+<!DOCTYPE html>
 <html lang="en">
 <head>
+  <!-- Meta Tags -->
   <meta charset="UTF-8">
   {% if refresh %}
   <meta http-equiv="refresh" content="{{ refresh }}">
   {% endif %}
-
-  <!-- Scripts -->
-  <script src="/static/js/libs/chart.js"></script>
-  <script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
-  <script src="/static/js/main.js"></script>
-  <script src="/static/js/tabs.js"></script>
-  <script src="/static/js/dropdown.js"></script>
-  <script src="/static/js/libs/popper.js"></script>
-  <script src="/static/js/libs/tippy.js"></script>
-  <script src="/static/js/tooltips.js"></script>
-
-  <!-- Styles -->
-  <link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet" />
+  <title>(BSX) BasicSwap - v{{ version }}</title>
+  
+  <!-- Favicon -->
+  <link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
+  
+  <!-- Stylesheets -->
+  <link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet">
   <link type="text/css" media="all" href="/static/css/libs/tailwind.min.css" rel="stylesheet">
   <link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
-
-  <link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
-
-  <title>(BSX) BasicSwap - v{{ version }}</title>
-
-  <!-- Initialize tooltips -->
+  
   <script>
-    document.addEventListener('DOMContentLoaded', () => {
-      const tooltipManager = TooltipManager.initialize();
-      tooltipManager.initializeTooltips();
-    });
-  </script>
-
-  <!-- Dark mode initialization -->
-  <script>
-    const isDarkMode = localStorage.getItem('color-theme') === 'dark' || 
-      (!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
-    
-    if (!localStorage.getItem('color-theme')) {
-      localStorage.setItem('color-theme', 'dark');
+    // API Keys Configuration
+    function getAPIKeys() {
+      return {
+        cryptoCompare: "{{ chart_api_key|safe }}",
+        coinGecko: "{{ coingecko_api_key|safe }}"
+      };
     }
-    document.documentElement.classList.toggle('dark', isDarkMode);
-  </script>
-
-  <!-- Shutdown modal functionality -->
-  <script>
-    document.addEventListener('DOMContentLoaded', function() {
-      const shutdownButtons = document.querySelectorAll('.shutdown-button');
-      const shutdownModal = document.getElementById('shutdownModal');
-      const closeModalButton = document.getElementById('closeShutdownModal');
-      const confirmShutdownButton = document.getElementById('confirmShutdown');
-      const shutdownWarning = document.getElementById('shutdownWarning');
-
-      function updateShutdownButtons() {
-        const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0');
-        shutdownButtons.forEach(button => {
-          if (activeSwaps > 0) {
-            button.classList.add('shutdown-disabled');
-            button.setAttribute('data-disabled', 'true');
-            button.setAttribute('title', 'Caution: Swaps in progress');
-          } else {
-            button.classList.remove('shutdown-disabled');
-            button.removeAttribute('data-disabled');
-            button.removeAttribute('title');
-          }
-        });
-      }
-
-      function showShutdownModal() {
-        const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0');
-        if (activeSwaps > 0) {
-          shutdownWarning.classList.remove('hidden');
-          confirmShutdownButton.textContent = 'Yes, Shut Down Anyway';
-        } else {
-          shutdownWarning.classList.add('hidden');
-          confirmShutdownButton.textContent = 'Yes, Shut Down';
-        }
-        shutdownModal.classList.remove('hidden');
-        document.body.style.overflow = 'hidden';
-      }
-
-      function hideShutdownModal() {
-        shutdownModal.classList.add('hidden');
-        document.body.style.overflow = '';
-      }
-
-      shutdownButtons.forEach(button => {
-        button.addEventListener('click', function(e) {
-          e.preventDefault();
-          showShutdownModal();
-        });
+    
+    // WebSocket Configuration
+    (function() {
+      Object.defineProperty(window, 'ws_port', {
+        value: "{{ ws_port|safe }}",
+        writable: false,
+        configurable: false,
+        enumerable: true
       });
-
-      closeModalButton.addEventListener('click', hideShutdownModal);
-
-      confirmShutdownButton.addEventListener('click', function() {
-        const shutdownToken = document.querySelector('.shutdown-button')
-          .getAttribute('href').split('/').pop();
-        window.location.href = '/shutdown/' + shutdownToken;
-      });
-
-      shutdownModal.addEventListener('click', function(e) {
-        if (e.target === this) {
-          hideShutdownModal();
-        }
-      });
-
-      updateShutdownButtons();
-    });
+      
+      window.getWebSocketConfig = window.getWebSocketConfig || function() {
+        return {
+          port: window.ws_port || '11701',
+          fallbackPort: '11700'
+        };
+      };
+    })();
+    
+    // Dark Mode Initialization
+    (function() {
+      const isDarkMode = localStorage.getItem('color-theme') === 'dark' || 
+        (!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
+      
+      if (!localStorage.getItem('color-theme')) {
+        localStorage.setItem('color-theme', 'dark');
+      }
+      document.documentElement.classList.toggle('dark', isDarkMode);
+    })();
   </script>
   
-  <script>
-  document.addEventListener('DOMContentLoaded', function() {
-  const closeButtons = document.querySelectorAll('[data-dismiss-target]');
+  <!-- Third-party Libraries -->
+  <script src="/static/js/libs/chart.js"></script>
+  <script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
+  <script src="/static/js/libs/popper.js"></script>
+  <script src="/static/js/libs/tippy.js"></script>
   
-  closeButtons.forEach(button => {
-    button.addEventListener('click', function() {
-      const targetId = this.getAttribute('data-dismiss-target');
-      const targetElement = document.querySelector(targetId);
-
-      if (targetElement) {
-        targetElement.style.display = 'none';
-      }
-    });
-  });
-});
-</script>
-
-<script>
-function getAPIKeys() {
-  return {
-    cryptoCompare: "{{ chart_api_key|safe }}",
-    coinGecko: "{{ coingecko_api_key|safe }}"
-  };
-}
-
-function getWebSocketConfig() {
-    return {
-        port: "{{ ws_port|safe }}",
-        fallbackPort: "11700"
-    };
-}
-</script>
-
+  <!-- UI Components -->
+  <script src="/static/js/ui/tabs.js"></script>
+  <script src="/static/js/ui/dropdown.js"></script>
+  
+  <!-- Core Application Modules -->
+  <script src="/static/js/modules/config-manager.js"></script>
+  <script src="/static/js/modules/cache-manager.js"></script>
+  <script src="/static/js/modules/cleanup-manager.js"></script>
+  
+  <!-- Connection & Communication Modules -->
+  <script src="/static/js/modules/websocket-manager.js"></script>
+  <script src="/static/js/modules/network-manager.js"></script>
+  <script src="/static/js/modules/api-manager.js"></script>
+  
+  <!-- UI & Interaction Modules -->
+  <script src="/static/js/modules/tooltips-manager.js"></script>
+  <script src="/static/js/modules/notification-manager.js"></script>
+  <script src="/static/js/modules/identity-manager.js"></script>
+  <script src="/static/js/modules/summary-manager.js"></script>
+  {% if current_page == 'wallets' or current_page == 'wallet' %}
+  <script src="/static/js/modules/wallet-manager.js"></script>
+  {% endif %}
+  <script src="/static/js/modules/memory-manager.js"></script>
+  
+  <!-- Global Script -->
+  <script src="/static/js/global.js"></script>
 </head>
-
 <body class="dark:bg-gray-700">
   <div id="shutdownModal" tabindex="-1" class="hidden fixed inset-0 z-50 overflow-y-auto overflow-x-hidden">
     <div class="fixed inset-0 bg-black bg-opacity-60 transition-opacity"></div>
@@ -757,194 +699,3 @@ function getWebSocketConfig() {
 </div>
     </div>
   </section>
-
-  <!-- WebSocket  -->
-  {% if ws_port %}
-  <script>
-(function() {
-  window.notificationConfig = {
-    showNewOffers: false,
-    showNewBids: true,
-    showBidAccepted: true
-  };
-
-  function ensureToastContainer() {
-    let container = document.getElementById('ul_updates');
-    if (!container) {
-      const floating_div = document.createElement('div');
-      floating_div.classList.add('floatright');
-      container = document.createElement('ul');
-      container.setAttribute('id', 'ul_updates');
-      floating_div.appendChild(container);
-      document.body.appendChild(floating_div);
-    }
-    return container;
-  }
-
-  function createToast(title, type = 'success') {
-    const messages = ensureToastContainer();
-    const message = document.createElement('li');
-    message.innerHTML = `
-      <div id="hide">
-        <div id="toast-${type}" class="flex items-center p-4 mb-4 w-full max-w-xs text-gray-500 
-          bg-white rounded-lg shadow" role="alert">
-          <div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10 
-            bg-blue-500 rounded-lg">
-            <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="18" width="18" 
-              viewBox="0 0 24 24">
-              <g fill="#ffffff">
-                <path d="M8.5,20a1.5,1.5,0,0,1-1.061-.439L.379,12.5,2.5,10.379l6,6,13-13L23.621,
-                  5.5,9.561,19.561A1.5,1.5,0,0,1,8.5,20Z"></path>
-              </g>
-            </svg>
-          </div>
-          <div class="uppercase w-40 ml-3 text-sm font-semibold text-gray-900">${title}</div>
-          <button type="button" onclick="closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5 
-            bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-0 focus:outline-none 
-            focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8">
-            <span class="sr-only">Close</span>
-            <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" 
-              xmlns="http://www.w3.org/2000/svg">
-              <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 
-                1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 
-                4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" 
-                clip-rule="evenodd"></path>
-            </svg>
-          </button>
-        </div>
-      </div>
-    `;
-    messages.appendChild(message);
-  }
-
-  function updateElement(elementId, value, options = {}) {
-    const element = document.getElementById(elementId);
-    if (!element) return false;
-    
-    const safeValue = (value !== undefined && value !== null) 
-      ? value 
-      : (element.dataset.lastValue || 0);
-
-    element.dataset.lastValue = safeValue;
-
-    if (elementId === 'sent-bids-counter' || elementId === 'recv-bids-counter') {
-      const svg = element.querySelector('svg');
-      element.textContent = safeValue;
-      if (svg) {
-        element.insertBefore(svg, element.firstChild);
-      }
-    } else {
-      element.textContent = safeValue;
-    }
-
-    if (['offers-counter', 'bid-requests-counter', 'sent-bids-counter', 
-         'recv-bids-counter', 'swaps-counter', 'network-offers-counter', 
-         'watched-outputs-counter'].includes(elementId)) {
-      element.classList.remove('bg-blue-500', 'bg-gray-400');
-      element.classList.add(safeValue > 0 ? 'bg-blue-500' : 'bg-gray-400');
-    }
-
-    if (elementId === 'swaps-counter') {
-      const swapContainer = document.getElementById('swapContainer');
-      if (swapContainer) {
-        const isSwapping = safeValue > 0;
-        if (isSwapping) {
-          swapContainer.innerHTML = `{{ swap_in_progress_green_svg | safe }}`;
-          swapContainer.style.animation = 'spin 2s linear infinite';
-        } else {
-          swapContainer.innerHTML = `{{ swap_in_progress_svg | safe }}`;
-          swapContainer.style.animation = 'none';
-        }
-      }
-    }
-    return true;
-  }
-
-  function fetchSummaryData() {
-    fetch('/json')
-      .then(response => response.json())
-      .then(data => {
-        updateElement('network-offers-counter', data.num_network_offers);
-        updateElement('offers-counter', data.num_sent_active_offers);
-        updateElement('sent-bids-counter', data.num_sent_active_bids);
-        updateElement('recv-bids-counter', data.num_recv_active_bids);
-        updateElement('bid-requests-counter', data.num_available_bids);
-        updateElement('swaps-counter', data.num_swapping);
-        updateElement('watched-outputs-counter', data.num_watched_outputs);
-      })
-      .catch(error => console.error('Summary data fetch error:', error));
-  }
-
-  function initWebSocket() {
-    const wsUrl = "ws://" + window.location.hostname + ":{{ ws_port }}";
-    const ws = new WebSocket(wsUrl);
-
-    ws.onopen = () => {
-      console.log('🟢  WebSocket connection established for Dynamic Counters');
-      fetchSummaryData();
-      setInterval(fetchSummaryData, 30000); // Refresh every 30 seconds
-    };
-
-    ws.onmessage = (event) => {
-      try {
-        const data = JSON.parse(event.data);
-        if (data.event) {
-          let toastTitle;
-          let shouldShowToast = false;
-
-          switch (data.event) {
-            case 'new_offer':
-              toastTitle = `New network <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
-              shouldShowToast = window.notificationConfig.showNewOffers;
-              break;
-            case 'new_bid':
-              toastTitle = `<a class="underline" href=/bid/${data.bid_id}>New bid</a> on 
-                <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
-              shouldShowToast = window.notificationConfig.showNewBids;
-              break;
-            case 'bid_accepted':
-              toastTitle = `<a class="underline" href=/bid/${data.bid_id}>Bid</a> accepted`;
-              shouldShowToast = window.notificationConfig.showBidAccepted;
-              break;
-          }
-
-          if (toastTitle && shouldShowToast) {
-            createToast(toastTitle);
-          }
-        }
-        fetchSummaryData();
-      } catch (error) {
-        console.error('WebSocket message processing error:', error);
-      }
-    };
-
-    ws.onerror = (error) => {
-      console.error('WebSocket Error:', error);
-    };
-
-    ws.onclose = (event) => {
-      console.log('WebSocket connection closed', event);
-      setTimeout(initWebSocket, 5000);
-    };
-  }
-
-  window.closeAlert = function(event) {
-    let element = event.target;
-    while (element.nodeName !== "BUTTON") {
-      element = element.parentNode;
-    }
-    element.parentNode.parentNode.removeChild(element.parentNode);
-  };
-
-  function init() {
-    initWebSocket();
-  }
-
-  if (document.readyState === 'loading') {
-    document.addEventListener('DOMContentLoaded', init);
-  } else {
-    init();
-  }
-})();
-  </script>
-  {% endif %}
diff --git a/basicswap/templates/offer_confirm.html b/basicswap/templates/offer_confirm.html
index c9c941c..08e2055 100644
--- a/basicswap/templates/offer_confirm.html
+++ b/basicswap/templates/offer_confirm.html
@@ -1,5 +1,4 @@
 {% include 'header.html' %} {% from 'style.html' import breadcrumb_line_svg, input_down_arrow_offer_svg, select_network_svg, select_address_svg, select_swap_type_svg, select_bid_amount_svg, select_rate_svg, step_one_svg, step_two_svg, step_three_svg, select_setup_svg, input_time_svg, select_target_svg , select_auto_strategy_svg %}
-<script src="static/js/coin_icons.js"></script>
 <div class="container mx-auto">
   <section class="p-5 mt-5">
     <div class="flex flex-wrap items-center -m-2">
diff --git a/basicswap/templates/offer_new_1.html b/basicswap/templates/offer_new_1.html
index 0165b0a..da9f42c 100644
--- a/basicswap/templates/offer_new_1.html
+++ b/basicswap/templates/offer_new_1.html
@@ -1,5 +1,4 @@
 {% include 'header.html' %} {% from 'style.html' import breadcrumb_line_svg, input_down_arrow_offer_svg, select_network_svg, select_address_svg, select_swap_type_svg, select_bid_amount_svg, select_rate_svg, step_one_svg, step_two_svg, step_three_svg %}
-<script src="static/js/coin_icons.js"></script>
 <div class="container mx-auto">
   <section class="p-5 mt-5">
     <div class="flex flex-wrap items-center -m-2">
@@ -117,63 +116,6 @@
     </div>
   </div>
 </div>
-<script>
-function handleNewOfferAddress() {
-    const selectElement = document.querySelector('select[name="addr_from"]');
-    const STORAGE_KEY = 'lastUsedAddressNewOffer';
-    const form = selectElement?.closest('form');
-    
-    if (!selectElement || !form) return;
-
-    function loadInitialAddress() {
-        const savedAddressJSON = localStorage.getItem(STORAGE_KEY);
-        if (savedAddressJSON) {
-            try {
-                const savedAddress = JSON.parse(savedAddressJSON);
-                selectElement.value = savedAddress.value;
-            } catch (e) {
-                selectFirstAddress();
-            }
-        } else {
-            selectFirstAddress();
-        }
-    }
-
-    function selectFirstAddress() {
-        if (selectElement.options.length > 1) {
-            const firstOption = selectElement.options[1];
-            if (firstOption) {
-                selectElement.value = firstOption.value;
-                saveAddress(firstOption.value, firstOption.text);
-            }
-        }
-    }
-
-    function saveAddress(value, text) {
-        const addressData = {
-            value: value,
-            text: text
-        };
-        localStorage.setItem(STORAGE_KEY, JSON.stringify(addressData));
-    }
-
-    form.addEventListener('submit', async (e) => {
-        saveAddress(selectElement.value, selectElement.selectedOptions[0].text);
-    });
-
-    selectElement.addEventListener('change', (event) => {
-        saveAddress(event.target.value, event.target.selectedOptions[0].text);
-    });
-
-    loadInitialAddress();
-}
-
-if (document.readyState === 'loading') {
-    document.addEventListener('DOMContentLoaded', handleNewOfferAddress);
-} else {
-    handleNewOfferAddress();
-}
-</script>
 
           <div class="py-0 border-b items-center justify-between -mx-4 mb-6 pb-3 border-gray-400 border-opacity-20">
             <div class="w-full md:w-10/12">
@@ -413,225 +355,6 @@ if (document.readyState === 'loading') {
       </div>
     </div>
   </section>
-<script>
-const xhr_rates = new XMLHttpRequest();
-xhr_rates.onload = () => {
-    if (xhr_rates.status == 200) {
-        const obj = JSON.parse(xhr_rates.response);
-        inner_html = '<pre><code>' + JSON.stringify(obj, null, '  ') + '</code></pre>';
-        document.getElementById('rates_display').innerHTML = inner_html;
-    }
-};
-
-const xhr_rate = new XMLHttpRequest();
-xhr_rate.onload = () => {
-    if (xhr_rate.status == 200) {
-        const obj = JSON.parse(xhr_rate.response);
-        if (obj.hasOwnProperty('rate')) {
-            document.getElementById('rate').value = obj['rate'];
-        } else if (obj.hasOwnProperty('amount_to')) {
-            document.getElementById('amt_to').value = obj['amount_to'];
-        } else if (obj.hasOwnProperty('amount_from')) {
-            document.getElementById('amt_from').value = obj['amount_from'];
-        }
-    }
-};
-
-function lookup_rates() {
-    const coin_from = document.getElementById('coin_from').value;
-    const coin_to = document.getElementById('coin_to').value;
-
-    if (coin_from === '-1' || coin_to === '-1') {
-        alert('Coins from and to must be set first.');
-        return;
-    }
-
-    const selectedCoin = (coin_from === '15') ? '3' : coin_from;
-
-    inner_html = '<p>Updating...</p>';
-    document.getElementById('rates_display').innerHTML = inner_html;
-    document.querySelector(".pricejsonhidden").classList.remove("hidden");
-
-    const xhr_rates = new XMLHttpRequest();
-    xhr_rates.onreadystatechange = function() {
-        if (xhr_rates.readyState === XMLHttpRequest.DONE) {
-            if (xhr_rates.status === 200) {
-                document.getElementById('rates_display').innerHTML = xhr_rates.responseText;
-            } else {
-                console.error('Error fetching data:', xhr_rates.statusText);
-            }
-        }
-    };
-
-    xhr_rates.open('POST', '/json/rates');
-    xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-    xhr_rates.send('coin_from=' + selectedCoin + '&coin_to=' + coin_to);
-}
-
-function getRateInferred(event) {
-    event.preventDefault();
-
-    const coin_from = document.getElementById('coin_from').value;
-    const coin_to = document.getElementById('coin_to').value;
-    const params = 'coin_from=' + encodeURIComponent(coin_from) + '&coin_to=' + encodeURIComponent(coin_to);
-
-    const xhr_rates = new XMLHttpRequest();
-    xhr_rates.onreadystatechange = function() {
-        if (xhr_rates.readyState === XMLHttpRequest.DONE) {
-            if (xhr_rates.status === 200) {
-                try {
-                    const responseData = JSON.parse(xhr_rates.responseText);
-                    if (responseData.coingecko && responseData.coingecko.rate_inferred) {
-                        const rateInferred = responseData.coingecko.rate_inferred;
-                        document.getElementById('rate').value = rateInferred;
-                        set_rate('rate');
-                    } else {
-                        document.getElementById('rate').value = 'Error: Rate limit';
-                        console.error('Rate limit reached or invalid response format');
-                    }
-                } catch (error) {
-                    document.getElementById('rate').value = 'Error: Rate limit';
-                    console.error('Error parsing response:', error);
-                }
-            } else {
-                document.getElementById('rate').value = 'Error: Rate limit';
-                console.error('Error fetching data:', xhr_rates.statusText);
-            }
-        }
-    };
-
-    xhr_rates.open('POST', '/json/rates');
-    xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-    xhr_rates.send(params);
-}
-
-document.getElementById('get_rate_inferred_button').addEventListener('click', getRateInferred);
-
-function set_swap_type_enabled(coin_from, coin_to, swap_type) {
-    const adaptor_sig_only_coins = [
-        '6',  /* XMR */
-        '9',  /* WOW */
-        '8',  /* PART_ANON */
-        '7',  /* PART_BLIND */
-        '13', /* FIRO */
-        '18', /* DOGE */
-        '17'  /* BCH */
-    ];
-    const secret_hash_only_coins = [
-        '11', /* PIVX */
-        '12'  /* DASH */
-    ];
-    
-    let make_hidden = false;
-
-    coin_from = String(coin_from);
-    coin_to = String(coin_to);
-
-    if (adaptor_sig_only_coins.indexOf(coin_from) !== -1 || adaptor_sig_only_coins.indexOf(coin_to) !== -1) {
-        swap_type.disabled = true;
-        swap_type.value = 'xmr_swap';
-        make_hidden = true;
-        swap_type.classList.add('select-disabled');
-    } else if (secret_hash_only_coins.indexOf(coin_from) !== -1 || secret_hash_only_coins.indexOf(coin_to) !== -1) {
-        swap_type.disabled = true;
-        swap_type.value = 'seller_first';
-        make_hidden = true;
-        swap_type.classList.add('select-disabled');
-    } else {
-        swap_type.disabled = false;
-        swap_type.classList.remove('select-disabled');
-        swap_type.value = 'xmr_swap';
-    }
-
-    let swap_type_hidden = document.getElementById('swap_type_hidden');
-    if (make_hidden) {
-        if (!swap_type_hidden) {
-            swap_type_hidden = document.createElement('input');
-            swap_type_hidden.setAttribute('id', 'swap_type_hidden');
-            swap_type_hidden.setAttribute('type', 'hidden');
-            swap_type_hidden.setAttribute('name', 'swap_type');
-            document.getElementById('form').appendChild(swap_type_hidden);
-        }
-        swap_type_hidden.setAttribute('value', swap_type.value);
-    } else if (swap_type_hidden) {
-        swap_type_hidden.parentNode.removeChild(swap_type_hidden);
-    }
-}
-
-document.addEventListener('DOMContentLoaded', function() {
-    const coin_from = document.getElementById('coin_from');
-    const coin_to = document.getElementById('coin_to');
-    
-    if (coin_from && coin_to) {
-        coin_from.addEventListener('change', function() {
-            const swap_type = document.getElementById('swap_type');
-            set_swap_type_enabled(this.value, coin_to.value, swap_type);
-        });
-        
-        coin_to.addEventListener('change', function() {
-            const swap_type = document.getElementById('swap_type');
-            set_swap_type_enabled(coin_from.value, this.value, swap_type);
-        });
-    }
-});
-
-function set_rate(value_changed) {
-    const coin_from = document.getElementById('coin_from').value;
-    const coin_to = document.getElementById('coin_to').value;
-    const amt_from = document.getElementById('amt_from').value;
-    const amt_to = document.getElementById('amt_to').value;
-    const rate = document.getElementById('rate').value;
-    const lock_rate = rate == '' ? false : document.getElementById('rate_lock').checked;
-
-    if (value_changed === 'coin_from' || value_changed === 'coin_to') {
-        document.getElementById('rate').value = '';
-        return;
-    }
-
-    const swap_type = document.getElementById('swap_type');
-    set_swap_type_enabled(coin_from, coin_to, swap_type);
-
-    if (coin_from == '-1' || coin_to == '-1') {
-        return;
-    }
-
-    let params = 'coin_from=' + coin_from + '&coin_to=' + coin_to;
-
-    if (value_changed == 'rate' || (lock_rate && value_changed == 'amt_from') || (amt_to == '' && value_changed == 'amt_from')) {
-        if (rate == '' || (amt_from == '' && amt_to == '')) {
-            return;
-        } else if (amt_from == '' && amt_to != '') {
-            if (value_changed == 'amt_from') {
-                return;
-            }
-            params += '&rate=' + rate + '&amt_to=' + amt_to;
-        } else {
-            params += '&rate=' + rate + '&amt_from=' + amt_from;
-        }
-    } else if (lock_rate && value_changed == 'amt_to') {
-        if (amt_to == '' || rate == '') {
-            return;
-        }
-        params += '&amt_to=' + amt_to + '&rate=' + rate;
-    } else {
-        if (amt_from == '' || amt_to == '') {
-            return;
-        }
-        params += '&amt_from=' + amt_from + '&amt_to=' + amt_to;
-    }
-
-    xhr_rate.open('POST', '/json/rate');
-    xhr_rate.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-    xhr_rate.send(params);
-}
-
-document.addEventListener("DOMContentLoaded", function() {
-    const coin_from = document.getElementById('coin_from').value;
-    const coin_to = document.getElementById('coin_to').value;
-    const swap_type = document.getElementById('swap_type');
-    set_swap_type_enabled(coin_from, coin_to, swap_type);
-});
-</script>
 </div>
 <script src="static/js/new_offer.js"></script>
 {% include 'footer.html' %}
diff --git a/basicswap/templates/offer_new_2.html b/basicswap/templates/offer_new_2.html
index 73169d3..b92a8a8 100644
--- a/basicswap/templates/offer_new_2.html
+++ b/basicswap/templates/offer_new_2.html
@@ -1,5 +1,4 @@
 {% include 'header.html' %} {% from 'style.html' import breadcrumb_line_svg, input_down_arrow_offer_svg, select_network_svg, select_address_svg, select_swap_type_svg, select_bid_amount_svg, select_rate_svg, step_one_svg, step_two_svg, step_three_svg, select_setup_svg, input_time_svg, select_target_svg , select_auto_strategy_svg %}
-<script src="static/js/coin_icons.js"></script>
 <div class="container mx-auto">
   <section class="p-5 mt-5">
     <div class="flex flex-wrap items-center -m-2">
diff --git a/basicswap/templates/offers.html b/basicswap/templates/offers.html
index 80b00c9..eb04efb 100644
--- a/basicswap/templates/offers.html
+++ b/basicswap/templates/offers.html
@@ -193,9 +193,9 @@
   </div>
  </div>
 </section>
-
-{% endif %}
 <script src="/static/js/pricechart.js"></script>
+{% endif %}
+
 
 <section>
   <div class="px-6 py-0 mt-5 h-full overflow-hidden">
@@ -401,4 +401,5 @@
 
 <input type="hidden" name="formid" value="{{ form_id }}">
 <script src="/static/js/offers.js"></script>
+
  {% include 'footer.html' %}
diff --git a/basicswap/templates/unlock.html b/basicswap/templates/unlock.html
index 3feeb3c..918b282 100644
--- a/basicswap/templates/unlock.html
+++ b/basicswap/templates/unlock.html
@@ -9,19 +9,49 @@
     <link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet" />
     <link type="text/css" media="all" href="/static/css/libs/tailwind.min.css" rel="stylesheet">
     <link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
-    <script src="/static/js/main.js"></script>
+
+  <!-- Third-party Libraries -->
+  <script src="/static/js/libs/chart.js"></script>
+  <script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
+  <script src="/static/js/libs/popper.js"></script>
+  <script src="/static/js/libs/tippy.js"></script>
+  
+  <!-- UI Components -->
+  <script src="/static/js/ui/tabs.js"></script>
+  <script src="/static/js/ui/dropdown.js"></script>
+  
+  <!-- Core Application Modules -->
+  <script src="/static/js/modules/config-manager.js"></script>
+  <script src="/static/js/modules/cache-manager.js"></script>
+  <script src="/static/js/modules/cleanup-manager.js"></script>
+  
+  <!-- Connection & Communication Modules -->
+  <script src="/static/js/modules/websocket-manager.js"></script>
+  <script src="/static/js/modules/network-manager.js"></script>
+  <script src="/static/js/modules/api-manager.js"></script>
+  
+  <!-- UI & Interaction Modules -->
+  <script src="/static/js/modules/tooltips-manager.js"></script>
+  <script src="/static/js/modules/notification-manager.js"></script>
+  <script src="/static/js/modules/identity-manager.js"></script>
+  <script src="/static/js/modules/summary-manager.js"></script>
+  {% if current_page == 'wallets' or current_page == 'wallet' %}
+  <script src="/static/js/modules/wallet-manager.js"></script>
+  {% endif %}
+  <script src="/static/js/modules/memory-manager.js"></script>
+  
     <script>
-      const isDarkMode =
-        localStorage.getItem('color-theme') === 'dark' ||
-        (!localStorage.getItem('color-theme') &&
-          window.matchMedia('(prefers-color-scheme: dark)').matches);
-
+    (function() {
+      const isDarkMode = localStorage.getItem('color-theme') === 'dark' || 
+        (!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
+      
       if (!localStorage.getItem('color-theme')) {
-        localStorage.setItem('color-theme', isDarkMode ? 'dark' : 'light');
+        localStorage.setItem('color-theme', 'dark');
       }
-
       document.documentElement.classList.toggle('dark', isDarkMode);
+    })();
     </script>
+
     <link rel=icon sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
     <title>(BSX) BasicSwap - v{{ version }}</title>
   </head>
@@ -107,7 +137,6 @@
     
     <script>
     document.addEventListener('DOMContentLoaded', () => {
-      // Password toggle functionality
       const passwordToggle = document.querySelector('.js-password-toggle');
       if (passwordToggle) {
         passwordToggle.addEventListener('change', function() {
@@ -126,7 +155,6 @@
         });
       }
 
-      // Image toggling function
       function toggleImages() {
         const html = document.querySelector('html');
         const darkImages = document.querySelectorAll('.dark-image');
@@ -147,42 +175,6 @@
         });
       }
 
-      // Theme toggle functionality
-      function setTheme(theme) {
-        if (theme === 'light') {
-          document.documentElement.classList.remove('dark');
-          localStorage.setItem('color-theme', 'light');
-        } else {
-          document.documentElement.classList.add('dark');
-          localStorage.setItem('color-theme', 'dark');
-        }
-      }
-
-      // Initialize theme
-      const themeToggle = document.getElementById('theme-toggle');
-      const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
-      const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
-
-      if (themeToggle && themeToggleDarkIcon && themeToggleLightIcon) {
-        if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
-          themeToggleLightIcon.classList.remove('hidden');
-        } else {
-          themeToggleDarkIcon.classList.remove('hidden');
-        }
-
-        themeToggle.addEventListener('click', () => {
-          if (localStorage.getItem('color-theme') === 'dark') {
-            setTheme('light');
-          } else {
-            setTheme('dark');
-          }
-          themeToggleDarkIcon.classList.toggle('hidden');
-          themeToggleLightIcon.classList.toggle('hidden');
-          toggleImages();
-        });
-      }
-
-      // Call toggleImages on load
       toggleImages();
     });
     </script>
diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html
index 175edf7..c4d3860 100644
--- a/basicswap/templates/wallet.html
+++ b/basicswap/templates/wallet.html
@@ -1183,7 +1183,6 @@ document.addEventListener('DOMContentLoaded', function() {
 });
 </script>
 
-<script src="/static/js/wallets.js"></script>
 {% include 'footer.html' %}
 </body>
 </html>
diff --git a/basicswap/templates/wallets.html b/basicswap/templates/wallets.html
index 86b0a2a..aa697f1 100644
--- a/basicswap/templates/wallets.html
+++ b/basicswap/templates/wallets.html
@@ -1,8 +1,8 @@
 {% include 'header.html' %}
 {% from 'style.html' import breadcrumb_line_svg, circular_arrows_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, lock_svg, eye_show_svg %}
 
-<section class="py-3 px-4">
-  <div class="lg:container mx-auto">>
+<section class="py-3 px-4 mt-6">
+  <div class="lg:container mx-auto">
    <div class="relative py-8 px-8 bg-coolGray-900 dark:bg-blue-500 rounded-md overflow-hidden">
       <img class="absolute z-10 left-4 top-4" src="/static/images/elements/dots-red.svg" alt="dots-red">
       <img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="dots-red">
@@ -189,9 +189,6 @@
    </div>
  </section>
 
-
-<script src="/static/js/wallets.js"></script>
-
 {% include 'footer.html' %}
 </body>
 </html>
diff --git a/basicswap/ui/page_bids.py b/basicswap/ui/page_bids.py
index fb1dea8..b649269 100644
--- a/basicswap/ui/page_bids.py
+++ b/basicswap/ui/page_bids.py
@@ -223,8 +223,6 @@ def page_bids(
         return self.render_template(
             template,
             {
-                "page_type_available": "Bids Available",
-                "page_type_available_description": "Bids available for you to accept.",
                 "messages": messages,
                 "filters": filters,
                 "data": page_data,