Compare commits

...

5 Commits

Author SHA1 Message Date
3f9003883d
localStorage as context provider added to the app
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-07 14:57:56 +03:00
f6a2fc6917
apply revision 14; applied partially
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-07 13:47:23 +03:00
8bfc14f2f0
add price estimation for bond notes and warmup during staking
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-07 13:46:39 +03:00
573bb73609
apply full changes on the side of hoodi
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-06 18:35:35 +03:00
60740e1e6a
apply latest hoodi deployments
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-04-06 16:28:49 +03:00
22 changed files with 268 additions and 206 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.6.14",
"version": "0.7.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -22,7 +22,7 @@ import Sidebar from "./components/Sidebar/Sidebar";
import TopBar from "./components/TopBar/TopBar";
import { shouldTriggerSafetyCheck } from "./helpers";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "./constants";
import { isNetworkAvailable, isGovernanceAvailable } from "./constants";
import useTheme from "./hooks/useTheme";
import { useUnstableProvider } from "./hooks/ghost";
import { dark as darkTheme } from "./themes/dark.js";
@ -34,8 +34,6 @@ const Bonds = lazy(() => import("./containers/Bond/Bonds"));
const BondModalContainer = lazy(() => import("./containers/Bond/BondModal"));
const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
const Faucet = lazy(() => import("./containers/Faucet/Faucet"));
const Wrapper = lazy(() => import("./containers/WethWrapper/WethWrapper"));
const Dex = lazy(() => import("./containers/Dex/Dex"));
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
@ -219,10 +217,6 @@ function App() {
<Route path="bonds" element={<Bonds connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="bonds/:id" element={<BondModalContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
{isNetworkLegacy(chainId)
? <Route path="faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
: <Route path="wrapper" element={<Wrapper config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
}
<Route path="bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="dex/:name" element={<Dex config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}

View File

@ -40,13 +40,14 @@ import BondIcon from "../Icon/BondIcon";
import StakeIcon from "../Icon/StakeIcon";
import WrapIcon from "../Icon/WrapIcon";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants";
import { isNetworkAvailable, isGovernanceAvailable } from "../../constants";
import { AVAILABLE_DEXES } from "../../constants/dexes";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
import { ECOSYSTEM } from "../../constants/ecosystem";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { sortBondsByDiscount, formatCurrency } from "../../helpers";
import BondDiscount from "../../containers/Bond/components/BondDiscount";
import Chip from "../Chip/Chip";
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShowerIcon from '@mui/icons-material/Shower';
@ -143,6 +144,9 @@ const NavContent = ({ chainId, addressChainId }) => {
to={`/${chainName}/bonds`}
children={
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
<Box width="180px" mb="10px" ml="auto">
<Typography component="span" variant="body2">Bond Discounts</Typography>
</Box>
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
return (
<Link
@ -156,6 +160,7 @@ const NavContent = ({ chainId, addressChainId }) => {
style={{
width: "180px",
justifyContent: "space-between",
alignItems: "center",
display: "flex",
gap: "10px"
}}
@ -163,7 +168,10 @@ const NavContent = ({ chainId, addressChainId }) => {
variant="body2"
>
{bond.displayName}
<BondDiscount textOnly discount={bond.discount} />
{bond.soldOut
? <Chip label="Sold Out" template="darkGray" />
: <BondDiscount discount={bond.discount} />
}
</Typography>
</Box>
</Link>
@ -172,7 +180,7 @@ const NavContent = ({ chainId, addressChainId }) => {
</AccordionDetails>
}
/>
<NavItem icon={ForkRightIcon} label={`${bridgeNumbers} GHOST Staking`} to={`/${chainName}/bridge`} />
<NavItem icon={ForkRightIcon} label={`${bridgeNumbers} Stake\u00B2`} to={`/${chainName}/bridge`} />
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to={`/${chainName}/governance`} />}
<Box className="menu-divider">
<Divider />

View File

@ -151,18 +151,6 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
<Divider />
</Box>
<Box
sx={{ display: "flex", flexDirection: "column" }}
style={{ gap: theme.spacing(1.5) }}
>
<SecondaryButton
fullWidth
onClick={() => onBtnClick("uniswap", RESERVE_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
>
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.reserve?.symbol} on Uniswap`}</Typography>
</SecondaryButton>
</Box>
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
<DisconnectButton onClose={onClose} />
</Box>

View File

@ -15,7 +15,6 @@ import { useQuery } from "react-query";
import { formatCurrency, formatNumber } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { isNetworkLegacy } from "../../../constants";
import { EMPTY_ADDRESS } from "../../../constants/addresses";
import GhostStyledIcon from "../../Icon/GhostIcon";
@ -240,10 +239,8 @@ export const useWallet = (chainId, userAddress) => {
address: reserveAddress,
balance: reserveBalance,
price: reservePrice,
icons: isNetworkLegacy(chainId) ? ["GDAI"] : [tokenNameConverter(chainId, reserveSymbol)],
externalUrl: isNetworkLegacy(chainId)
? "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg"
: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg",
icons: [tokenNameConverter(chainId, reserveSymbol)],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg",
refetch: reserveRefetch,
},
ftso: {

View File

@ -42,7 +42,10 @@ export const isNetworkAvailable = (chainId, addressChainId) => {
export const isNetworkLegacy = (chainId) => {
let isLegacy = false;
switch (chainId) {
case 560048:
case 11155111:
isLegacy = true
break;
case 63:
isLegacy = true
break;
default:

View File

@ -4,25 +4,25 @@ export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
[NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
[NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
};
export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
[NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
[NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
};
export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
[NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
[NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E",
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
};
export const FTSO_DAI_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
[NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D",
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
};
@ -34,7 +34,7 @@ export const FTSO_STNK_LP_ADDRESSES = {
export const RESERVE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
[NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
[NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4",
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
};
@ -46,41 +46,43 @@ export const WETH_ADDRESSES = {
export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
[NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
[NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
};
export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
[NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
[NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
};
export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
[NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
[NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
};
export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
[NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
[NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
};
export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
[NetworkId.TESTNET_HOODI]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
};
export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
[NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
[NetworkId.TESTNET_HOODI]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
}
export const GATEKEEPER_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B",
[NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
}
@ -116,6 +118,10 @@ export const CEX_TICKERS = {
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
],
[NetworkId.TESTNET_HOODI]: [
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
],
[NetworkId.TESTNET_MORDOR]: [
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",
"https://api.coinbase.com/v2/prices/ETC-USDT/spot",

View File

@ -1,8 +1,9 @@
import { ArrowBack } from "@mui/icons-material";
import { Box, Link, Skeleton, Typography } from "@mui/material";
import { useEffect, useState, useMemo } from "react";
import { useEffect, useState, useMemo, useCallback } from "react";
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
import PageTitle from "../../components/PageTitle/PageTitle";
@ -20,6 +21,7 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { NetworkId } from "../../constants";
import { formatCurrency } from "../../helpers";
import { useLocalStorage } from "../../hooks/localstorage";
import { useLiveBonds } from "../../hooks/bonds";
import { useFtsoPrice } from "../../hooks/prices";
@ -38,29 +40,38 @@ export const BondModal = ({ bond, chainId, address, connect }) => {
const { network } = useParams();
const { pathname } = useLocation();
const [slippage, setSlippage] = useState(localStorage.getItem("bond-slippage") || "10");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("bond-decimals") || "5");
const [isSettingsOpen, setSettingsOpen] = useState(false);
const [recipientAddress, setRecipientAddress] = useState(address);
const { getStorageValue, setStorageValue } = useLocalStorage();
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "bond-slippage", "10"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "bond-decimals", "5"));
const [recipientAddress, setRecipientAddressInner] = useState(() => getStorageValue(chainId, address, "bond-recipient", address));
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: pathname });
}, [])
const setSlippageInner = (value) => {
const setRecipientAddress = useCallback((value) => {
setRecipientAddressInner(value);
if (isAddress(value)) {
setStorageValue(chainId, address, "bond-recipient", value);
}
}, [chainId, address]);
const setSlippageInner = useCallback((value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value);
localStorage.setItem("bond-slippage", value);
}
setStorageValue(chainId, address, "bond-slippage", value);
}
}, [chainId, address]);
const setFormatDecimalsInner = (value) => {
const setFormatDecimalsInner = useCallback((value) => {
if (Number(value) <= 17) {
setFormatDecimals(value);
localStorage.setItem("bond-decimals", value);
}
setStorageValue(chainId, address, "bond-decimals", value);
}
}, [chainId, address]);
useEffect(() => {
const handleKeyDown = (event) => {

View File

@ -20,6 +20,7 @@ import { formatCurrency } from "../../../helpers";
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
import { useNotes, redeem } from "../../../hooks/bonds";
import { useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice } from "../../../hooks/prices";
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const isSmallScreen = useScreenSize("md");
@ -28,6 +29,7 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false);
const [isPayoutGhst, _] = useState(true);
const ghstPrice = useGhstPrice(chainId);
const { epoch } = useEpoch(chainId);
const { warmupExists } = useWarmupLength(chainId);
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
@ -97,6 +99,8 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
? formatCurrency(totalClaimableBalance, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
}
&nbsp;
({formatCurrency(totalClaimableBalance * ghstPrice, 2)})
</Typography>
</Box>
@ -139,12 +143,15 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
<Box display="flex" justifyContent="space-between" mt="8px">
<Typography>Payout</Typography>
<Box display="flex" flexDirection="column" alignItems="flex-end">
<Typography>
{isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
}
</Typography>
<Typography>{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
</Box>
</Box>
<Box mt="16px">
@ -195,12 +202,15 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<Box display="flex" flexDirection="column" alignItems="flex-start">
<Typography>
{isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
}
</Typography>
<Typography variant="body2">{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
</Box>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>

View File

@ -46,14 +46,13 @@ import {
useLatestBlockNumber,
useEraIndex,
} from "../../hooks/ghost";
import { useLocalStorage } from "../../hooks/localstorage";
import { ValidatorTable } from "./ValidatorTable";
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
import { BridgeHeader } from "./BridgeHeader";
import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard";
const STORAGE_PREFIX = "storedTransactions"
const Bridge = ({ chainId, address, config, connect }) => {
const isBigScreen = useMediaQuery("(max-width: 980px)")
const isSmallScreen = useMediaQuery("(max-width: 650px)");
@ -67,14 +66,15 @@ const Bridge = ({ chainId, address, config, connect }) => {
const [bridgeAction, setBridgeAction] = useState(true);
const [currentTime, setCurrentTime] = useState(Date.now());
const { getStorageValue, setStorageValue } = useLocalStorage();
useEffect(() => {
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
return () => clearInterval(interval);
}, []);
const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX);
const [storedTransactions, setStoredTransactions] = useState(
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
const [storedTransactions, setStoredTransactions] = useState(() =>
getStorageValue(chainId, address, "bridge-txs", [])
);
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
@ -281,7 +281,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
const removeStoredRecord = useCallback(() => {
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
setStoredTransactions(newStoredTransactions);
localStorage.setItem(storagePrefix, JSON.stringify(newStoredTransactions));
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
@ -307,8 +307,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
const newStoredTransactions = [transaction, ...storedTransactions];
setStoredTransactions(newStoredTransactions);
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(0)
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(0);
}
return (

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import { Box, Typography, Link, FormControlLabel, Checkbox, useTheme } from "@mui/material";
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
@ -19,10 +19,11 @@ import ContentPasteIcon from '@mui/icons-material/ContentPaste';
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import Modal from "../../components/Modal/Modal";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { PrimaryButton, TertiaryButton, SecondaryButton } from "../../components/Button";
import { formatCurrency } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
export const BridgeModal = ({
providerDetail,
@ -50,6 +51,12 @@ export const BridgeModal = ({
});
};
const bridgeNumbers = () => {
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
const number = 1 + connectedNetworks * 3;
return `(${number}, ${number})`;
};
return (
<Modal
data-testid="transaction-details-modal"
@ -89,15 +96,43 @@ export const BridgeModal = ({
minHeight={"100px"}
>
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
{!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<TertiaryButton
{!providerDetail && <Box
width="90%"
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
backgroundColor="#1f4771"
sx={{
position: 'absolute',
top: '130px',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1,
}}
>
<SecondaryButton
fullWidth
onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}
sx={{
marginTop: "0 !important",
marginBottom: "0 !important"
}}
onClick={() => window.open(
'https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases',
'_blank',
'noopener,noreferrer'
)}
>
Get GHOST Connect
</TertiaryButton>
</SecondaryButton>
</Box>}
{providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<Box
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
sx={{ filter: providerDetail ? '' : 'blur(5px)' }}
>
{currentRecord?.finalization > 0 && (
<>
<Box
@ -285,7 +320,7 @@ export const BridgeModal = ({
</Box>
</>
)}
</Box>}
</Box>
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
<Box display="flex" flexDirection="row" justifyContent="space-between">
@ -357,13 +392,21 @@ export const BridgeModal = ({
</Box>
<Box display="flex" flexDirection="column" gap="5px">
<PrimaryButton
{currentRecord && currentRecord.finalization < 1 && currentRecord.applaused && <PrimaryButton
fullWidth
loading={false}
onClick={() => removeStoredRecord()}
>
{`Get ${bridgeNumbers()} Stake\u00B2`}
</PrimaryButton>}
<TertiaryButton
fullWidth
loading={false}
onClick={() => removeStoredRecord()}
>
Erase Record
</PrimaryButton>
</TertiaryButton>
<Typography variant="body2" sx={{ fontStyle: "italic" }}>
This will permanently remove the bridge transaction record from the session storage, but it will not cancel the bridge transaction.
@ -399,6 +442,12 @@ export const BridgeConfirmModal = ({
>
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
<Box width="100%" display="flex" flexDirection="column" alignItems="start">
<Box>
<Typography variant="subtitle1">
You are bridging to GHOST Chain. We will guide you towards (10, 10) Stake<sup>2</sup> rewards right after that.
</Typography>
</Box>
<hr style={{ margin: "10px 0", width: "100%" }} />
<FormControlLabel
control={
<Checkbox

View File

@ -10,9 +10,10 @@ import {
useTheme,
} from "@mui/material";
import SettingsIcon from '@mui/icons-material/Settings';
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState, useCallback } from "react";
import { useParams, useLocation, useSearchParams } from "react-router-dom";
import { Helmet } from "react-helmet";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
@ -31,6 +32,7 @@ import {
EMPTY_ADDRESS,
WETH_ADDRESSES,
} from "../../constants/addresses";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol } from "../../hooks/tokens";
import { getTokenAddress } from "../../hooks/helpers";
@ -43,6 +45,7 @@ const Dex = ({ chainId, address, connect, config }) => {
const pathname = useParams();
const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
@ -55,10 +58,10 @@ const Dex = ({ chainId, address, connect, config }) => {
const [topTokenListOpen, setTopTokenListOpen] = useState(false);
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
const [actualDestinationAddress, setActualDestinationAddress] = useState(localStorage.getItem("dex-destination"));
const [secondsToWait, setSecondsToWait] = useState(() => getStorageValue(chainId, address, "dex-deadline", "60"));
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "dex-slippage", "5"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "dex-decimals", "5"));
const [actualDestinationAddress, setActualDestinationAddress] = useState(() => getStorageValue(chainId, address, "dex-destination", address));
const [destinationAddress, setDestinationAddress] = useState(actualDestinationAddress);
const [tokenAddressTop, setTokenAddressTop] = useState(EMPTY_ADDRESS);
@ -180,38 +183,34 @@ const Dex = ({ chainId, address, connect, config }) => {
setSearchParams(newQueryParameters);
}
const setSlippageInner = (value) => {
const setSlippageInner = useCallback((value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value);
localStorage.setItem("dex-slippage", value);
}
setStorageValue(chainId, address, "dex-slippage", value);
}
}, [chainId, address]);
const setSecondsToWaitInner = (value) => {
localStorage.setItem("dex-deadline", value);
const setSecondsToWaitInner = useCallback((value) => {
setSecondsToWait(value);
}
setStorageValue(chainId, address, "dex-deadline", value);
}, [chainId, address]);
const setFormatDecimalsInner = (value) => {
const setFormatDecimalsInner = useCallback((value) => {
if (Number(value) <= 17) {
localStorage.setItem("dex-decimals", value);
setFormatDecimals(value);
setStorageValue(chainId, address, "dex-decimals", value);
}
}
}, [chainId, address]);
const setDestinationAddressInner = (value) => {
const setDestinationAddressInner = useCallback((value) => {
const cleanedValue = value.trim();
const isEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(cleanedValue);
if (isEvmAddress) {
localStorage.setItem("dex-destination", value);
setActualDestinationAddress(value);
} else if (!isEvmAddress && actualDestinationAddress) {
localStorage.removeItem("dex-destination");
setActualDestinationAddress(undefined);
}
setDestinationAddress(value);
if (isAddress(cleanedValue)) {
setActualDestinationAddress(value);
setStorageValue(chainId, address, "dex-destination", value);
}
}, [chainId, address]);
const handleCloseSetting = () => {
setDestinationAddress(undefined);
@ -255,7 +254,7 @@ const Dex = ({ chainId, address, connect, config }) => {
}}
>
<Modal
maxWidth="376px"
maxWidth="450px"
minHeight="200px"
open={settingsOpen}
headerText={"Settings"}

View File

@ -20,7 +20,6 @@ import TokenStack from "../../components/TokenStack/TokenStack";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber } from "../../helpers/";
import { useBalance, useTokenSymbol } from "../../hooks/tokens";
import { isNetworkLegacy } from "../../constants";
import {
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
@ -72,7 +71,7 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
},
{
name: reserveSymbol,
icons: isNetworkLegacy(chainId) ? ["GDAI"] : [chainSymbol],
icons: [chainSymbol],
balance: reserveBalance,
address: RESERVE_ADDRESSES[chainId]
},

View File

@ -28,6 +28,7 @@ import PageTitle from "../../components/PageTitle/PageTitle";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
@ -42,11 +43,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposalsInner] = useState(() => myStoredProposals.map(id => BigInt(id)));
const [isPending, setIsPending] = useState(false);
const [isModalOpened, setIsModalOpened] = useState(false);
@ -61,10 +61,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
proposalDescription
} = useProposalHash(chainId, proposalFunctions);
useEffect(() => {
const toStore = JSON.stringify(myProposals.map(id => id.toString()));
localStorage.setItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`, toStore);
}, [myProposals]);
const setMyProposals = useCallback((proposals) => {
setMyProposalsInner(proposals);
setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString()))
}, [chainId, address]);
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/governance/create" });

View File

@ -1,4 +1,4 @@
import { useEffect, useState, useMemo } from "react";
import { useEffect, useState, useMemo, useCallback } from "react";
import ReactGA from "react-ga4";
import { useParams } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
@ -38,6 +38,7 @@ import { parseFunctionCalldata } from "./components/functions";
import { networkAvgBlockSpeed } from "../../constants";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
import {
getVoteValue,
@ -75,7 +76,6 @@ import RepeatIcon from '@mui/icons-material/Repeat';
const HUNDRED = new DecimalBigNumber(100n, 0);
const ProposalDetails = ({ chainId, address, connect, config }) => {
const { id } = useParams();
const proposalId = BigInt(id);
@ -89,6 +89,7 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const theme = useTheme();
const queryClient = useQueryClient();
const { getStorageValue, setStorageValue } = useLocalStorage();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance } = useBalance(chainId, "GHST", address);
@ -149,20 +150,19 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
return url;
}, [proposalProposer, config]);
const handleVote = async (support) => {
const handleVote = useCallback(async (support) => {
setIsPending(true);
const result = await castVote(chainId, address, proposalId, support);
if (result) {
const storedVotedProposals = localStorage.getItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`);
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
proposals.push(proposalId);
const toStore = JSON.stringify(proposals.map(id => id.toString()));
localStorage.setItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`, toStore);
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
await queryClient.invalidateQueries();
}
setIsPending(false);
}
}, [chainId, address, proposalId]);
const handleExecute = async () => {
setIsPending(true);

View File

@ -42,6 +42,7 @@ import {
import { useScreenSize } from "../../../hooks/useScreenSize";
import { useProposals } from "../../../hooks/governance";
import { useLocalStorage } from "../../../hooks/localstorage";
const MAX_PROPOSALS_TO_SHOW = 10;
@ -51,18 +52,15 @@ const ProposalsList = ({ chainId, address, config }) => {
const theme = useTheme();
const { network } = useParams();
const { getStorageValue, setStorageValue } = useLocalStorage();
const [proposalsFilter, setProposalFilter] = useState("active");
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposals] = useState(() => myStoredProposals.map(id => BigInt(id)));
const storedVotedProposals = localStorage.getItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`);
const [votedProposals, setVotedProposals] = useState(
storedVotedProposals ? JSON.parse(storedVotedProposals).map(id => BigInt(id)) : []
);
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const [votedProposals, setVotedProposals] = useState(() => storedVotedProposals.map(id => BigInt(id)));
const searchedIndexes = useMemo(() => {
switch (proposalsFilter) {

View File

@ -24,10 +24,11 @@ import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { formatNumber } from "../../../helpers";
import { formatNumber, formatCurrency } from "../../../helpers";
import { STAKING_ADDRESSES } from "../../../constants/addresses";
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice } from "../../../hooks/prices";
import ClaimConfirmationModal from "./ClaimConfirmationModal";
@ -53,6 +54,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [isPayoutGhst, _] = useState(true);
const ghstPrice = useGhstPrice(chainId);
const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address);
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
const { balanceForShares } = useBalanceForShares(chainId, "STNK", claim.shares);
@ -132,6 +134,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
ghstPrice={ghstPrice}
/>
</Table>
@ -141,7 +144,17 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
);
};
const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst, stnkSymbol, ghstSymbol }) => {
const ClaimInfo = ({
setConfirmationModalOpen,
prepareBalance,
claim,
epoch,
isClaimable,
isPayoutGhst,
stnkSymbol,
ghstSymbol,
ghstPrice
}) => {
return (
<TableBody>
<TableRow>
@ -155,7 +168,10 @@ const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isC
</TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}>
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}>
{`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? ghstSymbol : stnkSymbol}`}
{formatCurrency(prepareBalance, 5, isPayoutGhst ? ghstSymbol : stnkSymbol)}
</Typography>
<Typography variant="body2" gutterBottom={false} style={{ lineHeight: 1.4 }}>
{formatCurrency(prepareBalance * ghstPrice, 2)}
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}>

View File

@ -17,7 +17,6 @@ import TokenStack from "../../../components/TokenStack/TokenStack";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { formatCurrency } from "../../../helpers";
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { isNetworkLegacy } from "../../../constants";
import { useLpValuation } from "../../../hooks/treasury";
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
@ -36,7 +35,7 @@ const FarmPools = ({ chainId }) => {
const pools = [
{
icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
icons: ["FTSO", tokenNameConverter(chainId, reserveSymbol)],
name: `${ftsoSymbol}-${reserveSymbol}`,
dex: "Uniswap V2",
url: `/${network}/dex/uniswap`,

View File

@ -1,7 +1,7 @@
import { Avatar, Box, Link } from "@mui/material";
import { styled } from "@mui/material/styles";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
@ -23,6 +23,7 @@ import {
STAKING_ADDRESSES,
} from "../../../constants/addresses";
import { useCurrentIndex } from "../../../hooks/staking";
import { useLocalStorage } from "../../../hooks/localstorage";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { formatNumber } from "../../../helpers";
@ -82,20 +83,11 @@ export const StakeInputArea = ({
const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false);
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("stake-decimals")
? localStorage.getItem("stake-decimals")
: "5"
);
const { getStorageValue, setStorageValue } = useLocalStorage();
const [isClaim, setIsClaim] = useState(localStorage.getItem("stake-isClaim")
? localStorage.getItem("stake-isClaim") === 'true'
: true
);
const [isTrigger, setIsTrigger] = useState(localStorage.getItem("stake-isTrigger")
? localStorage.getItem("stake-isTrigger") === 'true'
: true
);
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "stake-decimals", "5"));
const [isClaim, setIsClaim] = useState(() => getStorageValue(chainId, address, "stake-isClaim", true));
const [isTrigger, setIsTrigger] = useState(() => getStorageValue(chainId, address, "stake-isTrigger", true));
const [amount, setAmount] = useState("");
const [receiveAmount, setReceiveAmount] = useState("");
@ -110,23 +102,22 @@ export const StakeInputArea = ({
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const setIsClaimInner = (value) => {
const setIsClaimInner = useCallback((value) => {
setIsClaim(value);
localStorage.setItem("stake-isClaim", value);
setStorageValue(chainId, address, "stake-isClaim", value);
}, [chainId, address]);
}
const setIsTriggerInner = (value) => {
const setIsTriggerInner = useCallback((value) => {
setIsTrigger(value);
localStorage.setItem("stake-isTrigger", value);
}
setStorageValue(chainId, address, "stake-isTrigger", value);
}, [chainId, address]);
const setFormatDecimalsInner = (value) => {
const setFormatDecimalsInner = useCallback((value) => {
if (Number(value) <= 17) {
setFormatDecimals(value);
localStorage.setItem("staking-decimals", value);
}
setStorageValue(chainId, address, "stake-decimals", value);
}
}, [chainId, address]);
useEffect(() => {
const innerBalance = upperToken === ghstSymbol ?
@ -242,36 +233,6 @@ export const StakeInputArea = ({
/>
</Box>
{upperTokenModalOpen && (
<TokenModal
open={upperTokenModalOpen}
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
handleClose={() => setUpperTokenModalOpen(false)}
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
isUpper={true}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
)}
{bottomTokenModalOpen && (
<TokenModal
open={bottomTokenModalOpen}
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
handleClose={() => setBottomTokenModalOpen(false)}
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
tokenToExclude={upperToken}
isUpper={false}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
)}
<Box>
<PrimaryButton
fullWidth

View File

@ -6,7 +6,6 @@ import Token from "../../../components/Token/Token";
import { SecondaryButton } from "../../../components/Button";
import { formatNumber, formatCurrency } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { isNetworkLegacy } from "../../../constants"
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import {
@ -129,7 +128,6 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
price={ghstPrice}
description={`${ghstSymbol} is the governance token enabling pure Web3 cross-chain magic. 1 ${ghstSymbol} = 1 ${ftsoSymbol} x Current Index.`}
/>
{!isNetworkLegacy(chainId) && (
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl={"https://ghostchain.io/faucet/"}
@ -139,7 +137,6 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
price={reservePrice}
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
/>
)}
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`}
@ -151,10 +148,7 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
tokenName={reserveSymbol}
balance={reserveBalance}
price={reservePrice}
description={isNetworkLegacy(chainId)
? `${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`
: `${reserveSymbol} (Wrapped ${nativeSymbol}) is an ERC-20 token that represents ${nativeSymbol} and is pegged 1:1 to the value of ${nativeSymbol}.`
}
description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`}
/>
</Box>
</Grid>

View File

@ -0,0 +1,27 @@
import { createContext, useContext, useState, useEffect } from "react";
const LocalStorageContext = createContext();
export const useLocalStorage = () => useContext(LocalStorageContext);
export const LocalStorageProvider = ({ children }) => {
const getStorageKey = (chainId, address, target) => {
return `${chainId}:${address}:${target}`;
}
const getStorageValue = (chainId, address, target, defaultValue) => {
const key = getStorageKey(chainId, address, target);
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : defaultValue;
}
const setStorageValue = (chainId, address, target, value) => {
const key = getStorageKey(prefix, chainId, address, target);
localStorage.setItem(key, JSON.stringify(value));
}
return (
<LocalStorageContext.Provider value={{ getStorageValue, setStorageValue }}>
{children}
</LocalStorageContext.Provider>
)
}

View File

@ -9,6 +9,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WagmiProvider } from "wagmi";
import { config } from "./config";
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
import { LocalStorageProvider } from "./hooks/localstorage";
import ReactGA from "react-ga4";
const queryClient = new QueryClient();
@ -24,7 +25,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<StyledEngineProvider injectFirst>
<UnstableProviderProvider>
<MetadataProviderProvider>
<LocalStorageProvider>
<App />
</LocalStorageProvider>
</MetadataProviderProvider>
</UnstableProviderProvider>
</StyledEngineProvider>