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