ghost-dao-interface/src/containers/Governance/components/functions/CreateBond.jsx
Uncle Fatso aaae299b32
app rev.8
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-02-20 12:12:53 +03:00

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>
)
}