From 9de96c41007b2129cbd2c87950ddfcc0b7369a65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 04:00:19 +0000 Subject: [PATCH 1/4] Initial plan From 3d922abdd55adc518f7b6d38a75f7341617948d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 04:04:40 +0000 Subject: [PATCH 2/4] Implement two-level cache for Bitcoin and EVM addresses - Change cache structure from Map to Map> - Addresses now persist across network switches - Only clear display state on network change, not the entire cache - Check cache before re-deriving addresses to avoid redundant work - Improves efficiency for users switching between networks Co-authored-by: Corey-Code <37006206+Corey-Code@users.noreply.github.com> --- src/popup/pages/Dashboard.tsx | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/popup/pages/Dashboard.tsx b/src/popup/pages/Dashboard.tsx index caa136b..e3af3d7 100644 --- a/src/popup/pages/Dashboard.tsx +++ b/src/popup/pages/Dashboard.tsx @@ -108,10 +108,10 @@ const Dashboard: React.FC = ({ const [addingLoading, setAddingLoading] = useState(false); const [bitcoinAddress, setBitcoinAddress] = useState(''); const [loadingBtcAddress, setLoadingBtcAddress] = useState(false); - // Cache of Bitcoin addresses for all accounts: cosmosAddress -> address - const [bitcoinAddressCache, setBitcoinAddressCache] = useState>(new Map()); - // Cache of EVM addresses for all accounts: cosmosAddress -> address - const [evmAddressCache, setEvmAddressCache] = useState>(new Map()); + // Cache of Bitcoin addresses for all accounts: cosmosAddress -> networkId -> address + const [bitcoinAddressCache, setBitcoinAddressCache] = useState>>(new Map()); + // Cache of EVM addresses for all accounts: cosmosAddress -> networkId -> address + const [evmAddressCache, setEvmAddressCache] = useState>>(new Map()); // Network tab: 0 = All, 1 = Cosmos, 2 = UTXO, 3 = EVM const [networkTab, setNetworkTab] = useState(0); @@ -175,16 +175,14 @@ const Dashboard: React.FC = ({ const [evmAddress, setEvmAddress] = useState(''); const [loadingEvmAddress, setLoadingEvmAddress] = useState(false); - // Clear Bitcoin address immediately when chain changes to prevent stale address usage + // Clear Bitcoin address display immediately when chain changes to prevent stale address display useEffect(() => { setBitcoinAddress(''); - setBitcoinAddressCache(new Map()); }, [selectedChainId]); - // Clear EVM address immediately when chain changes + // Clear EVM address display immediately when chain changes useEffect(() => { setEvmAddress(''); - setEvmAddressCache(new Map()); }, [selectedChainId]); // Derive Bitcoin address when Bitcoin network is selected @@ -227,9 +225,15 @@ const Dashboard: React.FC = ({ useEffect(() => { if (isBitcoinSelected && accounts.length > 0) { const deriveAllBitcoinAddresses = async () => { - const newCache = new Map(); + const newCache = new Map(bitcoinAddressCache); // Start with existing cache for (const account of accounts) { try { + // Check if address is already cached for this account and network + const accountCache = newCache.get(account.address); + if (accountCache?.has(selectedChainId)) { + continue; // Skip if already cached + } + // Pass cosmosAddress to identify imported accounts const addr = await getBitcoinAddress( selectedChainId, @@ -237,7 +241,10 @@ const Dashboard: React.FC = ({ account.address ); if (addr) { - newCache.set(account.address, addr); + // Create or update the account's network cache + const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); + updatedAccountCache.set(selectedChainId, addr); + newCache.set(account.address, updatedAccountCache); } } catch (err) { console.error(`Failed to derive Bitcoin address for account ${account.address}:`, err); @@ -246,18 +253,22 @@ const Dashboard: React.FC = ({ setBitcoinAddressCache(newCache); }; deriveAllBitcoinAddresses(); - } else { - setBitcoinAddressCache(new Map()); } - }, [isBitcoinSelected, selectedChainId, accounts, getBitcoinAddress]); + }, [isBitcoinSelected, selectedChainId, accounts, getBitcoinAddress, bitcoinAddressCache]); // Derive EVM addresses for all accounts when EVM network is selected useEffect(() => { if (isEvmSelected && accounts.length > 0) { const deriveAllEvmAddresses = async () => { - const newCache = new Map(); + const newCache = new Map(evmAddressCache); // Start with existing cache for (const account of accounts) { try { + // Check if address is already cached for this account and network + const accountCache = newCache.get(account.address); + if (accountCache?.has(selectedChainId)) { + continue; // Skip if already cached + } + // Pass cosmosAddress to identify imported accounts const addr = await getEvmAddress( selectedChainId, @@ -265,7 +276,10 @@ const Dashboard: React.FC = ({ account.address ); if (addr) { - newCache.set(account.address, addr); + // Create or update the account's network cache + const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); + updatedAccountCache.set(selectedChainId, addr); + newCache.set(account.address, updatedAccountCache); } } catch (err) { console.error(`Failed to derive EVM address for account ${account.address}:`, err); @@ -274,10 +288,8 @@ const Dashboard: React.FC = ({ setEvmAddressCache(newCache); }; deriveAllEvmAddresses(); - } else { - setEvmAddressCache(new Map()); } - }, [isEvmSelected, selectedChainId, accounts, getEvmAddress]); + }, [isEvmSelected, selectedChainId, accounts, getEvmAddress, evmAddressCache]); // Token config with colors and mock prices // Load chain assets from registry when chain changes @@ -361,13 +373,15 @@ const Dashboard: React.FC = ({ const getAccountChainAddress = (account: { address: string; accountIndex?: number }) => { // For Bitcoin, use cached derived address (keyed by cosmos address) if (isBitcoinSelected) { - const cachedAddr = bitcoinAddressCache.get(account.address); + const accountCache = bitcoinAddressCache.get(account.address); + const cachedAddr = accountCache?.get(selectedChainId); return cachedAddr || 'Deriving...'; } // For EVM, use cached derived address (keyed by cosmos address) if (isEvmSelected) { - const cachedAddr = evmAddressCache.get(account.address); + const accountCache = evmAddressCache.get(account.address); + const cachedAddr = accountCache?.get(selectedChainId); return cachedAddr || 'Deriving...'; } From 2d3e4daaf445c04290e651984b64fc98c0c3b765 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 04:06:43 +0000 Subject: [PATCH 3/4] Fix infinite loop in cache dependency arrays - Remove cache state from useEffect dependencies to prevent infinite loops - Use functional state updates to access previous cache state - Separate cache checking and updating into distinct phases - Maintains cache persistence while avoiding re-render cycles Co-authored-by: Corey-Code <37006206+Corey-Code@users.noreply.github.com> --- src/popup/pages/Dashboard.tsx | 136 ++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/src/popup/pages/Dashboard.tsx b/src/popup/pages/Dashboard.tsx index e3af3d7..c35987f 100644 --- a/src/popup/pages/Dashboard.tsx +++ b/src/popup/pages/Dashboard.tsx @@ -225,71 +225,115 @@ const Dashboard: React.FC = ({ useEffect(() => { if (isBitcoinSelected && accounts.length > 0) { const deriveAllBitcoinAddresses = async () => { - const newCache = new Map(bitcoinAddressCache); // Start with existing cache - for (const account of accounts) { - try { - // Check if address is already cached for this account and network - const accountCache = newCache.get(account.address); - if (accountCache?.has(selectedChainId)) { - continue; // Skip if already cached + // Collect addresses to derive + const addressesToDerive: Array<{ account: typeof accounts[0]; cosmosAddress: string }> = []; + + // Check current cache state synchronously + setBitcoinAddressCache((prevCache) => { + for (const account of accounts) { + const accountCache = prevCache.get(account.address); + if (!accountCache?.has(selectedChainId)) { + // Mark this address for derivation + addressesToDerive.push({ account, cosmosAddress: account.address }); } - - // Pass cosmosAddress to identify imported accounts - const addr = await getBitcoinAddress( - selectedChainId, - account.accountIndex, - account.address - ); - if (addr) { - // Create or update the account's network cache - const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); - updatedAccountCache.set(selectedChainId, addr); - newCache.set(account.address, updatedAccountCache); + } + return prevCache; // No changes yet + }); + + // Derive missing addresses + if (addressesToDerive.length > 0) { + const derivedAddresses = new Map(); + for (const { account, cosmosAddress } of addressesToDerive) { + try { + // Pass cosmosAddress to identify imported accounts + const addr = await getBitcoinAddress( + selectedChainId, + account.accountIndex, + cosmosAddress + ); + if (addr) { + derivedAddresses.set(cosmosAddress, addr); + } + } catch (err) { + console.error(`Failed to derive Bitcoin address for account ${cosmosAddress}:`, err); } - } catch (err) { - console.error(`Failed to derive Bitcoin address for account ${account.address}:`, err); + } + + // Update cache with newly derived addresses + if (derivedAddresses.size > 0) { + setBitcoinAddressCache((prevCache) => { + const newCache = new Map(prevCache); + for (const [cosmosAddress, address] of derivedAddresses) { + const accountCache = newCache.get(cosmosAddress); + const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); + updatedAccountCache.set(selectedChainId, address); + newCache.set(cosmosAddress, updatedAccountCache); + } + return newCache; + }); } } - setBitcoinAddressCache(newCache); }; deriveAllBitcoinAddresses(); } - }, [isBitcoinSelected, selectedChainId, accounts, getBitcoinAddress, bitcoinAddressCache]); + }, [isBitcoinSelected, selectedChainId, accounts, getBitcoinAddress]); // Derive EVM addresses for all accounts when EVM network is selected useEffect(() => { if (isEvmSelected && accounts.length > 0) { const deriveAllEvmAddresses = async () => { - const newCache = new Map(evmAddressCache); // Start with existing cache - for (const account of accounts) { - try { - // Check if address is already cached for this account and network - const accountCache = newCache.get(account.address); - if (accountCache?.has(selectedChainId)) { - continue; // Skip if already cached + // Collect addresses to derive + const addressesToDerive: Array<{ account: typeof accounts[0]; cosmosAddress: string }> = []; + + // Check current cache state synchronously + setEvmAddressCache((prevCache) => { + for (const account of accounts) { + const accountCache = prevCache.get(account.address); + if (!accountCache?.has(selectedChainId)) { + // Mark this address for derivation + addressesToDerive.push({ account, cosmosAddress: account.address }); } - - // Pass cosmosAddress to identify imported accounts - const addr = await getEvmAddress( - selectedChainId, - account.accountIndex, - account.address - ); - if (addr) { - // Create or update the account's network cache - const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); - updatedAccountCache.set(selectedChainId, addr); - newCache.set(account.address, updatedAccountCache); + } + return prevCache; // No changes yet + }); + + // Derive missing addresses + if (addressesToDerive.length > 0) { + const derivedAddresses = new Map(); + for (const { account, cosmosAddress } of addressesToDerive) { + try { + // Pass cosmosAddress to identify imported accounts + const addr = await getEvmAddress( + selectedChainId, + account.accountIndex, + cosmosAddress + ); + if (addr) { + derivedAddresses.set(cosmosAddress, addr); + } + } catch (err) { + console.error(`Failed to derive EVM address for account ${cosmosAddress}:`, err); } - } catch (err) { - console.error(`Failed to derive EVM address for account ${account.address}:`, err); + } + + // Update cache with newly derived addresses + if (derivedAddresses.size > 0) { + setEvmAddressCache((prevCache) => { + const newCache = new Map(prevCache); + for (const [cosmosAddress, address] of derivedAddresses) { + const accountCache = newCache.get(cosmosAddress); + const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); + updatedAccountCache.set(selectedChainId, address); + newCache.set(cosmosAddress, updatedAccountCache); + } + return newCache; + }); } } - setEvmAddressCache(newCache); }; deriveAllEvmAddresses(); } - }, [isEvmSelected, selectedChainId, accounts, getEvmAddress, evmAddressCache]); + }, [isEvmSelected, selectedChainId, accounts, getEvmAddress]); // Token config with colors and mock prices // Load chain assets from registry when chain changes From b76bb01574747c95fdbaad9f8e0d2f499f8f1613 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 04:08:53 +0000 Subject: [PATCH 4/4] Refactor cache to use useRef instead of setState for reads - Replace useState cache with useRef to avoid anti-pattern - Use trigger state to force re-renders when cache updates - Read directly from ref without needing setState callbacks - Cleaner code that follows React best practices Co-authored-by: Corey-Code <37006206+Corey-Code@users.noreply.github.com> --- src/popup/pages/Dashboard.tsx | 85 ++++++++++++++++------------------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/src/popup/pages/Dashboard.tsx b/src/popup/pages/Dashboard.tsx index c35987f..0759c6d 100644 --- a/src/popup/pages/Dashboard.tsx +++ b/src/popup/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { Box, VStack, @@ -109,9 +109,12 @@ const Dashboard: React.FC = ({ const [bitcoinAddress, setBitcoinAddress] = useState(''); const [loadingBtcAddress, setLoadingBtcAddress] = useState(false); // Cache of Bitcoin addresses for all accounts: cosmosAddress -> networkId -> address - const [bitcoinAddressCache, setBitcoinAddressCache] = useState>>(new Map()); + // Using refs to avoid including in dependencies while maintaining cache across renders + const bitcoinAddressCacheRef = useRef>>(new Map()); + const [, setBitcoinAddressCacheTrigger] = useState(0); // Trigger re-render when cache updates // Cache of EVM addresses for all accounts: cosmosAddress -> networkId -> address - const [evmAddressCache, setEvmAddressCache] = useState>>(new Map()); + const evmAddressCacheRef = useRef>>(new Map()); + const [, setEvmAddressCacheTrigger] = useState(0); // Trigger re-render when cache updates // Network tab: 0 = All, 1 = Cosmos, 2 = UTXO, 3 = EVM const [networkTab, setNetworkTab] = useState(0); @@ -225,20 +228,16 @@ const Dashboard: React.FC = ({ useEffect(() => { if (isBitcoinSelected && accounts.length > 0) { const deriveAllBitcoinAddresses = async () => { - // Collect addresses to derive + // Check current cache and collect addresses to derive const addressesToDerive: Array<{ account: typeof accounts[0]; cosmosAddress: string }> = []; - // Check current cache state synchronously - setBitcoinAddressCache((prevCache) => { - for (const account of accounts) { - const accountCache = prevCache.get(account.address); - if (!accountCache?.has(selectedChainId)) { - // Mark this address for derivation - addressesToDerive.push({ account, cosmosAddress: account.address }); - } + for (const account of accounts) { + const accountCache = bitcoinAddressCacheRef.current.get(account.address); + if (!accountCache?.has(selectedChainId)) { + // Mark this address for derivation + addressesToDerive.push({ account, cosmosAddress: account.address }); } - return prevCache; // No changes yet - }); + } // Derive missing addresses if (addressesToDerive.length > 0) { @@ -261,16 +260,14 @@ const Dashboard: React.FC = ({ // Update cache with newly derived addresses if (derivedAddresses.size > 0) { - setBitcoinAddressCache((prevCache) => { - const newCache = new Map(prevCache); - for (const [cosmosAddress, address] of derivedAddresses) { - const accountCache = newCache.get(cosmosAddress); - const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); - updatedAccountCache.set(selectedChainId, address); - newCache.set(cosmosAddress, updatedAccountCache); - } - return newCache; - }); + for (const [cosmosAddress, address] of derivedAddresses) { + const accountCache = bitcoinAddressCacheRef.current.get(cosmosAddress); + const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); + updatedAccountCache.set(selectedChainId, address); + bitcoinAddressCacheRef.current.set(cosmosAddress, updatedAccountCache); + } + // Trigger re-render to update UI + setBitcoinAddressCacheTrigger((prev) => prev + 1); } } }; @@ -282,20 +279,16 @@ const Dashboard: React.FC = ({ useEffect(() => { if (isEvmSelected && accounts.length > 0) { const deriveAllEvmAddresses = async () => { - // Collect addresses to derive + // Check current cache and collect addresses to derive const addressesToDerive: Array<{ account: typeof accounts[0]; cosmosAddress: string }> = []; - // Check current cache state synchronously - setEvmAddressCache((prevCache) => { - for (const account of accounts) { - const accountCache = prevCache.get(account.address); - if (!accountCache?.has(selectedChainId)) { - // Mark this address for derivation - addressesToDerive.push({ account, cosmosAddress: account.address }); - } + for (const account of accounts) { + const accountCache = evmAddressCacheRef.current.get(account.address); + if (!accountCache?.has(selectedChainId)) { + // Mark this address for derivation + addressesToDerive.push({ account, cosmosAddress: account.address }); } - return prevCache; // No changes yet - }); + } // Derive missing addresses if (addressesToDerive.length > 0) { @@ -318,16 +311,14 @@ const Dashboard: React.FC = ({ // Update cache with newly derived addresses if (derivedAddresses.size > 0) { - setEvmAddressCache((prevCache) => { - const newCache = new Map(prevCache); - for (const [cosmosAddress, address] of derivedAddresses) { - const accountCache = newCache.get(cosmosAddress); - const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); - updatedAccountCache.set(selectedChainId, address); - newCache.set(cosmosAddress, updatedAccountCache); - } - return newCache; - }); + for (const [cosmosAddress, address] of derivedAddresses) { + const accountCache = evmAddressCacheRef.current.get(cosmosAddress); + const updatedAccountCache = accountCache ? new Map(accountCache) : new Map(); + updatedAccountCache.set(selectedChainId, address); + evmAddressCacheRef.current.set(cosmosAddress, updatedAccountCache); + } + // Trigger re-render to update UI + setEvmAddressCacheTrigger((prev) => prev + 1); } } }; @@ -417,14 +408,14 @@ const Dashboard: React.FC = ({ const getAccountChainAddress = (account: { address: string; accountIndex?: number }) => { // For Bitcoin, use cached derived address (keyed by cosmos address) if (isBitcoinSelected) { - const accountCache = bitcoinAddressCache.get(account.address); + const accountCache = bitcoinAddressCacheRef.current.get(account.address); const cachedAddr = accountCache?.get(selectedChainId); return cachedAddr || 'Deriving...'; } // For EVM, use cached derived address (keyed by cosmos address) if (isEvmSelected) { - const accountCache = evmAddressCache.get(account.address); + const accountCache = evmAddressCacheRef.current.get(account.address); const cachedAddr = accountCache?.get(selectedChainId); return cachedAddr || 'Deriving...'; }