diff --git a/src/abis/ChainlinkPriceFeed.json b/src/abis/ChainlinkPriceFeed.json new file mode 100644 index 0000000..c996159 --- /dev/null +++ b/src/abis/ChainlinkPriceFeed.json @@ -0,0 +1,41 @@ +[ + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "description", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestAnswer", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index c079b30..004767a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -138,3 +138,15 @@ export const ACTIONS_TABS: { value: ActionType; label: string }[] = [ { value: 'mint', label: 'Mint' }, { value: 'withdraw', label: 'Withdraw' }, ]; + +export interface ChainlinkPriceFeeds { + [key: string]: string; +} + +export const CHAINLINK_PRICE_FEEDS: Record = { + [MAINNET_CHAIN_ID_STRING]: { + 'ETH': '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', + 'WETH': '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', + 'WSTETH': '0x8B6851156023f4f5A66F68BEA80851c3D905Ac93' + } +}; diff --git a/src/contexts/VaultContext.tsx b/src/contexts/VaultContext.tsx index bc406b4..53b8440 100644 --- a/src/contexts/VaultContext.tsx +++ b/src/contexts/VaultContext.tsx @@ -13,7 +13,8 @@ import { ltvToLeverage, getLendingProtocolAddress, isVaultExists, isUserRejected import { ApyData, isAddressWhitelistedToMint, refreshTokenHolders } from '@/utils/api'; import { isWETHAddress, GAS_RESERVE_WEI, SEPOLIA_CHAIN_ID_STRING, SEPOLIA_MORPHO_MARKET_ID, CONNECTOR_ADDRESSES } from '@/constants'; import { useAdaptiveInterval, useVaultApy, useVaultPointsRate } from '@/hooks'; -import { loadGhostLtv, loadAaveLtv, loadMorphoLtv, fetchTokenPrice } from '@/utils'; +import { loadGhostLtv, loadAaveLtv, loadMorphoLtv } from '@/utils'; +import { getAssetPrice } from '@/utils/getAssetPrice'; import vaultsConfig from '../../vaults.config.json'; import signaturesConfig from '../../signatures.config.json'; @@ -661,7 +662,7 @@ export const VaultContextProvider = ({ children, vaultAddress, params }: { child }, [publicProvider, vaultLens, lendingAddress, vaultAddress, borrowTokenDecimals, currentNetwork, vaultConfig]); const loadPrices = useCallback(async () => { - if (!isMainnet) { + if (!isMainnet || !publicProvider || !currentNetwork) { setBorrowTokenPrice(null); setCollateralTokenPrice(null); return; @@ -669,18 +670,26 @@ export const VaultContextProvider = ({ children, vaultAddress, params }: { child try { if (borrowTokenSymbol) { - const price = await fetchTokenPrice(borrowTokenSymbol); + const price = await getAssetPrice( + borrowTokenSymbol.toUpperCase(), + currentNetwork, + publicProvider + ); setBorrowTokenPrice(price); } if (collateralTokenSymbol) { - const price = await fetchTokenPrice(collateralTokenSymbol); + const price = await getAssetPrice( + collateralTokenSymbol.toUpperCase(), + currentNetwork, + publicProvider + ); setCollateralTokenPrice(price); } } catch (err) { console.error('Error loading token prices:', err); } - }, [isMainnet, borrowTokenSymbol, collateralTokenSymbol]); + }, [isMainnet, borrowTokenSymbol, collateralTokenSymbol, publicProvider, currentNetwork]); useAdaptiveInterval(loadPrices, { initialDelay: 60000, diff --git a/src/utils/getAssetPrice.ts b/src/utils/getAssetPrice.ts new file mode 100644 index 0000000..3c7ccdf --- /dev/null +++ b/src/utils/getAssetPrice.ts @@ -0,0 +1,29 @@ +import { JsonRpcProvider, formatUnits } from "ethers"; +import { CHAINLINK_PRICE_FEEDS } from "@/constants"; +import { ChainlinkPriceFeed__factory } from "@/typechain-types"; + +export const getAssetPrice = async ( + symbol: string, + networkId: string, + provider: JsonRpcProvider +): Promise => { + try { + const feedsForNetwork = CHAINLINK_PRICE_FEEDS[networkId]; + if (!feedsForNetwork) return null; + + const feedAddress = feedsForNetwork[symbol.toUpperCase()]; + if (!feedAddress) return null; + + const feedContract = ChainlinkPriceFeed__factory.connect(feedAddress, provider); + + const [latestAnswer, decimals] = await Promise.all([ + feedContract.latestAnswer(), + feedContract.decimals() + ]); + + return parseFloat(formatUnits(latestAnswer, decimals)); + } catch (err) { + console.error(`Error retrieving price for ${symbol} from Chainlink:`, err); + return null; + } +};