From 67229ab7c42577ae01bf4ed775b86c1633cfe088 Mon Sep 17 00:00:00 2001 From: Konstantin Fastov Date: Thu, 31 Oct 2024 15:54:18 +0700 Subject: [PATCH 1/3] feat: add draft asset portfolio implementation --- app/[addressOrDomain]/page.tsx | 107 ++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/app/[addressOrDomain]/page.tsx b/app/[addressOrDomain]/page.tsx index a41c9386..c3d8f3c5 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,99 @@ export default function Page({ params }: AddressOrDomainProps) { setQuestsLoading(false); }, []); + const calculateAssetPercentages = async (userTokens: ArgentUserToken[], tokens: ArgentTokenMap) => { + // First get dapps data + const dapps = await fetchDapps(); + + let totalValue = 0; + const assetValues: { [symbol: string]: number } = {}; + + // 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] + }); + } + + // Skip tokens that belong to staking protocols + if (tokenInfo.dappId && EXCLUDED_PROTOCOL_IDS.includes(tokenInfo.dappId)) { + continue; + } + + const value = await calculateTokenPrice( + token.tokenAddress, + tokenToDecimal(token.tokenBalance, tokenInfo.decimals), + "USD" + ); + + const symbol = tokenInfo.symbol || "Unknown"; + assetValues[symbol] = (assetValues[symbol] || 0) + value; + totalValue += 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 (addr: string) => { + setLoadingProtocols(true); + try { + const [dapps, tokens, userTokens, userDapps] = await Promise.all([ + fetchDapps(), + fetchTokens(), + fetchUserTokens(addr), + fetchUserDapps(addr) + ]); - // 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); + if (!tokens || !userTokens) return; + const assets = await calculateAssetPercentages(userTokens, tokens); + setPortfolioAssets(assets); + } catch (error) { + showNotification("Error while fetching portfolio assets", "error"); + console.log("Error while fetching portfolio assets", error); + } + setLoadingProtocols(false); }, []); const userHasDebt = (userDapps: ArgentUserDapp[]) => { From 77da829dde7ac50c3e49143357eb3fa64be16091 Mon Sep 17 00:00:00 2001 From: Konstantin Fastov Date: Thu, 31 Oct 2024 17:58:04 +0700 Subject: [PATCH 2/3] refactor: fetch portfolio data once for both charts --- app/[addressOrDomain]/page.tsx | 87 +++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/app/[addressOrDomain]/page.tsx b/app/[addressOrDomain]/page.tsx index c3d8f3c5..974ebe82 100644 --- a/app/[addressOrDomain]/page.tsx +++ b/app/[addressOrDomain]/page.tsx @@ -197,10 +197,7 @@ export default function Page({ params }: AddressOrDomainProps) { setQuestsLoading(false); }, []); - const calculateAssetPercentages = async (userTokens: ArgentUserToken[], tokens: ArgentTokenMap) => { - // First get dapps data - const dapps = await fetchDapps(); - + const calculateAssetPercentages = async (userTokens: ArgentUserToken[], tokens: ArgentTokenMap, dapps: ArgentDappMap) => { let totalValue = 0; const assetValues: { [symbol: string]: number } = {}; @@ -271,25 +268,21 @@ export default function Page({ params }: AddressOrDomainProps) { return sortedAssets; }; - const fetchPortfolioAssets = useCallback(async (addr: string) => { - setLoadingProtocols(true); + const fetchPortfolioAssets = useCallback(async (data: { + dapps: ArgentDappMap, + tokens: ArgentTokenMap, + userTokens: ArgentUserToken[], + userDapps: ArgentUserDapp[] + }) => { + const { dapps, tokens, userTokens, userDapps } = data; try { - const [dapps, tokens, userTokens, userDapps] = await Promise.all([ - fetchDapps(), - fetchTokens(), - fetchUserTokens(addr), - fetchUserDapps(addr) - ]); - if (!tokens || !userTokens) return; - - const assets = await calculateAssetPercentages(userTokens, tokens); + 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); } - setLoadingProtocols(false); }, []); const userHasDebt = (userDapps: ArgentUserDapp[]) => { @@ -403,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 = {}; @@ -439,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]); From d189ee79e22ec7c20518dfb878556b26fbacf821 Mon Sep 17 00:00:00 2001 From: Konstantin Fastov Date: Thu, 31 Oct 2024 21:03:54 +0700 Subject: [PATCH 3/3] fix: better percentage in portfolio assets chart --- app/[addressOrDomain]/page.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/[addressOrDomain]/page.tsx b/app/[addressOrDomain]/page.tsx index 974ebe82..774f91b2 100644 --- a/app/[addressOrDomain]/page.tsx +++ b/app/[addressOrDomain]/page.tsx @@ -216,20 +216,20 @@ export default function Page({ params }: AddressOrDomainProps) { }); } - // Skip tokens that belong to staking protocols - if (tokenInfo.dappId && EXCLUDED_PROTOCOL_IDS.includes(tokenInfo.dappId)) { - continue; - } - const value = await calculateTokenPrice( token.tokenAddress, tokenToDecimal(token.tokenBalance, tokenInfo.decimals), "USD" ); - const symbol = tokenInfo.symbol || "Unknown"; - assetValues[symbol] = (assetValues[symbol] || 0) + value; + // 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