From f9254100daaa566f175d487b9edf71e7856ef312 Mon Sep 17 00:00:00 2001 From: michaeldim Date: Tue, 24 Jun 2025 16:47:04 +0100 Subject: [PATCH] feat: add oracle price fallback for custom-priced tokens Some collateral tokens don't appear in the pools api or coingecko, causing getUsdRate() to return 0. These assets rely on custom oracles that chain multiple Curve pools or vault exchange rates. When both primary sources fail, fall back to https://prices.curve.finance/v1/lending/markets to retrieve the oracle-reported price. This restores missing USD values for lending markets that use custom oracles. --- src/external-api.ts | 33 +++++++++++++++++++++++++++++++++ src/utils.ts | 19 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/external-api.ts b/src/external-api.ts index 6a240d3..0dc6bbb 100644 --- a/src/external-api.ts +++ b/src/external-api.ts @@ -209,3 +209,36 @@ const _assembleTxOdosMemoized = memoize( export async function _assembleTxOdos(this: Llamalend, pathId: string): Promise { return _assembleTxOdosMemoized(this.constants.ALIASES.leverage_zap, pathId); } + +export const _getOraclePricesFromApi = memoize( + async (network: INetworkName): Promise> => { + const url = `https://prices.curve.finance/v1/lending/markets/${network}`; + const response = await fetch(url); + if (response.status !== 200) { + return {}; + } + + const { data } = await response.json() as { data: Array<{ + controller: string, + collateral_token: { + symbol: string, + address: string + }, + price_oracle: number + }> }; + + const oraclePrices: IDict = {}; + + for (const market of data) { + if (market.price_oracle && market.collateral_token && market.collateral_token.address) { + oraclePrices[market.collateral_token.address.toLowerCase()] = market.price_oracle; + } + } + + return oraclePrices; + }, + { + promise: true, + maxAge: 60 * 1000, + } +); diff --git a/src/utils.ts b/src/utils.ts index 94de240..fc92959 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import { ethers, BigNumberish, Numeric } from "ethers"; import { Call } from "@curvefi/ethcall"; import BigNumber from 'bignumber.js'; import { ICurveContract, IDict, TGas } from "./interfaces.js"; -import { _getUsdPricesFromApi } from "./external-api.js"; +import { _getUsdPricesFromApi, _getOraclePricesFromApi } from "./external-api.js"; import type { Llamalend } from "./llamalend.js"; import { JsonFragment } from "ethers/lib.esm"; import { L2Networks } from "./constants/L2Networks.js"; @@ -370,6 +370,23 @@ export const _getUsdRate = async function (this: Llamalend, assetId: string): Pr } } + if (_usdRatesCache[assetId]['rate'] === 0) { + const originalAssetId = arguments[0]; + const oraclePrices = await _getOraclePricesFromApi.call(this, this.constants.NETWORK_NAME); + + if (originalAssetId.toLowerCase() in oraclePrices) { + const oraclePriceInCrvUsd = oraclePrices[originalAssetId.toLowerCase()]; + + if (oraclePriceInCrvUsd > 0) { + const crvUsdAddress = this.constants.ALIASES.crvUSD; + const crvUsdPrice = assetId.toLowerCase() === crvUsdAddress.toLowerCase() ? 1 : + await _getUsdRate.call(this, crvUsdAddress); + + _usdRatesCache[assetId] = {'rate': oraclePriceInCrvUsd * crvUsdPrice, 'time': Date.now()}; + } + } + } + return _usdRatesCache[assetId]['rate'] }