diff --git a/app/[addressOrDomain]/page.tsx b/app/[addressOrDomain]/page.tsx index a41c9386..774f91b2 100644 --- a/app/[addressOrDomain]/page.tsx +++ b/app/[addressOrDomain]/page.tsx @@ -55,6 +55,14 @@ type DebtStatus = { }[]; }; +// Protocols to exclude from asset percentages +const EXCLUDED_PROTOCOL_IDS = [ + '8e4c3af5-f9bc-4646-9b66-3cd7bf7c8aec', // zkLend + '19a6474c-17a9-4d61-95ab-5f768cd01485', // Nimbora + '6203cd68-991a-4bae-a18a-ff42207ce813', // Vesu + // TODO: Add more protocols or add better exclusion logic (ban all protocols?) +]; + export default function Page({ params }: AddressOrDomainProps) { const router = useRouter(); const addressOrDomain = params.addressOrDomain; @@ -189,18 +197,92 @@ export default function Page({ params }: AddressOrDomainProps) { setQuestsLoading(false); }, []); - const fetchPortfolioAssets = useCallback(async (addr: string) => { + const calculateAssetPercentages = async (userTokens: ArgentUserToken[], tokens: ArgentTokenMap, dapps: ArgentDappMap) => { + let totalValue = 0; + const assetValues: { [symbol: string]: number } = {}; - // TODO: Implement fetch from Argent API - const assets = [ - { color: "#1E2097", itemLabel: "USDC", itemValue: "46.68", itemValueSymbol: "%" }, - { color: "#637DEB", itemLabel: "USDT", itemValue: "27.94", itemValueSymbol: "%" }, - { color: "#2775CA", itemLabel: "STRK", itemValue: "22.78", itemValueSymbol: "%" }, - { color: "#5CE3FE", itemLabel: "ETH", itemValue: "0.36", itemValueSymbol: "%" }, - { color: "#F4FAFF", itemLabel: "Others", itemValue: "2.36", itemValueSymbol: "%" }, - ]; - setPortfolioAssets(assets); + // First pass: calculate total value and individual token values + for await (const token of userTokens) { + const tokenInfo = tokens[token.tokenAddress]; + if (!tokenInfo || token.tokenBalance === "0") continue; + + console.log(tokenInfo); + + if (tokenInfo.dappId) { + console.log('Dapp info for token', tokenInfo.name, { + dappName: dapps[tokenInfo.dappId]?.name, + dappId: tokenInfo.dappId, + dappDetails: dapps[tokenInfo.dappId] + }); + } + + const value = await calculateTokenPrice( + token.tokenAddress, + tokenToDecimal(token.tokenBalance, tokenInfo.decimals), + "USD" + ); + // Add to total value regardless of protocol + totalValue += value; + + // Only add to asset breakdown if not from excluded protocol + if (!tokenInfo.dappId || !EXCLUDED_PROTOCOL_IDS.includes(tokenInfo.dappId)) { + const symbol = tokenInfo.symbol || "Unknown"; + assetValues[symbol] = (assetValues[symbol] || 0) + value; + } + } + + // Convert to percentages and format + const sortedAssets = Object.entries(assetValues) + .sort(([, a], [, b]) => b - a) + .map(([symbol, value]) => ({ + itemLabel: symbol, + itemValue: ((value / totalValue) * 100).toFixed(2), + itemValueSymbol: "%", + color: "" // Colors will be assigned later + })); + + console.log(sortedAssets.slice()); + + // Handle "Others" category if needed + if (sortedAssets.length > 4) { + const others = sortedAssets.slice(4).reduce( + (sum, asset) => sum + parseFloat(asset.itemValue), + 0 + ); + sortedAssets.splice(4); + sortedAssets.push({ + itemLabel: "Others", + itemValue: others.toFixed(2), + itemValueSymbol: "%", + color: "" + }); + } + + // Assign colors + const colors = ["#1E2097", "#637DEB", "#2775CA", "#5CE3FE", "#F4FAFF"]; + sortedAssets.forEach((asset, index) => { + asset.color = colors[index]; + }); + + return sortedAssets; + }; + + const fetchPortfolioAssets = useCallback(async (data: { + dapps: ArgentDappMap, + tokens: ArgentTokenMap, + userTokens: ArgentUserToken[], + userDapps: ArgentUserDapp[] + }) => { + const { dapps, tokens, userTokens, userDapps } = data; + try { + if (!tokens || !userTokens) return; + const assets = await calculateAssetPercentages(userTokens, tokens, dapps); + setPortfolioAssets(assets); + } catch (error) { + showNotification("Error while fetching portfolio assets", "error"); + console.log("Error while fetching portfolio assets", error); + } }, []); const userHasDebt = (userDapps: ArgentUserDapp[]) => { @@ -314,25 +396,15 @@ export default function Page({ params }: AddressOrDomainProps) { }); } - const fetchPortfolioProtocols = useCallback(async (addr: string) => { - let dapps: ArgentDappMap = {}; - let tokens: ArgentTokenMap = {}; - let userTokens: ArgentUserToken[] = []; - let userDapps: ArgentUserDapp[] = []; - - setLoadingProtocols(true); - try { - [dapps, tokens, userTokens, userDapps] = await Promise.all([ - fetchDapps(), - fetchTokens(), - fetchUserTokens(addr), - fetchUserDapps(addr) - ]); - } catch (error) { - showNotification("Error while fetching address portfolio", "error"); - console.log("Error while fetching address portfolio", error); - } + const fetchPortfolioProtocols = useCallback(async (data: { + dapps: ArgentDappMap, + tokens: ArgentTokenMap, + userTokens: ArgentUserToken[], + userDapps: ArgentUserDapp[] + }) => { + const { dapps, tokens, userTokens, userDapps } = data; + // TODO correct this if (!dapps || !tokens || (!userTokens && !userDapps)) return; let protocolsMap: ChartItemMap = {}; @@ -350,16 +422,44 @@ export default function Page({ params }: AddressOrDomainProps) { showNotification("Error while calculating address portfolio stats", "error"); console.log("Error while calculating address portfolio stats", error); } + }, [address]); - setLoadingProtocols(false); - }, []); + const fetchPortfolioData = useCallback(async (addr: string) => { + setLoadingProtocols(true); + try { + const [dappsData, tokensData, userTokensData, userDappsData] = + await Promise.all([ + fetchDapps(), + fetchTokens(), + fetchUserTokens(addr), + fetchUserDapps(addr), + ]); + + const data = { + dapps: dappsData, + tokens: tokensData, + userTokens: userTokensData, + userDapps: userDappsData, + }; + + await Promise.all([ + fetchPortfolioProtocols(data), + fetchPortfolioAssets(data), + ]); + } catch (error) { + showNotification("Error while fetching address portfolio", "error"); + console.log("Error while fetching address portfolio", error); + } finally { + setLoadingProtocols(false); + } + }, [fetchPortfolioProtocols, fetchPortfolioAssets]); useEffect(() => { if (!identity) return; + console.log("Fetching data for identity", identity.owner); fetchQuestData(identity.owner); fetchPageData(identity.owner); - fetchPortfolioAssets(identity.owner); - fetchPortfolioProtocols(identity.owner); + fetchPortfolioData(identity.owner); }, [identity]); useEffect(() => setNotFound(false), [dynamicRoute]);