Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] - RBS Price Display Improvements #3144

Merged
merged 5 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading