Integration of Subgraphs
This document outlines how the various subgraph endpoints are utilized to consume specific data sets within the frontend.
Messari Subgraphs
For pulling and consuming data within the frontend with regards to specific lenders, we will be utilizing the Messari standardized subgraph. This will allow us to create DRY subgraph queries that can be utilized throughout the entire user interface.
Messari description / purpose
Messari has standardized schemas across the entire spectrum of the DeFi landscape. This allows queries to be standardized across a wide swath of lenders. Additionally, the same schema is utilized across multiple chains.
This allows us to add multiple new lenders (as well as chains) without heavy modification of the frontend code. By using one generalized schema, we simply have to include the lender specific endpoint in order to pull all necessary data for use.
Concrete Specific Subgraphs
Integration of the concrete subgraph will be accomplished utilizing traditional flows. This will include setting up required graphQl queries throughout the user interface.
Flow of data
Data is to be pulled from the subgraphs utilizing specific Hooks for that particular data set. This is to allow pulling "raw" data from the graph, then formatting appropriately and storing in redux. All data (without specific reasoning) is stored in redux for use throughout the frontend.
Example
Below is an example of a hook that pulls the relevant data for a given lender. The hooks takes the parameter borrowId
. This parameter is a string representation of a number that is assigned to the particular lender.
export const useGetLenderData = (borrowId: string) => {
const dispatch = useDispatch(); // Redux dispatch hook
// The following "initializes" the required specific function calls upon initialization of the hook
// Uses a custom useEffectOnce() hook that ensures that the hook is only run once.
useEffectOnce(() => {
dispatch(setTotalLiquidationsLoading(true));
dispatch(setFinancialsLoading(true));
dispatch(setMarketsLoading(true));
dispatch(setGeneralInfo(lenderGeneralData[borrowId]));
});
// This useMemo maps the passed in borrowId with the appropriate subgraph endpoint
// This ensures that the correct subgraph is called
const subgraphURL = useMemo(() => {
switch (borrowId) {
case "1":
return `${baseURL}aave-v3-arbitrum`;
case "2":
return `${baseURL}radiant-capital-v2-arbitrum`;
case "3":
return `${baseURL}will-be-silo`;
default:
return "";
}
}, [borrowId]);
// Create a re-usable client for executing queries
const client = useMemo(() => {
return new ApolloClient({
link: new HttpLink({
uri: subgraphURL,
}),
cache: new InMemoryCache(),
});
}, [subgraphURL]);
// The following are the specific queries
const [
getDailyFinancials,
{
data: financialsData,
loading: financialsLoading,
error: financialsError,
},
] = useLazyQuery<FinancialDailySnapshotQueryResults>(
FINANCIALS_DAILY_SNAPSHOT,
{
client,
}
);
const [
getMarketsData,
{ data: marketsData, loading: marketsLoading, error: marketsError },
] = useLazyQuery<MarketsQueryResults>(MARKETS, { client });
const [
getMarketsDataForTokens,
{ data: marketsDataForToken, loading: marketsDataForTokensLoading },
] = useLazyQuery<MarketsForTokenQueryResults>(MARKETS_FOR_TOKEN, { client });
//Hook that intitializes the above queries
useEffect(() => {
getDailyFinancials();
getMarketsData();
getMarketsDataForTokens();
}, [getDailyFinancials, getMarketsData, getMarketsDataForTokens]);
// Format marketsData for a specific token and put into redux state.
// Triggers when the marketsDataForToken query returns
useMemo(() => {
if (
!marketsDataForToken?.markets ||
marketsDataForToken?.markets.length === 0
)
return;
const allowedReserves = [];
marketsDataForToken.markets.forEach((r) => {
if (
approvedCollateral[borrowId].includes(r.inputToken.symbol) &&
!allowedReserves.some(
(reserve) => reserve.symbol === r.inputToken.symbol
)
) {
allowedReserves.push({
underlyingAsset:
r.inputToken.symbol === "USDC"
? "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
: r.inputToken.id,
symbol: r.inputToken.symbol,
formattedBaseLTVasCollateral: r.maximumLTV,
variableBorrowAPR: r.rates.find((rate) => rate.type === "VARIABLE")
?.rate,
stableBorrowAPR: r.rates.find((rate) => rate.type === "STABLE")?.rate,
});
}
});
dispatch(setAllowedReserves(allowedReserves));
}, [marketsDataForToken, borrowId, dispatch]);
// Format marketsData data. Triggers when marketsData query is updated
// Utilizes specific functions for formatting data
useMemo(() => {
if (!marketsData?.markets || marketsData?.markets?.length === 0) return;
dispatch(setLTVRange(calcLTVLowHigh(marketsData?.markets)));
dispatch(setAvgLTV(calcAvgLTV(marketsData.markets)));
dispatch(setAvgAPR(calcAvgAPR(marketsData.markets)));
dispatch(setFinancialsLoading(false));
const aCollateral = approvedCollateral[borrowId];
const markets =
marketsData?.markets
?.filter((market) => aCollateral.includes(market.name.split(" ").pop()))
.map((market) => market.id) || [];
formatTVLByToken(markets, client).then((res) => {
dispatch(setAssetLTVArray(res.formattedTVLByAsset));
dispatch(setChartBadgeColors(res.badgeColors));
dispatch(setMarketsLoading(false));
});
}, [marketsData, client, dispatch, borrowId]);
useMemo(() => {
if (!financialsData || financialsData.financialsDailySnapshots.length === 0)
return;
dispatch(setTotalLTVArray(formatAvgData(financialsData)));
dispatch(setPercentAtRisk(formatAtRiskPercentage(financialsData)));
dispatch(
setTotalLiquidations(
financialsData.financialsDailySnapshots[
financialsData.financialsDailySnapshots.length - 1
].cumulativeLiquidateUSD
)
);
dispatch(setTotalLiquidationsLoading(false));
}, [financialsData, dispatch]);
};
Last updated