From cdf1f7cabff6dde422806bf390eb69df96036946 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Tue, 28 Apr 2026 13:17:09 +0300 Subject: [PATCH] implement new breakout logic Signed-off-by: Uncle Fatso --- package.json | 2 +- src/App.jsx | 19 +- src/components/Token/Token.jsx | 4 +- src/components/TopBar/Wallet/Token.tsx | 5 +- src/containers/Bond/components/ClaimBonds.jsx | 44 +- src/containers/Breakout/BreakoutModal.jsx | 475 ++++++++++++++++++ src/containers/Bridge/Bridge.jsx | 3 +- src/containers/Dex/Dex.jsx | 2 +- .../components/ClaimConfirmationModal.jsx | 2 - .../Stake/components/ClaimsArea.jsx | 28 +- src/helpers/index.js | 4 +- src/main.jsx | 5 +- 12 files changed, 556 insertions(+), 37 deletions(-) create mode 100644 src/containers/Breakout/BreakoutModal.jsx diff --git a/package.json b/package.json index 1fdba16..a7093be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.7.23", + "version": "0.7.24", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.jsx b/src/App.jsx index 442b940..84e1bd9 100644 --- a/src/App.jsx +++ b/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() {
}> + } /> {chainExists && diff --git a/src/components/Token/Token.jsx b/src/components/Token/Token.jsx index 3f95823..073bd67 100644 --- a/src/components/Token/Token.jsx +++ b/src/components/Token/Token.jsx @@ -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%" }} diff --git a/src/components/TopBar/Wallet/Token.tsx b/src/components/TopBar/Wallet/Token.tsx index 802eb56..ccc85d9 100644 --- a/src/components/TopBar/Wallet/Token.tsx +++ b/src/components/TopBar/Wallet/Token.tsx @@ -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"); diff --git a/src/containers/Bond/components/ClaimBonds.jsx b/src/containers/Bond/components/ClaimBonds.jsx index bf8b4cb..0e3eeb3 100644 --- a/src/containers/Bond/components/ClaimBonds.jsx +++ b/src/containers/Bond/components/ClaimBonds.jsx @@ -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 ( diff --git a/src/containers/Breakout/BreakoutModal.jsx b/src/containers/Breakout/BreakoutModal.jsx new file mode 100644 index 0000000..dc28c64 --- /dev/null +++ b/src/containers/Breakout/BreakoutModal.jsx @@ -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 ( + + {step === 0 ? header : step === 1 ? `Start ${bridgeNumbers} ${"Stake\u00B2"}` : "Bridge Confirmation"} + + } + open={isOpened} + onClose={closeModal} + maxWidth="380px" + minHeight="200px" + > + + {step === 0 + ? setStep(1)} + closeModal={closeModal} + /> + : step === 1 + ? setStep(2)} + /> + : + } + + + ) +} + +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 ( + <> + Bridge to start earning {bridgeNumbers} {"Stake\u00B2"} on your {ghstSymbol} balance: + + + {formatNumber(estimatedAmount, 5)} {ghstSymbol} + + + + Generate a unique address for per tx with GHOST connect for privacy. + + + setReceiver(convertedReceiver ? "" : event.currentTarget.value)} + inputProps={{ "data-testid": "fromInput" }} + placeholder="GHOST address (sf prefixed)" + endString={convertedReceiver + ? + : undefined + } + type="text" + maxWidth="100%" + /> + + + + Gatekeeper + + + {shorten(gatekeeperAddress, 10, -8)} + + + + + Bridge Fee + {formatNumber(incomingFee, 4)}% + + + Est. Time + 20 mins + + + + + Proceed + + + ) +} + +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 ( + <> + {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:` + } + + + {formatNumber(apyInner, 2)}% APY + + + {warmupPeriod <= 0 && callDefaultFunction()} + disabled={isPending || warmupPeriod > 0} + fullWidth + > + Claim {isStakingOpened ? "(3, 3) Stake" : "(1, 1) Bond"} + } + + +
+ OR +
+
+ + + Skip the Warm-up Now! + {`Bridge your ${ghstSymbol} to GHOST Chain and stake at:`} + + + + {formatNumber(apyInner * gatekeepedApy, 2)}% APY + + + + {`Start ${bridgeNumbers} ${"Stake\u00B2"}`} + + + ) +} + +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 ( + <> + + + + + + {ghstSymbol} + + + + + + + + {ghstSymbol} + + + + + {`You are bridging to GHOST Chain now to claim ${bridgeNumbers} ${"Stake\u00B2"} rewards.`} + +
+ + + setAcknowledgeBridgingRisk(event.target.checked)} + icon={} + checkedIcon={} + /> + } + label={ + + {`I acknowledge decentralized bridging risk.`}  + + Learn more. + + + } + sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }} + /> + setAcknowledgeWalletCustody(event.target.checked)} + icon={} + checkedIcon={} + /> + } + label={ + + I confirm that recipient address is a self-custodial wallet, not an exchange, third party service, or smart-contract. + + } + sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }} + /> + + + execute()} + disabled={isPending || !acknowledgeWalletCustody || !acknowledgeBridgingRisk} + fullWidth + > + I Confirm + + + ) +} + +export default BreakoutModal; diff --git a/src/containers/Bridge/Bridge.jsx b/src/containers/Bridge/Bridge.jsx index a03b17e..2b73e2b 100644 --- a/src/containers/Bridge/Bridge.jsx +++ b/src/containers/Bridge/Bridge.jsx @@ -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); diff --git a/src/containers/Dex/Dex.jsx b/src/containers/Dex/Dex.jsx index 9826fe5..923ae86 100644 --- a/src/containers/Dex/Dex.jsx +++ b/src/containers/Dex/Dex.jsx @@ -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) { diff --git a/src/containers/Stake/components/ClaimConfirmationModal.jsx b/src/containers/Stake/components/ClaimConfirmationModal.jsx index dc2653b..4a57c06 100644 --- a/src/containers/Stake/components/ClaimConfirmationModal.jsx +++ b/src/containers/Stake/components/ClaimConfirmationModal.jsx @@ -30,8 +30,6 @@ const ClaimConfirmationModal = (props) => { props.ghstSymbol ); break; - default: - console.log("Unknown action") } setIsPending(false); diff --git a/src/containers/Stake/components/ClaimsArea.jsx b/src/containers/Stake/components/ClaimsArea.jsx index 310b907..c270860 100644 --- a/src/containers/Stake/components/ClaimsArea.jsx +++ b/src/containers/Stake/components/ClaimsArea.jsx @@ -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 }) => { 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 diff --git a/src/helpers/index.js b/src/helpers/index.js index 373b812..df52466 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -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) { diff --git a/src/main.jsx b/src/main.jsx index 73536b4..6212fa0 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -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( - + + +