diff --git a/basicswap/static/js/pricechart.js b/basicswap/static/js/pricechart.js
index ce4c4ba..d9b52da 100644
--- a/basicswap/static/js/pricechart.js
+++ b/basicswap/static/js/pricechart.js
@@ -1,3 +1,128 @@
+// CLEANUP
+const cleanupManager = {
+  eventListeners: [],
+  timeouts: [],
+  intervals: [],
+  animationFrames: [],
+
+  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(),
@@ -393,49 +518,138 @@ const rateLimiter = {
 
 // CACHE
 const cache = {
-  set: (key, value, customTtl = null) => {
+  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 || app.cacheTTL)
+      expiresAt: Date.now() + (customTtl || this.cacheTTL)
     };
-    localStorage.setItem(key, JSON.stringify(item));
-    //console.log(`Cache set for ${key}, expires in ${(customTtl || app.cacheTTL) / 1000} seconds`);
+
+    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: (key) => {
+
+  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) {
-        //console.log(`Cache hit for ${key}, ${(item.expiresAt - now) / 1000} seconds remaining`);
         return {
           value: item.value,
           remainingTime: item.expiresAt - now
         };
       } else {
-        //console.log(`Cache expired for ${key}`);
         localStorage.removeItem(key);
       }
     } catch (error) {
-      //console.error('Error parsing cache item:', error.message);
+      console.error('Error parsing cache item:', error.message);
       localStorage.removeItem(key);
     }
+
     return null;
   },
-  isValid: (key) => {
-    return cache.get(key) !== null;
+
+  isValid: function(key) {
+    return this.get(key) !== null;
   },
-  clear: () => {
-    Object.keys(localStorage).forEach(key => {
+
+  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') {
-        localStorage.removeItem(key);
+        keysToRemove.push(key);
       }
+    }
+
+    keysToRemove.forEach(key => {
+      localStorage.removeItem(key);
     });
-    //console.log('Cache cleared');
+
+    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
+    };
   }
 };
 
@@ -631,6 +845,8 @@ const chartModule = {
   chart: null,
   currentCoin: 'BTC',
   loadStartTime: 0,
+  chartRefs: new WeakMap(),
+  
   verticalLinePlugin: {
     id: 'verticalLine',
     beforeDraw: (chart, args, options) => {
@@ -652,15 +868,44 @@ const chartModule = {
     }
   },
 
-  initChart: () => {
-    const ctx = document.getElementById('coin-chart')?.getContext('2d');
+  getChartByElement: function(element) {
+    return this.chartRefs.get(element);
+  },
+
+  setChartReference: function(element, chart) {
+    this.chartRefs.set(element, chart);
+  },
+
+  destroyChart: function() {
+    if (chartModule.chart) {
+      try {
+        chartModule.chart.destroy();
+      } catch (e) {
+        console.error('Error destroying chart:', e);
+      }
+      chartModule.chart = null;
+    }
+  },
+
+  initChart: function() {
+    this.destroyChart();
+    
+    const canvas = document.getElementById('coin-chart');
+    if (!canvas) {
+      logger.error('Chart canvas element not found');
+      return;
+    }
+    
+    const ctx = canvas.getContext('2d');
     if (!ctx) {
       logger.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)');
+    
     chartModule.chart = new Chart(ctx, {
       type: 'line',
       data: {
@@ -811,11 +1056,15 @@ const chartModule = {
       },
       plugins: [chartModule.verticalLinePlugin]
     });
+
+    this.setChartReference(canvas, chartModule.chart);
   },
-  prepareChartData: (coinSymbol, data) => {
+
+  prepareChartData: function(coinSymbol, data) {
     if (!data) {
       return [];
     }
+
     try {
       let preparedData;
 
@@ -825,9 +1074,11 @@ const chartModule = {
         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]);
@@ -868,6 +1119,7 @@ const chartModule = {
           y: price
         }));
       } else {
+        console.warn('Unknown data format for chartData:', data);
         return [];
       }
       return preparedData.map(point => ({
@@ -880,7 +1132,7 @@ const chartModule = {
     }
   },
 
-  ensureHourlyData: (data) => {
+  ensureHourlyData: function(data) {
     const now = new Date();
     now.setUTCMinutes(0, 0, 0);
     const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
@@ -888,136 +1140,176 @@ const chartModule = {
 
     for (let i = 0; i < 24; i++) {
       const targetTime = new Date(twentyFourHoursAgo.getTime() + i * 60 * 60 * 1000);
-      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
-      );
-      hourlyData.push({
-        x: targetTime.getTime(),
-        y: closestDataPoint.y
-      });
+
+      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
+);
+        hourlyData.push({
+          x: targetTime.getTime(),
+          y: closestDataPoint.y
+        });
+      }
     }
+
     return hourlyData;
   },
 
-    updateChart: async (coinSymbol, forceRefresh = false) => {
+  updateChart: async function(coinSymbol, forceRefresh = false) {
+    try {
+      if (!chartModule.chart) {
+        chartModule.initChart();
+      }
+      const currentChartData = chartModule.chart?.data?.datasets[0]?.data || [];
+      if (currentChartData.length === 0) {
+        chartModule.showChartLoader();
+      }
+      chartModule.loadStartTime = Date.now();
+      const cacheKey = `chartData_${coinSymbol}_${config.currentResolution}`;
+      let cachedData = !forceRefresh ? cache.get(cacheKey) : null;
+      let data;
+      if (cachedData && Object.keys(cachedData.value).length > 0) {
+        data = cachedData.value;
+      } else {
         try {
-            const currentChartData = chartModule.chart?.data.datasets[0].data || [];
-            if (currentChartData.length === 0) {
-                chartModule.showChartLoader();
-            }
-            chartModule.loadStartTime = Date.now();
-            const cacheKey = `chartData_${coinSymbol}_${config.currentResolution}`;
-            let cachedData = !forceRefresh ? cache.get(cacheKey) : null;
-            let data;
-            if (cachedData && Object.keys(cachedData.value).length > 0) {
-                data = cachedData.value;
-                //console.log(`Using cached data for ${coinSymbol}`);
-            } else {
-                try {
-                    const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
-                    data = allData[coinSymbol];
-                    if (!data || Object.keys(data).length === 0) {
-                        throw new Error(`No data returned for ${coinSymbol}`);
-                    }
-                    cache.set(cacheKey, data, config.cacheTTL);
-                } catch (error) {
-                    if (error.message.includes('429') && currentChartData.length > 0) {
-                        //console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`);
-                        return;
-                    }
-                    const expiredCache = localStorage.getItem(cacheKey);
-                    if (expiredCache) {
-                        try {
-                            const parsedCache = JSON.parse(expiredCache);
-                            data = parsedCache.value;
-                            //console.log(`Using expired cache data for ${coinSymbol}`);
-                        } catch (cacheError) {
-                            throw error;
-                        }
-                    } else {
-                        throw error;
-                    }
-                }
-            }
-            const chartData = chartModule.prepareChartData(coinSymbol, data);
-            if (chartData.length > 0 && chartModule.chart) {
-                chartModule.chart.data.datasets[0].data = chartData;
-                chartModule.chart.data.datasets[0].label = `${coinSymbol} Price (USD)`;
-                if (coinSymbol === 'WOW') {
-                    chartModule.chart.options.scales.x.time.unit = 'hour';
-                } else {
-                    const resolution = config.resolutions[config.currentResolution];
-                    chartModule.chart.options.scales.x.time.unit = 
-                        resolution.interval === 'hourly' ? 'hour' : 
-                        config.currentResolution === 'year' ? 'month' : 'day';
-                }
-                chartModule.chart.update('active');
-                chartModule.currentCoin = coinSymbol;
-                const loadTime = Date.now() - chartModule.loadStartTime;
-                ui.updateLoadTimeAndCache(loadTime, cachedData);
-            }
-        } catch (error) {
-            console.error(`Error updating chart for ${coinSymbol}:`, error);
-            if (!(chartModule.chart?.data.datasets[0].data.length > 0)) {
-                chartModule.chart.data.datasets[0].data = [];
-                chartModule.chart.update('active');
-            }
-        } finally {
-            chartModule.hideChartLoader();
-        }
-    },
+          const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
+          data = allData[coinSymbol];
+          
+          if (!data || Object.keys(data).length === 0) {
+            throw new Error(`No data returned for ${coinSymbol}`);
+          }
 
-  showChartLoader: () => {
+          cache.set(cacheKey, data, config.cacheTTL);
+        } catch (error) {
+          if (error.message.includes('429') && currentChartData.length > 0) {
+            console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`);
+            chartModule.hideChartLoader();
+            return;
+          }
+          const expiredCache = localStorage.getItem(cacheKey);
+          if (expiredCache) {
+            try {
+              const parsedCache = JSON.parse(expiredCache);
+              data = parsedCache.value;
+            } catch (cacheError) {
+              throw error;
+            }
+          } else {
+            throw error;
+          }
+        }
+      }
+      if (chartModule.currentCoin !== coinSymbol) {
+        chartModule.destroyChart();
+        chartModule.initChart();
+      }
+
+      const chartData = chartModule.prepareChartData(coinSymbol, data);
+      if (chartData.length > 0 && chartModule.chart) {
+        chartModule.chart.data.datasets[0].data = chartData;
+        chartModule.chart.data.datasets[0].label = `${coinSymbol} Price (USD)`;
+        if (coinSymbol === 'WOW') {
+          chartModule.chart.options.scales.x.time.unit = 'hour';
+        } else {
+          const resolution = config.resolutions[config.currentResolution];
+          chartModule.chart.options.scales.x.time.unit = 
+            resolution.interval === 'hourly' ? 'hour' : 
+            config.currentResolution === 'year' ? 'month' : 'day';
+        }
+        chartModule.chart.update('active');
+        chartModule.currentCoin = coinSymbol;
+        const loadTime = Date.now() - chartModule.loadStartTime;
+        ui.updateLoadTimeAndCache(loadTime, cachedData);
+      }
+    } catch (error) {
+      console.error(`Error updating chart for ${coinSymbol}:`, error);
+      
+      // Keep existing chart data if possible
+      if (!(chartModule.chart?.data?.datasets[0]?.data?.length > 0)) {
+        if (!chartModule.chart) {
+          chartModule.initChart();
+        }
+        if (chartModule.chart) {
+          chartModule.chart.data.datasets[0].data = [];
+          chartModule.chart.update('active');
+        }
+      }
+    } finally {
+      chartModule.hideChartLoader();
+    }
+  },
+
+  showChartLoader: function() {
     const loader = document.getElementById('chart-loader');
     const chart = document.getElementById('coin-chart');
     if (!loader || !chart) {
-      //console.warn('Chart loader or chart container elements not found');
       return;
     }
     loader.classList.remove('hidden');
     chart.classList.add('hidden');
   },
 
-  hideChartLoader: () => {
+  hideChartLoader: function() {
     const loader = document.getElementById('chart-loader');
     const chart = document.getElementById('coin-chart');
     if (!loader || !chart) {
-      //console.warn('Chart loader or chart container elements not found');
       return;
     }
     loader.classList.add('hidden');
     chart.classList.remove('hidden');
   },
+  cleanup: function() {
+    this.destroyChart();
+    this.currentCoin = null;
+    this.loadStartTime = 0;
+    console.log('Chart module cleaned up');
+  }
 };
 
 Chart.register(chartModule.verticalLinePlugin);
 
-  const volumeToggle = {
-    isVisible: localStorage.getItem('volumeToggleState') === 'true',
-    init: () => {
-      const toggleButton = document.getElementById('toggle-volume');
-      if (toggleButton) {
+const volumeToggle = {
+  isVisible: localStorage.getItem('volumeToggleState') === 'true',
+  init: function() {
+    const toggleButton = document.getElementById('toggle-volume');
+    if (toggleButton) {
+      if (typeof cleanupManager !== 'undefined') {
+        cleanupManager.addListener(toggleButton, 'click', volumeToggle.toggle);
+      } else {
         toggleButton.addEventListener('click', volumeToggle.toggle);
-        volumeToggle.updateVolumeDisplay();
       }
-    },
-    toggle: () => {
-      volumeToggle.isVisible = !volumeToggle.isVisible;
-      localStorage.setItem('volumeToggleState', volumeToggle.isVisible.toString());
       volumeToggle.updateVolumeDisplay();
-    },
-    updateVolumeDisplay: () => {
-      const volumeDivs = document.querySelectorAll('[id$="-volume-div"]');
-      volumeDivs.forEach(div => {
-        div.style.display = volumeToggle.isVisible ? 'flex' : 'none';
-      });
-      const toggleButton = document.getElementById('toggle-volume');
-      if (toggleButton) {
-        updateButtonStyles(toggleButton, volumeToggle.isVisible, 'green');
-      }
     }
-  };
+  },
+
+  toggle: function() {
+    volumeToggle.isVisible = !volumeToggle.isVisible;
+    localStorage.setItem('volumeToggleState', volumeToggle.isVisible.toString());
+    volumeToggle.updateVolumeDisplay();
+  },
+
+  updateVolumeDisplay: function() {
+    const volumeDivs = document.querySelectorAll('[id$="-volume-div"]');
+    volumeDivs.forEach(div => {
+      if (div) {
+        div.style.display = volumeToggle.isVisible ? 'flex' : 'none';
+      }
+    });
+
+    const toggleButton = document.getElementById('toggle-volume');
+    if (toggleButton) {
+      updateButtonStyles(toggleButton, volumeToggle.isVisible, 'green');
+    }
+  },
+
+  cleanup: function() {
+    const toggleButton = document.getElementById('toggle-volume');
+    if (toggleButton) {
+      toggleButton.removeEventListener('click', volumeToggle.toggle);
+    }
+  }
+};
 
   function updateButtonStyles(button, isActive, color) {
     button.classList.toggle('text-' + color + '-500', isActive);
@@ -1689,5 +1981,48 @@ resolutionButtons.forEach(button => {
   });
 });
 
+// LOAD
+const appCleanup = {
+  init: function() {
+    memoryMonitor.startMonitoring();
+    window.addEventListener('beforeunload', this.globalCleanup);
+  },
+
+  globalCleanup: function() {
+    try {
+      if (app.autoRefreshInterval) {
+        clearTimeout(app.autoRefreshInterval);
+      }
+      if (chartModule) {
+        chartModule.cleanup();
+      }
+      if (volumeToggle) {
+        volumeToggle.cleanup();
+      }
+      cleanupManager.clearAll();
+      memoryMonitor.stopMonitoring();
+      cache.clear();
+
+      console.log('Global application cleanup completed');
+    } catch (error) {
+      console.error('Error during global cleanup:', error);
+    }
+  },
+  manualCleanup: function() {
+    this.globalCleanup();
+    window.location.reload();
+  }
+};
+
+app.init = () => {
+  //console.log('Init');
+  window.addEventListener('load', app.onLoad);
+  appCleanup.init();
+  app.loadLastRefreshedTime();
+  app.updateAutoRefreshButton();
+  memoryMonitor.startMonitoring();
+  //console.log('App initialized');
+};
+
 // LOAD
 app.init();