diff --git a/src/components/AddressCheck.tsx b/src/components/AddressCheck.tsx index b77f33c84..08c56dba7 100644 --- a/src/components/AddressCheck.tsx +++ b/src/components/AddressCheck.tsx @@ -1,20 +1,14 @@ -import React, { useContext, useEffect, useState } from 'react' +import React, { useContext } from 'react' import { ContractAddressContext } from '../context/ContractAddressesContext' import styles from '../css/AddressCheck.module.css' function AddressCheck() { const data = useContext(ContractAddressContext) - const [loading, setLoading] = useState(true) const checks = data.checks const failedChecks = data.checks.failedChecks const allChecksPassed = data.checks.allChecksPassed - useEffect(() => { - if (checks && Object.keys(checks).length > 0) { - // Check if checks object isn't empty - setLoading(false) - } - }, [checks]) + const loading = !checks || Object.keys(checks).length === 0 if (loading) { return ( @@ -29,8 +23,6 @@ function AddressCheck() { ) } - // allChecksPassed = false // only for testing!! - // Log failed checks to the console if (!allChecksPassed) { console.log('Some checks failed:', failedChecks) diff --git a/src/components/ContractAddress.tsx b/src/components/ContractAddress.tsx index 3d7e479e9..8c9a23795 100644 --- a/src/components/ContractAddress.tsx +++ b/src/components/ContractAddress.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from 'react' +import React, { useContext } from 'react' import { ContractAddressContext } from '../context/ContractAddressesContext' /** @@ -14,19 +14,13 @@ import { ContractAddressContext } from '../context/ContractAddressesContext' */ const ContractAddress = ({ contractName }) => { const data = useContext(ContractAddressContext) - const [loading, setLoading] = useState(true) const addresses = data.addresses + const loading = !data const getNestedProperty = (obj, keys) => { return keys.reduce((acc, key) => acc && acc[key], obj) } - useEffect(() => { - if (data) { - setLoading(false) - } - }, [data]) - if (loading) { return Loading Contract Address... } diff --git a/src/components/StaticContractAddress.tsx b/src/components/StaticContractAddress.tsx index 79c3b5624..58e2acf96 100644 --- a/src/components/StaticContractAddress.tsx +++ b/src/components/StaticContractAddress.tsx @@ -1,10 +1,7 @@ -import React, { useContext, useState, useEffect } from 'react' -import { ContractAddressContext } from '../context/ContractAddressesContext' +import React from 'react' import * as constants from '../ethereum/constants' const ContractAddress = ({ contractName }) => { - const data = useContext(ContractAddressContext) - const getNestedProperty = (obj, keys) => { return keys.reduce((acc, key) => acc && acc[key], obj) } diff --git a/src/context/ContractAddressesContext.tsx b/src/context/ContractAddressesContext.tsx index d16c65f5a..45a5276dd 100644 --- a/src/context/ContractAddressesContext.tsx +++ b/src/context/ContractAddressesContext.tsx @@ -13,7 +13,7 @@ import { ContractAddresses } from '../ethereum/types' export const ContractAddressContext = createContext<{ addresses: ContractAddresses | Record checks: Record -}>({ addresses: {}, checks: {} }) // Modified code: Updated context type +}>({ addresses: {}, checks: {} }) /** * Provides contract addresses to the component tree. @@ -43,17 +43,17 @@ export const ContractAddressProvider = ({ children }) => { useEffect(() => { const fetchAddresses = async () => { try { - let checkFlag: boolean | undefined + let checkFlag: boolean | undefined = true const failedChecks: string[] = [] - checkFlag = true + const topLevelData = await fetchTopLevelAddressesFromENS( publicClient, checkFlag, failedChecks ) - checkFlag = topLevelData?.checkFlag if (!topLevelData) throw new Error('Failed to fetch top-level contract addresses') + checkFlag = topLevelData.checkFlag const protocolPeripheryData = await fetchAndCheckProtocolAddresses( topLevelData.addresses.v3ProtocolAddressProvider, @@ -61,9 +61,9 @@ export const ContractAddressProvider = ({ children }) => { checkFlag, failedChecks ) - checkFlag = protocolPeripheryData?.checkFlag if (!protocolPeripheryData || !protocolPeripheryData?.addresses) throw new Error('Failed to fetch protocol addresses') + checkFlag = protocolPeripheryData.checkFlag const releaseRegistryData = await fetchAndCheckFromReleaseRegistry( topLevelData.addresses.v3ReleaseRegistry, @@ -71,9 +71,9 @@ export const ContractAddressProvider = ({ children }) => { checkFlag, failedChecks ) - checkFlag = releaseRegistryData?.checkFlag if (!releaseRegistryData) throw new Error('Failed to fetch release registry addresses') + checkFlag = releaseRegistryData?.checkFlag const yearnV3Data = await fetchAndCheckYearnV3Addresses( topLevelData.addresses.v3RoleManager, @@ -81,8 +81,8 @@ export const ContractAddressProvider = ({ children }) => { checkFlag, failedChecks ) - checkFlag = yearnV3Data?.checkFlag if (!yearnV3Data) throw new Error('Failed to fetch Yearn V3 addresses') + checkFlag = yearnV3Data.checkFlag const addressesData: ContractAddresses = { topLevel: topLevelData.addresses, diff --git a/src/context/ContractDataContext.tsx b/src/context/ContractDataContext.tsx index b083cc6d6..77a635e35 100644 --- a/src/context/ContractDataContext.tsx +++ b/src/context/ContractDataContext.tsx @@ -1,16 +1,20 @@ -// src/context/ContractDataContext.tsx -import React, { createContext, useState, useEffect, useContext } from 'react' +import React, { + createContext, + useState, + useEffect, + useContext, + useMemo, +} from 'react' import { PublicClientContext } from './PublicClientContext' import * as ABIs from '../ethereum/ABIs' -import { createPublicClient, getContract, http, getAddress } from 'viem' -import { mainnet } from 'viem/chains' +import { getAddress, getContract } from 'viem' -interface MethodWithArgs { +type MethodWithArgs = { name: string args: string[] } -interface ContractReadData { +type ContractReadData = { name: string chain: string address: string @@ -39,69 +43,102 @@ const isContractReadData = (obj: any): obj is ContractReadData => { export const ContractDataContext = createContext({}) -export const ContractDataProvider = ({ children, contractParams }) => { - const [data, setData] = useState({}) - const publicClient = useContext(PublicClientContext) +/** + * Fetches data from multiple contract read calls and updates the state with the results. + * + * @param {ContractReadData[]} contractReadParams - An array of contract read parameters, each containing the contract address, ABI name, and methods to call. + * @param {any} publicClient - The public client used to interact with the blockchain. + * @param {Record} ABIs - A record of ABI names to ABI definitions. + * @param {(value: React.SetStateAction<{}>) => void} setData - A function to update the state with the fetched data. + * + * @returns {Promise} A promise that resolves when all contract read calls have been completed and the state has been updated. + * + * @throws Will throw an error if there is an issue with fetching contract data. + */ +const fetchData = async ( + contractReadParams: ContractReadData[], + publicClient, + ABIs, + setData: { + (value: React.SetStateAction<{}>): void + (arg0: (prevData: any) => any): void + } +) => { + try { + for (const contractReadCall of contractReadParams) { + const address = contractReadCall.address + const abi = ABIs[contractReadCall.abiName] - useEffect(() => { - const fetchData = async () => { - try { - const contractReadParams: ContractReadData[] = [] - for (const rpcCall of contractParams) { - if (isContractReadData(rpcCall)) { - contractReadParams.push(rpcCall) - } else { - console.error('Invalid contract read data:', rpcCall) - } + if (!publicClient) { + console.error('publicClient is null') + return + } + + const contract = getContract({ + address: getAddress(address), + abi: abi, + client: publicClient, + }) + + // Dynamically call methods from contractReadCall + const methodCalls = contractReadCall.methods.map((method) => { + if (typeof method === 'string') { + // @ts-ignore + return contract.read[method]() + } else { + // @ts-ignore + return contract.read[method.name](method.args) } - for (const contractReadCall of contractReadParams) { - const address = contractReadCall.address + }) - const abi = ABIs[contractReadCall.abiName] + const results = await Promise.all(methodCalls) // Await all method calls - if (!publicClient) { - console.error('publicClient is null') - return + setData((prevData) => { + const newData = { ...prevData } + results.forEach((result, index) => { + const methodName = + typeof contractReadCall.methods[index] === 'string' + ? contractReadCall.methods[index] + : contractReadCall.methods[index].name + if (!newData[contractReadCall.name]) { + newData[contractReadCall.name] = {} } - const contract = getContract({ - address: getAddress(address), - abi: abi, - client: publicClient, - }) - - // Dynamically call methods from contractReadCall - const methodCalls = contractReadCall.methods.map((method) => { - if (typeof method === 'string') { - return contract.read[method]() // Call method without arguments - } else { - return contract.read[method.name](method.args) // Call method with arguments - } - }) + newData[contractReadCall.name][methodName] = result + }) + return newData + }) + } + } catch (error) { + console.error('Error fetching contract data:', error) + } +} - const results = await Promise.all(methodCalls) // Await all method calls +/** + * Provides contract data to its children components. + * + * This context provider fetches on-chain data based on the provided contract parameters + * and makes it available to its children components via context. + * + * @param {Object} props - The props object. + * @param {React.ReactNode} props.children - The child components that will have access to the contract data. + * @param {Array} props.contractParams - The parameters used to fetch contract data. + * + * @returns {JSX.Element} The context provider component that supplies contract data. + */ +export const ContractDataProvider = ({ children, contractParams }) => { + const [data, setData] = useState({}) + const publicClient = useContext(PublicClientContext) - setData((prevData) => { - const newData = { ...prevData } - results.forEach((result, index) => { - const methodName = - typeof contractReadCall.methods[index] === 'string' - ? contractReadCall.methods[index] - : contractReadCall.methods[index].name - if (!newData[contractReadCall.name]) { - newData[contractReadCall.name] = {} - } - newData[contractReadCall.name][methodName] = result - }) - return newData - }) - } - } catch (error) { - console.error('Error fetching contract data:', error) - } - } + // Memoize contractReadParams to prevent unnecessary re-renders + const contractReadParams = useMemo( + () => contractParams.filter(isContractReadData), + [contractParams] + ) - fetchData() - }, [contractParams, publicClient]) + useEffect(() => { + console.log('fetching on-chain data...') + fetchData(contractReadParams, publicClient, ABIs, setData) + }, [contractReadParams, publicClient]) return (