diff --git a/packages/dev-frontend/src/components/Bonds/BondsTable.tsx b/packages/dev-frontend/src/components/Bonds/BondsTable.tsx index aaf0e5e09..95cdb02a0 100644 --- a/packages/dev-frontend/src/components/Bonds/BondsTable.tsx +++ b/packages/dev-frontend/src/components/Bonds/BondsTable.tsx @@ -6,6 +6,8 @@ import * as lexicon from "./lexicon"; import { Empty } from "./views/idle/Empty"; import { Link } from "../Link"; import { useBondView } from "./context/BondViewContext"; +import { Decimal } from "@liquity/lib-base"; +import { InfiniteEstimate } from "./views/InfiniteEstimation"; const { BONDS, @@ -34,6 +36,8 @@ const formatDays = (days: number) => ? "Now" : parseFloat(days.toFixed(1)) < 1 ? `${days.toFixed(1)} days` + : days > 10000 + ? Decimal.INFINITY.toString() : `${days.toFixed(0)} days`; const Line = (columns: number) => @@ -107,17 +111,23 @@ export const BondsTable: React.FC = () => { {Line(5)} {pendingBonds.map((bond, idx) => { - const breakEvenDays = - (bond.breakEvenTime.getTime() - Date.now()) / 1000 / 60 / 60 / 24; - const rebondDays = (bond.rebondTime.getTime() - Date.now()) / 1000 / 60 / 60 / 24; + const breakEvenDays = formatDays( + (bond.breakEvenTime.getTime() - Date.now()) / 1000 / 60 / 60 / 24 + ); + const rebondDays = formatDays( + (bond.rebondTime.getTime() - Date.now()) / 1000 / 60 / 60 / 24 + ); return ( {bond.deposit.shorten()} LUSD {bond.accrued.shorten()} bLUSD {bond.marketValue.shorten()} LUSD - {formatDays(breakEvenDays)} - {formatDays(rebondDays)} - + + + + + + {Line(5)} ); diff --git a/packages/dev-frontend/src/components/Bonds/context/api.ts b/packages/dev-frontend/src/components/Bonds/context/api.ts index e01b550cb..27f899114 100644 --- a/packages/dev-frontend/src/components/Bonds/context/api.ts +++ b/packages/dev-frontend/src/components/Bonds/context/api.ts @@ -574,7 +574,7 @@ const getProtocolInfo = async ( rebondPeriodInDays } = _getProtocolInfo(marketPrice, floorPrice, claimBondFee, alphaAccrualFactor); - const simulatedMarketPrice = hasMarketPremium ? marketPrice : floorPrice.mul(1.1); + const simulatedMarketPrice = marketPrice; const controllerTargetAge = Decimal.from( (await chickenBondManager.targetAverageAgeSeconds()).toString() diff --git a/packages/dev-frontend/src/components/Bonds/lexicon.ts b/packages/dev-frontend/src/components/Bonds/lexicon.ts index f0855817d..38f2687d1 100644 --- a/packages/dev-frontend/src/components/Bonds/lexicon.ts +++ b/packages/dev-frontend/src/components/Bonds/lexicon.ts @@ -224,3 +224,8 @@ export const TREASURY_PERMANENT = { export const ESTIMATES_ONLY_NOTICE = { description: "These metrics are estimations based on the current bLUSD market price" }; + +export const INFINITE_ESTIMATION = { + description: + "The market price premium is currently too low to make bonding profitable. Bonds will be profitable again if the premium returns." +}; diff --git a/packages/dev-frontend/src/components/Bonds/views/InfiniteEstimation.tsx b/packages/dev-frontend/src/components/Bonds/views/InfiniteEstimation.tsx new file mode 100644 index 000000000..03f671f57 --- /dev/null +++ b/packages/dev-frontend/src/components/Bonds/views/InfiniteEstimation.tsx @@ -0,0 +1,23 @@ +import { Decimal, Decimalish } from "@liquity/lib-base"; +import { InfoIcon } from "../../InfoIcon"; +import { Card } from "theme-ui"; +import * as l from "../lexicon"; + +type InfiniteEstimateProps = { + estimate: Decimalish; +}; + +export const InfiniteEstimate: React.FC = ({ estimate, children }) => { + if (estimate.toString() !== Decimal.INFINITY.toString()) return <>{children ?? estimate}; + + return ( + <> + {Decimal.INFINITY.toString()} + {l.INFINITE_ESTIMATION.description}} + /> +   + + ); +}; diff --git a/packages/dev-frontend/src/components/Bonds/views/actioning/Actioning.tsx b/packages/dev-frontend/src/components/Bonds/views/actioning/Actioning.tsx index 99a544599..a9830d9a9 100644 --- a/packages/dev-frontend/src/components/Bonds/views/actioning/Actioning.tsx +++ b/packages/dev-frontend/src/components/Bonds/views/actioning/Actioning.tsx @@ -12,6 +12,7 @@ import { Warning } from "../../../Warning"; import { ReactModal } from "../../../ReactModal"; import { percentify } from "../../utils"; import { Decimal } from "@liquity/lib-base"; +import { InfiniteEstimate } from "../InfiniteEstimation"; export const Actioning: React.FC = () => { const { dispatchEvent, view, selectedBond: bond } = useBondView(); @@ -49,7 +50,11 @@ export const Actioning: React.FC = () => { label: ( <> - {`${bond.breakEvenAccrual.prettify(2)} bLUSD`} + + + {bond.breakEvenAccrual.prettify(2)} bLUSD + + ) }, @@ -58,7 +63,11 @@ export const Actioning: React.FC = () => { label: ( <> - {`${bond.rebondAccrual.prettify(2)} bLUSD`} + + + {bond.rebondAccrual.prettify(2)} bLUSD + + ) }, diff --git a/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx b/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx index c47d08f5a..68d802725 100644 --- a/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx +++ b/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx @@ -6,7 +6,7 @@ import { EditableRow } from "../../../Trove/Editor"; import { Record } from "../../Record"; import { InfoIcon } from "../../../InfoIcon"; import { useBondView } from "../../context/BondViewContext"; -import { HorizontalTimeline, Label, SubLabel } from "../../../HorizontalTimeline"; +import { HorizontalTimeline, Label, SubLabel, UNKNOWN_DATE } from "../../../HorizontalTimeline"; import { EXAMPLE_NFT } from "../../context/BondViewProvider"; import * as l from "../../lexicon"; import { useWizard } from "../../../Wizard/Context"; @@ -25,6 +25,7 @@ import { import { HorizontalSlider } from "../../../HorizontalSlider"; import { ErrorDescription } from "../../../ErrorDescription"; import { Amount } from "../../../ActionDescription"; +import { InfiniteEstimate } from "../InfiniteEstimation"; type DetailsProps = { onBack?: () => void }; @@ -70,6 +71,7 @@ export const Details: React.FC = ({ onBack }) => { if (protocolInfo === undefined || simulatedProtocolInfo === undefined || lusdBalance === undefined) return null; + const hasMarketPremium = simulatedProtocolInfo.hasMarketPremium; const depositMinusClaimBondFee = Decimal.ONE.sub(protocolInfo.claimBondFee).mul(deposit); const rebondReturn = getReturn( depositMinusClaimBondFee.mul(simulatedProtocolInfo.rebondAccrualFactor), @@ -77,7 +79,7 @@ export const Details: React.FC = ({ onBack }) => { simulatedProtocolInfo.simulatedMarketPrice ); const rebondRoi = rebondReturn / toFloat(deposit) || 0; - const marketPriceMin = protocolInfo.floorPrice.add(0.015).prettify(2); // Add 0.015 to prevent market_price=floor_price infinity issues + const marketPriceMin = protocolInfo.floorPrice.mul(1.025).prettify(2); // Enough to display what happens below the 3% chicken in fee const marketPriceMax = Decimal.max( protocolInfo.marketPrice.mul(1.1), protocolInfo.floorPrice.mul(1.5) @@ -102,19 +104,31 @@ export const Details: React.FC = ({ onBack }) => { protocolInfo.claimBondFee ); - const breakEvenTime = getRebondOrBreakEvenTimeWithControllerAdjustment( - Decimal.ZERO, - simulatedProtocolInfo.controllerTargetAge, - simulatedProtocolInfo.averageBondAge, - Decimal.from(breakEvenDays) - ); + const breakEvenTime = breakEvenDays.eq(Decimal.INFINITY) + ? UNKNOWN_DATE + : getRebondOrBreakEvenTimeWithControllerAdjustment( + Decimal.ZERO, + simulatedProtocolInfo.controllerTargetAge, + simulatedProtocolInfo.averageBondAge, + breakEvenDays + ); - const rebondTime = getRebondOrBreakEvenTimeWithControllerAdjustment( - Decimal.ZERO, - simulatedProtocolInfo.controllerTargetAge, - simulatedProtocolInfo.averageBondAge, - Decimal.from(rebondDays) - ); + const rebondTime = rebondDays.eq(Decimal.INFINITY) + ? UNKNOWN_DATE + : getRebondOrBreakEvenTimeWithControllerAdjustment( + Decimal.ZERO, + simulatedProtocolInfo.controllerTargetAge, + simulatedProtocolInfo.averageBondAge, + rebondDays + ); + + const breakEvenAccrual = hasMarketPremium + ? depositMinusClaimBondFee.mul(simulatedProtocolInfo.breakEvenAccrualFactor) + : Decimal.INFINITY; + + const rebondAccrual = hasMarketPremium + ? depositMinusClaimBondFee.mul(simulatedProtocolInfo.rebondAccrualFactor) + : Decimal.INFINITY; return ( <> @@ -169,9 +183,11 @@ export const Details: React.FC = ({ onBack }) => { label: ( <> - {`${depositMinusClaimBondFee - .mul(simulatedProtocolInfo.breakEvenAccrualFactor) - .prettify(2)} bLUSD`} + + + {breakEvenAccrual.prettify(2)} bLUSD + + ) }, @@ -182,9 +198,11 @@ export const Details: React.FC = ({ onBack }) => { - {`${depositMinusClaimBondFee - .mul(simulatedProtocolInfo.rebondAccrualFactor) - .prettify(2)} bLUSD`} + + + {rebondAccrual.prettify(2)} bLUSD + + ) } @@ -207,21 +225,25 @@ export const Details: React.FC = ({ onBack }) => { @@ -235,11 +257,18 @@ export const Details: React.FC = ({ onBack }) => { max={marketPriceMax} type="LUSD" onSliderChange={value => setSimulatedMarketPrice(value)} - onReset={() => setSimulatedMarketPrice(protocolInfo.marketPrice)} + onReset={() => resetSimulatedMarketPrice()} /> {statuses.CREATE === "FAILED" && Failed to create bond. Please try again.} + {!protocolInfo.hasMarketPremium && ( + + When the market price is less than 3% above the floor price, it's not profitable to bond. + Buying bLUSD from the market currently generates a higher return than bonding. + + )} + {!isDepositEnough && The minimum bond amount is 100 LUSD.} {doesDepositExceedBalance && ( diff --git a/packages/dev-frontend/src/components/Bonds/views/idle/Bond.tsx b/packages/dev-frontend/src/components/Bonds/views/idle/Bond.tsx index 794f9d5bd..cb1559a97 100644 --- a/packages/dev-frontend/src/components/Bonds/views/idle/Bond.tsx +++ b/packages/dev-frontend/src/components/Bonds/views/idle/Bond.tsx @@ -7,6 +7,7 @@ import { Label, SubLabel } from "../../../HorizontalTimeline"; import * as l from "../../lexicon"; import { statuses, useBondView } from "../../context/BondViewContext"; import { useBondAddresses } from "../../context/BondAddressesContext"; +import { InfiniteEstimate } from "../InfiniteEstimation"; const getBondEvents = (bond: BondType): EventType[] => { const events = [ @@ -14,9 +15,7 @@ const getBondEvents = (bond: BondType): EventType[] => { date: new Date(bond.startTime), label: ( <> - + {`0.00 bLUSD`} ) @@ -55,7 +54,11 @@ const getBondEvents = (bond: BondType): EventType[] => { label: ( <> - {`${bond?.breakEvenAccrual?.prettify(2) ?? "?"} bLUSD`} + + + {bond?.breakEvenAccrual?.prettify(2) ?? "?"} bLUSD + + ) }); @@ -65,7 +68,11 @@ const getBondEvents = (bond: BondType): EventType[] => { label: ( <> - {`${bond?.rebondAccrual?.prettify(2) ?? "?"} bLUSD`} + + + {bond?.rebondAccrual?.prettify(2) ?? "?"} bLUSD + + ) });