From 3e0acea4656c69f30ce99e9f81142a803ab70592 Mon Sep 17 00:00:00 2001 From: Yayvan <105392699+batata-dev-in@users.noreply.github.com> Date: Sun, 23 Nov 2025 03:12:36 +0300 Subject: [PATCH] Update app.js --- app.js | 299 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 167 insertions(+), 132 deletions(-) diff --git a/app.js b/app.js index 9b72486..97925a4 100644 --- a/app.js +++ b/app.js @@ -1,32 +1,45 @@ -const protocolData = window.protocolData; +/** + * Optimized Compound Protocol Data Fetcher + * * Improvements: + * 1. Removed Array.prototype pollution (replaced with modern async loops). + * 2. Instantiated Web3 once per network instead of per token. + * 3. Batched DOM updates to prevent layout thrashing. + * 4. Improved error handling and configuration management. + */ + +// ---------------------------------------------------------------------------- +// Configuration & Constants +// ---------------------------------------------------------------------------- + +const CONFIG = { + infuraId: '7db01e82204d4e789e22cf8e4f640ebe', // Consider moving to env variable + endpoints: { + cToken: 'https://api.compound.finance/api/v2/ctoken' + } +}; + +// Dependency check +if (!window.protocolData || !window.erc20cTokenAbi || !window.comptrollerAbi) { + console.error("Missing required global data (protocolData or ABIs)."); +} + +const protocolData = window.protocolData || {}; const erc20cTokenAbi = window.erc20cTokenAbi; const comptrollerAbi = window.comptrollerAbi; const networks = Object.keys(protocolData); -const cTokenEndpoint = 'https://api.compound.finance/api/v2/ctoken'; -const infuraApiKey = '7db01e82204d4e789e22cf8e4f640ebe' -let web3; -const numbFormat = new Intl.NumberFormat('en-US', { +// Initialize Intl formatter once +const currencyFormatter = new Intl.NumberFormat('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 18 -}).format; - -Array.prototype.forEachWithCallback = function(callback, final) { - const arrayCopy = JSON.parse(JSON.stringify(this)); - let index = -1; - const next = () => { - index++; - if (arrayCopy.length > 0) { - callback(arrayCopy.shift(), index, next); - } else { - if (final) final(); - } - } - next(); -} +}); + +// ---------------------------------------------------------------------------- +// Templates +// ---------------------------------------------------------------------------- const tableTemplate = Handlebars.compile(` -
+

{{ name }}

@@ -111,127 +124,149 @@ const networkTemplate = Handlebars.compile(`
-
+
`); -const assetTemplate = Handlebars.compile(` -
-`); +// ---------------------------------------------------------------------------- +// Core Logic +// ---------------------------------------------------------------------------- -const scrollTop = document.getElementById('scroll-top'); -scrollTop.onclick = () => { window.scrollTo(0, 0) }; +// Setup Scroll Handler +const scrollTopBtn = document.getElementById('scroll-top'); +if (scrollTopBtn) { + scrollTopBtn.addEventListener('click', () => window.scrollTo(0, 0)); +} window.addEventListener('load', async () => { const navigation = document.getElementById('navigation'); const networksContainer = document.getElementById('networks-container'); - let networksHtml = ''; - networks.forEach((net) => { - networksHtml += networkTemplate(net); - }); - networksContainer.innerHTML = networksHtml; - - networks.forEach((net) => { - const networkContainer = document.getElementById(net); - const loadingElement = networkContainer.children[0]; - const tablesContainer = networkContainer.children[1]; - - let tablesHtml = ''; - protocolData[net].cTokens.forEach((cToken, i) => { - const symbol = cToken.symbol; - tablesHtml += assetTemplate(`${net}-${symbol}`); - }); - - tablesContainer.innerHTML = tablesHtml; - - protocolData[net].cTokens.forEachWithCallback(async (cToken, index, done) => { - const comptrollerAddr = protocolData[net].comptroller; - const cTokenAddr = cToken.token_address; - const symbol = cToken.symbol; - try { - const data = await getCTokenData(net, cTokenAddr, comptrollerAddr, symbol); - data.name = cToken.name; - loadingElement.classList.add('hidden'); - tablesContainer.children[`${net}-${symbol}`].innerHTML += tableTemplate(data); - - const nav = document.createElement("A"); - nav.href = `#${net}-${symbol}` - nav.innerText = `${net} - ${symbol}` - navigation.appendChild(nav); - navigation.appendChild(document.createElement("BR")); - } catch (e) { - console.error(`${net} ${symbol}:`, e); - } - done(); - }); - }); + + if (!networksContainer) return; + + // 1. Render Network Skeletons + networksContainer.innerHTML = networks.map(net => networkTemplate(net)).join(''); + + // 2. Process each network + for (const net of networks) { + await processNetwork(net, navigation); + } }); -const getCTokenData = (network, cTokenAddr, comptrollerAddr, symbol) => { - return new Promise(async (resolve, reject) => { +async function processNetwork(networkName, navContainer) { + const networkData = protocolData[networkName]; + const networkContainer = document.getElementById(networkName); + const loadingElement = networkContainer.querySelector('.loading-table'); + const tablesContainer = networkContainer.querySelector('.tables-container'); + + // Initialize Web3 ONCE per network + const web3Instance = new Web3(`https://${networkName}.infura.io/v3/${CONFIG.infuraId}`); + const comptrollerContract = new web3Instance.eth.Contract(comptrollerAbi, networkData.comptroller); + + // Create a DocumentFragment to batch DOM updates (Performance boost) + const docFragment = document.createDocumentFragment(); + const navFragment = document.createDocumentFragment(); + + // Use modern for...of loop instead of recursive callbacks + // Allows for sequential execution to avoid rate-limiting, or use Promise.all for parallel + for (const cToken of networkData.cTokens) { + const symbol = cToken.symbol; + const uniqueId = `${networkName}-${symbol}`; + try { - web3 = new Web3(`https://${network}.infura.io/v3/${infuraApiKey}`); - const cToken = new web3.eth.Contract(erc20cTokenAbi, cTokenAddr); - const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddr); - - const exchangeRateCurrent = await cToken.methods.exchangeRateCurrent().call(); - const liquidityPoolTotal = await cToken.methods.getCash().call(); - const totalBorrowsCurrent = await cToken.methods.totalBorrowsCurrent().call(); - const borrowRatePerBlock = await cToken.methods.borrowRatePerBlock().call(); - const totalSupply = await cToken.methods.totalSupply().call(); - const supplyRatePerBlock = await cToken.methods.supplyRatePerBlock().call(); - const totalReserves = await cToken.methods.totalReserves().call(); - const reserveFactor = await cToken.methods.reserveFactorMantissa().call(); - const collateralFactor = await comptroller.methods.markets(cTokenAddr).call(); - const cTokenDecimals = await cToken.methods.decimals().call(); - const cTokenMantissa = parseFloat('1e'+cTokenDecimals); - const underlyingAddress = await cToken.methods.underlying().call(); - - // const closeFactorMantissa = await comptroller.methods.closeFactorMantissa().call(); - // const liquidationIncentiveMantissa = await comptroller.methods.liquidationIncentiveMantissa().call(); - - const result = { - exchangeRateCurrent, - liquidityPoolTotal, - totalBorrowsCurrent, - borrowRatePerBlock, - totalSupply, - supplyRatePerBlock, - totalReserves, - reserveFactor, - collateralFactor, - cTokenDecimals, - underlyingAddress, - textExchangeRate: `1 c${symbol} = ${numbFormat(exchangeRateCurrent / 1e18 / 1e10)} ${symbol}`, - textContractHoldings: `${numbFormat(liquidityPoolTotal / 1e18)} ${symbol}`, - textOpenBorrows: `${numbFormat(totalBorrowsCurrent / 1e18)} ${symbol}`, - textSupplyRate: `${(supplyRatePerBlock / 1e18).toFixed(18)} ${symbol} per ${symbol} supplied`, - textBorrowRate: `${(borrowRatePerBlock / 1e18).toFixed(18)} ${symbol} per ${symbol} borrowed`, - textCTokenCirculation: `${numbFormat(totalSupply / cTokenMantissa)} c${symbol}`, - textReservesSum: `${numbFormat(totalReserves / 1e18)} ${symbol}`, - textReserveFactor: `${reserveFactor / 1e18 * 100}%`, - textCollateralFactor: `${collateralFactor.collateralFactorMantissa / 1e18 * 100}%`, - textcTokenMantissa: cTokenMantissa - } - - console.log(` - ~~ ${network} ~~ ${symbol} ~~ - Current Exchange Rate: ${result.textExchangeRate} - ${symbol} in contract: ${result.textContractHoldings} - Open Borrows Sum: ${result.textOpenBorrows} - Supply Rate / Block: ${result.textSupplyRate} - Borrow Rate / Block: ${result.textBorrowRate} - c${symbol} in circulation: ${result.textCTokenCirculation} - Reserves: ${result.textReservesSum} - Reserve Factor: ${result.textReserveFactor} - Collateral Factor: ${result.textCollateralFactor} - cToken Mantissa: ${cTokenMantissa} - Underlying Address: ${underlyingAddress} - `); - - resolve(result); + // Create placeholder in container if needed, or just append result later + const data = await fetchCTokenData( + web3Instance, + cToken.token_address, + comptrollerContract, + symbol + ); + + data.name = cToken.name; + data.uniqueId = uniqueId; + + // Create temporary container for HTML string + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = tableTemplate(data); + docFragment.appendChild(tempDiv); + + // Update Navigation + const navLink = document.createElement("a"); + navLink.href = `#${uniqueId}`; + navLink.innerText = `${networkName} - ${symbol}`; + navFragment.appendChild(navLink); + navFragment.appendChild(document.createElement("br")); + } catch (e) { - reject(e); + console.error(`Failed to fetch data for ${networkName} ${symbol}:`, e); } - }); -}; + } + + // Final DOM Update for this network + loadingElement.classList.add('hidden'); + tablesContainer.appendChild(docFragment); + if (navContainer) { + navContainer.appendChild(navFragment); + } +} + +async function fetchCTokenData(web3, cTokenAddr, comptrollerContract, symbol) { + const cTokenContract = new web3.eth.Contract(erc20cTokenAbi, cTokenAddr); + + // Parallel execution of independent read calls + const [ + exchangeRateCurrent, + liquidityPoolTotal, + totalBorrowsCurrent, + borrowRatePerBlock, + totalSupply, + supplyRatePerBlock, + totalReserves, + reserveFactor, + collateralFactor, + cTokenDecimals, + underlyingAddress + ] = await Promise.all([ + cTokenContract.methods.exchangeRateCurrent().call(), + cTokenContract.methods.getCash().call(), + cTokenContract.methods.totalBorrowsCurrent().call(), + cTokenContract.methods.borrowRatePerBlock().call(), + cTokenContract.methods.totalSupply().call(), + cTokenContract.methods.supplyRatePerBlock().call(), + cTokenContract.methods.totalReserves().call(), + cTokenContract.methods.reserveFactorMantissa().call(), + comptrollerContract.methods.markets(cTokenAddr).call(), + cTokenContract.methods.decimals().call(), + cTokenContract.methods.underlying().call().catch(() => 'N/A') // Handle tokens without underlying (e.g. cETH sometimes differs) + ]); + + const cTokenMantissa = parseFloat('1e' + cTokenDecimals); + + // Note: Assuming 18 decimals for underlying in formatted text. + // For production, fetch underlying decimals dynamically. + const standardMantissa = 1e18; + + return { + exchangeRateCurrent, + liquidityPoolTotal, + totalBorrowsCurrent, + borrowRatePerBlock, + totalSupply, + supplyRatePerBlock, + totalReserves, + reserveFactor, + collateralFactor, + cTokenDecimals, + underlyingAddress, + textExchangeRate: `1 c${symbol} = ${currencyFormatter.format(exchangeRateCurrent / standardMantissa / 1e10)} ${symbol}`, + textContractHoldings: `${currencyFormatter.format(liquidityPoolTotal / standardMantissa)} ${symbol}`, + textOpenBorrows: `${currencyFormatter.format(totalBorrowsCurrent / standardMantissa)} ${symbol}`, + textSupplyRate: `${(supplyRatePerBlock / standardMantissa).toFixed(18)} ${symbol} per ${symbol} supplied`, + textBorrowRate: `${(borrowRatePerBlock / standardMantissa).toFixed(18)} ${symbol} per ${symbol} borrowed`, + textCTokenCirculation: `${currencyFormatter.format(totalSupply / cTokenMantissa)} c${symbol}`, + textReservesSum: `${currencyFormatter.format(totalReserves / standardMantissa)} ${symbol}`, + textReserveFactor: `${reserveFactor / standardMantissa * 100}%`, + textCollateralFactor: `${collateralFactor.collateralFactorMantissa / standardMantissa * 100}%`, + textcTokenMantissa: cTokenMantissa + }; +}