diff --git a/basicswap/js_server.py b/basicswap/js_server.py
index b95c662..5e799fd 100644
--- a/basicswap/js_server.py
+++ b/basicswap/js_server.py
@@ -980,6 +980,67 @@ def js_readurl(self, url_split, post_string, is_json) -> bytes:
     raise ValueError("Requires URL.")
 
 
+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"
+    }
+    EXCLUDED_STATES = [
+        'Completed',
+        'Expired',
+        'Timed-out',
+        'Abandoned',
+        'Failed, refunded',
+        'Failed, swiped',
+        'Failed',
+        'Error',
+        'received'
+    ]
+    all_bids = []
+
+    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_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
+                swap_data = {
+                    "bid_id": bid[2].hex(),
+                    "offer_id": bid[3].hex(),
+                    "created_at": bid[0],
+                    "bid_state": bid_state,
+                    "tx_state_a": tx_state_a if tx_state_a else 'None',
+                    "tx_state_b": tx_state_b if tx_state_b else 'None',
+                    "coin_from": swap_client.ci(bid[9]).coin_name(),
+                    "coin_to": swap_client.ci(offer.coin_to).coin_name(),
+                    "amount_from": swap_client.ci(bid[9]).format_amount(bid[4]),
+                    "amount_to": swap_client.ci(offer.coin_to).format_amount(
+                        (bid[4] * bid[10]) // swap_client.ci(bid[9]).COIN()
+                    ),
+                    "addr_from": bid[11],
+                    "status": {
+                        "main": bid_state,
+                        "initial_tx": tx_state_a if tx_state_a else 'None',
+                        "payment_tx": tx_state_b if tx_state_b else 'None'
+                    }
+                }
+                all_bids.append(swap_data)
+            except Exception:
+                continue
+    except Exception:
+        return bytes(json.dumps([]), "UTF-8")
+    return bytes(json.dumps(all_bids), "UTF-8")
+
+
 pages = {
     "coins": js_coins,
     "wallets": js_wallets,
@@ -1005,6 +1066,7 @@ pages = {
     "lock": js_lock,
     "help": js_help,
     "readurl": js_readurl,
+    "active": js_active,
 }
 
 
diff --git a/basicswap/static/js/active.js b/basicswap/static/js/active.js
new file mode 100644
index 0000000..d6d1767
--- /dev/null
+++ b/basicswap/static/js/active.js
@@ -0,0 +1,872 @@
+// Constants and State
+const PAGE_SIZE = 50;
+const COIN_NAME_TO_SYMBOL = {
+    'Bitcoin': 'BTC',
+    'Litecoin': 'LTC',
+    'Monero': 'XMR',
+    'Particl': 'PART',
+    'Particl Blind': 'PART',
+    'Particl Anon': 'PART',
+    'PIVX': 'PIVX',
+    'Firo': 'FIRO',
+    'Dash': 'DASH',
+    'Decred': 'DCR',
+    'Wownero': 'WOW',
+    'Bitcoin Cash': 'BCH',
+    'Dogecoin': 'DOGE'
+};
+
+// Global state
+const state = {
+    identities: new Map(),
+    currentPage: 1,
+    wsConnected: false,
+    swapsData: [],
+    isLoading: false,
+    isRefreshing: false,
+    refreshPromise: null
+};
+
+// DOM
+const elements = {
+    swapsBody: document.getElementById('active-swaps-body'),
+    prevPageButton: document.getElementById('prevPage'),
+    nextPageButton: document.getElementById('nextPage'),
+    currentPageSpan: document.getElementById('currentPage'),
+    paginationControls: document.getElementById('pagination-controls'),
+    activeSwapsCount: document.getElementById('activeSwapsCount'),
+    refreshSwapsButton: document.getElementById('refreshSwaps'),
+    statusDot: document.getElementById('status-dot'),
+    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;
+};
+
+const getStatusClass = (status, tx_a, tx_b) => {
+    switch (status) {
+        case 'Completed':
+            return 'bg-green-300 text-black dark:bg-green-600 dark:text-white';
+        case 'Expired':
+        case 'Timed-out':
+            return 'bg-gray-200 text-black dark:bg-gray-400 dark:text-white';
+        case 'Error':
+        case 'Failed':
+            return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
+        case 'Failed, swiped':
+        case 'Failed, refunded':
+            return 'bg-gray-200 text-black dark:bg-gray-400 dark:text-red-500';
+        case 'InProgress':
+        case 'Script coin locked':
+        case 'Scriptless coin locked':
+        case 'Script coin lock released':
+        case 'SendingInitialTx':
+        case 'SendingPaymentTx':
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+        case 'Received':
+        case 'Exchanged script lock tx sigs msg':
+        case 'Exchanged script lock spend tx msg':
+        case 'Script tx redeemed':
+        case 'Scriptless tx redeemed':
+        case 'Scriptless tx recovered':
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+        case 'Accepted':
+        case 'Request accepted':
+            return 'bg-green-300 text-black dark:bg-green-600 dark:text-white';
+        case 'Delaying':
+        case 'Auto accept delay':
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+        case 'Abandoned':
+        case 'Rejected':
+            return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
+        default:
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+    }
+};
+
+const getTxStatusClass = (status) => {
+    if (!status || status === 'None') return 'text-gray-400';
+    
+    if (status.includes('Complete') || status.includes('Confirmed')) {
+        return 'text-green-500';
+    }
+    if (status.includes('Error') || status.includes('Failed')) {
+        return 'text-red-500';
+    }
+    if (status.includes('Progress') || status.includes('Sending')) {
+        return 'text-yellow-500';
+    }
+    return 'text-blue-500';
+};
+
+// Util
+const formatTimeAgo = (timestamp) => {
+    const now = Math.floor(Date.now() / 1000);
+    const diff = now - timestamp;
+
+    if (diff < 60) return `${diff} seconds ago`;
+    if (diff < 3600) return `${Math.floor(diff / 60)} minutes ago`;
+    if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`;
+    return `${Math.floor(diff / 86400)} days ago`;
+};
+
+
+const formatTime = (timestamp) => {
+    if (!timestamp) return '';
+    const date = new Date(timestamp * 1000);
+    return date.toLocaleString('en-US', {
+        year: 'numeric',
+        month: 'short',
+        day: 'numeric',
+        hour: '2-digit',
+        minute: '2-digit'
+    });
+};
+
+const formatAddress = (address, displayLength = 15) => {
+    if (!address) return '';
+    if (address.length <= displayLength) return address;
+    return `${address.slice(0, displayLength)}...`;
+};
+
+const getStatusColor = (status) => {
+    const statusColors = {
+        'Received': 'text-blue-500',
+        'Accepted': 'text-green-500',
+        'InProgress': 'text-yellow-500',
+        'Complete': 'text-green-600',
+        'Failed': 'text-red-500',
+        'Expired': 'text-gray-500'
+    };
+    return statusColors[status] || 'text-gray-500';
+};
+
+const getTimeStrokeColor = (expireTime) => {
+    const now = Math.floor(Date.now() / 1000);
+    const timeLeft = expireTime - now;
+
+    if (timeLeft <= 300) return '#9CA3AF'; // 5 minutes or less
+    if (timeLeft <= 1800) return '#3B82F6'; // 30 minutes or less
+    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 {
+            const wsPort = window.ws_port || '11700';
+            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;
+
+    const statusConfig = {
+        connected: {
+            dotClass: 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2',
+            textClass: 'text-sm text-green-500',
+            message: 'Connected'
+        },
+        disconnected: {
+            dotClass: 'w-2.5 h-2.5 rounded-full bg-red-500 mr-2',
+            textClass: 'text-sm text-red-500',
+            message: 'Disconnected - Reconnecting...'
+        },
+        error: {
+            dotClass: 'w-2.5 h-2.5 rounded-full bg-yellow-500 mr-2',
+            textClass: 'text-sm text-yellow-500',
+            message: 'Connection Error'
+        },
+        default: {
+            dotClass: 'w-2.5 h-2.5 rounded-full bg-gray-500 mr-2',
+            textClass: 'text-sm text-gray-500',
+            message: 'Connecting...'
+        }
+    };
+
+    const config = statusConfig[status] || statusConfig.default;
+    statusDot.className = config.dotClass;
+    statusText.className = config.textClass;
+    statusText.textContent = config.message;
+};
+
+const updateLoadingState = (isLoading) => {
+    state.isLoading = isLoading;
+    if (elements.refreshSwapsButton) {
+        elements.refreshSwapsButton.disabled = isLoading;
+        elements.refreshSwapsButton.classList.toggle('opacity-75', isLoading);
+        elements.refreshSwapsButton.classList.toggle('cursor-wait', isLoading);
+
+        const refreshIcon = elements.refreshSwapsButton.querySelector('svg');
+        const refreshText = elements.refreshSwapsButton.querySelector('#refreshText');
+
+        if (refreshIcon) {
+            refreshIcon.style.transition = 'transform 0.3s ease';
+            refreshIcon.classList.toggle('animate-spin', isLoading);
+        }
+
+        if (refreshText) {
+            refreshText.textContent = isLoading ? 'Refreshing...' : 'Refresh';
+        }
+    }
+};
+
+const processIdentityStats = (identity) => {
+    if (!identity) return null;
+
+    const stats = {
+        sentSuccessful: safeParseInt(identity.num_sent_bids_successful),
+        recvSuccessful: safeParseInt(identity.num_recv_bids_successful),
+        sentFailed: safeParseInt(identity.num_sent_bids_failed),
+        recvFailed: safeParseInt(identity.num_recv_bids_failed),
+        sentRejected: safeParseInt(identity.num_sent_bids_rejected),
+        recvRejected: safeParseInt(identity.num_recv_bids_rejected)
+    };
+
+    stats.totalSuccessful = stats.sentSuccessful + stats.recvSuccessful;
+    stats.totalFailed = stats.sentFailed + stats.recvFailed;
+    stats.totalRejected = stats.sentRejected + stats.recvRejected;
+    stats.totalBids = stats.totalSuccessful + stats.totalFailed + stats.totalRejected;
+
+    stats.successRate = stats.totalBids > 0
+        ? ((stats.totalSuccessful / stats.totalBids) * 100).toFixed(1)
+        : '0.0';
+
+    return stats;
+};
+
+const createIdentityTooltip = (identity) => {
+    if (!identity) return '';
+
+    const stats = processIdentityStats(identity);
+    if (!stats) return '';
+
+    const getSuccessRateColor = (rate) => {
+        const numRate = parseFloat(rate);
+        if (numRate >= 80) return 'text-green-600';
+        if (numRate >= 60) return 'text-yellow-600';
+        return 'text-red-600';
+    };
+
+    return `
+        <div class="identity-info space-y-2">
+            ${identity.label ? `
+                <div class="border-b border-gray-400 pb-2">
+                    <div class="text-white text-xs tracking-wide font-semibold">Label:</div>
+                    <div class="text-white">${identity.label}</div>
+                </div>
+            ` : ''}
+
+            <div class="space-y-1">
+                <div class="text-white text-xs tracking-wide font-semibold">Address:</div>
+                <div class="monospace text-xs break-all bg-gray-500 p-2 rounded-md text-white">
+                    ${identity.address || ''}
+                </div>
+            </div>
+
+            ${identity.note ? `
+                <div class="space-y-1">
+                    <div class="text-white text-xs tracking-wide font-semibold">Note:</div>
+                    <div class="text-white text-sm italic">${identity.note}</div>
+                </div>
+            ` : ''}
+
+            <div class="pt-2 mt-2">
+                <div class="text-white text-xs tracking-wide font-semibold mb-2">Swap History:</div>
+                <div class="grid grid-cols-2 gap-2">
+                    <div class="text-center p-2 bg-gray-500 rounded-md">
+                        <div class="text-lg font-bold ${getSuccessRateColor(stats.successRate)}">
+                            ${stats.successRate}%
+                        </div>
+                        <div class="text-xs text-white">Success Rate</div>
+                    </div>
+                    <div class="text-center p-2 bg-gray-500 rounded-md">
+                        <div class="text-lg font-bold text-blue-500">${stats.totalBids}</div>
+                        <div class="text-xs text-white">Total Trades</div>
+                    </div>
+                </div>
+                <div class="grid grid-cols-3 gap-2 mt-2 text-center text-xs">
+                    <div>
+                        <div class="text-green-600 font-semibold">
+                            ${stats.totalSuccessful}
+                        </div>
+                        <div class="text-white">Successful</div>
+                    </div>
+                    <div>
+                        <div class="text-yellow-600 font-semibold">
+                            ${stats.totalRejected}
+                        </div>
+                        <div class="text-white">Rejected</div>
+                    </div>
+                    <div>
+                        <div class="text-red-600 font-semibold">
+                            ${stats.totalFailed}
+                        </div>
+                        <div class="text-white">Failed</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    `;
+};
+
+const createSwapTableRow = async (swap) => {
+    if (!swap || !swap.bid_id) {
+        console.warn('Invalid swap data:', swap);
+        return '';
+    }
+
+    const identity = await IdentityManager.getIdentityData(swap.addr_from);
+    const uniqueId = `${swap.bid_id}_${swap.created_at}`;
+    const fromSymbol = COIN_NAME_TO_SYMBOL[swap.coin_from] || swap.coin_from;
+    const toSymbol = COIN_NAME_TO_SYMBOL[swap.coin_to] || swap.coin_to;
+    const timeColor = getTimeStrokeColor(swap.expire_at);
+    const fromAmount = parseFloat(swap.amount_from) || 0;
+    const toAmount = parseFloat(swap.amount_to) || 0;
+
+    return `
+        <tr class="relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600" data-bid-id="${swap.bid_id}">
+            <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">
+                    <div class="relative" data-tooltip-target="tooltip-time-${uniqueId}">
+                        <svg class="w-5 h-5 rounded-full mr-4 cursor-pointer" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                            <g stroke-linecap="round" stroke-width="2" fill="none" stroke="${timeColor}" stroke-linejoin="round">
+                                <circle cx="12" cy="12" r="11"></circle>
+                                <polyline points="12,6 12,12 18,12"></polyline>
+                            </g>
+                        </svg>
+                    </div>
+                    <div class="flex flex-col hidden xl:block">
+                        <div class="text-xs whitespace-nowrap">
+                            <span class="bold">Posted:</span> ${formatTimeAgo(swap.created_at)}
+                        </div>
+                    </div>
+                </div>
+            </td>
+
+           <!-- Details Column -->
+            <td class="py-8 px-4 text-xs text-left hidden xl:block">
+              <div class="flex flex-col gap-2 relative">
+                  <div class="flex items-center">
+                     <a href="/identity/${swap.addr_from}" data-tooltip-target="tooltip-identity-${uniqueId}" class="flex items-center">
+                        <svg class="w-4 h-4 mr-2 text-gray-400 dark:text-white" fill="currentColor" viewBox="0 0 20 20">
+                        <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
+                    </svg>
+                    <span class="monospace ${identity?.label ? 'dark:text-white' : 'dark:text-white'}">
+                        ${identity?.label || formatAddress(swap.addr_from)}
+                    </span>
+                </a>
+            </div>
+            <div class="monospace text-xs text-gray-500 dark:text-gray-300">
+                <span class="font-semibold">Bid ID:</span>
+                <a href="/bid/${swap.bid_id}" data-tooltip-target="tooltip-bid-${uniqueId}" class="hover:underline">
+                    ${formatAddress(swap.bid_id)}
+                </a>
+            </div>
+            <div class="monospace text-xs text-gray-500 dark:text-gray-300">
+                <span class="font-semibold">Offer ID:</span>
+                <a href="/offer/${swap.offer_id}" data-tooltip-target="tooltip-offer-${uniqueId}" class="hover:underline">
+                    ${formatAddress(swap.offer_id)}
+                </a>
+             </div>
+            </div>
+           </td>
+            <!-- You Send Column -->
+            <td class="py-0">
+                <div class="py-3 px-4 text-left">
+                    <div class="items-center monospace">
+                        <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>
+
+            <!-- Swap Column -->
+            <td class="py-0">
+                <div class="py-3 px-4 text-center">
+                    <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_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">
+                            <path fill-rule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"></path>
+                        </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_to.replace(' ', '-')}.png" 
+                                 alt="${swap.coin_to}"
+                                 onerror="this.src='/static/images/coins/default.png'">
+                        </span>
+                    </div>
+                </div>
+            </td>
+
+            <!-- You Receive Column -->
+            <td class="py-0">
+                <div class="py-3 px-4 text-right">
+                    <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>
+                </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)}">
+                        ${swap.bid_state}
+                    </span>
+                </div>
+            </td>
+
+            <!-- Actions Column -->
+            <td class="py-3 px-4 text-center">
+                <a href="/bid/${swap.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">
+                    Details
+                </a>
+            </td>
+
+            <!-- Tooltips -->
+            <div id="tooltip-time-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
+                <div class="active-revoked-expired">
+                    <span class="bold">
+                        <div class="text-xs"><span class="bold">Posted:</span> ${formatTimeAgo(swap.created_at)}</div>
+                        <div class="text-xs"><span class="bold">Expires in:</span> ${formatTime(swap.expire_at)}</div>
+                    </span>
+                </div>
+                <div class="mt-5 text-xs">
+                    <p class="font-bold mb-3">Time Indicator Colors:</p>
+                    <p class="flex items-center">
+                        <svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                            <g stroke-linecap="round" stroke-width="2" fill="none" stroke="#10B981" stroke-linejoin="round">
+                                <circle cx="12" cy="12" r="11"></circle>
+                                <polyline points="12,6 12,12 18,12" stroke="#10B981"></polyline>
+                            </g>
+                        </svg>
+                        Green: More than 30 minutes left
+                    </p>
+                    <p class="flex items-center mt-3">
+                        <svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                            <g stroke-linecap="round" stroke-width="2" fill="none" stroke="#3B82F6" stroke-linejoin="round">
+                                <circle cx="12" cy="12" r="11"></circle>
+                                <polyline points="12,6 12,12 18,12" stroke="#3B82F6"></polyline>
+                            </g>
+                        </svg>
+                        Blue: Between 5 and 30 minutes left
+                    </p>
+                    <p class="flex items-center mt-3 mb-3">
+                        <svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                            <g stroke-linecap="round" stroke-width="2" fill="none" stroke="#9CA3AF" stroke-linejoin="round">
+                                <circle cx="12" cy="12" r="11"></circle>
+                                <polyline points="12,6 12,12 18,12" stroke="#9CA3AF"></polyline>
+                            </g>
+                        </svg>
+                        Grey: Less than 5 minutes left or expired
+                    </p>
+                </div>
+                <div class="tooltip-arrow" data-popper-arrow></div>
+            </div>
+
+            <div id="tooltip-identity-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
+                ${createIdentityTooltip(identity)}
+                <div class="tooltip-arrow" data-popper-arrow></div>
+            </div>
+
+            <div id="tooltip-offer-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
+                <div class="space-y-1">
+                    <div class="text-white text-xs tracking-wide font-semibold">Offer ID:</div>
+                    <div class="monospace text-xs break-all">
+                        ${swap.offer_id}
+                    </div>
+                </div>
+                <div class="tooltip-arrow" data-popper-arrow></div>
+            </div>
+
+            <div id="tooltip-bid-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
+                <div class="space-y-1">
+                    <div class="text-white text-xs tracking-wide font-semibold">Bid ID:</div>
+                    <div class="monospace text-xs break-all">
+                        ${swap.bid_id}
+                    </div>
+                </div>
+                <div class="tooltip-arrow" data-popper-arrow></div>
+            </div>
+
+            <div id="tooltip-status-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
+                <div class="text-white">
+                    <p class="font-bold mb-2">Transaction Status</p>
+                    <div class="grid grid-cols-2 gap-2">
+                        <div class="bg-gray-500 p-2 rounded">
+                            <p class="text-xs font-bold">ITX:</p>
+                            <p>${swap.tx_state_a || 'N/A'}</p>
+                        </div>
+                        <div class="bg-gray-500 p-2 rounded">
+                            <p class="text-xs font-bold">PTX:</p>
+                            <p>${swap.tx_state_b || 'N/A'}</p>
+                        </div>
+                    </div>
+                </div>
+                <div class="tooltip-arrow" data-popper-arrow></div>
+            </div>
+        </tr>
+    `;
+};
+
+async function updateSwapsTable(options = {}) {
+    const { resetPage = false, refreshData = true } = options;
+
+    if (state.refreshPromise) {
+        await state.refreshPromise;
+        return;
+    }
+
+    try {
+        updateLoadingState(true);
+
+        if (refreshData) {
+            state.refreshPromise = (async () => {
+                try {
+                    const response = await fetch('/json/active', {
+                        method: 'POST',
+                        headers: { 'Content-Type': 'application/json' },
+                        body: JSON.stringify({
+                            sort_by: "created_at",
+                            sort_dir: "desc"
+                        })
+                    });
+
+                    if (!response.ok) {
+                        throw new Error(`HTTP error! status: ${response.status}`);
+                    }
+
+                    const data = await response.json();
+                    state.swapsData = Array.isArray(data) ? data : [];
+                } catch (error) {
+                    console.error('Error fetching swap data:', error);
+                    state.swapsData = [];
+                } finally {
+                    state.refreshPromise = null;
+                }
+            })();
+
+            await state.refreshPromise;
+        }
+
+        if (elements.activeSwapsCount) {
+            elements.activeSwapsCount.textContent = state.swapsData.length;
+        }
+
+        const totalPages = Math.ceil(state.swapsData.length / PAGE_SIZE);
+        
+        if (resetPage && state.swapsData.length > 0) {
+            state.currentPage = 1;
+        }
+
+        state.currentPage = Math.min(Math.max(1, state.currentPage), Math.max(1, totalPages));
+
+        const startIndex = (state.currentPage - 1) * PAGE_SIZE;
+        const endIndex = startIndex + PAGE_SIZE;
+        const currentPageSwaps = state.swapsData.slice(startIndex, endIndex);
+
+        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]');
+                    tooltipTriggers.forEach(trigger => {
+                        const targetId = trigger.getAttribute('data-tooltip-target');
+                        const tooltipContent = document.getElementById(targetId);
+                        if (tooltipContent) {
+                            window.TooltipManager.create(trigger, tooltipContent.innerHTML, {
+                                placement: trigger.getAttribute('data-tooltip-placement') || 'top'
+                            });
+                        }
+                    });
+                }
+            } else {
+                elements.swapsBody.innerHTML = `
+                    <tr>
+                        <td colspan="8" class="text-center py-4 text-gray-500 dark:text-white">
+                            No active swaps found
+                        </td>
+                    </tr>`;
+            }
+        }
+
+        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) {
+            elements.swapsBody.innerHTML = `
+                <tr>
+                    <td colspan="8" class="text-center py-4 text-red-500">
+                        Error loading active swaps. Please try again later.
+                    </td>
+                </tr>`;
+        }
+    } finally {
+        updateLoadingState(false);
+    }
+}
+
+// Event
+const setupEventListeners = () => {
+    if (elements.refreshSwapsButton) {
+        elements.refreshSwapsButton.addEventListener('click', async (e) => {
+            e.preventDefault();
+            if (state.isRefreshing) return;
+
+            updateLoadingState(true);
+            try {
+                await updateSwapsTable({ resetPage: true, refreshData: true });
+            } finally {
+                updateLoadingState(false);
+            }
+        });
+    }
+
+    if (elements.prevPageButton) {
+        elements.prevPageButton.addEventListener('click', async (e) => {
+            e.preventDefault();
+            if (state.isLoading) return;
+            if (state.currentPage > 1) {
+                state.currentPage--;
+                await updateSwapsTable({ resetPage: false, refreshData: false });
+            }
+        });
+    }
+
+    if (elements.nextPageButton) {
+        elements.nextPageButton.addEventListener('click', async (e) => {
+            e.preventDefault();
+            if (state.isLoading) return;
+            const totalPages = Math.ceil(state.swapsData.length / PAGE_SIZE);
+            if (state.currentPage < totalPages) {
+                state.currentPage++;
+                await updateSwapsTable({ resetPage: false, refreshData: false });
+            }
+        });
+    }
+};
+
+// Init
+document.addEventListener('DOMContentLoaded', () => {
+    WebSocketManager.initialize();
+    setupEventListeners();
+});
diff --git a/basicswap/static/js/bids_available.js b/basicswap/static/js/bids_available.js
index b3b7e48..a183fd2 100644
--- a/basicswap/static/js/bids_available.js
+++ b/basicswap/static/js/bids_available.js
@@ -374,7 +374,7 @@ const WebSocketManager = {
             state.wsConnected = true;
             this.reconnectAttempts = 0;
             updateConnectionStatus('connected');
-            console.log('🟢  WebSocket connection established');
+            console.log('🟢  WebSocket connection established for Bid Requests');
             updateBidsTable({ resetPage: true, refreshData: true });
         };
 
diff --git a/basicswap/static/js/bids_export.js b/basicswap/static/js/bids_export.js
new file mode 100644
index 0000000..823a1e4
--- /dev/null
+++ b/basicswap/static/js/bids_export.js
@@ -0,0 +1,141 @@
+const BidExporter = {
+    toCSV(bids, type) {
+        if (!bids || !bids.length) {
+            return 'No data to export';
+        }
+
+        const isSent = type === 'sent';
+        
+        const headers = [
+            'Date/Time',
+            'Bid ID',
+            'Offer ID',
+            'From Address',
+            isSent ? 'You Send Amount' : 'You Receive Amount',
+            isSent ? 'You Send Coin' : 'You Receive Coin',
+            isSent ? 'You Receive Amount' : 'You Send Amount',
+            isSent ? 'You Receive Coin' : 'You Send Coin',
+            'Status',
+            'Created At',
+            'Expires At'
+        ];
+        
+        let csvContent = headers.join(',') + '\n';
+        
+        bids.forEach(bid => {
+            const row = [
+                `"${formatTime(bid.created_at)}"`,
+                `"${bid.bid_id}"`,
+                `"${bid.offer_id}"`,
+                `"${bid.addr_from}"`,
+                isSent ? bid.amount_from : bid.amount_to,
+                `"${isSent ? bid.coin_from : bid.coin_to}"`,
+                isSent ? bid.amount_to : bid.amount_from,
+                `"${isSent ? bid.coin_to : bid.coin_from}"`,
+                `"${bid.bid_state}"`,
+                bid.created_at,
+                bid.expire_at
+            ];
+            
+            csvContent += row.join(',') + '\n';
+        });
+        
+        return csvContent;
+    },
+    
+    download(content, filename) {
+        try {
+            const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
+            
+            if (window.navigator && window.navigator.msSaveOrOpenBlob) {
+                window.navigator.msSaveOrOpenBlob(blob, filename);
+                return;
+            }
+
+            const url = URL.createObjectURL(blob);
+            const link = document.createElement('a');
+            
+            link.href = url;
+            link.download = filename;
+            link.style.display = 'none';
+            
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            
+            setTimeout(() => {
+                URL.revokeObjectURL(url);
+            }, 100);
+        } catch (error) {
+            console.error('Error downloading CSV:', error);
+            
+            const csvData = 'data:text/csv;charset=utf-8,' + encodeURIComponent(content);
+            const link = document.createElement('a');
+            link.setAttribute('href', csvData);
+            link.setAttribute('download', filename);
+            link.style.display = 'none';
+            
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+        }
+    },
+    
+    exportCurrentView() {
+        const type = state.currentTab;
+        const data = state.data[type];
+        
+        if (!data || !data.length) {
+            alert('No data to export');
+            return;
+        }
+        
+        const csvContent = this.toCSV(data, type);
+        
+        const now = new Date();
+        const dateStr = now.toISOString().split('T')[0];
+        const filename = `bsx_${type}_bids_${dateStr}.csv`;
+        
+        this.download(csvContent, filename);
+    }
+};
+
+document.addEventListener('DOMContentLoaded', function() {
+    setTimeout(function() {
+        if (typeof state !== 'undefined' && typeof EventManager !== 'undefined') {
+            const exportSentButton = document.getElementById('exportSentBids');
+            if (exportSentButton) {
+                EventManager.add(exportSentButton, 'click', (e) => {
+                    e.preventDefault();
+                    state.currentTab = 'sent';
+                    BidExporter.exportCurrentView();
+                });
+            }
+            
+            const exportReceivedButton = document.getElementById('exportReceivedBids');
+            if (exportReceivedButton) {
+                EventManager.add(exportReceivedButton, 'click', (e) => {
+                    e.preventDefault();
+                    state.currentTab = 'received';
+                    BidExporter.exportCurrentView();
+                });
+            }
+        }
+    }, 500);
+});
+
+const originalCleanup = window.cleanup || function(){};
+window.cleanup = function() {
+    originalCleanup();
+    
+    const exportSentButton = document.getElementById('exportSentBids');
+    const exportReceivedButton = document.getElementById('exportReceivedBids');
+    
+    if (exportSentButton && typeof EventManager !== 'undefined') {
+        EventManager.remove(exportSentButton, 'click');
+    }
+    
+    if (exportReceivedButton && typeof EventManager !== 'undefined') {
+        EventManager.remove(exportReceivedButton, 'click');
+    }
+};
diff --git a/basicswap/static/js/bids_sentreceived.js b/basicswap/static/js/bids_sentreceived.js
index 61e1522..c3321a9 100644
--- a/basicswap/static/js/bids_sentreceived.js
+++ b/basicswap/static/js/bids_sentreceived.js
@@ -93,6 +93,140 @@ const elements = {
     refreshReceivedBids: document.getElementById('refreshReceivedBids')
 };
 
+const EventManager = {
+    listeners: new Map(),
+
+    add(element, type, handler, options = false) {
+        if (!element) return null;
+        
+        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) {
+        if (!element) return;
+        
+        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) {
+        if (!element) return;
+        
+        const elementListeners = this.listeners.get(element);
+        if (!elementListeners) return;
+
+        elementListeners.forEach((typeListeners, type) => {
+            typeListeners.forEach(info => {
+                try {
+                    element.removeEventListener(type, info.handler, info.options);
+                } catch (e) {
+                    console.warn('Error removing event listener:', e);
+                }
+            });
+        });
+
+        this.listeners.delete(element);
+    },
+
+    clearAll() {
+        this.listeners.forEach((elementListeners, element) => {
+            this.removeAll(element);
+        });
+        this.listeners.clear();
+    }
+};
+
+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('Cleanup completed');
+}
+
+document.addEventListener('beforeunload', cleanup);
+document.addEventListener('visibilitychange', () => {
+    if (document.hidden) {
+        WebSocketManager.pause();
+    } else {
+        WebSocketManager.resume();
+    }
+});
+
 // WebSocket Management
 const WebSocketManager = {
     ws: null,
@@ -101,7 +235,10 @@ const WebSocketManager = {
     maxReconnectAttempts: 5,
     reconnectAttempts: 0,
     reconnectDelay: 5000,
-
+    healthCheckInterval: null,
+    isPaused: false,
+    lastMessageTime: Date.now(),
+    
     initialize() {
         this.connect();
         this.startHealthCheck();
@@ -112,7 +249,11 @@ const WebSocketManager = {
     },
 
     connect() {
-        if (this.isConnected()) return;
+        if (this.isConnected() || this.isPaused) return;
+
+        if (this.ws) {
+            this.cleanupConnection();
+        }
 
         try {
             const wsPort = window.ws_port || '11700';
@@ -125,15 +266,21 @@ const WebSocketManager = {
     },
 
     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');
+            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 () => {
@@ -151,7 +298,9 @@ const WebSocketManager = {
         this.ws.onclose = () => {
             state.wsConnected = false;
             updateConnectionStatus('disconnected');
-            this.handleReconnect();
+            if (!this.isPaused) {
+                this.handleReconnect();
+            }
         };
 
         this.ws.onerror = () => {
@@ -160,29 +309,100 @@ const WebSocketManager = {
     },
 
     startHealthCheck() {
-        setInterval(() => {
+        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();
     }
 };
 
@@ -226,13 +446,37 @@ const getStatusClass = (status) => {
         case 'Completed':
             return 'bg-green-300 text-black dark:bg-green-600 dark:text-white';
         case 'Expired':
+        case 'Timed-out':
             return 'bg-gray-200 text-black dark:bg-gray-400 dark:text-white';
         case 'Error':
-            return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
         case 'Failed':
             return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
+        case 'Failed, swiped':
         case 'Failed, refunded':
             return 'bg-gray-200 text-black dark:bg-gray-400 dark:text-red-500';
+        case 'InProgress':
+        case 'Script coin locked':
+        case 'Scriptless coin locked':
+        case 'Script coin lock released':
+        case 'SendingInitialTx':
+        case 'SendingPaymentTx':
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+        case 'Received':
+        case 'Exchanged script lock tx sigs msg':
+        case 'Exchanged script lock spend tx msg':
+        case 'Script tx redeemed':
+        case 'Scriptless tx redeemed':
+        case 'Scriptless tx recovered':
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+        case 'Accepted':
+        case 'Request accepted':
+            return 'bg-green-300 text-black dark:bg-green-600 dark:text-white';
+        case 'Delaying':
+        case 'Auto accept delay':
+            return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
+        case 'Abandoned':
+        case 'Rejected':
+            return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
         default:
             return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
     }
@@ -289,7 +533,6 @@ function hasActiveFilters() {
 
 function filterAndSortData(bids) {
     if (!Array.isArray(bids)) {
-        console.log('Invalid bids data:', bids);
         return [];
     }
 
@@ -313,7 +556,7 @@ function filterAndSortData(bids) {
             const coinName = selectedOption?.textContent.trim();
 
             if (coinName) {
-                const coinToMatch = state.currentTab === 'sent' ? bid.coin_from : bid.coin_to;
+                const coinToMatch = state.currentTab === 'sent' ? bid.coin_to : bid.coin_from;
                 if (!coinMatches(coinToMatch, coinName)) {
                     return false;
                 }
@@ -326,7 +569,7 @@ function filterAndSortData(bids) {
             const coinName = selectedOption?.textContent.trim();
 
             if (coinName) {
-                const coinToMatch = state.currentTab === 'sent' ? bid.coin_to : bid.coin_from;
+                const coinToMatch = state.currentTab === 'sent' ? bid.coin_from : bid.coin_to;
                 if (!coinMatches(coinToMatch, coinName)) {
                     return false;
                 }
@@ -457,6 +700,7 @@ const IdentityManager = {
     retryDelay: 2000,
     maxRetries: 3,
     cacheTimeout: 5 * 60 * 1000,
+    maxCacheSize: 500,
 
     async getIdentityData(address) {
         if (!address) return { address: '' };
@@ -465,8 +709,12 @@ const IdentityManager = {
         if (cachedData) return { ...cachedData, address };
 
         if (this.pendingRequests.has(address)) {
-            const pendingData = await this.pendingRequests.get(address);
-            return { ...pendingData, address };
+            try {
+                const pendingData = await this.pendingRequests.get(address);
+                return { ...pendingData, address };
+            } catch (error) {
+                this.pendingRequests.delete(address);
+            }
         }
 
         const request = this.fetchWithRetry(address);
@@ -474,10 +722,14 @@ const IdentityManager = {
 
         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);
@@ -490,6 +742,7 @@ const IdentityManager = {
     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) {
@@ -498,9 +751,36 @@ const IdentityManager = {
         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 response = await fetch(`/json/identities/${address}`);
+            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) {
@@ -615,9 +895,132 @@ const createIdentityTooltipContent = (identity) => {
 };
 
 // Table
+let tooltipIdsToCleanup = new Set();
+
+const cleanupTooltips = () => {
+    if (window.TooltipManager) {
+        Array.from(tooltipIdsToCleanup).forEach(id => {
+            const element = document.getElementById(id);
+            if (element) {
+                element.remove();
+            }
+        });
+        tooltipIdsToCleanup.clear();
+    }
+    forceTooltipDOMCleanup();
+};
+
+const forceTooltipDOMCleanup = () => {
+    let foundCount = 0;
+    let removedCount = 0;
+    const allTooltipElements = document.querySelectorAll('[role="tooltip"], [id^="tooltip-"], .tippy-box, [data-tippy-root]');
+    foundCount += allTooltipElements.length;
+
+    allTooltipElements.forEach(element => {
+
+        const isDetached = !document.body.contains(element) || 
+                           element.classList.contains('hidden') ||
+                           element.style.display === 'none';
+
+        if (element.id && element.id.startsWith('tooltip-')) {
+            const triggerId = element.id;
+            const triggerElement = document.querySelector(`[data-tooltip-target="${triggerId}"]`);
+
+            if (!triggerElement || 
+                !document.body.contains(triggerElement) ||
+                triggerElement.classList.contains('hidden')) {
+                element.remove();
+                removedCount++;
+                return;
+            }
+        }
+
+        if (isDetached) {
+            try {
+                element.remove();
+                removedCount++;
+            } catch (e) {
+                console.warn('Error removing detached tooltip:', e);
+            }
+        }
+    });
+
+    const tippyRoots = document.querySelectorAll('[data-tippy-root]');
+    foundCount += tippyRoots.length;
+    
+    tippyRoots.forEach(element => {
+        const isOrphan = !element.children.length || 
+                         element.children[0].classList.contains('hidden') ||
+                         !document.body.contains(element);
+
+        if (isOrphan) {
+            try {
+                element.remove();
+                removedCount++;
+            } catch (e) {
+                console.warn('Error removing tippy root:', e);
+            }
+        }
+    });
+
+    const tippyBoxes = document.querySelectorAll('.tippy-box');
+    foundCount += tippyBoxes.length;
+    tippyBoxes.forEach(element => {
+        if (!element.parentElement || !document.body.contains(element.parentElement)) {
+            try {
+                element.remove();
+                removedCount++;
+            } catch (e) {
+                console.warn('Error removing tippy box:', e);
+            }
+        }
+    });
+    
+    // Handle legacy tooltip elements
+    document.querySelectorAll('.tooltip').forEach(element => {
+        const isTrulyDetached = !element.parentElement || 
+                               !document.body.contains(element.parentElement) ||
+                               element.classList.contains('hidden');
+
+        if (isTrulyDetached) {
+            try {
+                element.remove();
+                removedCount++;
+            } catch (e) {
+                console.warn('Error removing legacy tooltip:', e);
+            }
+        }
+    });
+
+    if (window.TooltipManager && window.TooltipManager.activeTooltips) {
+        window.TooltipManager.activeTooltips.forEach((instance, id) => {
+            const tooltipElement = document.getElementById(id.split('tooltip-trigger-')[1]);
+            const triggerElement = document.querySelector(`[data-tooltip-trigger-id="${id}"]`);
+
+            if (!tooltipElement || !triggerElement || 
+                !document.body.contains(tooltipElement) || 
+                !document.body.contains(triggerElement)) {
+                if (instance?.[0]) {
+                    try {
+                        instance[0].destroy();
+                    } catch (e) {
+                        console.warn('Error destroying tooltip instance:', e);
+                    }
+                }
+                window.TooltipManager.activeTooltips.delete(id);
+            }
+        });
+    }
+    if (removedCount > 0) {
+       // console.log(`Tooltip cleanup: found ${foundCount}, removed ${removedCount} detached tooltips`);
+    }
+};
+
 const createTableRow = async (bid) => {
     const identity = await IdentityManager.getIdentityData(bid.addr_from);
     const uniqueId = `${bid.bid_id}_${Date.now()}`;
+    tooltipIdsToCleanup.add(`tooltip-identity-${uniqueId}`);
+    tooltipIdsToCleanup.add(`tooltip-status-${uniqueId}`);
     const timeColor = getTimeStrokeColor(bid.expire_at);
 
     return `
@@ -641,8 +1044,8 @@ const createTableRow = async (bid) => {
                     <div class="flex items-center min-w-max">
                         <div class="relative" data-tooltip-target="tooltip-identity-${uniqueId}">
                             <a href="/identity/${bid.addr_from}" class="text-xs font-mono">
-                                <span class="mr-2">
-                                    ${state.currentTab === 'sent' ? 'Out' : 'In'}
+                                <span>
+                                    ${state.currentTab === 'sent' ? 'Out:' : 'In:'}
                                 </span>
                                 ${identity?.label || formatAddressSMSG(bid.addr_from)}
                             </a>
@@ -684,27 +1087,27 @@ const createTableRow = async (bid) => {
                 </div>
             </td>
 
-            <!-- Status Column -->
-            <td class="p-3">
-                <div class="flex justify-center" data-tooltip-target="tooltip-status-${uniqueId}">
-                    <span class="w-full xl:w-4/5 flex bold justify-center items-center px-2.5 py-1 rounded-full text-xs font-medium
-                                ${getStatusClass(bid.bid_state)}">
-                        ${bid.bid_state}
-                    </span>
-                </div>
+           <!-- 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)}">
+                ${bid.bid_state}
+                </span>
+              </div>
             </td>
 
             <!-- Actions Column -->
-            <td class="py-3 pr-6 pl-3">
+            <td class="py-3 pr-4 pl-3">
                 <div class="flex justify-center">
                     <a href="/bid/${bid.bid_id}"
-                        class="inline-block w-24 py-2 px-3 text-center text-sm font-medium text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors">
+                        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">
                         View Bid
                     </a>
                 </div>
             </td>
         </tr>
-
+        
+        
         <!-- Tooltips -->
         <div id="tooltip-identity-${uniqueId}" role="tooltip" class="fixed z-50 py-3 px-4 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600 max-w-sm pointer-events-none">
             ${createIdentityTooltipContent(identity)}
@@ -734,30 +1137,78 @@ const updateTableContent = async (type) => {
     const tbody = elements[`${type}BidsBody`];
     if (!tbody) return;
 
+    if (window.TooltipManager) {
+        window.TooltipManager.cleanup();
+    }
+
+    cleanupTooltips();
+    forceTooltipDOMCleanup();
+
+    tooltipIdsToCleanup.clear();
+
     const filteredData = state.data[type];
 
     const startIndex = (state.currentPage[type] - 1) * PAGE_SIZE;
     const endIndex = startIndex + PAGE_SIZE;
+
     const currentPageData = filteredData.slice(startIndex, endIndex);
 
-    console.log('Updating table content:', {
-        type: type,
-        totalFilteredBids: filteredData.length,
-        currentPageBids: currentPageData.length,
-        startIndex: startIndex,
-        endIndex: endIndex
-    });
+    //console.log('Updating table content:', {
+    //    type: type,
+    //    totalFilteredBids: filteredData.length,
+    //    currentPageBids: currentPageData.length,
+    //    startIndex: startIndex,
+    //    endIndex: endIndex
+    //});
 
-    if (currentPageData.length > 0) {
-        const rowPromises = currentPageData.map(bid => createTableRow(bid));
-        const rows = await Promise.all(rowPromises);
-        tbody.innerHTML = rows.join('');
-        initializeTooltips();
-    } else {
+    try {
+        if (currentPageData.length > 0) {
+            const BATCH_SIZE = 10;
+            let allRows = [];
+            
+            for (let i = 0; i < currentPageData.length; i += BATCH_SIZE) {
+                const batch = currentPageData.slice(i, i + BATCH_SIZE);
+                const rowPromises = batch.map(bid => createTableRow(bid));
+                const rows = await Promise.all(rowPromises);
+                allRows = allRows.concat(rows);
+
+                if (i + BATCH_SIZE < currentPageData.length) {
+                    await new Promise(resolve => setTimeout(resolve, 5));
+                }
+            }
+
+            const scrollPosition = tbody.parentElement?.scrollTop || 0;
+
+            tbody.innerHTML = allRows.join('');
+
+            if (tbody.parentElement && scrollPosition > 0) {
+                tbody.parentElement.scrollTop = scrollPosition;
+            }
+
+            if (document.visibilityState === 'visible') {
+
+                setTimeout(() => {
+                    initializeTooltips();
+
+                    setTimeout(() => {
+                        forceTooltipDOMCleanup();
+                    }, 100);
+                }, 10);
+            }
+        } else {
+            tbody.innerHTML = `
+                <tr>
+                    <td colspan="8" class="text-center py-4 text-gray-500 dark:text-white">
+                        No ${type} bids found
+                    </td>
+                </tr>`;
+        }
+    } catch (error) {
+        console.error('Error updating table content:', error);
         tbody.innerHTML = `
             <tr>
-                <td colspan="8" class="text-center py-4 text-gray-500 dark:text-white">
-                    No ${type} bids found
+                <td colspan="8" class="text-center py-4 text-red-500">
+                    Error loading data. Please try refreshing.
                 </td>
             </tr>`;
     }
@@ -766,38 +1217,160 @@ const updateTableContent = async (type) => {
 };
 
 const initializeTooltips = () => {
-    if (window.TooltipManager) {
-        window.TooltipManager.cleanup();
+    if (!window.TooltipManager || document.hidden) {
+        return;
+    }
+
+    window.TooltipManager.cleanup();
+
+    let selector = '#' + state.currentTab + ' [data-tooltip-target]';
+    const tooltipTriggers = document.querySelectorAll(selector);
+    const tooltipCount = tooltipTriggers.length;
+    if (tooltipCount > 50) {
+        //console.log(`Optimizing ${tooltipCount} tooltips`);
+        const viewportMargin = 200;
+        const viewportTooltips = Array.from(tooltipTriggers).filter(trigger => {
+            const rect = trigger.getBoundingClientRect();
+            return (
+                rect.bottom >= -viewportMargin &&
+                rect.top <= (window.innerHeight + viewportMargin) &&
+                rect.right >= 0 &&
+                rect.left <= window.innerWidth
+            );
+        });
+
+        viewportTooltips.forEach(trigger => {
+            createTooltipForTrigger(trigger);
+        });
+
+        const offscreenTooltips = Array.from(tooltipTriggers).filter(t => !viewportTooltips.includes(t));
+        
+        offscreenTooltips.forEach(trigger => {
+            const createTooltipOnHover = () => {
+                createTooltipForTrigger(trigger);
+                trigger.removeEventListener('mouseenter', createTooltipOnHover);
+            };
+
+            trigger.addEventListener('mouseenter', createTooltipOnHover);
+        });
+    } else {
 
-        const tooltipTriggers = document.querySelectorAll('[data-tooltip-target]');
         tooltipTriggers.forEach(trigger => {
-            const targetId = trigger.getAttribute('data-tooltip-target');
-            const tooltipContent = document.getElementById(targetId);
-
-            if (tooltipContent) {
-                window.TooltipManager.create(trigger, tooltipContent.innerHTML, {
-                    placement: trigger.getAttribute('data-tooltip-placement') || 'top',
-                    interactive: true,
-                    animation: 'shift-away',
-                    maxWidth: 400,
-                    allowHTML: true,
-                    offset: [0, 8],
-                    zIndex: 50
-                });
-            }
+            createTooltipForTrigger(trigger);
         });
     }
 };
 
+const createTooltipForTrigger = (trigger) => {
+    if (!trigger || !window.TooltipManager) return;
+    
+    const targetId = trigger.getAttribute('data-tooltip-target');
+    const tooltipContent = document.getElementById(targetId);
+
+    if (tooltipContent) {
+        window.TooltipManager.create(trigger, tooltipContent.innerHTML, {
+            placement: trigger.getAttribute('data-tooltip-placement') || 'top',
+            interactive: true,
+            animation: false,
+            maxWidth: 400,
+            allowHTML: true,
+            offset: [0, 8],
+            zIndex: 50,
+            delay: [200, 0],
+            appendTo: () => document.body
+        });
+    }
+};
+
+function optimizeForLargeDatasets() {
+    if (state.data[state.currentTab]?.length > 50) {
+
+        const simplifyTooltips = tooltipIdsToCleanup.size > 50;
+
+        implementVirtualizedRows();
+
+        let scrollTimeout;
+        window.addEventListener('scroll', () => {
+            clearTimeout(scrollTimeout);
+            scrollTimeout = setTimeout(() => {
+                cleanupOffscreenTooltips();
+            }, 150);
+        }, { passive: true });
+    }
+}
+
+function cleanupOffscreenTooltips() {
+    if (!window.TooltipManager) return;
+
+    const selector = '#' + state.currentTab + ' [data-tooltip-target]';
+    const tooltipTriggers = document.querySelectorAll(selector);
+
+    const farOffscreenTriggers = Array.from(tooltipTriggers).filter(trigger => {
+        const rect = trigger.getBoundingClientRect();
+        return (rect.bottom < -window.innerHeight * 2 || 
+                rect.top > window.innerHeight * 3);
+    });
+
+    farOffscreenTriggers.forEach(trigger => {
+        const targetId = trigger.getAttribute('data-tooltip-target');
+        if (targetId) {
+            const tooltipElement = document.getElementById(targetId);
+            if (tooltipElement) {
+                window.TooltipManager.destroy(trigger);
+                trigger.addEventListener('mouseenter', () => {
+                    createTooltipForTrigger(trigger);
+                }, { once: true });
+            }
+        }
+    });
+}
+
+function implementVirtualizedRows() {
+    const tbody = elements[`${state.currentTab}BidsBody`];
+    if (!tbody) return;
+
+    const tableRows = tbody.querySelectorAll('tr');
+    if (tableRows.length < 30) return;
+
+    Array.from(tableRows).forEach(row => {
+        const rect = row.getBoundingClientRect();
+        const isVisible = (
+            rect.bottom >= 0 &&
+            rect.top <= window.innerHeight
+        );
+
+        if (!isVisible && (rect.bottom < -window.innerHeight || rect.top > window.innerHeight * 2)) {
+            const tooltipTriggers = row.querySelectorAll('[data-tooltip-target]');
+            tooltipTriggers.forEach(trigger => {
+                if (window.TooltipManager) {
+                    window.TooltipManager.destroy(trigger);
+                }
+            });
+        }
+    });
+}
+
 // Fetching
+let activeFetchController = null;
+
 const fetchBids = async () => {
     try {
+        if (activeFetchController) {
+            activeFetchController.abort();
+        }
+        activeFetchController = new AbortController();
         const endpoint = state.currentTab === 'sent' ? '/json/sentbids' : '/json/bids';
         const withExpiredSelect = document.getElementById('with_expired');
         const includeExpired = withExpiredSelect ? withExpiredSelect.value === 'true' : true;
 
-        console.log('Fetching bids, include expired:', includeExpired);
+        //console.log('Fetching bids, include expired:', includeExpired);
 
+        const timeoutId = setTimeout(() => {
+            if (activeFetchController) {
+                activeFetchController.abort();
+            }
+        }, 30000);
+        
         const response = await fetch(endpoint, {
             method: 'POST',
             headers: {
@@ -807,128 +1380,67 @@ const fetchBids = async () => {
             body: JSON.stringify({
                 sort_by: state.filters.sort_by || 'created_at',
                 sort_dir: state.filters.sort_dir || 'desc',
-                with_expired: true, // Always fetch all bids
+                with_expired: true,
                 state: state.filters.state ?? -1,
                 with_extra_info: true
-            })
+            }),
+            signal: activeFetchController.signal
         });
+        
+        clearTimeout(timeoutId);
 
         if (!response.ok) {
             throw new Error(`HTTP error! status: ${response.status}`);
         }
 
         let data = await response.json();
-        console.log('Received raw data:', data.length, 'bids');
+        //console.log('Received raw data:', data.length, 'bids');
 
         state.filters.with_expired = includeExpired;
 
-        data = filterAndSortData(data);
-
-        return data;
+        let processedData;
+        if (data.length > 500) {
+            processedData = await new Promise(resolve => {
+                setTimeout(() => {
+                    const filtered = filterAndSortData(data);
+                    resolve(filtered);
+                }, 10);
+            });
+        } else {
+            processedData = filterAndSortData(data);
+        }
+        
+        return processedData;
     } catch (error) {
-        console.error('Error in fetchBids:', error);
+        if (error.name === 'AbortError') {
+            console.log('Fetch request was aborted');
+        } else {
+            console.error('Error in fetchBids:', error);
+        }
         throw error;
+    } finally {
+        activeFetchController = null;
     }
 };
 
 const updateBidsTable = async () => {
     if (state.isLoading) {
-        console.log('Already loading, skipping update');
+        //console.log('Already loading, skipping update');
         return;
     }
 
     try {
-        console.log('Starting updateBidsTable for tab:', state.currentTab);
-        console.log('Current filters:', state.filters);
+        //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);
+       //console.log('Fetched bids:', bids.length);
 
-        const filteredBids = bids.filter(bid => {
-            // State filter
-            if (state.filters.state !== -1) {
-                const allowedStates = STATE_MAP[state.filters.state] || [];
-                if (allowedStates.length > 0 && !allowedStates.includes(bid.bid_state)) {
-                    return false;
-                }
-            }
-
-            const now = Math.floor(Date.now() / 1000);
-            if (!state.filters.with_expired && bid.expire_at <= now) {
-                return false;
-            }
-
-            let yourCoinMatch = true;
-            let theirCoinMatch = true;
-
-            if (state.filters.coin_from !== 'any') {
-                const coinFromSelect = document.getElementById('coin_from');
-                const selectedOption = coinFromSelect?.querySelector(`option[value="${state.filters.coin_from}"]`);
-                const coinName = selectedOption?.textContent.trim();
-
-                if (coinName) {
-                    const coinToMatch = state.currentTab === 'sent' ? bid.coin_from : bid.coin_to;
-                    yourCoinMatch = coinMatches(coinToMatch, coinName);
-                    console.log('Your Coin filtering:', {
-                        filterCoin: coinName,
-                        bidCoin: coinToMatch,
-                        matches: yourCoinMatch
-                    });
-                }
-            }
-
-            if (state.filters.coin_to !== 'any') {
-                const coinToSelect = document.getElementById('coin_to');
-                const selectedOption = coinToSelect?.querySelector(`option[value="${state.filters.coin_to}"]`);
-                const coinName = selectedOption?.textContent.trim();
-
-                if (coinName) {
-                    const coinToMatch = state.currentTab === 'sent' ? bid.coin_to : bid.coin_from;
-                    theirCoinMatch = coinMatches(coinToMatch, coinName);
-                    console.log('Their Coin filtering:', {
-                        filterCoin: coinName,
-                        bidCoin: coinToMatch,
-                        matches: theirCoinMatch
-                    });
-                }
-            }
-
-            if (!yourCoinMatch || !theirCoinMatch) {
-                return false;
-            }
-
-            if (state.filters.searchQuery) {
-                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 || '';
-                const matchesLabel = label.toLowerCase().includes(searchStr);
-
-                if (!(matchesBidId || matchesIdentity || matchesLabel)) {
-                    return false;
-                }
-            }
-
-            return true;
-        });
-
-        console.log('Filtered bids:', filteredBids.length);
-
-        filteredBids.sort((a, b) => {
-            const direction = state.filters.sort_dir === 'asc' ? 1 : -1;
-            if (state.filters.sort_by === 'created_at') {
-                return direction * (a.created_at - b.created_at);
-            }
-            return 0;
-        });
-
-        state.data[state.currentTab] = filteredBids;
+        state.data[state.currentTab] = bids;
         state.currentPage[state.currentTab] = 1;
 
         await updateTableContent(state.currentTab);
@@ -952,15 +1464,15 @@ const updatePaginationControls = (type) => {
     const currentPageSpan = elements[`currentPage${type.charAt(0).toUpperCase() + type.slice(1)}`];
     const bidsCount = elements[`${type}BidsCount`];
 
-    console.log('Pagination controls update:', {
-        type: type,
-        totalBids: data.length,
-        totalPages: totalPages,
-        currentPage: state.currentPage[type]
-    });
+    //console.log('Pagination controls update:', {
+    //    type: type,
+    //    totalBids: data.length,
+    //    totalPages: totalPages,
+    //    currentPage: state.currentPage[type]
+    //});
 
     if (state.currentPage[type] > totalPages) {
-        state.currentPage[type] = totalPages;
+        state.currentPage[type] = totalPages > 0 ? totalPages : 1;
     }
 
     if (controls) {
@@ -1092,7 +1604,7 @@ function setupFilterEventListeners() {
     const withExpiredSelect = document.getElementById('with_expired');
 
     if (coinToSelect) {
-        coinToSelect.addEventListener('change', () => {
+        EventManager.add(coinToSelect, 'change', () => {
             state.filters.coin_to = coinToSelect.value;
             updateBidsTable();
             updateCoinFilterImages();
@@ -1101,7 +1613,7 @@ function setupFilterEventListeners() {
     }
 
     if (coinFromSelect) {
-        coinFromSelect.addEventListener('change', () => {
+        EventManager.add(coinFromSelect, 'change', () => {
             state.filters.coin_from = coinFromSelect.value;
             updateBidsTable();
             updateCoinFilterImages();
@@ -1110,7 +1622,7 @@ function setupFilterEventListeners() {
     }
 
     if (withExpiredSelect) {
-        withExpiredSelect.addEventListener('change', () => {
+        EventManager.add(withExpiredSelect, 'change', () => {
             state.filters.with_expired = withExpiredSelect.value === 'true';
             updateBidsTable();
             updateClearFiltersButton();
@@ -1119,7 +1631,7 @@ function setupFilterEventListeners() {
 
     const searchInput = document.getElementById('searchInput');
     if (searchInput) {
-        searchInput.addEventListener('input', (event) => {
+        EventManager.add(searchInput, 'input', (event) => {
             if (searchTimeout) {
                 clearTimeout(searchTimeout);
             }
@@ -1133,228 +1645,11 @@ function setupFilterEventListeners() {
     }
 }
 
-// Tabs
-const switchTab = (tabId) => {
-    if (state.isLoading) return;
-
-    state.currentTab = tabId === '#sent' ? 'sent' : 'received';
-
-    elements.sentContent.classList.add('hidden');
-    elements.receivedContent.classList.add('hidden');
-
-    const targetPanel = document.querySelector(tabId);
-    if (targetPanel) {
-        targetPanel.classList.remove('hidden');
-    }
-
-    elements.tabButtons.forEach(tab => {
-        const selected = tab.dataset.tabsTarget === tabId;
-        tab.setAttribute('aria-selected', selected);
-        if (selected) {
-            tab.classList.add('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
-            tab.classList.remove('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
-        } else {
-            tab.classList.remove('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
-            tab.classList.add('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
-        }
-    });
-
-    updateBidsTable();
-};
-
-const setupEventListeners = () => {
-    const filterControls = document.querySelector('.flex.flex-wrap.justify-center');
-    if (filterControls) {
-        filterControls.addEventListener('submit', (e) => {
-            e.preventDefault();
-        });
-    }
-
-    const applyFiltersBtn = document.getElementById('applyFilters');
-    if (applyFiltersBtn) {
-        applyFiltersBtn.remove();
-    }
-
-    if (elements.tabButtons) {
-        elements.tabButtons.forEach(button => {
-            button.addEventListener('click', () => {
-                if (state.isLoading) return;
-
-                const targetId = button.getAttribute('data-tabs-target');
-                if (!targetId) return;
-
-                // Update tab button styles
-                elements.tabButtons.forEach(tab => {
-                    const isSelected = tab.getAttribute('data-tabs-target') === targetId;
-                    tab.setAttribute('aria-selected', isSelected);
-
-                    if (isSelected) {
-                        tab.classList.add('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
-                        tab.classList.remove('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
-                    } else {
-                        tab.classList.remove('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
-                        tab.classList.add('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
-                    }
-                });
-
-                elements.sentContent.classList.toggle('hidden', targetId !== '#sent');
-                elements.receivedContent.classList.toggle('hidden', targetId !== '#received');
-
-                state.currentTab = targetId === '#sent' ? 'sent' : 'received';
-                state.currentPage[state.currentTab] = 1;
-
-                updateBidsTable();
-            });
-        });
-    }
-
-    ['Sent', 'Received'].forEach(type => {
-        const lowerType = type.toLowerCase();
-
-        if (elements[`prevPage${type}`]) {
-            elements[`prevPage${type}`].addEventListener('click', () => {
-                if (state.isLoading) return;
-                if (state.currentPage[lowerType] > 1) {
-                    state.currentPage[lowerType]--;
-                    updateTableContent(lowerType);
-                    updatePaginationControls(lowerType);
-                }
-            });
-        }
-
-        if (elements[`nextPage${type}`]) {
-            elements[`nextPage${type}`].addEventListener('click', () => {
-                if (state.isLoading) return;
-                const totalPages = Math.ceil(state.data[lowerType].length / PAGE_SIZE);
-                if (state.currentPage[lowerType] < totalPages) {
-                    state.currentPage[lowerType]++;
-                    updateTableContent(lowerType);
-                    updatePaginationControls(lowerType);
-                }
-            });
-        }
-    });
-
-    const searchInput = document.getElementById('searchInput');
-    if (searchInput) {
-        searchInput.addEventListener('input', handleSearch);
-    }
-
-    const coinToSelect = document.getElementById('coin_to');
-    const coinFromSelect = document.getElementById('coin_from');
-
-    if (coinToSelect) {
-        coinToSelect.addEventListener('change', () => {
-            state.filters.coin_to = coinToSelect.value;
-            updateBidsTable();
-            updateCoinFilterImages();
-        });
-    }
-
-    if (coinFromSelect) {
-        coinFromSelect.addEventListener('change', () => {
-            state.filters.coin_from = coinFromSelect.value;
-            updateBidsTable();
-            updateCoinFilterImages();
-        });
-    }
-
-    const filterElements = {
-        stateSelect: document.getElementById('state'),
-        sortBySelect: document.getElementById('sort_by'),
-        sortDirSelect: document.getElementById('sort_dir'),
-        withExpiredSelect: document.getElementById('with_expired'),
-        clearFiltersBtn: document.getElementById('clearFilters')
-    };
-
-    if (filterElements.stateSelect) {
-        filterElements.stateSelect.addEventListener('change', () => {
-            const stateValue = parseInt(filterElements.stateSelect.value);
-
-            state.filters.state = isNaN(stateValue) ? -1 : stateValue;
-
-            console.log('State filter changed:', {
-                selectedValue: filterElements.stateSelect.value,
-                parsedState: state.filters.state
-            });
-
-            updateBidsTable();
-            updateClearFiltersButton();
-        });
-    }
-
-    [
-        filterElements.sortBySelect,
-        filterElements.sortDirSelect,
-        filterElements.withExpiredSelect
-    ].forEach(element => {
-        if (element) {
-            element.addEventListener('change', () => {
-                updateBidsTable();
-                updateClearFiltersButton();
-            });
-        }
-    });
-
-    if (filterElements.clearFiltersBtn) {
-        filterElements.clearFiltersBtn.addEventListener('click', () => {
-            if (filterElements.clearFiltersBtn.disabled) return;
-            clearFilters();
-        });
-    }
-
-    initializeTooltips();
-
-    document.addEventListener('change', (event) => {
-        const target = event.target;
-        const filterForm = document.querySelector('.flex.flex-wrap.justify-center');
-
-        if (filterForm && filterForm.contains(target)) {
-            const formData = {
-                state: filterElements.stateSelect?.value,
-                sort_by: filterElements.sortBySelect?.value,
-                sort_dir: filterElements.sortDirSelect?.value,
-                with_expired: filterElements.withExpiredSelect?.value,
-                coin_from: coinFromSelect?.value,
-                coin_to: coinToSelect?.value,
-                searchQuery: searchInput?.value
-            };
-
-            localStorage.setItem('bidsTableSettings', JSON.stringify(formData));
-        }
-    });
-
-    const savedSettings = localStorage.getItem('bidsTableSettings');
-    if (savedSettings) {
-        const settings = JSON.parse(savedSettings);
-
-        Object.entries(settings).forEach(([key, value]) => {
-            const element = document.querySelector(`[name="${key}"]`);
-            if (element) {
-                element.value = value;
-            }
-        });
-
-        state.filters = {
-            state: settings.state ? parseInt(settings.state) : -1,
-            sort_by: settings.sort_by || 'created_at',
-            sort_dir: settings.sort_dir || 'desc',
-            with_expired: settings.with_expired === 'true',
-            searchQuery: settings.searchQuery || '',
-            coin_from: settings.coin_from || 'any',
-            coin_to: settings.coin_to || 'any'
-        };
-    }
-
-    updateCoinFilterImages();
-    updateClearFiltersButton();
-};
-
 const setupRefreshButtons = () => {
     ['Sent', 'Received'].forEach(type => {
         const refreshButton = elements[`refresh${type}Bids`];
         if (refreshButton) {
-            refreshButton.addEventListener('click', async () => {
+            EventManager.add(refreshButton, 'click', async () => {
                 const lowerType = type.toLowerCase();
 
                 if (state.isRefreshing) {
@@ -1397,6 +1692,7 @@ const setupRefreshButtons = () => {
                 } catch (error) {
                     console.error(`Error refreshing ${type} bids:`, error);
                 } finally {
+                    state.isRefreshing = false;
                     state.isLoading = false;
                     updateLoadingState(false);
                 }
@@ -1405,8 +1701,247 @@ const setupRefreshButtons = () => {
     });
 };
 
+// Tabs
+const switchTab = (tabId) => {
+    if (state.isLoading) return;
+
+    if (window.TooltipManager) {
+        window.TooltipManager.cleanup();
+    }
+
+    cleanupTooltips();
+    forceTooltipDOMCleanup();
+
+    tooltipIdsToCleanup.clear();
+
+    state.currentTab = tabId === '#sent' ? 'sent' : 'received';
+
+    elements.sentContent.classList.add('hidden');
+    elements.receivedContent.classList.add('hidden');
+
+    const targetPanel = document.querySelector(tabId);
+    if (targetPanel) {
+        targetPanel.classList.remove('hidden');
+    }
+
+    elements.tabButtons.forEach(tab => {
+        const selected = tab.dataset.tabsTarget === tabId;
+        tab.setAttribute('aria-selected', selected);
+        if (selected) {
+            tab.classList.add('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
+            tab.classList.remove('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
+        } else {
+            tab.classList.remove('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
+            tab.classList.add('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
+        }
+    });
+    setTimeout(() => {
+        updateBidsTable();
+    }, 10);
+};
+
+const setupEventListeners = () => {
+    const filterControls = document.querySelector('.flex.flex-wrap.justify-center');
+    if (filterControls) {
+        EventManager.add(filterControls, 'submit', (e) => {
+            e.preventDefault();
+        });
+    }
+
+    const applyFiltersBtn = document.getElementById('applyFilters');
+    if (applyFiltersBtn) {
+        applyFiltersBtn.remove();
+    }
+
+    if (elements.tabButtons) {
+        elements.tabButtons.forEach(button => {
+            EventManager.add(button, 'click', () => {
+                if (state.isLoading) return;
+
+                const targetId = button.getAttribute('data-tabs-target');
+                if (!targetId) return;
+
+                elements.tabButtons.forEach(tab => {
+                    const isSelected = tab.getAttribute('data-tabs-target') === targetId;
+                    tab.setAttribute('aria-selected', isSelected);
+
+                    if (isSelected) {
+                        tab.classList.add('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
+                        tab.classList.remove('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
+                    } else {
+                        tab.classList.remove('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
+                        tab.classList.add('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
+                    }
+                });
+
+                elements.sentContent.classList.toggle('hidden', targetId !== '#sent');
+                elements.receivedContent.classList.toggle('hidden', targetId !== '#received');
+
+                state.currentTab = targetId === '#sent' ? 'sent' : 'received';
+                state.currentPage[state.currentTab] = 1;
+
+                if (window.TooltipManager) {
+                    window.TooltipManager.cleanup();
+                }
+                cleanupTooltips();
+
+                updateBidsTable();
+            });
+        });
+    }
+
+    ['Sent', 'Received'].forEach(type => {
+        const lowerType = type.toLowerCase();
+
+        if (elements[`prevPage${type}`]) {
+            EventManager.add(elements[`prevPage${type}`], 'click', () => {
+                if (state.isLoading) return;
+                if (state.currentPage[lowerType] > 1) {
+                    state.currentPage[lowerType]--;
+                    updateTableContent(lowerType);
+                }
+            });
+        }
+
+        if (elements[`nextPage${type}`]) {
+            EventManager.add(elements[`nextPage${type}`], 'click', () => {
+                if (state.isLoading) return;
+                const totalPages = Math.ceil(state.data[lowerType].length / PAGE_SIZE);
+                if (state.currentPage[lowerType] < totalPages) {
+                    state.currentPage[lowerType]++;
+                    updateTableContent(lowerType);
+                }
+            });
+        }
+    });
+
+    const searchInput = document.getElementById('searchInput');
+    if (searchInput) {
+        EventManager.add(searchInput, 'input', handleSearch);
+    }
+
+    const coinToSelect = document.getElementById('coin_to');
+    const coinFromSelect = document.getElementById('coin_from');
+
+    if (coinToSelect) {
+        EventManager.add(coinToSelect, 'change', () => {
+            state.filters.coin_to = coinToSelect.value;
+            updateBidsTable();
+            updateCoinFilterImages();
+        });
+    }
+
+    if (coinFromSelect) {
+        EventManager.add(coinFromSelect, 'change', () => {
+            state.filters.coin_from = coinFromSelect.value;
+            updateBidsTable();
+            updateCoinFilterImages();
+        });
+    }
+
+    const filterElements = {
+        stateSelect: document.getElementById('state'),
+        sortBySelect: document.getElementById('sort_by'),
+        sortDirSelect: document.getElementById('sort_dir'),
+        withExpiredSelect: document.getElementById('with_expired'),
+        clearFiltersBtn: document.getElementById('clearFilters')
+    };
+
+    if (filterElements.stateSelect) {
+        EventManager.add(filterElements.stateSelect, 'change', () => {
+            const stateValue = parseInt(filterElements.stateSelect.value);
+
+            state.filters.state = isNaN(stateValue) ? -1 : stateValue;
+
+            console.log('State filter changed:', {
+                selectedValue: filterElements.stateSelect.value,
+                parsedState: state.filters.state
+            });
+
+            updateBidsTable();
+            updateClearFiltersButton();
+        });
+    }
+
+    [
+        filterElements.sortBySelect,
+        filterElements.sortDirSelect,
+        filterElements.withExpiredSelect
+    ].forEach(element => {
+        if (element) {
+            EventManager.add(element, 'change', () => {
+                updateBidsTable();
+                updateClearFiltersButton();
+            });
+        }
+    });
+
+    if (filterElements.clearFiltersBtn) {
+        EventManager.add(filterElements.clearFiltersBtn, 'click', () => {
+            if (filterElements.clearFiltersBtn.disabled) return;
+            clearFilters();
+        });
+    }
+
+    EventManager.add(document, 'change', (event) => {
+        const target = event.target;
+        const filterForm = document.querySelector('.flex.flex-wrap.justify-center');
+
+        if (filterForm && filterForm.contains(target)) {
+            const formData = {
+                state: filterElements.stateSelect?.value,
+                sort_by: filterElements.sortBySelect?.value,
+                sort_dir: filterElements.sortDirSelect?.value,
+                with_expired: filterElements.withExpiredSelect?.value,
+                coin_from: coinFromSelect?.value,
+                coin_to: coinToSelect?.value,
+                searchQuery: searchInput?.value
+            };
+
+            localStorage.setItem('bidsTableSettings', JSON.stringify(formData));
+        }
+    });
+
+    EventManager.add(window, 'scroll', () => {
+        if (!document.hidden && !state.isLoading) {
+            setTimeout(initializeTooltips, 100);
+        }
+    }, { passive: true });
+    initializeTooltips();
+    updateCoinFilterImages();
+    updateClearFiltersButton();
+};
+
+function setupMemoryMonitoring() {
+    const MEMORY_CHECK_INTERVAL = 2 * 60 * 1000;
+
+    const intervalId = setInterval(() => {
+        if (document.hidden) {
+            console.log('Tab hidden - running memory optimization');
+            IdentityManager.trimCacheIfNeeded();
+            if (window.TooltipManager) {
+                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);
+            }
+        } else {
+            cleanupTooltips();
+        }
+    }, MEMORY_CHECK_INTERVAL);
+    document.addEventListener('beforeunload', () => {
+        clearInterval(intervalId);
+    }, { once: true });
+}
+
 // Init
-document.addEventListener('DOMContentLoaded', () => {
+function initialize() {
     const filterElements = {
         stateSelect: document.getElementById('state'),
         sortBySelect: document.getElementById('sort_by'),
@@ -1423,13 +1958,31 @@ document.addEventListener('DOMContentLoaded', () => {
     if (filterElements.coinFrom) filterElements.coinFrom.value = 'any';
     if (filterElements.coinTo) filterElements.coinTo.value = 'any';
 
-    WebSocketManager.initialize();
-    setupEventListeners();
-    setupRefreshButtons();
-    setupFilterEventListeners();
+    setupMemoryMonitoring();
 
-    updateClearFiltersButton();
-    state.currentTab = 'sent';
-    state.filters.state = -1;
-    updateBidsTable();
-});
+    setTimeout(() => {
+        WebSocketManager.initialize();
+        setupEventListeners();
+    }, 10);
+    
+    setTimeout(() => {
+        setupRefreshButtons();
+        setupFilterEventListeners();
+        updateCoinFilterImages();
+    }, 50);
+    
+    setTimeout(() => {
+        updateClearFiltersButton();
+        state.currentTab = 'sent';
+        state.filters.state = -1;
+        updateBidsTable();
+    }, 100);
+
+    window.cleanupBidsTable = cleanup;
+}
+
+if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', initialize);
+} else {
+    initialize();
+}
diff --git a/basicswap/templates/active.html b/basicswap/templates/active.html
index c336938..d6e1c34 100644
--- a/basicswap/templates/active.html
+++ b/basicswap/templates/active.html
@@ -1,114 +1,118 @@
 {% include 'header.html' %}
-{% from 'style.html' import breadcrumb_line_svg, circular_arrows_svg %} 
-<div class="container mx-auto">
- <section class="p-5 mt-5">
-  <div class="flex flex-wrap items-center -m-2">
-   <div class="w-full md:w-1/2 p-2">
-    <ul class="flex flex-wrap items-center gap-x-3 mb-2">
-     <li>
-      <a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">
-       <p>Home</p>
-      </a>
-     </li>
-    <li>{{ breadcrumb_line_svg | safe }}</li>
-     <li>
-      <a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/active">Swaps In Progress</a>
-     </li>
-    </ul>
-   </div>
-  </div>
- </section>
- <section class="py-4">
-  <div class="container px-4 mx-auto">
-   <div class="relative py-11 px-16 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="">
-    <img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
-    <img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
-    <div class="relative z-20 flex flex-wrap items-center -m-3">
-     <div class="w-full md:w-1/2 p-3">
-      <h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Swaps in Progress</h2>
-      <p class="font-normal text-coolGray-200 dark:text-white">Your swaps that are currently in progress.</p>
-     </div>
-     <div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
-      {% if refresh %}
-      <a id="refresh" href="/active" class="rounded-full flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
-        {{ circular_arrows_svg | safe }}
-       <span>Refresh 30 seconds</span>
-      </a>
-      {% else %}
-      <a id="refresh" href="/active" class="flex flex-wrap justify-center px-5 py-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white borderdark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
-        {{ circular_arrows_svg | safe }}
-       <span>Refresh</span>
-      </a>
-      {% endif %}
-      </div>
+{% from 'style.html' import breadcrumb_line_svg, page_back_svg, page_forwards_svg, filter_clear_svg, filter_apply_svg, input_arrow_down_svg %}
+
+<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="">
+   <img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
+   <img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
+   <div class="relative z-20 flex flex-wrap items-center -m-3">
+    <div class="w-full md:w-1/2 p-3">
+     <h2 class="mb-3 text-2xl font-bold text-white tracking-tighter">Swaps in Progress</h2>
+     <p class="font-normal text-coolGray-200 dark:text-white">Monitor your currently active swap transactions.</p>
     </div>
    </div>
   </div>
- </section>
- <section>
-  <div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
-   <div class="pb-6 border-coolGray-100">
-    <div class="flex flex-wrap items-center justify-between -m-2">
-     <div class="w-full pt-2">
-      <div class="container mt-5 mx-auto">
-       <div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
-        <div class="px-6">
-         <div class="w-full mt-6 pb-6 overflow-x-auto">
-          <table class="w-full min-w-max text-sm">
-           <thead class="uppercase">
-            <tr class="text-left">
-             <th class="p-0">
-              <div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
-               <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid ID</span>
-              </div>
-             </th>
-             <th class="p-0">
-              <div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
-               <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Offer ID</span>
-              </div>
-             </th>
-             <th class="p-0">
-              <div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
-               <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid Status</span>
-              </div>
-             </th>
-             <th class="p-0">
-              <div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
-               <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">ITX Status</span>
-              </div>
-             </th>
-             <th class="p-0">
-              <div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
-               <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">PTX Status</span>
-              </div>
-             </th>
-            </tr>
-           </thead>
-           {% for s in active_swaps %}
-           <tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
-            <td class="py-3 px-6 monospace">
-             <a href=/bid/{{ s[0] }}>{{ s[0]|truncate(50,true,'...',0) }}</a>
-            </td>
-            <td class="py-3 px-6 monospace">
-             <a href=/offer/{{ s[1] }}>{{ s[1]|truncate(50,true,'...',0) }}</a>
-            </td>
-            <td class="py-3 px-6 w-52 whitespace-normal break-words">{{ s[2] }}</td>
-            <td class="py-3 px-6">{{ s[3] }}</td>
-            <td class="py-3 px-6">{{ s[4] }}</td>
-           </tr>
-           {% endfor %}
-          </table>
+ </div>
+</section>
+
+{% include 'inc_messages.html' %}
+
+<section>
+ <div class="mt-5 lg:container mx-auto lg:px-0 px-6">
+  <div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
+   <div class="px-0">
+    <div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
+     <table class="w-full min-w-max">
+      <thead class="uppercase">
+       <tr>
+        <th class="p-0" data-sortable="true" data-column-index="0">
+         <div class="py-3 pl-4 justify-center rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
+          <span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold"></span>
          </div>
-        </div>
+        </th>
+        <th class="p-0">
+         <div class="py-3 pl-6 pr-3 justify-center bg-coolGray-200 dark:bg-gray-600">
+          <span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold">Time</span>
+         </div>
+        </th>
+        <th class="p-0 hidden xl:block">
+         <div class="py-3 px-4 text-left bg-coolGray-200 dark:bg-gray-600">
+          <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
+         </div>
+        </th>
+        <th class="p-0">
+         <div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-left">
+          <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
+         </div>
+        </th>
+        <th class="p-0">
+         <div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-center">
+          <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Swap</span>
+         </div>
+        </th>
+        <th class="p-0">
+         <div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right">
+          <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
+         </div>
+        </th>
+        <th class="p-0">
+         <div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-center">
+          <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Status</span>
+         </div>
+        </th>
+        <th class="p-0">
+         <div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 rounded-tr-xl">
+          <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
+         </div>
+        </th>
+       </tr>
+      </thead>
+      <tbody id="active-swaps-body"></tbody>
+     </table>
+    </div>
+   </div>
+   <div class="rounded-b-md">
+    <div class="w-full">
+     <div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
+      <div class="flex items-center">
+       <div class="flex items-center mr-4">
+        <span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
+        <span id="status-text" class="text-sm text-gray-500">Connecting...</span>
+       </div>
+       <p class="text-sm font-heading dark:text-gray-400 mr-4">Active Swaps: <span id="activeSwapsCount">0</span></p>
+       {% if debug_ui_mode == true %}
+       <button type="button" id="refreshSwaps" class="inline-flex items-center px-4 py-2.5 font-medium text-sm text-white bg-blue-600 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
+        <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
+        </svg>
+        <span id="refreshText">Refresh</span>
+       </button>
+       {% endif %}
+       <div id="pagination-controls" class="flex items-center space-x-2" style="display: none;">
+        <button id="prevPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200">
+         <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
+         </svg>
+         Previous
+        </button>
+        <p class="text-sm font-heading dark:text-white">Page <span id="currentPage">1</span></p>
+        <button id="nextPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200">
+         Next
+         <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
+         </svg>
+        </button>
        </div>
       </div>
      </div>
     </div>
    </div>
   </div>
- </section>
-</div>
+ </div>
+</section>
+
+<script src="/static/js/active.js"></script>
+
 {% include 'footer.html' %}
-</body>
-</html>
diff --git a/basicswap/templates/bids.html b/basicswap/templates/bids.html
index 29589be..1bf3d5d 100644
--- a/basicswap/templates/bids.html
+++ b/basicswap/templates/bids.html
@@ -1,9 +1,9 @@
 {% include 'header.html' %}
 {% from 'style.html' import breadcrumb_line_svg, page_back_svg, page_forwards_svg, filter_clear_svg, filter_apply_svg, circular_arrows_svg, input_arrow_down_svg, arrow_right_svg %}
 
-<div class="xl:container mx-auto">
- <section class="py-3 px-4 mt-6">
-  <div class="xl: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="">
     <img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
@@ -20,7 +20,8 @@
 
  {% include 'inc_messages.html' %}
 
- <section>
+<div class="xl:container mx-auto">
+<section>
   <div class="pl-6 pr-6 pt-0 mt-5 h-full overflow-hidden">
    <div class="flex flex-wrap items-center justify-between -m-2">
     <div class="w-full pt-2">
@@ -28,12 +29,12 @@
       <ul class="flex flex-wrap text-sm font-medium text-center text-gray-500 dark:text-gray-400" id="myTab" data-tabs-toggle="#bidstab" role="tablist">
        <li class="mr-2">
         <button class="inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white focus:outline-none focus:ring-0" id="sent-tab" data-tabs-target="#sent" type="button" role="tab" aria-controls="sent" aria-selected="true">
-         Sent Bids ({{ sent_bids_count }})
+         Sent Bids <span class="text-gray-500 dark:text-gray-400">({{ sent_bids_count }})</span>
         </button>
        </li>
        <li class="mr-2">
         <button class="inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white focus:outline-none focus:ring-0" id="received-tab" data-tabs-target="#received" type="button" role="tab" aria-controls="received" aria-selected="false">
-         Received Bids ({{ received_bids_count }})
+         Received Bids <span class="text-gray-500 dark:text-gray-400">({{ received_bids_count }})</span>
         </button>
        </li>
       </ul>
@@ -41,7 +42,8 @@
     </div>
    </div>
   </div>
- </section>
+</section>
+</div>
 
  <section>
   <div class="px-6 py-0 h-full overflow-hidden">
@@ -52,7 +54,7 @@
        <input type="text"
            id="searchInput"
            name="search"  autocomplete="off" placeholder="Search bid ID, offer ID, address or label..."
-           class="w-full md:w-auto hover:border-blue-500 dark:hover:bg-gray-50 text-gray-900 pl-4 pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none block w-96 p-2.5 focus:ring-blue-500 focus:border-blue-500 focus:ring-0 dark:focus:bg-gray-500 dark:focus:text-white">
+           class="w-full md:w-96 hover:border-blue-500 dark:hover:bg-gray-50 text-gray-900 pl-4 pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none block p-2.5 focus:ring-blue-500 focus:border-blue-500 focus:ring-0 dark:focus:bg-gray-500 dark:focus:text-white">
        <div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
         <svg class="w-5 h-5 text-gray-500 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
@@ -176,32 +178,32 @@
           <tr class="text-left">
            <th class="p-0">
             <div class="py-3 pl-16 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Date/Time</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Date/Time</span>
             </div>
            </th>
            <th class="p-0 hidden lg:block">
             <div class="p-3 bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Details</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 text-center bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Status</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Status</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 pr-6 text-center rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
             </div>
            </th>
           </tr>
@@ -229,6 +231,14 @@
             <span id="refreshSentText">Refresh</span>
            </button>
            {% endif %}
+
+           <button id="exportSentBids" class="ml-4 inline-flex items-center px-4 py-2.5 font-medium text-sm text-white bg-green-600 hover:bg-green-700 hover:border-green-700 rounded-lg transition duration-200 border border-green-600 rounded-md shadow-button focus:ring-0 focus:outline-none">
+            <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
+            </svg>
+            <span>Export CSV</span>
+           </button>
+
           </div>
           <div id="pagination-controls-sent" class="flex items-center space-x-2" style="display: none;">
            <button id="prevPageSent" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
@@ -254,7 +264,6 @@
    </div>
   </div>
 
-  <!-- Received Bids Tab -->
   <div class="hidden rounded-lg lg:px-6" id="received" role="tabpanel" aria-labelledby="received-tab">
    <div id="received-content">
     <div class="xl:container mx-auto lg:px-0">
@@ -266,32 +275,32 @@
           <tr class="text-left">
            <th class="p-0">
             <div class="p-3 pl-16 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Date/Time</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Date/Time</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Details</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 text-center bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Status</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Status</span>
             </div>
            </th>
            <th class="p-0">
             <div class="p-3 pr-6 text-center rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
-             <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
+             <span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
             </div>
            </th>
           </tr>
@@ -319,6 +328,14 @@
             <span id="refreshReceivedText">Refresh</span>
            </button>
            {% endif %}
+
+           <button id="exportReceivedBids" class="ml-4 inline-flex items-center px-4 py-2.5 font-medium text-sm text-white bg-green-600 hover:bg-green-700 hover:border-green-700 rounded-lg transition duration-200 border border-green-600 rounded-md shadow-button focus:ring-0 focus:outline-none">
+            <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
+            </svg>
+            <span>Export CSV</span>
+           </button>
+
           </div>
           <div id="pagination-controls-received" class="flex items-center space-x-2" style="display: none;">
            <button id="prevPageReceived" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
@@ -344,8 +361,8 @@
    </div>
   </div>
  </div>
-</div>
 
 <script src="/static/js/bids_sentreceived.js"></script>
+<script src="/static/js/bids_export.js"></script>
 
 {% include 'footer.html' %}