Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fetch data for "Portfolio by asset type" donut chart #920

Draft
wants to merge 3 commits into
base: testnet
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 132 additions & 32 deletions app/[addressOrDomain]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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[]) => {
Expand Down Expand Up @@ -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 = {};

Expand All @@ -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]);
Expand Down