implement new breakout logic
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
f237e2c037
commit
cdf1f7cabf
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ghost-dao-interface",
|
||||
"private": true,
|
||||
"version": "0.7.23",
|
||||
"version": "0.7.24",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
19
src/App.jsx
19
src/App.jsx
@ -15,11 +15,13 @@ import {
|
||||
useSwitchChain,
|
||||
injected
|
||||
} from "wagmi";
|
||||
import { watchChainId } from '@wagmi/core'
|
||||
|
||||
import Messages from "./components/Messages/Messages";
|
||||
import NavDrawer from "./components/Sidebar/NavDrawer";
|
||||
import Sidebar from "./components/Sidebar/Sidebar";
|
||||
import TopBar from "./components/TopBar/TopBar";
|
||||
import BreakoutModal from "./containers/Breakout/BreakoutModal";
|
||||
|
||||
import { shouldTriggerSafetyCheck } from "./helpers";
|
||||
import { isNetworkAvailable } from "./constants";
|
||||
@ -150,15 +152,15 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && chainId !== addressChainId) {
|
||||
const toastId = toast.loading("You are connected to wrong network. Use one of the deployed networks please.", {
|
||||
position: 'bottom-right'
|
||||
});
|
||||
setWrongNetworkToastId(toastId);
|
||||
} else {
|
||||
if (wrongNetworkToastId) {
|
||||
toast.dismiss(wrongNetworkToastId);
|
||||
setWrongNetworkToastId(null);
|
||||
if (wrongNetworkToastId === null) {
|
||||
const toastId = toast.loading("You are connected to wrong network. Use one of the deployed networks please.", {
|
||||
position: 'bottom-right'
|
||||
});
|
||||
setWrongNetworkToastId(toastId);
|
||||
}
|
||||
} else {
|
||||
toast.dismiss(wrongNetworkToastId);
|
||||
setWrongNetworkToastId(null);
|
||||
}
|
||||
}, [chainId, addressChainId, isConnected, wrongNetworkToastId])
|
||||
|
||||
@ -209,6 +211,7 @@ function App() {
|
||||
|
||||
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
|
||||
<Suspense fallback={<div></div>}>
|
||||
<BreakoutModal chainId={chainId} address={address} />
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to={chainExists ? `/${chains.at(0).name.toLowerCase()}/dashboard` : "/empty"} />} />
|
||||
{chainExists &&
|
||||
|
||||
@ -86,8 +86,8 @@ const Token = ({ chainTokenName, name, viewBox = "0 0 260 260", fontSize = "larg
|
||||
position: "absolute",
|
||||
marginLeft: "70%",
|
||||
marginTop: "45%",
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
width: "45%",
|
||||
height: "45%",
|
||||
border: "1px solid #fff",
|
||||
borderRadius: "100%"
|
||||
}}
|
||||
|
||||
@ -205,7 +205,10 @@ export const useWallet = (chainId, userAddress) => {
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
const nativeSymbol = useMemo(() => {
|
||||
return config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
}, [config]);
|
||||
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
|
||||
@ -13,14 +13,16 @@ import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||
|
||||
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||
import { isNetworkLegacy } from "../../../constants";
|
||||
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { formatCurrency } from "../../../helpers";
|
||||
|
||||
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
|
||||
import { useNotes, redeem } from "../../../hooks/bonds";
|
||||
import { useNotes, redeem, forceRedeem } from "../../../hooks/bonds";
|
||||
import { useTokenSymbol } from "../../../hooks/tokens";
|
||||
import { useGhstPrice } from "../../../hooks/prices";
|
||||
import { useBreakoutModal } from "../../../hooks/breakoutModal";
|
||||
|
||||
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
const isSmallScreen = useScreenSize("md");
|
||||
@ -30,6 +32,7 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
const [isPayoutGhst, _] = useState(true);
|
||||
|
||||
const ghstPrice = useGhstPrice(chainId);
|
||||
const { breakoutFromBonding } = useBreakoutModal();
|
||||
const { epoch } = useEpoch(chainId);
|
||||
const { warmupExists } = useWarmupLength(chainId);
|
||||
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
|
||||
@ -51,20 +54,35 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
);
|
||||
|
||||
const onSubmit = async (indexes) => {
|
||||
const isFundsInWarmup = warmupInfo.deposit._value > 0n;
|
||||
if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
|
||||
setIsWapmup(true);
|
||||
} else {
|
||||
setIsPending(true);
|
||||
await redeem({
|
||||
chainId,
|
||||
user: address,
|
||||
isGhst: isPayoutGhst,
|
||||
indexes
|
||||
});
|
||||
setIsPending(true);
|
||||
|
||||
const defaultFunction = async () => {
|
||||
await redeem({ chainId, user: address, isGhst: isPayoutGhst, indexes });
|
||||
await notesRefetch();
|
||||
setIsPending(false);
|
||||
};
|
||||
|
||||
if (isNetworkLegacy(chainId)) {
|
||||
await defaultFunction();
|
||||
// const isFundsInWarmup = warmupInfo.deposit._value > 0n;
|
||||
// if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
|
||||
// setIsWapmup(true);
|
||||
// } else {
|
||||
// await defaultFunction();
|
||||
// }
|
||||
} else {
|
||||
const warmupLeft = warmupInfo.expiry - epoch.number;
|
||||
const amount = notes
|
||||
.filter(note => indexes.includes(note.id))
|
||||
.reduce((sum, note) => sum.add(note.payout), new DecimalBigNumber(0, 0));
|
||||
|
||||
const toExecute = (receiver) => {
|
||||
forceRedeem({ chainId, user: address, receiver, indexes });
|
||||
notesRefetch();
|
||||
}
|
||||
|
||||
breakoutFromBonding({ defaultFunction, toExecute, amount, warmupLeft })
|
||||
}
|
||||
setIsPending(false);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
475
src/containers/Breakout/BreakoutModal.jsx
Normal file
475
src/containers/Breakout/BreakoutModal.jsx
Normal file
@ -0,0 +1,475 @@
|
||||
import { useMemo, useState, useCallback, useEffect } from "react";
|
||||
import { Box, Typography, Link, Checkbox, FormControlLabel, useTheme } from "@mui/material";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getBlockNumber } from "@wagmi/core";
|
||||
import { useConfig } from "wagmi";
|
||||
import { ss58Decode } from "@polkadot-labs/hdkd-helpers";
|
||||
import { toHex } from "@polkadot-api/utils";
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||
|
||||
import Metric from "../../components/Metric/Metric";
|
||||
import Modal from "../../components/Modal/Modal";
|
||||
import SwapCard from "../../components/Swap/SwapCard";
|
||||
import Token from "../../components/Token/Token";
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import { PrimaryButton, SecondaryButton } from "../../components/Button";
|
||||
import { GATEKEEPER_ADDRESSES, EMPTY_ADDRESS } from "../../constants/addresses";
|
||||
|
||||
import { useLocalStorage } from "../../hooks/localstorage";
|
||||
import { useBreakoutModal } from "../../hooks/breakoutModal";
|
||||
import { useTokenSymbol, useCirculatingSupply } from "../../hooks/tokens";
|
||||
import { useEpoch, useGatekeeperApy, useGatekeeperAddress } from "../../hooks/staking";
|
||||
import { useEvmNetwork, useCurrentIndex } from "../../hooks/ghost";
|
||||
import { formatNumber, shorten } from "../../helpers";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
|
||||
const BreakoutModal = ({ chainId, address }) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [receiver, setReceiver] = useState("");
|
||||
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
|
||||
const evmNetwork = useEvmNetwork({ evmChainId: chainId });
|
||||
|
||||
const {
|
||||
isOpened,
|
||||
closeModal: closeModalInner,
|
||||
isStakingOpened,
|
||||
isClaimBondOpened,
|
||||
warmupPeriod,
|
||||
setActiveTxIndex,
|
||||
defaultFunction,
|
||||
executableFunction,
|
||||
estimatedAmount
|
||||
} = useBreakoutModal();
|
||||
|
||||
const incomingFee = useMemo(() => {
|
||||
return new DecimalBigNumber(
|
||||
evmNetwork ? evmNetwork.incoming_fee : 100000000,
|
||||
7
|
||||
);
|
||||
}, [evmNetwork]);
|
||||
|
||||
const closeModal = () => {
|
||||
setActiveTxIndex(-1);
|
||||
closeModalPure();
|
||||
}
|
||||
|
||||
const closeModalPure = () => {
|
||||
setStep(0);
|
||||
setReceiver("");
|
||||
closeModalInner();
|
||||
}
|
||||
|
||||
const header = useMemo(() => {
|
||||
if (isStakingOpened && warmupPeriod <= 0) return "Stake Warmed-up"
|
||||
if (isClaimBondOpened && warmupPeriod <= 0) return "Bond Warmed-up"
|
||||
if (isStakingOpened && warmupPeriod > 0) return "Stake in Warmup"
|
||||
if (isClaimBondOpened && warmupPeriod > 0) return "Bond in Warmup"
|
||||
}, [isStakingOpened, isClaimBondOpened, warmupPeriod]);
|
||||
|
||||
const bridgeNumbers = useMemo(() => {
|
||||
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
|
||||
const number = 1 + connectedNetworks * 3;
|
||||
return `(${number}, ${number})`;
|
||||
}, [chainId]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
headerContent={
|
||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||
<Typography variant="h4">{step === 0 ? header : step === 1 ? `Start ${bridgeNumbers} ${"Stake\u00B2"}` : "Bridge Confirmation"}</Typography>
|
||||
</Box>
|
||||
}
|
||||
open={isOpened}
|
||||
onClose={closeModal}
|
||||
maxWidth="380px"
|
||||
minHeight="200px"
|
||||
>
|
||||
<Box height="420px" display="flex" flexDirection="column" justifyContent="space-between">
|
||||
{step === 0
|
||||
? <WelcomeView
|
||||
isStakingOpened={isStakingOpened}
|
||||
chainId={chainId}
|
||||
warmupPeriod={warmupPeriod}
|
||||
ghstSymbol={ghstSymbol}
|
||||
ftsoSymbol={ftsoSymbol}
|
||||
bridgeNumbers={bridgeNumbers}
|
||||
defaultFunction={defaultFunction}
|
||||
goNext={() => setStep(1)}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
: step === 1
|
||||
? <BridgeView
|
||||
receiver={receiver}
|
||||
setReceiver={setReceiver}
|
||||
chainId={chainId}
|
||||
bridgeNumbers={bridgeNumbers}
|
||||
ghstSymbol={ghstSymbol}
|
||||
estimatedAmount={estimatedAmount}
|
||||
incomingFee={incomingFee}
|
||||
goNext={() => setStep(2)}
|
||||
/>
|
||||
: <ConfirmStep
|
||||
chainId={chainId}
|
||||
address={address}
|
||||
receiver={receiver}
|
||||
executableFunction={executableFunction}
|
||||
isStakingOpened={isStakingOpened}
|
||||
bridgeNumbers={bridgeNumbers}
|
||||
incomingFee={incomingFee}
|
||||
estimatedAmount={estimatedAmount}
|
||||
ghstSymbol={ghstSymbol}
|
||||
setActiveTxIndex={setActiveTxIndex}
|
||||
closeModal={closeModalPure}
|
||||
evmNetwork={evmNetwork}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const BridgeView = ({
|
||||
chainId,
|
||||
receiver,
|
||||
setReceiver,
|
||||
bridgeNumbers,
|
||||
ghstSymbol,
|
||||
estimatedAmount,
|
||||
goNext,
|
||||
incomingFee
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [convertedReceiver, setConvertedReceiver] = useState(undefined);
|
||||
|
||||
const config = useConfig();
|
||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
||||
|
||||
const chainExplorerUrl = useMemo(() => {
|
||||
const client = config?.getClient();
|
||||
return client?.chain?.blockExplorers?.default?.url;
|
||||
}, [config]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const [publicKey, prefix] = ss58Decode(receiver);
|
||||
if (prefix !== 1995 && prefix !== 1996) {
|
||||
throw new Error("bad prefix");
|
||||
}
|
||||
setConvertedReceiver(toHex(publicKey));
|
||||
} catch {
|
||||
setConvertedReceiver(undefined);
|
||||
}
|
||||
}, [receiver]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>Bridge to start earning {bridgeNumbers} {"Stake\u00B2"} on your {ghstSymbol} balance:</Typography>
|
||||
|
||||
<Box display="flex" justifyContent="center">
|
||||
<Typography variant="h5">{formatNumber(estimatedAmount, 5)} {ghstSymbol}</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography>
|
||||
Generate a unique address for per tx with <Link underline="hover" href={"https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases"} color={theme.colors.primary[300]}>GHOST connect</Link> for privacy.
|
||||
</Typography>
|
||||
|
||||
<SwapCard
|
||||
id={`bridge-token-receiver`}
|
||||
inputWidth={"100%"}
|
||||
value={convertedReceiver ? shorten(receiver, 15, -10) : receiver}
|
||||
onChange={event => setReceiver(convertedReceiver ? "" : event.currentTarget.value)}
|
||||
inputProps={{ "data-testid": "fromInput" }}
|
||||
placeholder="GHOST address (sf prefixed)"
|
||||
endString={convertedReceiver
|
||||
? <GhostStyledIcon color="success" viewBox="0 0 25 25" component={CheckCircleIcon} />
|
||||
: undefined
|
||||
}
|
||||
type="text"
|
||||
maxWidth="100%"
|
||||
/>
|
||||
|
||||
<Box display="flex" justifyContent="center" flexDirection="column" alignItems="center">
|
||||
<Box width="100%" display="flex" justifyContent="row" justifyContent="space-between">
|
||||
<Typography variant="body2">Gatekeeper</Typography>
|
||||
<Link
|
||||
fontSize="12px"
|
||||
lineHeight="15px"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`${chainExplorerUrl}/token/${gatekeeperAddress}`}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
{shorten(gatekeeperAddress, 10, -8)}
|
||||
</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
<Box width="100%" display="flex" justifyContent="row" justifyContent="space-between">
|
||||
<Typography variant="body2">Bridge Fee</Typography>
|
||||
<Typography variant="body2">{formatNumber(incomingFee, 4)}%</Typography>
|
||||
</Box>
|
||||
<Box width="100%" display="flex" justifyContent="row" justifyContent="space-between">
|
||||
<Typography variant="body2">Est. Time</Typography>
|
||||
<Typography variant="body2">20 mins</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<PrimaryButton
|
||||
onClick={goNext}
|
||||
disabled={convertedReceiver === undefined}
|
||||
fullWidth
|
||||
>
|
||||
Proceed
|
||||
</PrimaryButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const WelcomeView = ({
|
||||
bridgeNumbers,
|
||||
goNext,
|
||||
isStakingOpened,
|
||||
chainId,
|
||||
warmupPeriod,
|
||||
ghstSymbol,
|
||||
ftsoSymbol,
|
||||
defaultFunction,
|
||||
closeModal
|
||||
}) => {
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const { epoch } = useEpoch(chainId);
|
||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
||||
const circulatingSupply = useCirculatingSupply(chainId, "STNK");
|
||||
const gatekeepedApy = useGatekeeperApy(chainId);
|
||||
|
||||
const apyInner = useMemo(() => {
|
||||
let apy = Infinity;
|
||||
if (circulatingSupply._value > 0n) {
|
||||
const value = epoch.distribute.div(circulatingSupply);
|
||||
apy = 100 * (Math.pow(1 + parseFloat(value.toString()), 1095) - 1);
|
||||
if (apy === 0) apy = Infinity;
|
||||
}
|
||||
return apy;
|
||||
}, [circulatingSupply, epoch]);
|
||||
|
||||
const callDefaultFunction = useCallback(async () => {
|
||||
setIsPending(true);
|
||||
await defaultFunction()();
|
||||
setIsPending(false);
|
||||
closeModal();
|
||||
}, [defaultFunction]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>{warmupPeriod <= 0
|
||||
? `You've succesfully warmed-up your ${isStakingOpened ? " " : "bonded "}${ftsoSymbol} staked at:`
|
||||
: `${isStakingOpened ? "Stake" : "Bond"} is in warm-up${isStakingOpened ? "" : ", which extends with each purchase"}. Your ${ftsoSymbol} is staked at:`
|
||||
}</Typography>
|
||||
|
||||
<Box display="flex" justifyContent="center">
|
||||
<Typography variant="h5">{formatNumber(apyInner, 2)}% APY</Typography>
|
||||
</Box>
|
||||
|
||||
{warmupPeriod <= 0 && <SecondaryButton
|
||||
onClick={() => callDefaultFunction()}
|
||||
disabled={isPending || warmupPeriod > 0}
|
||||
fullWidth
|
||||
>
|
||||
Claim {isStakingOpened ? "(3, 3) Stake" : "(1, 1) Bond"}
|
||||
</SecondaryButton>}
|
||||
|
||||
<Box display="flex" justifyContent="center" flexDirection="column" alignItems="center">
|
||||
<hr style={{ width: "100%" }} />
|
||||
<Typography variant="h5">OR</Typography>
|
||||
<hr style={{ width: "100%" }} />
|
||||
</Box>
|
||||
|
||||
<Box display="flex" flexDirection="column" justifyContent="space-between" gap="10px">
|
||||
<Typography fontWeight="bold">Skip the Warm-up Now!</Typography>
|
||||
<Typography>{`Bridge your ${ghstSymbol} to GHOST Chain and stake at:`}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" justifyContent="center" flexDirection="column" alignItems="center">
|
||||
<Typography variant="h5">{formatNumber(apyInner * gatekeepedApy, 2)}% APY</Typography>
|
||||
</Box>
|
||||
|
||||
<PrimaryButton
|
||||
disabled={isPending || gatekeeperAddress === EMPTY_ADDRESS}
|
||||
onClick={goNext}
|
||||
fullWidth
|
||||
>
|
||||
{`Start ${bridgeNumbers} ${"Stake\u00B2"}`}
|
||||
</PrimaryButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ConfirmStep = ({
|
||||
chainId,
|
||||
address,
|
||||
receiver,
|
||||
executableFunction,
|
||||
ghstSymbol,
|
||||
bridgeNumbers,
|
||||
estimatedAmount,
|
||||
bridgingRisk,
|
||||
incomingFee,
|
||||
setActiveTxIndex,
|
||||
closeModal,
|
||||
evmNetwork
|
||||
}) => {
|
||||
const config = useConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const currentSession = useCurrentIndex();
|
||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
||||
|
||||
const [blockNumber, setBlockNumber] = useState(0n);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [acknowledgeBridgingRisk, setAcknowledgeBridgingRisk] = useState(false);
|
||||
const [acknowledgeWalletCustody, setAcknowledgeWalletCustody] = useState(false);
|
||||
|
||||
getBlockNumber(config).then(block => setBlockNumber(block));
|
||||
|
||||
const nativeSymbol = useMemo(() => config?.getClient()?.chain?.nativeCurrency?.symbol, [config]);
|
||||
const networkName= useMemo(() => config?.getClient()?.chain?.name.toLowerCase(), [config]);
|
||||
|
||||
const receivedEstimation = useMemo(() => {
|
||||
const decimals = incomingFee._decimals + 2;
|
||||
const afterFee = new DecimalBigNumber(
|
||||
Math.pow(10, decimals) - incomingFee._value,
|
||||
decimals
|
||||
);
|
||||
return estimatedAmount.mul(afterFee);
|
||||
}, [incomingFee, estimatedAmount]);
|
||||
|
||||
const execute = useCallback(async () => {
|
||||
setIsPending(true);
|
||||
try {
|
||||
const txHash = await executableFunction()(receiver);
|
||||
if (txHash) {
|
||||
const expectedSessionIndex = (currentSession ?? 0) + (evmNetwork
|
||||
? Number((evmNetwork.avg_block_speed * evmNetwork.finality_delay) / (1000n * 14400n))
|
||||
: 0);
|
||||
|
||||
const transaction = {
|
||||
sessionIndex: expectedSessionIndex,
|
||||
transactionHash: txHash,
|
||||
receiverAddress: receiver,
|
||||
amount: estimatedAmount._value.toString(),
|
||||
chainId: chainId,
|
||||
blockNumber: Number(blockNumber),
|
||||
bridgeStability: 69, // TODO: avoid stability
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
const storedTransactions = getStorageValue(chainId, address, "bridge-txs", []);
|
||||
const newStoredTransactions = [transaction, ...storedTransactions];
|
||||
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
|
||||
|
||||
setActiveTxIndex(0);
|
||||
navigate(`${networkName}/bridge`);
|
||||
}
|
||||
} finally {
|
||||
setIsPending(false);
|
||||
closeModal();
|
||||
}
|
||||
}, [executableFunction, receiver, networkName, chainId, address, blockNumber, evmNetwork, currentSession]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="center" gap="10px">
|
||||
<Metric label="Bridged" metric={formatNumber(estimatedAmount, 5)} />
|
||||
<Box width="100%" display="flex" flexDirection="column" justifyContent="center" alignItems="center">
|
||||
<Token chainTokenName={nativeSymbol} name={"GHST"} sx={{ fontSize: "55px" }} />
|
||||
<Typography>{ghstSymbol}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />
|
||||
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="center" gap="10px">
|
||||
<Metric label="Received" metric={formatNumber(receivedEstimation, 5)} />
|
||||
<Box width="100%" display="flex" flexDirection="column" justifyContent="center" alignItems="center">
|
||||
<Token name={"GHST"} sx={{ fontSize: "55px" }} />
|
||||
<Typography>{ghstSymbol}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography>{`You are bridging to GHOST Chain now to claim ${bridgeNumbers} ${"Stake\u00B2"} rewards.`}</Typography>
|
||||
|
||||
<hr style={{ width: "100%" }} />
|
||||
|
||||
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="left">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="acknowledge-breakout-warmup"
|
||||
checked={acknowledgeBridgingRisk}
|
||||
onChange={event => setAcknowledgeBridgingRisk(event.target.checked)}
|
||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2">
|
||||
{`I acknowledge decentralized bridging risk.`}
|
||||
<Link
|
||||
sx={{
|
||||
margin: "0px",
|
||||
font: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
textDecoration: "underline",
|
||||
textUnderlineOffset: "0.23rem",
|
||||
cursor: "pointer",
|
||||
textDecorationThickness: "1px",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
}
|
||||
}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://ghostchain.io/bridge-disclaimer"
|
||||
>
|
||||
Learn more.
|
||||
</Link>
|
||||
</Typography>
|
||||
}
|
||||
sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="acknowledge-breakout-warmup"
|
||||
checked={acknowledgeWalletCustody}
|
||||
onChange={event => setAcknowledgeWalletCustody(event.target.checked)}
|
||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2">
|
||||
I confirm that recipient address is a self-custodial wallet, not an exchange, third party service, or smart-contract.
|
||||
</Typography>
|
||||
}
|
||||
sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<PrimaryButton
|
||||
onClick={() => execute()}
|
||||
disabled={isPending || !acknowledgeWalletCustody || !acknowledgeBridgingRisk}
|
||||
fullWidth
|
||||
>
|
||||
I Confirm
|
||||
</PrimaryButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BreakoutModal;
|
||||
@ -47,6 +47,7 @@ import {
|
||||
useEraIndex,
|
||||
} from "../../hooks/ghost";
|
||||
import { useLocalStorage } from "../../hooks/localstorage";
|
||||
import { useBreakoutModal } from "../../hooks/breakoutModal";
|
||||
|
||||
import { ValidatorTable } from "./ValidatorTable";
|
||||
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
|
||||
@ -61,12 +62,12 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
||||
|
||||
const [bridgeModalOpen, setBridgeModalOpen] = useState(false);
|
||||
const [isConfirmed, setIsConfirmed] = useState(false);
|
||||
const [activeTxIndex, setActiveTxIndex] = useState(-1);
|
||||
const [blockNumber, setBlockNumber] = useState(0n);
|
||||
const [bridgeAction, setBridgeAction] = useState(true);
|
||||
const [currentTime, setCurrentTime] = useState(Date.now());
|
||||
|
||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
||||
const { activeTxIndex, setActiveTxIndex } = useBreakoutModal();
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
|
||||
|
||||
@ -74,7 +74,7 @@ const Dex = ({ chainId, address, connect, config }) => {
|
||||
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
if (chainSymbol) return chainSymbol;
|
||||
return "WTF";
|
||||
}, [config])
|
||||
}, [config, chainId])
|
||||
|
||||
const tokenNameTop = useMemo(() => {
|
||||
if (chainSymbol && tokenAddressTop === EMPTY_ADDRESS) {
|
||||
|
||||
@ -30,8 +30,6 @@ const ClaimConfirmationModal = (props) => {
|
||||
props.ghstSymbol
|
||||
);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown action")
|
||||
}
|
||||
|
||||
setIsPending(false);
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
Skeleton,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
|
||||
@ -26,9 +26,10 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
||||
import { formatNumber, formatCurrency } from "../../../helpers";
|
||||
import { STAKING_ADDRESSES } from "../../../constants/addresses";
|
||||
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
|
||||
import { useCurrentIndex, useWarmupInfo, claim, breakout } from "../../../hooks/staking";
|
||||
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
|
||||
import { useGhstPrice, useStnkPrice } from "../../../hooks/prices";
|
||||
import { useBreakoutModal } from "../../../hooks/breakoutModal";
|
||||
import { isNetworkLegacy } from "../../../constants";
|
||||
|
||||
import ClaimConfirmationModal from "./ClaimConfirmationModal";
|
||||
@ -52,7 +53,8 @@ const StyledTableHeader = styled(TableHead)(({ theme }) => ({
|
||||
export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
const isSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||
|
||||
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
|
||||
const { breakoutFromStaking } = useBreakoutModal();
|
||||
const [confirmationModalOpen, setConfirmationModalOpenInner] = useState(false);
|
||||
const [isPayoutGhst, _] = useState(true);
|
||||
|
||||
const ghstPrice = useGhstPrice(chainId);
|
||||
@ -74,6 +76,23 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
return isPayoutGhst ? toClaim : toClaim.mul(currentIndex);
|
||||
}, [chainId, claim, currentIndex, balanceForShares]);
|
||||
|
||||
const setConfirmationModalOpen = useCallback(async (value) => {
|
||||
if (isNetworkLegacy(chainId) || claim.expiry > epoch.number) {
|
||||
setConfirmationModalOpenInner(value);
|
||||
} else {
|
||||
const defaultFunction = async () => {
|
||||
await claim(chainId, address, false, stnkSymbol, ghstSymbol);
|
||||
await claimRefetch();
|
||||
}
|
||||
const warmupLeft = claim.expiry - epoch.number;
|
||||
const toExecute = async (receiver) => {
|
||||
await breakout(chainId, address, receiver, claimableBalance);
|
||||
await claimRefetch();
|
||||
}
|
||||
breakoutFromStaking({ defaultFunction, toExecute, amount: claimableBalance, warmupLeft })
|
||||
}
|
||||
}, [claim, epoch, address, chainId, ghstSymbol]);
|
||||
|
||||
const closeConfirmationModal = () => {
|
||||
setConfirmationModalOpen(false);
|
||||
claimRefetch();
|
||||
@ -89,7 +108,6 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
<ClaimConfirmationModal
|
||||
open={confirmationModalOpen}
|
||||
onClose={() => closeConfirmationModal()}
|
||||
chainid={chainId}
|
||||
receiver={address}
|
||||
receiveAmount={claim.expiry > epoch.number ? claim.deposit : claimableBalance}
|
||||
outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
|
||||
@ -257,7 +275,7 @@ const ActionButtons = ({ setConfirmationModalOpen, isSmallScreen = false, isClai
|
||||
fullWidth={isSmallScreen}
|
||||
sx={{ flexGrow: 1 }}
|
||||
onClick={() => setConfirmationModalOpen(true)}
|
||||
disabled={!isClaimable}
|
||||
disabled={isClaimable}
|
||||
>
|
||||
Emergency Withdrawal
|
||||
</SecondaryButton>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export function shorten(str) {
|
||||
export function shorten(str, first = 6, second = -4) {
|
||||
if (str.length < 10) return str;
|
||||
return `${str.slice(0, 6)}...${str.slice(str.length - 4)}`;
|
||||
return `${str.slice(0, first)}...${str.slice(second)}`;
|
||||
}
|
||||
|
||||
export function capitalize(str) {
|
||||
|
||||
@ -10,6 +10,7 @@ import { WagmiProvider } from "wagmi";
|
||||
import { config } from "./config";
|
||||
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
|
||||
import { LocalStorageProvider } from "./hooks/localstorage";
|
||||
import { BreakoutModalProvider } from "./hooks/breakoutModal";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
@ -26,7 +27,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<UnstableProviderProvider>
|
||||
<MetadataProviderProvider>
|
||||
<LocalStorageProvider>
|
||||
<App />
|
||||
<BreakoutModalProvider>
|
||||
<App />
|
||||
</BreakoutModalProvider>
|
||||
</LocalStorageProvider>
|
||||
</MetadataProviderProvider>
|
||||
</UnstableProviderProvider>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user