Skip to content

Commit

Permalink
Merge pull request #3143 from OlympusDAO/rbsPriceDisplayUpdates
Browse files Browse the repository at this point in the history
Update RBS Pricing Display
  • Loading branch information
brightiron authored Jan 29, 2024
2 parents de42067 + f01db85 commit 8da919b
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 25 deletions.
46 changes: 46 additions & 0 deletions src/helpers/CountdownTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useEffect, useState } from "react";

type CountdownProps = {
targetDate: Date;
};

const CountdownTimer: React.FC<CountdownProps> = ({ targetDate }) => {
const calculateTimeLeft = () => {
const now = new Date();
const difference = targetDate.getTime() - now.getTime();
let timeLeft = { hours: 0, minutes: 0 };

if (difference > 0) {
timeLeft = {
hours: Math.floor(difference / (1000 * 60 * 60)),
minutes: Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)),
};
}

return timeLeft;
};

const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());

useEffect(() => {
const timer = setTimeout(() => {
setTimeLeft(calculateTimeLeft());
}, 60000);

return () => clearTimeout(timer);
});

return (
<>
{timeLeft.hours > 0 || timeLeft.minutes > 0 ? (
<>
in {timeLeft.hours} hours, {timeLeft.minutes} minutes
</>
) : (
<> in 0 hours, 0 minutes</>
)}
</>
);
};

export default CountdownTimer;
29 changes: 29 additions & 0 deletions src/helpers/pricing/useGetDefillamaPrice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

interface DefillamaPriceResponse {
coins: {
[address: string]: {
symbol: string;
price: number;
};
};
}
/**Mainnet only addresses */
export const useGetDefillamaPrice = ({ addresses }: { addresses: string[] }) => {
return useQuery(
["useGetDefillamaPrice", addresses],
async () => {
try {
const joinedAddresses = addresses.map(address => `ethereum:${address}`).join(",");
const response = await axios.get<DefillamaPriceResponse>(
`https://coins.llama.fi/prices/current/${joinedAddresses}`,
);
return response.data.coins;
} catch (error) {
return {};
}
},
{ enabled: addresses.length > 0 },
);
};
2 changes: 1 addition & 1 deletion src/views/Range/RangeChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const RangeChart = (props: {
<Typography fontSize="15px" fontWeight={600} mt="33px">
Price
</Typography>
<DataRow title="Market Price" balance={formatCurrency(price ? price : currentPrice, 2, reserveSymbol)} />
<DataRow title="Snapshot Price" balance={formatCurrency(price ? price : currentPrice, 2, reserveSymbol)} />
{label === "now" && (
<>
<DataRow title="Target Price" balance={`${formatCurrency(targetPrice, 2, reserveSymbol)}`} />
Expand Down
30 changes: 22 additions & 8 deletions src/views/Range/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,10 @@ export const DetermineRangePrice = (bidOrAsk: "bid" | "ask") => {
bidOrAsk === "ask"
? upperBondMarket?.price.inBaseToken.gt(new DecimalBigNumber(rangeData.high.wall.price, 18))
: lowerBondMarket
? new DecimalBigNumber("1")
.div(lowerBondMarket?.price.inBaseToken)
.lt(new DecimalBigNumber(rangeData.low.wall.price, 18))
: false;
? new DecimalBigNumber("1")
.div(lowerBondMarket?.price.inBaseToken)
.lt(new DecimalBigNumber(rangeData.low.wall.price, 18))
: false;
if (sideActive && activeBondMarket && !bondOutsideWall) {
return {
price:
Expand All @@ -259,8 +259,8 @@ export const DetermineRangePrice = (bidOrAsk: "bid" | "ask") => {
? Number(upperBondMarket?.price.inBaseToken.toString())
: 0
: lowerBondMarket
? 1 / Number(lowerBondMarket?.price.inBaseToken.toString())
: 0,
? 1 / Number(lowerBondMarket?.price.inBaseToken.toString())
: 0,
contract: "bond" as RangeContracts,
discount:
bidOrAsk === "ask"
Expand Down Expand Up @@ -307,8 +307,8 @@ export const DetermineRangeDiscount = (bidOrAsk: "bid" | "ask") => {
? parseBigNumber(rangeData.low.wall.price, 18)
: parseBigNumber(rangeData.high.wall.price, 18)
: sellActive
? bidOrAskPrice.price
: bidOrAskPrice.price;
? bidOrAskPrice.price
: bidOrAskPrice.price;
const discount =
bondDiscount && !swapWithOperator
? bondDiscount
Expand Down Expand Up @@ -413,3 +413,17 @@ export const RangeSwap = () => {
},
);
};

export const RangeNextBeat = () => {
const networks = useTestableNetworks();
const contract = RANGE_PRICE_CONTRACT.getEthersContract(networks.MAINNET);
const { data, isFetched, isLoading } = useQuery(["getRangeNextBeat", networks.MAINNET], async () => {
const lastObservationTime = await contract.lastObservationTime();
const observationFrequency = await contract.observationFrequency();

//take the unix timestamp of the last observation time, add the observation frequency, and convert to date
const nextBeat = new Date((lastObservationTime + observationFrequency) * 1000);
return nextBeat;
});
return { data, isFetched, isLoading };
};
87 changes: 71 additions & 16 deletions src/views/Range/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { Box, CircularProgress, Grid, Link, Paper, Typography, useTheme } from "@mui/material";
import { DataRow, Icon, InfoNotification, OHMTokenProps, PrimaryButton } from "@olympusdao/component-library";
import {
DataRow,
Icon,
InfoNotification,
InfoTooltip,
Metric,
OHMTokenProps,
PrimaryButton,
} from "@olympusdao/component-library";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";
import PageTitle from "src/components/PageTitle";
import { WalletConnectedGuard } from "src/components/WalletConnectedGuard";
import { DAI_ADDRESSES, OHM_ADDRESSES } from "src/constants/addresses";
import { formatNumber, parseBigNumber } from "src/helpers";
import CountdownTimer from "src/helpers/CountdownTimer";
import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
import { useGetDefillamaPrice } from "src/helpers/pricing/useGetDefillamaPrice";
import { useBalance } from "src/hooks/useBalance";
import { usePathForNetwork } from "src/hooks/usePathForNetwork";
import { useOhmPrice } from "src/hooks/useProtocolMetrics";
import { useTestableNetworks } from "src/hooks/useTestableNetworks";
import { DetermineRangePrice, OperatorReserveSymbol, RangeBondMaxPayout, RangeData } from "src/views/Range/hooks";
import {
DetermineRangePrice,
OperatorPrice,
OperatorReserveSymbol,
RangeBondMaxPayout,
RangeData,
RangeNextBeat,
} from "src/views/Range/hooks";
import RangeChart from "src/views/Range/RangeChart";
import RangeConfirmationModal from "src/views/Range/RangeConfirmationModal";
import RangeInputForm from "src/views/Range/RangeInputForm";
Expand Down Expand Up @@ -44,7 +60,19 @@ export const Range = () => {
const { data: reserveBalance = new DecimalBigNumber("0", 18) } = useBalance(DAI_ADDRESSES)[networks.MAINNET];
const { data: ohmBalance = new DecimalBigNumber("0", 9) } = useBalance(OHM_ADDRESSES)[networks.MAINNET];

const currentPrice = useOhmPrice({}) || 0;
const { data: currentPrice } = OperatorPrice();
const { data: currentMarketPrices } = useGetDefillamaPrice({
addresses: [DAI_ADDRESSES[1], OHM_ADDRESSES[1]],
});
const { data: nextBeat } = RangeNextBeat();
//format date for display in local time
const nextBeatDateString = nextBeat && nextBeat.toLocaleString();

const daiPriceUSD = currentMarketPrices?.[`ethereum:${DAI_ADDRESSES[1]}`].price;
const ohmPriceUSD = currentMarketPrices?.[`ethereum:${OHM_ADDRESSES[1]}`].price;
const marketOhmPriceDAI = daiPriceUSD && ohmPriceUSD ? ohmPriceUSD / daiPriceUSD : 0;

console;

const maxString = sellActive ? `Max You Can Sell` : `Max You Can Buy`;

Expand Down Expand Up @@ -78,10 +106,10 @@ export const Range = () => {
}, [sellActive]);

useEffect(() => {
if (currentPrice < parseBigNumber(rangeData.low.cushion.price, 18)) {
if (marketOhmPriceDAI < parseBigNumber(rangeData.low.cushion.price, 18)) {
setSellActive(true);
}
}, [rangeData.low.cushion.price, currentPrice]);
}, [rangeData.low.cushion.price, marketOhmPriceDAI]);

const maxBalanceString = `${formatNumber(maxCapacity, 2)} ${buyAsset} (${formatNumber(
sellActive ? maxCapacity / bidPrice.price : maxCapacity * askPrice.price,
Expand Down Expand Up @@ -109,7 +137,7 @@ export const Range = () => {

const swapPriceFormatted = formatNumber(swapPrice, 2);

const discount = (currentPrice - swapPrice) / (sellActive ? -currentPrice : currentPrice);
const discount = (marketOhmPriceDAI - swapPrice) / (sellActive ? -marketOhmPriceDAI : marketOhmPriceDAI);

const hasPrice = (sellActive && askPrice.price) || (!sellActive && bidPrice.price) ? true : false;

Expand Down Expand Up @@ -145,6 +173,11 @@ export const Range = () => {
<Paper sx={{ width: "98%" }}>
{currentPrice ? (
<>
<Metric
label="Market Price"
metric={`${formatNumber(marketOhmPriceDAI, 2)} DAI`}
tooltip="Market Price uses DefiLlama API, and is also used when calculating premium/discount on RBS"
/>
<Grid container>
<Grid item xs={12} lg={6}>
{!rangeDataLoading && (
Expand All @@ -157,6 +190,26 @@ export const Range = () => {
askPrice={askPrice.price}
sellActive={sellActive}
/>
<Typography fontSize="18px" fontWeight="500" mt="12px">
Snapshot
</Typography>
<DataRow
title="Last Snapshot Price"
balance={
<Box display="flex" alignItems="center">
{formatNumber(currentPrice, 2)} {reserveSymbol}
<Box display="flex" fontSize="12px" alignItems="center">
<InfoTooltip
message={`Snapshot Price is returned from price feed connected to RBS Operator. The current price feed is Chainlink, which updates price if there's a 2% deviation or 24 hours, whichever comes first. The Snapshot Price is used by RBS Operator to turn on/off bond markets.`}
/>
</Box>
</Box>
}
/>
<DataRow
title="Estimated Next Snapshot"
balance={<>{nextBeat && <CountdownTimer targetDate={nextBeat} />}</>}
/>
</Box>
)}
</Grid>
Expand Down Expand Up @@ -200,7 +253,7 @@ export const Range = () => {
? "premium"
: "discount"}{" "}
of {formatNumber(Math.abs(discount) * 100, 2)}% relative to market price of{" "}
{formatNumber(currentPrice, 2)} {reserveSymbol}
{formatNumber(marketOhmPriceDAI, 2)} {reserveSymbol}
</InfoNotification>
</Box>
<div data-testid="max-row">
Expand All @@ -218,18 +271,20 @@ export const Range = () => {
: "Discount"
}
balance={
<Typography
sx={{
color: discount > 0 ? theme.colors.feedback.pnlGain : theme.colors.feedback.error,
}}
>
{formatNumber(Math.abs(discount) * 100, 2)}%
</Typography>
<Box display="flex" alignItems="center" style={{ fontSize: "12px" }}>
<Typography
sx={{
color: discount > 0 ? theme.colors.feedback.pnlGain : theme.colors.feedback.error,
}}
>
{formatNumber(Math.abs(discount) * 100, 2)}%
</Typography>
</Box>
}
/>
</div>
<div data-testid="swap-price">
<DataRow title={`Swap Price per OHM`} balance={swapPriceFormatted} />
<DataRow title={`RBS quote per OHM`} balance={`${swapPriceFormatted} ${reserveSymbol}`} />
</div>
<Box mt="8px">
<WalletConnectedGuard fullWidth>
Expand Down

0 comments on commit 8da919b

Please sign in to comment.