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", "name": "ghost-dao-interface",
"private": true, "private": true,
"version": "0.6.14", "version": "0.7.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -22,7 +22,7 @@ import Sidebar from "./components/Sidebar/Sidebar";
import TopBar from "./components/TopBar/TopBar"; import TopBar from "./components/TopBar/TopBar";
import { shouldTriggerSafetyCheck } from "./helpers"; import { shouldTriggerSafetyCheck } from "./helpers";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "./constants"; import { isNetworkAvailable, isGovernanceAvailable } from "./constants";
import useTheme from "./hooks/useTheme"; import useTheme from "./hooks/useTheme";
import { useUnstableProvider } from "./hooks/ghost"; import { useUnstableProvider } from "./hooks/ghost";
import { dark as darkTheme } from "./themes/dark.js"; 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 BondModalContainer = lazy(() => import("./containers/Bond/BondModal"));
const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer")); const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard")); 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 Dex = lazy(() => import("./containers/Dex/Dex"));
const Bridge = lazy(() => import("./containers/Bridge/Bridge")); const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
const NotFound = lazy(() => import("./containers/NotFound/NotFound")); 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" 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="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} />} /> <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="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} />} /> <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} />} />} {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 StakeIcon from "../Icon/StakeIcon";
import WrapIcon from "../Icon/WrapIcon"; import WrapIcon from "../Icon/WrapIcon";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants"; import { isNetworkAvailable, isGovernanceAvailable } from "../../constants";
import { AVAILABLE_DEXES } from "../../constants/dexes"; import { AVAILABLE_DEXES } from "../../constants/dexes";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses"; import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
import { ECOSYSTEM } from "../../constants/ecosystem"; import { ECOSYSTEM } from "../../constants/ecosystem";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { sortBondsByDiscount, formatCurrency } from "../../helpers"; import { sortBondsByDiscount, formatCurrency } from "../../helpers";
import BondDiscount from "../../containers/Bond/components/BondDiscount"; import BondDiscount from "../../containers/Bond/components/BondDiscount";
import Chip from "../Chip/Chip";
import DashboardIcon from '@mui/icons-material/Dashboard'; import DashboardIcon from '@mui/icons-material/Dashboard';
import ShowerIcon from '@mui/icons-material/Shower'; import ShowerIcon from '@mui/icons-material/Shower';
@ -143,6 +144,9 @@ const NavContent = ({ chainId, addressChainId }) => {
to={`/${chainName}/bonds`} to={`/${chainName}/bonds`}
children={ children={
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}> <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) => { {sortBondsByDiscount(ghostBonds).map((bond, index) => {
return ( return (
<Link <Link
@ -156,6 +160,7 @@ const NavContent = ({ chainId, addressChainId }) => {
style={{ style={{
width: "180px", width: "180px",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center",
display: "flex", display: "flex",
gap: "10px" gap: "10px"
}} }}
@ -163,7 +168,10 @@ const NavContent = ({ chainId, addressChainId }) => {
variant="body2" variant="body2"
> >
{bond.displayName} {bond.displayName}
<BondDiscount textOnly discount={bond.discount} /> {bond.soldOut
? <Chip label="Sold Out" template="darkGray" />
: <BondDiscount discount={bond.discount} />
}
</Typography> </Typography>
</Box> </Box>
</Link> </Link>
@ -172,7 +180,7 @@ const NavContent = ({ chainId, addressChainId }) => {
</AccordionDetails> </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`} />} {isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to={`/${chainName}/governance`} />}
<Box className="menu-divider"> <Box className="menu-divider">
<Divider /> <Divider />

View File

@ -151,18 +151,6 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
<Divider /> <Divider />
</Box> </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) }}> <Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
<DisconnectButton onClose={onClose} /> <DisconnectButton onClose={onClose} />
</Box> </Box>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,14 +46,13 @@ import {
useLatestBlockNumber, useLatestBlockNumber,
useEraIndex, useEraIndex,
} from "../../hooks/ghost"; } from "../../hooks/ghost";
import { useLocalStorage } from "../../hooks/localstorage";
import { ValidatorTable } from "./ValidatorTable"; import { ValidatorTable } from "./ValidatorTable";
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal"; import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
import { BridgeHeader } from "./BridgeHeader"; import { BridgeHeader } from "./BridgeHeader";
import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard"; import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard";
const STORAGE_PREFIX = "storedTransactions"
const Bridge = ({ chainId, address, config, connect }) => { const Bridge = ({ chainId, address, config, connect }) => {
const isBigScreen = useMediaQuery("(max-width: 980px)") const isBigScreen = useMediaQuery("(max-width: 980px)")
const isSmallScreen = useMediaQuery("(max-width: 650px)"); const isSmallScreen = useMediaQuery("(max-width: 650px)");
@ -67,14 +66,15 @@ const Bridge = ({ chainId, address, config, connect }) => {
const [bridgeAction, setBridgeAction] = useState(true); const [bridgeAction, setBridgeAction] = useState(true);
const [currentTime, setCurrentTime] = useState(Date.now()); const [currentTime, setCurrentTime] = useState(Date.now());
const { getStorageValue, setStorageValue } = useLocalStorage();
useEffect(() => { useEffect(() => {
const interval = setInterval(() => setCurrentTime(Date.now()), 1000); const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX); const [storedTransactions, setStoredTransactions] = useState(() =>
const [storedTransactions, setStoredTransactions] = useState( getStorageValue(chainId, address, "bridge-txs", [])
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
); );
const { gatekeeperAddress } = useGatekeeperAddress(chainId); const { gatekeeperAddress } = useGatekeeperAddress(chainId);
@ -281,7 +281,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
const removeStoredRecord = useCallback(() => { const removeStoredRecord = useCallback(() => {
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex) const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
setStoredTransactions(newStoredTransactions); setStoredTransactions(newStoredTransactions);
localStorage.setItem(storagePrefix, JSON.stringify(newStoredTransactions)); setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(-1); setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]); }, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
@ -307,8 +307,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
const newStoredTransactions = [transaction, ...storedTransactions]; const newStoredTransactions = [transaction, ...storedTransactions];
setStoredTransactions(newStoredTransactions); setStoredTransactions(newStoredTransactions);
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions)); setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(0) setActiveTxIndex(0);
} }
return ( 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 { Box, Typography, Link, FormControlLabel, Checkbox, useTheme } from "@mui/material";
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-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 InfoTooltip from "../../components/Tooltip/InfoTooltip";
import Modal from "../../components/Modal/Modal"; import Modal from "../../components/Modal/Modal";
import GhostStyledIcon from "../../components/Icon/GhostIcon"; import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { PrimaryButton, TertiaryButton } from "../../components/Button"; import { PrimaryButton, TertiaryButton, SecondaryButton } from "../../components/Button";
import { formatCurrency } from "../../helpers"; import { formatCurrency } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
export const BridgeModal = ({ export const BridgeModal = ({
providerDetail, 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 ( return (
<Modal <Modal
data-testid="transaction-details-modal" data-testid="transaction-details-modal"
@ -89,15 +96,43 @@ export const BridgeModal = ({
minHeight={"100px"} minHeight={"100px"}
> >
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem"> <Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
{!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center"> {!providerDetail && <Box
<TertiaryButton 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 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 Get GHOST Connect
</TertiaryButton> </SecondaryButton>
</Box>} </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 && ( {currentRecord?.finalization > 0 && (
<> <>
<Box <Box
@ -285,7 +320,7 @@ export const BridgeModal = ({
</Box> </Box>
</> </>
)} )}
</Box>} </Box>
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0"> <Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
<Box display="flex" flexDirection="row" justifyContent="space-between"> <Box display="flex" flexDirection="row" justifyContent="space-between">
@ -357,13 +392,21 @@ export const BridgeModal = ({
</Box> </Box>
<Box display="flex" flexDirection="column" gap="5px"> <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 fullWidth
loading={false} loading={false}
onClick={() => removeStoredRecord()} onClick={() => removeStoredRecord()}
> >
Erase Record Erase Record
</PrimaryButton> </TertiaryButton>
<Typography variant="body2" sx={{ fontStyle: "italic" }}> <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. 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 gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
<Box width="100%" display="flex" flexDirection="column" alignItems="start"> <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 <FormControlLabel
control={ control={
<Checkbox <Checkbox
@ -434,7 +483,7 @@ export const BridgeConfirmModal = ({
</span> </span>
} }
/> />
<hr style={{ margin: "10px 0", width: "100%" }} /> <hr style={{ margin: "10px 0", width: "100%" }} />
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox

View File

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

View File

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

View File

@ -28,6 +28,7 @@ import PageTitle from "../../components/PageTitle/PageTitle";
import { PrimaryButton, TertiaryButton } from "../../components/Button"; import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard"; import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, useBalance } from "../../hooks/tokens"; import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance"; import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
@ -42,11 +43,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
const isVerySmallScreen = useMediaQuery("(max-width: 379px)"); const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const theme = useTheme(); const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`); const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposals] = useState( const [myProposals, setMyProposalsInner] = useState(() => myStoredProposals.map(id => BigInt(id)));
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const [isPending, setIsPending] = useState(false); const [isPending, setIsPending] = useState(false);
const [isModalOpened, setIsModalOpened] = useState(false); const [isModalOpened, setIsModalOpened] = useState(false);
@ -61,10 +61,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
proposalDescription proposalDescription
} = useProposalHash(chainId, proposalFunctions); } = useProposalHash(chainId, proposalFunctions);
useEffect(() => { const setMyProposals = useCallback((proposals) => {
const toStore = JSON.stringify(myProposals.map(id => id.toString())); setMyProposalsInner(proposals);
localStorage.setItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`, toStore); setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString()))
}, [myProposals]); }, [chainId, address]);
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/governance/create" }); 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 ReactGA from "react-ga4";
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
@ -38,6 +38,7 @@ import { parseFunctionCalldata } from "./components/functions";
import { networkAvgBlockSpeed } from "../../constants"; import { networkAvgBlockSpeed } from "../../constants";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens"; import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
import { import {
getVoteValue, getVoteValue,
@ -75,7 +76,6 @@ import RepeatIcon from '@mui/icons-material/Repeat';
const HUNDRED = new DecimalBigNumber(100n, 0); const HUNDRED = new DecimalBigNumber(100n, 0);
const ProposalDetails = ({ chainId, address, connect, config }) => { const ProposalDetails = ({ chainId, address, connect, config }) => {
const { id } = useParams(); const { id } = useParams();
const proposalId = BigInt(id); const proposalId = BigInt(id);
@ -89,6 +89,7 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const theme = useTheme(); const theme = useTheme();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { getStorageValue, setStorageValue } = useLocalStorage();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance } = useBalance(chainId, "GHST", address); const { balance } = useBalance(chainId, "GHST", address);
@ -149,20 +150,19 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
return url; return url;
}, [proposalProposer, config]); }, [proposalProposer, config]);
const handleVote = async (support) => { const handleVote = useCallback(async (support) => {
setIsPending(true); setIsPending(true);
const result = await castVote(chainId, address, proposalId, support); const result = await castVote(chainId, address, proposalId, support);
if (result) { 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)); const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
proposals.push(proposalId); proposals.push(proposalId);
const toStore = JSON.stringify(proposals.map(id => id.toString())); setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
localStorage.setItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`, toStore);
await queryClient.invalidateQueries(); await queryClient.invalidateQueries();
} }
setIsPending(false); setIsPending(false);
} }, [chainId, address, proposalId]);
const handleExecute = async () => { const handleExecute = async () => {
setIsPending(true); setIsPending(true);

View File

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

View File

@ -24,10 +24,11 @@ import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../../helpers/timeUtil"; import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { formatNumber } from "../../../helpers"; import { formatNumber, formatCurrency } from "../../../helpers";
import { STAKING_ADDRESSES } from "../../../constants/addresses"; import { STAKING_ADDRESSES } from "../../../constants/addresses";
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking"; import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens"; import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice } from "../../../hooks/prices";
import ClaimConfirmationModal from "./ClaimConfirmationModal"; import ClaimConfirmationModal from "./ClaimConfirmationModal";
@ -53,6 +54,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false); const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [isPayoutGhst, _] = useState(true); const [isPayoutGhst, _] = useState(true);
const ghstPrice = useGhstPrice(chainId);
const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address); const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address);
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId); const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
const { balanceForShares } = useBalanceForShares(chainId, "STNK", claim.shares); const { balanceForShares } = useBalanceForShares(chainId, "STNK", claim.shares);
@ -132,6 +134,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
isClaimable={claim.expiry > epoch.number} isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
ghstPrice={ghstPrice}
/> />
</Table> </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 ( return (
<TableBody> <TableBody>
<TableRow> <TableRow>
@ -155,7 +168,10 @@ const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isC
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}> <TableCell style={{ padding: "8px 8px 8px 0" }}>
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}> <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> </Typography>
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}> <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 { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { formatCurrency } from "../../../helpers"; import { formatCurrency } from "../../../helpers";
import { tokenNameConverter } from "../../../helpers/tokenConverter"; import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { isNetworkLegacy } from "../../../constants";
import { useLpValuation } from "../../../hooks/treasury"; import { useLpValuation } from "../../../hooks/treasury";
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens"; import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
@ -36,7 +35,7 @@ const FarmPools = ({ chainId }) => {
const pools = [ const pools = [
{ {
icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)], icons: ["FTSO", tokenNameConverter(chainId, reserveSymbol)],
name: `${ftsoSymbol}-${reserveSymbol}`, name: `${ftsoSymbol}-${reserveSymbol}`,
dex: "Uniswap V2", dex: "Uniswap V2",
url: `/${network}/dex/uniswap`, url: `/${network}/dex/uniswap`,

View File

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

View File

@ -6,7 +6,6 @@ import Token from "../../../components/Token/Token";
import { SecondaryButton } from "../../../components/Button"; import { SecondaryButton } from "../../../components/Button";
import { formatNumber, formatCurrency } from "../../../helpers"; import { formatNumber, formatCurrency } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { isNetworkLegacy } from "../../../constants"
import { useBalance, useTokenSymbol } from "../../../hooks/tokens"; import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { import {
@ -129,17 +128,15 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
price={ghstPrice} price={ghstPrice}
description={`${ghstSymbol} is the governance token enabling pure Web3 cross-chain magic. 1 ${ghstSymbol} = 1 ${ftsoSymbol} x Current Index.`} description={`${ghstSymbol} is the governance token enabling pure Web3 cross-chain magic. 1 ${ghstSymbol} = 1 ${ftsoSymbol} x Current Index.`}
/> />
{!isNetworkLegacy(chainId) && ( <TokenTab
<TokenTab isMobileScreen={isMobileScreen}
isMobileScreen={isMobileScreen} tokenUrl={"https://ghostchain.io/faucet/"}
tokenUrl={"https://ghostchain.io/faucet/"} theme={theme}
theme={theme} tokenName={nativeSymbol}
tokenName={nativeSymbol} balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)}
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)} price={reservePrice}
price={reservePrice} description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`} />
/>
)}
<TokenTab <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`} tokenUrl={`/${network}/dex/uniswap`}
@ -151,10 +148,7 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
tokenName={reserveSymbol} tokenName={reserveSymbol}
balance={reserveBalance} balance={reserveBalance}
price={reservePrice} price={reservePrice}
description={isNetworkLegacy(chainId) description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`}
? `${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}.`
}
/> />
</Box> </Box>
</Grid> </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 { WagmiProvider } from "wagmi";
import { config } from "./config"; import { config } from "./config";
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost" import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
import { LocalStorageProvider } from "./hooks/localstorage";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@ -24,7 +25,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<UnstableProviderProvider> <UnstableProviderProvider>
<MetadataProviderProvider> <MetadataProviderProvider>
<App /> <LocalStorageProvider>
<App />
</LocalStorageProvider>
</MetadataProviderProvider> </MetadataProviderProvider>
</UnstableProviderProvider> </UnstableProviderProvider>
</StyledEngineProvider> </StyledEngineProvider>