Skip to content

Commit

Permalink
Create usePriceRelay tanstack hook (#865)
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenshively authored May 22, 2024
1 parent 184b3ca commit e23d58d
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 144 deletions.
2 changes: 1 addition & 1 deletion earn/src/components/boost/ImportBoostWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { GN, GNFormat } from 'shared/lib/data/GoodNumber';
import useChain from 'shared/lib/data/hooks/UseChain';
import { useChainDependentState } from 'shared/lib/data/hooks/UseChainDependentState';
import { useLendingPair, useLendingPairs } from 'shared/lib/data/hooks/UseLendingPairs';
import { PriceRelayLatestResponse } from 'shared/lib/data/hooks/UsePriceRelay';
import { Token } from 'shared/lib/data/Token';
import { getTokenBySymbol } from 'shared/lib/data/TokenData';
import {
Expand All @@ -46,7 +47,6 @@ import {
import SlippageWidget from './SlippageWidget';
import { fetchListOfBorrowerNfts } from '../../data/BorrowerNft';
import { API_PRICE_RELAY_LATEST_URL } from '../../data/constants/Values';
import { PriceRelayLatestResponse } from '../../data/PriceRelayResponse';
import { BoostCardInfo } from '../../data/Uniboost';
import { BOOST_MAX, BOOST_MIN } from '../../pages/boost/ImportBoostPage';
import { useEthersProvider } from '../../util/Provider';
Expand Down
19 changes: 0 additions & 19 deletions earn/src/data/PriceRelayResponse.ts
Original file line number Diff line number Diff line change
@@ -1,19 +0,0 @@
export type PriceRelayLatestResponse = {
[key: string]: {
price: number;
};
};

export type PriceRelayHistoricalResponse = {
[key: string]: {
prices: {
price: number;
timestamp: number;
}[];
};
};

export type PriceRelayConsolidatedResponse = {
latest: PriceRelayLatestResponse;
historical: PriceRelayHistoricalResponse;
};
71 changes: 9 additions & 62 deletions earn/src/pages/MarketsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';

import { type WriteContractReturnType } from '@wagmi/core';
import axios, { AxiosResponse } from 'axios';
import { useSearchParams } from 'react-router-dom';
import AppPage from 'shared/lib/components/common/AppPage';
import { Display, Text } from 'shared/lib/components/common/Typography';
Expand All @@ -11,6 +10,7 @@ import { GetNumericFeeTier } from 'shared/lib/data/FeeTier';
import useChain from 'shared/lib/data/hooks/UseChain';
import { useChainDependentState } from 'shared/lib/data/hooks/UseChainDependentState';
import { useLendingPairs } from 'shared/lib/data/hooks/UseLendingPairs';
import { useLatestPriceRelay } from 'shared/lib/data/hooks/UsePriceRelay';
import { getLendingPairBalances, LendingPairBalancesMap } from 'shared/lib/data/LendingPair';
import { Token } from 'shared/lib/data/Token';
import { formatUSDAuto } from 'shared/lib/util/Numbers';
Expand All @@ -26,9 +26,7 @@ import InfoTab from '../components/markets/monitor/InfoTab';
import SupplyTable, { SupplyTableRow } from '../components/markets/supply/SupplyTable';
import { BorrowerNftBorrower, fetchListOfFuse2BorrowNfts } from '../data/BorrowerNft';
import { ZERO_ADDRESS } from '../data/constants/Addresses';
import { API_PRICE_RELAY_LATEST_URL } from '../data/constants/Values';
import { fetchBorrowerDatas, UniswapPoolInfo } from '../data/MarginAccount';
import { PriceRelayLatestResponse } from '../data/PriceRelayResponse';
import { getProminentColor } from '../util/Colors';
import { useEthersProvider } from '../util/Provider';

Expand Down Expand Up @@ -78,13 +76,10 @@ enum TabOption {
Liquidate = 'liquidate',
}

type TokenSymbol = string;
type Quote = number;

export default function MarketsPage() {
const activeChain = useChain();
// MARK: component state
const [tokenQuotes, setTokenQuotes] = useChainDependentState<Map<TokenSymbol, Quote>>(new Map(), activeChain.id);
// const [tokenQuotes, setTokenQuotes] = useChainDependentState<Map<TokenSymbol, Quote>>(new Map(), activeChain.id);
const [balancesMap, setBalancesMap] = useChainDependentState<LendingPairBalancesMap>(new Map(), activeChain.id);
const [borrowers, setBorrowers] = useChainDependentState<BorrowerNftBorrower[] | null>(null, activeChain.id);
const [tokenColors, setTokenColors] = useChainDependentState<Map<Address, string>>(new Map(), activeChain.id);
Expand All @@ -107,6 +102,7 @@ export default function MarketsPage() {

// MARK: custom hooks
const { lendingPairs, refetchOracleData, refetchLenderData } = useLendingPairs(activeChain.id);
const { data: tokenQuotes } = useLatestPriceRelay(lendingPairs);

useWatchBlockNumber({
onBlockNumber(/* blockNumber */) {
Expand Down Expand Up @@ -182,55 +178,6 @@ export default function MarketsPage() {
})();
}, [uniqueTokens, setTokenColors]);

// MARK: Fetching token prices
useEffect(() => {
let mounted = true;
(async () => {
// Determine set of unique token symbols (tickers)
const symbolSet = new Set<string>();
lendingPairs.forEach((pair) => {
symbolSet.add(pair.token0.symbol);
symbolSet.add(pair.token1.symbol);
});
const uniqueSymbols = Array.from(symbolSet.values());

// Return early if there's nothing new to fetch
if (
uniqueSymbols.length === 0 ||
uniqueSymbols.every((symbol) => {
return tokenQuotes.has(symbol.toLowerCase()) || (symbol === 'USDC.e' && tokenQuotes.has('USDC'));
})
) {
return;
}

// Query API for price data, returning early if request fails
let quoteDataResponse: AxiosResponse<PriceRelayLatestResponse>;
try {
quoteDataResponse = await axios.get(
`${API_PRICE_RELAY_LATEST_URL}?symbols=${uniqueSymbols.join(',').toUpperCase()}`
);
} catch {
return;
}
const prResponse: PriceRelayLatestResponse = quoteDataResponse.data;
if (!prResponse) return;

// Convert response to the desired Map format
const symbolToPriceMap = new Map<TokenSymbol, Quote>();
Object.entries(prResponse).forEach(([k, v]) => {
symbolToPriceMap.set(k.toLowerCase(), v.price);
symbolToPriceMap.set(k, v.price);
});

if (mounted) setTokenQuotes(symbolToPriceMap);
})();

return () => {
mounted = false;
};
}, [lendingPairs, tokenQuotes, setTokenQuotes]);

// MARK: Fetching token balances
useEffect(() => {
(async () => {
Expand Down Expand Up @@ -280,8 +227,8 @@ export default function MarketsPage() {
const isToken0Weth = pair.token0.name === 'Wrapped Ether';
const isToken1Weth = pair.token1.name === 'Wrapped Ether';

const token0Price = tokenQuotes.get(pair.token0.symbol) || 0;
const token1Price = tokenQuotes.get(pair.token1.symbol) || 0;
const token0Price = tokenQuotes?.get(pair.token0.symbol) || 0;
const token1Price = tokenQuotes?.get(pair.token1.symbol) || 0;
const token0Balance =
(balancesMap.get(pair.token0.address)?.value || 0) + ((isToken0Weth && ethBalance?.value) || 0);
const token1Balance =
Expand Down Expand Up @@ -351,7 +298,7 @@ export default function MarketsPage() {
lendingPairs={lendingPairs}
uniqueTokens={uniqueTokens}
tokenBalances={balancesMap}
tokenQuotes={tokenQuotes}
tokenQuotes={tokenQuotes!}
tokenColors={tokenColors}
setPendingTxn={setPendingTxn}
/>
Expand All @@ -374,7 +321,7 @@ export default function MarketsPage() {
<LiquidateTab
chainId={activeChain.id}
lendingPairs={lendingPairs}
tokenQuotes={tokenQuotes}
tokenQuotes={tokenQuotes!}
setPendingTxn={setPendingTxn}
/>
);
Expand All @@ -388,8 +335,8 @@ export default function MarketsPage() {

const totalBorrowed = useMemo(() => {
return lendingPairs.reduce((acc, pair) => {
const token0Price = tokenQuotes.get(pair.token0.symbol) || 0;
const token1Price = tokenQuotes.get(pair.token1.symbol) || 0;
const token0Price = tokenQuotes?.get(pair.token0.symbol) || 0;
const token1Price = tokenQuotes?.get(pair.token1.symbol) || 0;
const token0BorrowedUsd = pair.kitty0Info.totalBorrows.toNumber() * token0Price;
const token1BorrowedUsd = pair.kitty1Info.totalBorrows.toNumber() * token1Price;
return acc + token0BorrowedUsd + token1BorrowedUsd;
Expand Down
87 changes: 27 additions & 60 deletions earn/src/pages/PortfolioPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useEffect, useMemo, useState } from 'react';

import { type WriteContractReturnType } from '@wagmi/core';
import axios, { AxiosResponse } from 'axios';
import { useNavigate } from 'react-router-dom';
import AppPage from 'shared/lib/components/common/AppPage';
import { Text } from 'shared/lib/components/common/Typography';
import { GREY_700 } from 'shared/lib/data/constants/Colors';
import useChain from 'shared/lib/data/hooks/UseChain';
import { useChainDependentState } from 'shared/lib/data/hooks/UseChainDependentState';
import { useLendingPairs } from 'shared/lib/data/hooks/UseLendingPairs';
import { useConsolidatedPriceRelay } from 'shared/lib/data/hooks/UsePriceRelay';
import { getLendingPairBalances, LendingPairBalances } from 'shared/lib/data/LendingPair';
import { Token } from 'shared/lib/data/Token';
import { getTokenBySymbol } from 'shared/lib/data/TokenData';
Expand All @@ -33,8 +33,6 @@ import PortfolioBalance from '../components/portfolio/PortfolioBalance';
import PortfolioGrid from '../components/portfolio/PortfolioGrid';
import PortfolioPageWidgetWrapper from '../components/portfolio/PortfolioPageWidgetWrapper';
import { RESPONSIVE_BREAKPOINT_SM, RESPONSIVE_BREAKPOINT_XS } from '../data/constants/Breakpoints';
import { API_PRICE_RELAY_CONSOLIDATED_URL } from '../data/constants/Values';
import { PriceRelayConsolidatedResponse } from '../data/PriceRelayResponse';
import { getProminentColor } from '../util/Colors';
import { useEthersProvider } from '../util/Provider';

Expand Down Expand Up @@ -107,14 +105,10 @@ export default function PortfolioPage() {

const [pendingTxn, setPendingTxn] = useState<WriteContractReturnType | null>(null);
const [tokenColors, setTokenColors] = useState<Map<string, string>>(new Map());
const [tokenQuotes, setTokenQuotes] = useChainDependentState<TokenQuote[]>([], activeChain.id);
const [lendingPairBalances, setLendingPairBalances] = useChainDependentState<LendingPairBalances[]>(
[],
activeChain.id
);
const [tokenPriceData, setTokenPriceData] = useState<TokenPriceData[]>([]);
const [isLoadingPrices, setIsLoadingPrices] = useState(true);
const [errorLoadingPrices, setErrorLoadingPrices] = useState(false);
const [activeAsset, setActiveAsset] = useState<Token | null>(null);
const [isSendCryptoModalOpen, setIsSendCryptoModalOpen] = useState(false);
const [isEarnInterestModalOpen, setIsEarnInterestModalOpen] = useState(false);
Expand All @@ -124,15 +118,19 @@ export default function PortfolioPage() {
const [pendingTxnModalStatus, setPendingTxnModalStatus] = useState<PendingTxnModalStatus | null>(null);

const { lendingPairs } = useLendingPairs(activeChain.id);
const {
data: consolidatedPriceData,
isPending: isPendingPrices,
isFetching: isFetchingPrices,
isError: errorLoadingPrices,
} = useConsolidatedPriceRelay(lendingPairs, 2 * 60 * 1_000);
const isLoadingPrices = isPendingPrices || isFetchingPrices || consolidatedPriceData?.latestPrices.size === 0;

const client = useClient<Config>({ chainId: activeChain.id });
const provider = useEthersProvider(client);
const { address, isConnecting, isConnected } = useAccount();
const navigate = useNavigate();

useEffect(() => {
setIsLoadingPrices(true);
}, [activeChain.id, setIsLoadingPrices]);

const uniqueTokens = useMemo(() => {
const tokens = new Set<Token>();
lendingPairs.forEach((pair) => {
Expand All @@ -142,55 +140,24 @@ export default function PortfolioPage() {
return Array.from(tokens);
}, [lendingPairs]);

/**
* Get the latest and historical prices for all tokens
*/
useEffect(() => {
(async () => {
// Only fetch prices for tokens if they are all on the same (active) chain
if (uniqueTokens.length > 0 && uniqueTokens.some((token) => token.chainId !== activeChain.id)) {
return;
}
const symbols = uniqueTokens
.map((token) => token?.symbol)
.filter((symbol) => symbol !== undefined)
.join(',');
if (symbols.length === 0) {
return;
}
let priceRelayResponses: AxiosResponse<PriceRelayConsolidatedResponse> | null = null;
try {
priceRelayResponses = await axios.get(`${API_PRICE_RELAY_CONSOLIDATED_URL}?symbols=${symbols}`);
} catch (error) {
setErrorLoadingPrices(true);
setIsLoadingPrices(false);
return;
}
if (priceRelayResponses == null) {
return;
}
const latestPriceResponse = priceRelayResponses.data.latest;
const historicalPriceResponse = priceRelayResponses.data.historical;
if (!latestPriceResponse || !historicalPriceResponse) {
return;
}
const tokenQuoteData: TokenQuote[] = Object.entries(latestPriceResponse).map(([symbol, data]) => {
return {
token: getTokenBySymbol(activeChain.id, symbol),
price: data.price,
};
});
const tokenPriceData: TokenPriceData[] = Object.entries(historicalPriceResponse).map(([symbol, data]) => {
return {
token: getTokenBySymbol(activeChain.id, symbol),
priceEntries: data.prices,
};
});
setTokenQuotes(tokenQuoteData);
setTokenPriceData(tokenPriceData);
setIsLoadingPrices(false);
})();
}, [activeChain, uniqueTokens, setTokenQuotes, setTokenPriceData, setIsLoadingPrices, setErrorLoadingPrices]);
const { tokenQuotes, tokenPriceData } = useMemo(() => {
if (!consolidatedPriceData)
return {
tokenQuotes: [],
tokenPriceData: [],
};

return {
tokenQuotes: Array.from(consolidatedPriceData.latestPrices.entries()).map(([k, v]) => ({
token: getTokenBySymbol(activeChain.id, k),
price: v,
})),
tokenPriceData: Array.from(consolidatedPriceData.historicalPrices.entries()).map(([k, v]) => ({
token: getTokenBySymbol(activeChain.id, k),
priceEntries: v,
})),
};
}, [activeChain.id, consolidatedPriceData]);

useEffect(() => {
(async () => {
Expand Down
2 changes: 1 addition & 1 deletion earn/src/pages/boost/ManageBoostPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { sqrtRatioToTick } from 'shared/lib/data/BalanceSheet';
import { ALOE_II_BORROWER_NFT_ADDRESS } from 'shared/lib/data/constants/ChainSpecific';
import useChain from 'shared/lib/data/hooks/UseChain';
import { useChainDependentState } from 'shared/lib/data/hooks/UseChainDependentState';
import { PriceRelayLatestResponse } from 'shared/lib/data/hooks/UsePriceRelay';
import styled from 'styled-components';
import { Address } from 'viem';
import { Config, useAccount, useClient, usePublicClient, useReadContract } from 'wagmi';
Expand All @@ -22,7 +23,6 @@ import BurnBoostModal from '../../components/boost/BurnBoostModal';
import CollectFeesWidget from '../../components/boost/CollectFeesWidget';
import PendingTxnModal, { PendingTxnModalStatus } from '../../components/common/PendingTxnModal';
import { API_PRICE_RELAY_LATEST_URL } from '../../data/constants/Values';
import { PriceRelayLatestResponse } from '../../data/PriceRelayResponse';
import { BoostCardInfo, BoostCardType, fetchBoostBorrower } from '../../data/Uniboost';
import { getProminentColor, rgb } from '../../util/Colors';
import { useEthersProvider } from '../../util/Provider';
Expand Down
8 changes: 7 additions & 1 deletion shared/src/data/constants/Values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import { toBig } from '../../util/Numbers';
export const DEFAULT_CHAIN = optimism;
export const DEFAULT_ETHERSCAN_URL = 'https://etherscan.io';
export const Q32 = 0x100000000;

export const API_GEO_FENCING_URL = 'https://geo-fencing.aloe.capital/v1/verify';
export const API_SCREENING_URL = 'https://geo-fencing.aloe.capital/v1/screen';
export const API_PRICE_RELAY_LATEST_URL = 'https://api-price.aloe.capital/price-relay/v1/latest';
export const API_PRICE_RELAY_HISTORICAL_URL = 'https://api-price.aloe.capital/price-relay/v1/historical';
export const API_PRICE_RELAY_CONSOLIDATED_URL = 'https://api-price.aloe.capital/price-relay/v1/consolidated';
export const API_LEADERBOARD_URL = 'https://leaderboard.aloe.capital/v1/leaderboard';

export const NOTIFICATION_BOT_URL = 'https://t.me/aloe_notifier_bot';
export const TERMS_OF_SERVICE_URL = 'https://aloe.capital/legal/terms-of-service';
export const PRIVACY_POLICY_URL = 'https://aloe.capital/legal/privacy-policy';
export const API_LEADERBOARD_URL = 'https://leaderboard.aloe.capital/v1/leaderboard';

export const LAUNCH_DATE = new Date('2024-01-02T06:00:00.000Z'); // 12 AM CST on Jan 2, 2024
export const DEAD_ADDRESS = '0xdead00000000000000000000000000000000dead';
export const ROUTER_TRANSMITTANCE = 9999;
Expand Down
Loading

0 comments on commit e23d58d

Please sign in to comment.