Skip to content

Commit

Permalink
feat: Fix price button + add fetch for all underlying + update pools …
Browse files Browse the repository at this point in the history
…updater
  • Loading branch information
Space-Bean committed Sep 16, 2024
1 parent 9b60f54 commit afe897a
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 297 deletions.
396 changes: 198 additions & 198 deletions projects/ui/src/components/Nav/Buttons/PriceButton.tsx

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions projects/ui/src/constants/abi/Beanstalk/abiSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,34 @@ const price = [
},
] as const;

const oraclePrices = [
{
inputs: [{ internalType: 'address', name: 'token', type: 'address' }],
name: 'getTokenUsdPrice',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'uint256', name: 'lookback', type: 'uint256' },
],
name: 'getTokenUsdTwap',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
] as const;

const BEANSTALK_ABI_SNIPPETS = {
getGaugePointsPerBdvForToken: getGaugePointsPerBdvForToken,
tokenSettings: tokenSettings,
poolDeltaB: poolDeltaB,
siloGetters: siloGetters,
price: price,
totalDeltaB: totalDeltaB,
oraclePrices: oraclePrices,
} as const;

export default BEANSTALK_ABI_SNIPPETS;
Expand Down
2 changes: 1 addition & 1 deletion projects/ui/src/graph/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export const apolloClient = new ApolloClient({
ApolloLink.split(
({ getContext }) => getContext().subgraph === 'bean',
beanLinks.arb, // true
beanstalkLinks.eth // false
beanstalkLinks.arb // false
)
)
)
Expand Down
198 changes: 120 additions & 78 deletions projects/ui/src/hooks/beanstalk/useDataFeedTokenPrices.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { useCallback, useMemo } from 'react';
import { Token } from '@beanstalk/sdk';
import { BigNumber } from 'bignumber.js';
import { useDispatch } from 'react-redux';
import { ContractFunctionParameters } from 'viem';

import { useAggregatorV3Contract } from '~/hooks/ledger/useContract';
import { updateTokenPrices } from '~/state/beanstalk/tokenPrices/actions';
import { getTokenIndex } from '~/util';
import { TokenMap } from '../../constants/index';
import { bigNumberResult } from '../../util/Ledger';
import {
DAI_CHAINLINK_ADDRESSES,
USDT_CHAINLINK_ADDRESSES,
USDC_CHAINLINK_ADDRESSES,
} from '../../constants/addresses';
import { useAppSelector } from '../../state/index';
import useSdk from '../sdk';
import { useTokens } from './useTokens';
import useL2OnlyEffect from '../chain/useL2OnlyEffect';
import { chunkArray, bigNumberResult, getTokenIndex } from '~/util';
import BEANSTALK_ABI_SNIPPETS from '~/constants/abi/Beanstalk/abiSnippets';
import { TokenMap } from '~/constants/index';
import { DAI_CHAINLINK_ADDRESSES } from '~/constants/addresses';
import { useAppSelector } from '~/state/index';
import useSdk from '~/hooks/sdk';
import { useTokens } from '~/hooks/beanstalk/useTokens';
import useL2OnlyEffect from '~/hooks/chain/useL2OnlyEffect';
import { multicall } from '@wagmi/core';
import { config } from '~/util/wagmi/config';
import { extractMulticallResult } from '~/util/Multicall';

const getBNResult = (result: any, decimals: number) => {
const bnResult = bigNumberResult(result);
Expand All @@ -26,51 +28,45 @@ const getBNResult = (result: any, decimals: number) => {
* fetches data from Chainlink DataFeeds.
* Currently supports prices for the following pairs:
* - DAI/USD
* - USDT/USD
* - USDC/USD
* - ETH/USD
*
* - Other tokens are fetched from the Beanstalk oracle
*/
export default function useDataFeedTokenPrices() {
const tokenPriceMap = useAppSelector((state) => state._beanstalk.tokenPrices);

const sdk = useSdk();
const tokens = useTokens();
const beanstalk = sdk.contracts.beanstalk;

const daiPriceFeed = useAggregatorV3Contract(DAI_CHAINLINK_ADDRESSES);
const usdtPriceFeed = useAggregatorV3Contract(USDT_CHAINLINK_ADDRESSES);
const usdcPriceFeed = useAggregatorV3Contract(USDC_CHAINLINK_ADDRESSES);
const usdOracle = sdk.contracts.usdOracle;
const dispatch = useDispatch();

const hasEntries = Object.values(tokenPriceMap).length;

const fetch = useCallback(async () => {
if (Object.values(tokenPriceMap).length) return;
if (!daiPriceFeed || !usdtPriceFeed || !usdcPriceFeed || !usdOracle) return;

console.debug('[beanstalk/tokenPrices/useCrvUnderlylingPrices] FETCH');

const [
daiPriceData,
daiPriceDecimals,
usdtPriceData,
usdtPriceDecimals,
usdcPriceData,
usdcPriceDecimals,
ethPrice,
ethPriceTWA,
wstETHPrice,
wstETHPriceTWA,
] = await Promise.all([
if (hasEntries || !daiPriceFeed) return;

const underlyingTokens = [
tokens.WETH,
tokens.WSTETH,
tokens.WBTC,
tokens.WEETH,
tokens.USDT,
tokens.USDC,
];

console.debug('[beanstalk/tokenPrices/useDataFeedTokenPrices] FETCH');

const calls = makeMultiCall(beanstalk.address, underlyingTokens);

const [daiPriceData, daiPriceDecimals, oracleResults] = await Promise.all([
daiPriceFeed.latestRoundData(),
daiPriceFeed.decimals(),
usdtPriceFeed.latestRoundData(),
usdtPriceFeed.decimals(),
usdcPriceFeed.latestRoundData(),
usdcPriceFeed.decimals(),
// beanstalk.getTokenUsdPrice(sdk.tokens.WETH.address),
usdOracle.getEthUsdPrice(),
usdOracle.getEthUsdTwap(0),
usdOracle.getWstethUsdPrice(),
usdOracle.getWstethUsdTwap(0),
Promise.all(
calls.contracts.map((oracleCalls) =>
multicall(config, { contracts: oracleCalls })
)
).then((results) => chunkArray(results.flat(), calls.chunkSize)),
]);

const priceDataCache: TokenMap<BigNumber> = {};
Expand All @@ -81,45 +77,56 @@ export default function useDataFeedTokenPrices() {
daiPriceDecimals
);
}
if (usdtPriceData && usdtPriceDecimals) {
priceDataCache[getTokenIndex(tokens.USDT)] = getBNResult(
usdtPriceData.answer,
usdtPriceDecimals
);
}
if (usdcPriceData && usdcPriceDecimals) {
priceDataCache[getTokenIndex(tokens.USDC)] = getBNResult(
usdcPriceData.answer,
usdcPriceDecimals
);
}
if (ethPrice && ethPriceTWA) {
priceDataCache[getTokenIndex(tokens.ETH)] = getBNResult(ethPrice, 6);
priceDataCache[getTokenIndex(tokens.WETH)] = getBNResult(ethPrice, 6);
priceDataCache['ETH-TWA'] = getBNResult(ethPriceTWA, 6);
}

if (wstETHPrice && wstETHPriceTWA) {
priceDataCache[getTokenIndex(tokens.WSTETH)] = getBNResult(
wstETHPrice,
6
);
priceDataCache['wstETH-TWA'] = getBNResult(wstETHPriceTWA, 6);
}
oracleResults.forEach((result, index) => {
const price = extractMulticallResult(result[0]);
const twap = extractMulticallResult(result[1]);
const token = underlyingTokens[index];
if (!token) return;

// BS3TODO: REMOVE ME
const decimals = ['wstETH', 'weETH'].includes(token.symbol) ? 40 : 6;
// BS3TODO: REMOVE ME
const divBy = ['wstETH', 'weETH'].includes(token.symbol) ? 20000 : 1;

if (price) {
priceDataCache[getTokenIndex(token)] = getBNResult(
price.toString(),
decimals
).div(divBy);
}
if (twap) {
priceDataCache[`${token.symbol}-TWA`] = getBNResult(
twap.toString(),
decimals
).div(divBy);
}

// add it for ETH as well
if (token.equals(tokens.WETH)) {
priceDataCache[getTokenIndex(tokens.ETH)] =
priceDataCache[getTokenIndex(tokens.WETH)];
priceDataCache[`ETH-TWA`] = priceDataCache[`${token.symbol}-TWA`];
}
});

console.log(
'Oracle Result: ',
Object.fromEntries(
Object.entries(priceDataCache).map(([key, value]) => [
key,
value.toNumber(),
])
)
);

console.debug(
`[beanstalk/tokenPrices/useCrvUnderlyingPrices] RESULT: ${priceDataCache}`
`[beanstalk/tokenPrices/useCrvUnderlyingPrices] RESULT:`,
priceDataCache
);

return priceDataCache;
}, [
tokenPriceMap,
daiPriceFeed,
usdtPriceFeed,
usdcPriceFeed,
usdOracle,
tokens,
]);
}, [hasEntries, daiPriceFeed, beanstalk, tokens]);

const handleUpdatePrices = useCallback(async () => {
const prices = await fetch();
Expand All @@ -128,7 +135,42 @@ export default function useDataFeedTokenPrices() {

useL2OnlyEffect(() => {
handleUpdatePrices();
}, [handleUpdatePrices]);
}, []);

return useMemo(() => ({ ...tokenPriceMap }), [tokenPriceMap]);
}

type OraclePriceCall = ContractFunctionParameters<
typeof BEANSTALK_ABI_SNIPPETS.oraclePrices
>;
function makeMultiCall(beanstalkAddress: string, tokens: Token[]) {
const calls: OraclePriceCall[] = [];

const contract = {
address: beanstalkAddress as `0x${string}`,
abi: BEANSTALK_ABI_SNIPPETS.oraclePrices,
};

let address: `0x${string}` = '0x';

for (const token of tokens) {
address = token.address as `0x${string}`;
const instantPriceCall: OraclePriceCall = {
...contract,
functionName: 'getTokenUsdPrice',
args: [address as `0x${string}`],
};
const twapPriceCall: OraclePriceCall = {
...contract,
functionName: 'getTokenUsdTwap',
args: [address as `0x${string}`, 3600n],
};

calls.push(instantPriceCall, twapPriceCall);
}

return {
contracts: chunkArray(calls, 20),
chunkSize: 2,
};
}
10 changes: 5 additions & 5 deletions projects/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ if (import.meta.env.DEV) {
const container = document.getElementById('app');
const root = createRoot(container!);
root.render(
<React.StrictMode>
<Wrapper>
<App />
</Wrapper>
</React.StrictMode>
// <React.StrictMode>
<Wrapper>
<App />
</Wrapper>
// </React.StrictMode>
);

reportWebVitals();
Loading

0 comments on commit afe897a

Please sign in to comment.