335 lines
13 KiB
JavaScript
335 lines
13 KiB
JavaScript
import { useRef, useMemo, useState, useEffect } from "react";
|
|
import { encodeFunctionData } from 'viem';
|
|
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
|
|
|
|
import Modal from "../../../../components/Modal/Modal";
|
|
import Select from "../../../../components/Select/Select";
|
|
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
|
|
import { shorten } from "../../../../helpers";
|
|
import { useTokenSymbol } from "../../../../hooks/tokens";
|
|
|
|
import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./index";
|
|
|
|
import {
|
|
RESERVE_ADDRESSES,
|
|
FTSO_DAI_LP_ADDRESSES,
|
|
BOND_DEPOSITORY_ADDRESSES,
|
|
} from "../../../../constants/addresses";
|
|
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
|
|
|
export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, intervals, booleans) => {
|
|
const value = 0n;
|
|
const label = "create";
|
|
const target = BOND_DEPOSITORY_ADDRESSES[chainId];
|
|
const calldata = encodeFunctionData({
|
|
abi: DepositoryAbi,
|
|
functionName: 'create',
|
|
args: [markets, terms, quoteToken, intervals, booleans]
|
|
});
|
|
return { label, target, calldata, value };
|
|
}
|
|
|
|
export const prepareCreateBondDescription = "function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol.";
|
|
|
|
export const CreateBondParsed = (props) => {
|
|
const [isOpened, setIsOpened] = useState(false);
|
|
const { symbol: ftsoSymbol } = useTokenSymbol(props.chainId, "FTSO");
|
|
return (
|
|
<>
|
|
<Modal
|
|
headerContent={
|
|
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
|
<Typography variant="h4">View create</Typography>
|
|
</Box>
|
|
}
|
|
open={isOpened}
|
|
onClose={() => setIsOpened(false)}
|
|
maxWidth="460px"
|
|
minHeight="200px"
|
|
>
|
|
<Box minHeight="220px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
|
<CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} />
|
|
</Box>
|
|
</Modal>
|
|
{props.isTable && <CreateBondParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props}/>}
|
|
</>
|
|
)
|
|
}
|
|
|
|
const CreateBondParsedCell = (props) => {
|
|
return <ParsedCell {...props} />
|
|
}
|
|
|
|
export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata, args }) => {
|
|
const createMode = args === undefined;
|
|
|
|
const [step, setStep] = useState(1);
|
|
const [nextDisabled, setNextDisabled] = useState(false);
|
|
|
|
const [capacity, setCapacity] = useState(args?.at(0)?.at(0));
|
|
const [initialPrice, setInitialPrice] = useState(args?.at(0)?.at(1));
|
|
const [debtBuffer, setDebtBuffer] = useState(args?.at(0)?.at(2));
|
|
|
|
const [depositInterval, setDepositInterval] = useState(args?.at(3)?.at(0));
|
|
const [tuneInterval, setTuneInterval] = useState(args?.at(3)?.at(1));
|
|
|
|
const [tokenAddress, setTokenAddress] = useState(args?.at(2));
|
|
const [capacityInQuote, setCapacityInQuote] = useState(args?.at(4)?.at(0) ?? true);
|
|
const [fixedTerm, setFixedTerm] = useState(args?.at(4)?.at(1) ?? false);
|
|
|
|
const [vestingLength, setVestingLength] = useState(args?.at(1)?.at(0));
|
|
const [conclusionTimestamp, setConclusionTimestamp] = useState(args?.at(1)?.at(1));
|
|
|
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, RESERVE_ADDRESSES[chainId]);
|
|
|
|
const handleProceed = () => {
|
|
const markets = [capacity, initialPrice, debtBuffer];
|
|
const terms = [vestingLength, conclusionTimestamp];
|
|
const intervals = [depositInterval, tuneInterval];
|
|
const booleans = [capacityInQuote, fixedTerm];
|
|
|
|
addCalldata(prepareCreateBondCalldata(chainId, markets, terms, tokenAddress, intervals, booleans))
|
|
}
|
|
|
|
const empty = () => {};
|
|
|
|
const incrementStep = () => {
|
|
setStep(prev => prev + 1);
|
|
}
|
|
|
|
const decrementStep = () => {
|
|
if (step > 1) setStep(prev => prev - 1);
|
|
else toInitialStep();
|
|
}
|
|
|
|
const isNextDisabled = useMemo(() => {
|
|
switch (step) {
|
|
case 1:
|
|
return tokenAddress === undefined;
|
|
case 2:
|
|
return !capacity || !initialPrice || !debtBuffer;
|
|
case 3:
|
|
return !depositInterval || !tuneInterval;
|
|
case 4:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
|
|
|
const possibleTokens = [
|
|
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
|
|
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
|
|
];
|
|
|
|
return (
|
|
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent="space-between">
|
|
{step === 1 && <TokenAndBooleansArguments
|
|
tokenAddress={tokenAddress}
|
|
createMode={createMode}
|
|
possibleTokens={possibleTokens}
|
|
setTokenAddress={createMode ? setTokenAddress : empty}
|
|
nativeCurrency={reserveSymbol}
|
|
ftsoSymbol={ftsoSymbol}
|
|
capacityInQuote={capacityInQuote}
|
|
setCapacityInQuote={createMode ? setCapacityInQuote : empty}
|
|
fixedTerm={fixedTerm}
|
|
setFixedTerm={createMode ? setFixedTerm : empty}
|
|
/>}
|
|
{step === 2 && <MarketArguments
|
|
capacityInQuote={capacityInQuote}
|
|
currencySymbol={possibleTokens.find(token => token.value === tokenAddress).symbol}
|
|
ftsoSymbol={ftsoSymbol}
|
|
capacity={capacity}
|
|
setCapacity={createMode ? setCapacity : empty}
|
|
initialPrice={initialPrice}
|
|
setInitialPrice={createMode ? setInitialPrice : empty}
|
|
debtBuffer={debtBuffer}
|
|
setDebtBuffer={createMode ? setDebtBuffer : empty}
|
|
/>}
|
|
{step === 3 && <IntervalsArguments
|
|
depositInterval={depositInterval}
|
|
setDepositInterval={createMode ? setDepositInterval : empty}
|
|
tuneInterval={tuneInterval}
|
|
setTuneInterval={createMode ? setTuneInterval : empty}
|
|
/>}
|
|
{step === 4 && <TermsAgruments
|
|
fixedTerm={fixedTerm}
|
|
vestingLength={vestingLength}
|
|
setVestingLength={createMode ? setVestingLength : empty}
|
|
conclusionTimestamp={conclusionTimestamp}
|
|
setConclusionTimestamp={createMode ? setConclusionTimestamp : empty}
|
|
/>}
|
|
<Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
|
<Box display="flex" gap="10px">
|
|
<TertiaryButton onClick={() => decrementStep()} fullWidth>Back</TertiaryButton>
|
|
<TertiaryButton disabled={isNextDisabled} onClick={() => incrementStep()} fullWidth>Next</TertiaryButton>
|
|
</Box>
|
|
{(step === 4 && createMode) && <PrimaryButton onClick={() => handleProceed()} fullWidth>Create Function</PrimaryButton>}
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
const MarketArguments = ({
|
|
capacityInQuote,
|
|
currencySymbol,
|
|
ftsoSymbol,
|
|
capacity,
|
|
setCapacity,
|
|
initialPrice,
|
|
setInitialPrice,
|
|
debtBuffer,
|
|
setDebtBuffer
|
|
}) => {
|
|
return (
|
|
<Box>
|
|
<ArgumentInput
|
|
endString={capacityInQuote ? currencySymbol : ftsoSymbol}
|
|
label="market[0]"
|
|
value={capacity ?? ""}
|
|
setValue={setCapacity}
|
|
tooltip={`Total capacity limit of the bond market denominated in ${capacityInQuote ? currencySymbol : ftsoSymbol}, representing the maximum amount of bonds that can be sold in this market.`}
|
|
/>
|
|
<ArgumentInput
|
|
endString={currencySymbol}
|
|
label="market[1]"
|
|
value={initialPrice ?? ""}
|
|
setValue={setInitialPrice}
|
|
tooltip={`Initial bond price of 1 ${ftsoSymbol} denominated in ${currencySymbol}. The bond price adjusts dynamically via Dutch auction based on market demand.`}
|
|
/>
|
|
<ArgumentInput
|
|
endString="%"
|
|
label="market[2]"
|
|
value={debtBuffer ?? ""}
|
|
setValue={setDebtBuffer}
|
|
tooltip="Debt buffer in basis points (e.g. 30,000 = 30%). Determines how far bond price can fall from initial price before tuning is triggered."
|
|
/>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
const IntervalsArguments = ({
|
|
depositInterval,
|
|
setDepositInterval,
|
|
tuneInterval,
|
|
setTuneInterval,
|
|
}) => {
|
|
return (
|
|
<Box>
|
|
<ArgumentInput
|
|
endString="seconds"
|
|
label="_intervals[0]"
|
|
value={depositInterval ?? ""}
|
|
setValue={setDepositInterval}
|
|
tooltip="Time in seconds that determines per-transaction bond purchase limit based on time until expiration. Higher values = larger per-transaction purchases allowed."
|
|
/>
|
|
<ArgumentInput
|
|
endString="seconds"
|
|
label="_intervals[0]"
|
|
value={tuneInterval ?? ""}
|
|
setValue={setTuneInterval}
|
|
tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval."
|
|
/>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
const TermsAgruments = ({
|
|
fixedTerm,
|
|
vestingLength,
|
|
setVestingLength,
|
|
conclusionTimestamp,
|
|
setConclusionTimestamp,
|
|
}) => {
|
|
return (
|
|
<Box>
|
|
<ArgumentInput
|
|
endString="seconds"
|
|
label={"_terms[0]"}
|
|
value={vestingLength ?? ""}
|
|
setValue={setVestingLength}
|
|
tooltip={fixedTerm
|
|
? "Time in seconds representing bond vesting schedule. Bond becomes fully claimable after this time elapses from purchase date."
|
|
: "Absolute Unix timestamp when all bonds mature and become fully claimable, regardless of the purchase date."
|
|
}
|
|
/>
|
|
<ArgumentInput
|
|
endString="seconds"
|
|
label="_terms[1]"
|
|
value={conclusionTimestamp ?? ""}
|
|
setValue={setConclusionTimestamp}
|
|
tooltip="Unix timestamp when bond market stops accepting new purchases. Bond market closes after this time."
|
|
/>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
const TokenAndBooleansArguments = ({
|
|
createMode,
|
|
tokenAddress,
|
|
possibleTokens,
|
|
nativeCurrency,
|
|
ftsoSymbol,
|
|
setTokenAddress,
|
|
capacityInQuote,
|
|
setCapacityInQuote,
|
|
fixedTerm,
|
|
setFixedTerm,
|
|
}) => {
|
|
const theme = useTheme();
|
|
const [selectedOption, setSelectedOption] = useState(tokenAddress);
|
|
|
|
const handleChange = (event) => {
|
|
if (createMode) {
|
|
setSelectedOption(event.target.value);
|
|
setTokenAddress(event.target.value);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Box>
|
|
<BooleanTrigger
|
|
value={capacityInQuote}
|
|
label="_booleans[0]"
|
|
leftText={"True"}
|
|
rightText={"False"}
|
|
setLeftValue={() => setCapacityInQuote(true)}
|
|
setRightValue={() => setCapacityInQuote(false)}
|
|
tooltip={`Determines how the bond market capacity is measured. True = measured in _quoteToken, False = measured in ${ftsoSymbol}.`}
|
|
/>
|
|
<BooleanTrigger
|
|
value={fixedTerm}
|
|
label="_booleans[1]"
|
|
leftText="True"
|
|
rightText="False"
|
|
setLeftValue={() => setFixedTerm(true)}
|
|
setRightValue={() => setFixedTerm(false)}
|
|
tooltip={`Defines the bond maturity model. True = each purchase matures after vesting duration from its purchase date. False = all purchases mature at the conclusion timestamp.`}
|
|
/>
|
|
<ArgumentsWrapper
|
|
label="_quoteToken"
|
|
tooltip={`ERC20 token that users pay with to purchase bonds (e.g. ${nativeCurrency} or ${nativeCurrency}-${ftsoSymbol} LP token).`}
|
|
>
|
|
<Select
|
|
value={selectedOption ?? ""}
|
|
onChange={handleChange}
|
|
options={possibleTokens}
|
|
inputWidth="100%"
|
|
renderValue={(selected) => {
|
|
if (!selected || selected.length === 0) {
|
|
return (
|
|
<span style={{ color: theme.colors.gray[500], opacity: 0.7 }}>
|
|
Select payment token
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return possibleTokens.find(opt => opt.value === selected)?.label || selected;
|
|
}}
|
|
/>
|
|
</ArgumentsWrapper>
|
|
</Box>
|
|
)
|
|
}
|