Compare commits

..

No commits in common. "main" and "dao-governance" have entirely different histories.

79 changed files with 1152 additions and 2276 deletions

View File

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

View File

@ -5,16 +5,8 @@ import CssBaseline from "@mui/material/CssBaseline";
import { styled, ThemeProvider } from "@mui/material/styles"; import { styled, ThemeProvider } from "@mui/material/styles";
import { lazy, Suspense, useCallback, useEffect, useState } from "react"; import { lazy, Suspense, useCallback, useEffect, useState } from "react";
import toast, { Toaster } from "react-hot-toast"; import toast, { Toaster } from "react-hot-toast";
import { Outlet, Navigate, Route, Routes, useLocation, useParams } from "react-router-dom"; import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { import { useAccount, useConnect, useChainId, useConfig, usePublicClient, injected } from "wagmi";
useAccount,
useConnect,
useChainId,
useConfig,
usePublicClient,
useSwitchChain,
injected
} from "wagmi";
import Messages from "./components/Messages/Messages"; import Messages from "./components/Messages/Messages";
import NavDrawer from "./components/Sidebar/NavDrawer"; import NavDrawer from "./components/Sidebar/NavDrawer";
@ -22,7 +14,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, isGovernanceAvailable } from "./constants"; import { isNetworkAvailable, isNetworkLegacy, 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,6 +26,8 @@ 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"));
@ -106,7 +100,6 @@ function App() {
const [theme, toggleTheme] = useTheme(); const [theme, toggleTheme] = useTheme();
const config = useConfig(); const config = useConfig();
const { chains } = useSwitchChain();
const { connect, error: errorMessage } = useConnect(); const { connect, error: errorMessage } = useConnect();
const tryConnectInjected = () => connect({ connector: injected() }); const tryConnectInjected = () => connect({ connector: injected() });
@ -116,12 +109,12 @@ function App() {
const [isSidebarExpanded, setIsSidebarExpanded] = useState(false); const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const { address = "", chainId: addressChainId, status, isConnected, isReconnecting } = useAccount(); const { address = "", chainId: addressChainId, isConnected, isReconnecting } = useAccount();
const provider = usePublicClient(); const provider = usePublicClient();
const chainId = useChainId(); const chainId = useChainId();
const isSmallerScreen = useMediaQuery("(max-width: 1130px)"); const isSmallerScreen = useMediaQuery("(max-width: 1047px)");
const isSmallScreen = useMediaQuery("(max-width: 600px)"); const isSmallScreen = useMediaQuery("(max-width: 600px)");
const { const {
@ -160,7 +153,7 @@ function App() {
setWrongNetworkToastId(null); setWrongNetworkToastId(null);
} }
} }
}, [chainId, addressChainId, isConnected, wrongNetworkToastId]) }, [chainId, addressChainId, isConnected])
useEffect(() => { useEffect(() => {
if (errorMessage) { if (errorMessage) {
@ -177,6 +170,7 @@ function App() {
}; };
const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme; const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme;
const chainExists = isNetworkAvailable(chainId, addressChainId); const chainExists = isNetworkAvailable(chainId, addressChainId);
useEffect(() => { useEffect(() => {
@ -193,7 +187,6 @@ function App() {
wrongNetworkToastId={wrongNetworkToastId} wrongNetworkToastId={wrongNetworkToastId}
setWrongNetworkToastId={setWrongNetworkToastId} setWrongNetworkToastId={setWrongNetworkToastId}
address={address} address={address}
status={status}
chainId={addressChainId ? addressChainId : chainId} chainId={addressChainId ? addressChainId : chainId}
chainExists={chainExists} chainExists={chainExists}
handleDrawerToggle={handleDrawerToggle} handleDrawerToggle={handleDrawerToggle}
@ -210,19 +203,23 @@ function App() {
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}> <div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
<Suspense fallback={<div></div>}> <Suspense fallback={<div></div>}>
<Routes> <Routes>
<Route path="/" element={<Navigate to={chainExists ? `/${chains.at(0).name.toLowerCase()}/dashboard` : "/empty"} />} /> <Route path="/" element={<Navigate to={chainExists ? "/dashboard" : "/empty"} />} />
{chainExists && {chainExists &&
<Route path="/:network" element={<AvailableNetworkGuard allowedNetworks={chains.map(chain => chain.name.toLowerCase())} /> }> <>
<Route path="dashboard" element={<TreasuryDashboard chainId={addressChainId ? addressChainId : chainId} />} /> <Route path="/dashboard" element={<TreasuryDashboard chainId={addressChainId ? addressChainId : chainId} />} />
<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 config={config} 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}/>} />
<Route path="bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} /> {isNetworkLegacy(chainId)
<Route path="dex/:name" element={<Dex config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} /> ? <Route path="/faucet" element={<Faucet 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} />} />} : <Route path="/wrapper" element={<Wrapper config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />} }
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance/create" element={<NewProposal 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> <Route path="/dex/:name" element={<Dex 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/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
</>
} }
<Route path="/empty" element={<NotFound <Route path="/empty" element={<NotFound
wrongNetworkToastId={wrongNetworkToastId} wrongNetworkToastId={wrongNetworkToastId}
@ -245,12 +242,4 @@ function App() {
); );
} }
const AvailableNetworkGuard = ({ allowedNetworks }) => {
const { network } = useParams();
if (!allowedNetworks.includes(network)) {
return <Navigate to="/empty" replace />;
}
return <Outlet />
}
export default App; export default App;

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg id="uuid-61b3c585-44d6-4fd8-a0e7-6a5e76478eb1" data-name="uuid-70ba15fb-c44e-4945-a02a-8d07f94a9abb" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 513 513"> <svg id="uuid-61b3c585-44d6-4fd8-a0e7-6a5e76478eb1" data-name="uuid-70ba15fb-c44e-4945-a02a-8d07f94a9abb" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 505 505">
<defs> <defs>
<style> <style>
.uuid-6437a6f0-42e0-4c9c-b0ef-b632253e989a { .uuid-6437a6f0-42e0-4c9c-b0ef-b632253e989a {

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 254 254"> <svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
<defs> <defs>
<style> <style>
.uuid-e080659c-91a4-4965-9905-603394020b2c { .uuid-e080659c-91a4-4965-9905-603394020b2c {

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg id="uuid-cf283373-2eb4-44d0-88d2-93533a7f4b1d" data-name="eGHST" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 254 254"> <svg id="uuid-cf283373-2eb4-44d0-88d2-93533a7f4b1d" data-name="eGHST" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
<defs> <defs>
<style> <style>
.uuid-26b0e53c-73c9-4369-9725-1cee728298be { .uuid-26b0e53c-73c9-4369-9725-1cee728298be {

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 258.751 258.751"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 254.751 254.751">
<defs> <defs>
<style> <style>
.uuid-67f42f03-098b-49dc-a7ed-dc13bd713387 { .uuid-67f42f03-098b-49dc-a7ed-dc13bd713387 {

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg id="uuid-0fc887e6-719b-4e51-bef8-e5c269508f6b" data-name="sGHST" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 254 254"> <svg id="uuid-0fc887e6-719b-4e51-bef8-e5c269508f6b" data-name="sGHST" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
<defs> <defs>
<style> <style>
.uuid-a205ebe2-7c8f-434c-9bde-29ac218716be { .uuid-a205ebe2-7c8f-434c-9bde-29ac218716be {

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -9,12 +9,12 @@ const classes = {
const StyledMuiChip = styled(MuiChip, { const StyledMuiChip = styled(MuiChip, {
shouldForwardProp: prop => prop !== "template" && prop !== "strong", shouldForwardProp: prop => prop !== "template" && prop !== "strong",
})(({ theme, template, background, strong }) => { })(({ theme, template, strong }) => {
return { return {
[`&.${classes.chip}`]: { [`&.${classes.chip}`]: {
height: "21px", height: "21px",
borderRadius: "16px", borderRadius: "16px",
backgroundColor: background ? background : template backgroundColor: template
? template === "purple" ? template === "purple"
? theme.colors.special["olyZaps"] ? theme.colors.special["olyZaps"]
: template === "gray" : template === "gray"
@ -42,8 +42,8 @@ const StyledMuiChip = styled(MuiChip, {
}; };
}); });
const Chip = ({ background, template, strong = false, ...props }) => { const Chip = ({ template, strong = false, ...props }) => {
return <StyledMuiChip className={classes.chip} background={background} template={template} strong={strong} {...props} />; return <StyledMuiChip className={classes.chip} template={template} strong={strong} {...props} />;
}; };
export default Chip; export default Chip;

View File

@ -130,7 +130,7 @@ const NavItem = ({
}; };
const LinkItem = () => ( const LinkItem = () => (
<Link {...linkProps} {...props} underline="hover" onClick={(e) => e.stopPropagation()}> <Link {...linkProps} {...props} underline="hover">
<Box <Box
sx={{ fontFamily: "Ubuntu" }} sx={{ fontFamily: "Ubuntu" }}
display="flex" display="flex"

View File

@ -2,7 +2,7 @@ import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
const PageTitle = ({ name, subtitle, noMargin }) => { const PageTitle = ({ name, subtitle, noMargin }) => {
const theme = useTheme(); const theme = useTheme();
const mobile = useMediaQuery(theme.breakpoints.down("900")); const mobile = useMediaQuery(theme.breakpoints.down("700"));
return ( return (
<Box <Box

View File

@ -25,7 +25,7 @@ const LinearProgressBar = (props) => {
{props.target && <Box {props.target && <Box
sx={{ sx={{
position: 'absolute', position: 'absolute',
left: `${Math.min(Math.max(props.target, 0), 100)}%`, left: `${props.target}%`,
top: props.targetTop || 0, top: props.targetTop || 0,
bottom: props.targetBottom || 0, bottom: props.targetBottom || 0,
width: props.targetWidth || "2px", width: props.targetWidth || "2px",

View File

@ -34,7 +34,6 @@ const Select = ({
inputWidth, inputWidth,
renderValue = null, renderValue = null,
width = "100%", width = "100%",
maxWidth = "100%"
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
return ( return (
@ -42,7 +41,6 @@ const Select = ({
display="flex" display="flex"
flexDirection="column" flexDirection="column"
width={width} width={width}
maxWidth={maxWidth}
sx={{ backgroundColor: theme.colors.gray[750] }} sx={{ backgroundColor: theme.colors.gray[750] }}
borderRadius="12px" borderRadius="12px"
padding="15px" padding="15px"
@ -61,15 +59,6 @@ const Select = ({
IconComponent={KeyboardArrowDownIcon} IconComponent={KeyboardArrowDownIcon}
renderValue={renderValue} renderValue={renderValue}
displayEmpty displayEmpty
MenuProps={{
PaperProps: {
sx: {
maxHeight: 160,
overflowY: 'auto',
},
},
disableScrollLock: true,
}}
> >
{options.map((opt) => ( {options.map((opt) => (
<MenuItem key={opt.value} value={opt.value}> <MenuItem key={opt.value} value={opt.value}>

View File

@ -1,4 +1,4 @@
import { useMemo } from "react"; import React from "react";
import "./Sidebar.scss"; import "./Sidebar.scss";
@ -16,7 +16,6 @@ import {
} from "@mui/material"; } from "@mui/material";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { useSwitchChain } from "wagmi";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import GitHubIcon from '@mui/icons-material/GitHub'; import GitHubIcon from '@mui/icons-material/GitHub';
@ -27,7 +26,7 @@ import HubIcon from '@mui/icons-material/Hub';
import PublicIcon from '@mui/icons-material/Public'; import PublicIcon from '@mui/icons-material/Public';
import ForkRightIcon from '@mui/icons-material/ForkRight'; import ForkRightIcon from '@mui/icons-material/ForkRight';
import GavelIcon from '@mui/icons-material/Gavel'; import GavelIcon from '@mui/icons-material/Gavel';
import CasinoIcon from '@mui/icons-material/Casino'; import ForumIcon from '@mui/icons-material/Forum';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import BookIcon from '@mui/icons-material/Book'; import BookIcon from '@mui/icons-material/Book';
import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange'; import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange';
@ -40,20 +39,19 @@ 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, isGovernanceAvailable } from "../../constants"; import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants";
import { AVAILABLE_DEXES } from "../../constants/dexes"; import { AVAILABLE_DEXES } from "../../constants/dexes";
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';
import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup'; import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup';
import { useTokenSymbol } from "../../hooks/tokens"; import { useTokenSymbol } from "../../hooks/tokens";
import { useFtsoPrice, useGhstPrice, useGhostedSupplyPrice } from "../../hooks/prices";
import { useLiveBonds } from "../../hooks/bonds/index"; import { useLiveBonds } from "../../hooks/bonds/index";
import pckg from "../../../package.json" import pckg from "../../../package.json"
@ -70,21 +68,14 @@ const StyledBox = styled(Box)(({ theme }) => ({
})); }));
const NavContent = ({ chainId, addressChainId }) => { const NavContent = ({ chainId, addressChainId }) => {
const { chains } = useSwitchChain();
const chainName = useMemo(() => {
return chains.find(chain => chain.id === chainId).name.toLowerCase();
}, [chains, chainId, addressChainId])
const { liveBonds: ghostBonds } = useLiveBonds(chainId); const { liveBonds: ghostBonds } = useLiveBonds(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const ghostedSupplyPrice = useGhostedSupplyPrice(chainId);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const bridgeNumbers = useMemo(() => {
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
const number = 1 + connectedNetworks * 3;
return `(${number}, ${number})`;
}, [chainId]);
return ( return (
<Paper className="dapp-sidebar"> <Paper className="dapp-sidebar">
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column"> <Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
@ -101,6 +92,17 @@ const NavContent = ({ chainId, addressChainId }) => {
Version {pckg.version} Version {pckg.version}
</Box> </Box>
</Link> </Link>
<Box display="flex" flexDirection="column" mt="10px">
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
{ftsoSymbol} Price: {formatCurrency(ftsoPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight="15px">
{ghstSymbol} Price: {formatCurrency(ghstPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
GHOSTed Supply: {formatCurrency(ghostedSupplyPrice, 2)}
</Box>
</Box>
</Box> </Box>
<Box className="menu-divider"> <Box className="menu-divider">
@ -111,18 +113,58 @@ const NavContent = ({ chainId, addressChainId }) => {
<div className="dapp-nav" id="navbarNav"> <div className="dapp-nav" id="navbarNav">
{isNetworkAvailable(chainId, addressChainId) && {isNetworkAvailable(chainId, addressChainId) &&
<> <>
<NavItem icon={DashboardIcon} label={`Dashboard`} to={`/${chainName}/dashboard`} /> <NavItem icon={DashboardIcon} label={`Dashboard`} to="/dashboard" />
{isNetworkLegacy(chainId)
? <NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
: <NavItem icon={WifiProtectedSetupIcon} label={`Wrapper`} to="/wrapper" />
}
<NavItem
defaultExpanded
icon={BondIcon}
label={`Bond`}
to="/bonds"
children={
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
return (
<Link
component={NavLink}
to={`/bonds/${bond.id}`}
key={index}
style={{ textDecoration: "none" }}
>
<Box mb="10px" display="flex" justifyContent="end">
<Typography
style={{
width: "180px",
justifyContent: "space-between",
display: "flex",
gap: "10px"
}}
component="span"
variant="body2"
>
{bond.displayName}
<BondDiscount textOnly discount={bond.discount} />
</Typography>
</Box>
</Link>
)
})}
</AccordionDetails>
}
/>
<NavItem <NavItem
icon={CurrencyExchangeIcon} icon={CurrencyExchangeIcon}
label={`(3, 3) Swap`} label={`Dex`}
to={`/${chainName}/dex/uniswap`} to={'/dex/uniswap'}
children={ children={
AVAILABLE_DEXES[chainId].length > 1 && <AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}> <AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
{AVAILABLE_DEXES[chainId].map((dex, index) => { {AVAILABLE_DEXES[chainId].map((dex, index) => {
return ( return (
<Link <Link
component={NavLink} component={NavLink}
to={`/${chainName}/dex/${dex.name.toLowerCase()}`} to={`/dex/${dex.name.toLowerCase()}`}
key={index} key={index}
style={{ textDecoration: "none" }} style={{ textDecoration: "none" }}
> >
@ -136,52 +178,9 @@ const NavContent = ({ chainId, addressChainId }) => {
</AccordionDetails> </AccordionDetails>
} }
/> />
<NavItem icon={StakeIcon} label={`(3, 3) Stake`} to={`/${chainName}/stake`} /> <NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
<NavItem <NavItem icon={ForkRightIcon} label={`Bridge`} to="/bridge" />
defaultExpanded {isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to="/governance" />}
icon={BondIcon}
label={`(1, 1) Bond`}
to={`/${chainName}/bonds`}
children={
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
{ghostBonds.length > 0 && <Box width="180px" mb="10px" ml="auto">
<Typography component="span" variant="body2">Bond Discounts</Typography>
</Box>}
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
return (
<Link
component={NavLink}
to={`/${chainName}/bonds/${bond.id}`}
key={index}
style={{ textDecoration: "none" }}
>
<Box mb="10px" display="flex" justifyContent="end">
<Typography
style={{
width: "180px",
justifyContent: "space-between",
alignItems: "center",
display: "flex",
gap: "10px"
}}
component="span"
variant="body2"
>
{bond.displayName}
{bond.soldOut
? <Chip label="Sold Out" template="darkGray" />
: <BondDiscount discount={bond.discount} />
}
</Typography>
</Box>
</Link>
)
})}
</AccordionDetails>
}
/>
<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"> <Box className="menu-divider">
<Divider /> <Divider />
</Box> </Box>
@ -192,9 +191,10 @@ const NavContent = ({ chainId, addressChainId }) => {
</div> </div>
<Box> <Box>
<NavItem href="https://ghostchain.io/game-theory-3-3" icon={CasinoIcon} label={`${bridgeNumbers} Game Theory`} />
<NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} /> <NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} />
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Documentation`} /> <NavItem href="http://ghostchain.io/builders" icon={ForumIcon} label={`Forum`} />
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Docs`} />
<NavItem href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts/issues" icon={ErrorOutlineIcon} label={`Git Issues`} />
<StyledBox display="flex" justifyContent="space-around" paddingY="24px"> <StyledBox display="flex" justifyContent="space-around" paddingY="24px">
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer"> <Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">
<GhostStyledIcon viewBox="0 0 24 24" component={GitHubIcon} className={classes.gray} /> <GhostStyledIcon viewBox="0 0 24 24" component={GitHubIcon} className={classes.gray} />

View File

@ -58,7 +58,6 @@ export const TokenAllowanceGuard = ({
height = "auto", height = "auto",
spendAmount, spendAmount,
tokenName, tokenName,
isNative,
owner, owner,
spender, spender,
decimals, decimals,
@ -89,7 +88,7 @@ export const TokenAllowanceGuard = ({
); );
} }
if (!isNative && allowance && spendAmount && allowance.lt(spendAmount)) if (allowance && spendAmount && allowance.lt(spendAmount))
return ( return (
<Grid container alignItems="center"> <Grid container alignItems="center">
<Grid item xs={12} sm={isVertical ? 12 : 8}> <Grid item xs={12} sm={isVertical ? 12 : 8}>

View File

@ -1,58 +0,0 @@
import { Box, Typography, SvgIcon, useTheme } from "@mui/material";
import { parseKnownToken } from "../../components/Token/Token";
import { useUnstableProvider } from "../../hooks/ghost";
import { PrimaryButton } from "../Button"
const GHOST_CONNECT = 'https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases';
function GhostChainSelect({ small }) {
const { providerDetail, isConnected } = useUnstableProvider();
const theme = useTheme();
if (providerDetail) {
return (
<Box
height="39px"
width={small ? "100px" : "155px"}
borderRadius="4px"
padding="0 14px"
border="1px solid #50759e"
>
<Box
display="flex"
flexDirection="row"
alignItems="center"
justifyContent="space-between"
width="100%"
height="100%"
>
<Box display="flex" flexDirection="row">
<SvgIcon component={parseKnownToken("CSPR")} inheritViewBox />
{!small && <Typography sx={{ paddingLeft: "6px"}}>CASPER</Typography>}
</Box>
<Box
width="21px"
height="21px"
backgroundColor={isConnected ? theme.colors.feedback.success : theme.colors.feedback.error}
borderRadius="50%"
></Box>
</Box>
</Box>
)
}
return (
<Box height="39px" width={small ? "100px" : "155px"}>
<PrimaryButton
sx={{ margin: "0 !important", padding: "0 !important" }}
onClick={() => window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}
fullWidth
>
Get GHOST {small ? "" : "Connect"}
</PrimaryButton>
</Box>
)
}
export default GhostChainSelect;

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect } from "react";
import { Box, Typography, SvgIcon, useTheme } from "@mui/material"; import { Box, Typography, SvgIcon, useTheme } from "@mui/material";
@ -12,71 +12,36 @@ import { isNetworkAvailable } from "../../constants";
import { parseKnownToken } from "../../components/Token/Token"; import { parseKnownToken } from "../../components/Token/Token";
import { useSwitchChain } from 'wagmi'; import { useSwitchChain } from 'wagmi';
import { useNavigate, useLocation } from 'react-router-dom';
import toast from "react-hot-toast"; import toast from "react-hot-toast";
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small, status }) { function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small }) {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate();
const hasAttemptedSwitch = useRef(false);
const { chains, switchChain } = useSwitchChain(); const { chains, switchChain } = useSwitchChain();
const { pathname } = useLocation();
const [networkId, setNetworkId] = useState(chainId); const [networkId, setNetworkId] = useState(chainId);
useEffect(() => { useEffect(() => {
if (status === 'reconnecting' || status === 'connecting') return; if (chainId !== networkId) setNetworkId(chainId);
}, [chainId])
const parts = pathname.split('/'); const handleChange = (event) => {
if (!hasAttemptedSwitch.current && parts.length > 2) { const chainName = chains.find((chain) => chain.id === event.target.value).name;
let targetChain = chains.at(0);
const chainName = parts[1].toLowerCase();
const chain = chains.find(chain => chain.name.toLowerCase() === chainName);
if (chain && targetChain.name !== chain.name) {
targetChain = chain;
}
hasAttemptedSwitch.current = true;
changeNetworkId(targetChain.name, targetChain.id);
}
}, [status, chains, pathname]);
const changeNetworkId = (chainName, newNetworkId) => {
toast.dismiss(wrongNetworkToastId); toast.dismiss(wrongNetworkToastId);
const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, { const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, {
position: 'bottom-right' position: 'bottom-right'
}); });
setWrongNetworkToastId(toastId); setWrongNetworkToastId(toastId);
setNetworkId(newNetworkId); setNetworkId(event.target.value);
switchChain({ switchChain({ chainId: event.target.value });
chainId: newNetworkId
}, {
onSuccess: (data) => console.log("Network switched to", data.name),
onError: (error) => console.log("Error during network switching", error),
});
}
const handleChange = (event) => {
const chainName = chains.find((chain) => chain.id === event.target.value).name;
changeNetworkId(chainName, event.target.value);
const parts = pathname.split('/');
if (parts.length > 2) {
parts[1] = chainName.toLowerCase();
const newPath = parts.join("/");
navigate(newPath, { replace: true });
}
} }
return( return(
<FormControl sx={{ width: small ? "auto" : "155px" }}> <FormControl sx={{ width: small ? "100px" : "155px" }}>
<Select <Select
labelId="network-select-helper-label" labelId="network-select-helper-label"
id="network-select-helper" id="network-select-helper"
value={networkId} value={networkId}
disabled={!hasAttemptedSwitch.current}
onChange={handleChange} onChange={handleChange}
sx={{ sx={{
boxShadow: 'none', boxShadow: 'none',
@ -92,7 +57,7 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
return ( return (
<MenuItem key={chain.name} value={chain.id}> <MenuItem key={chain.name} value={chain.id}>
<Box gap="10px" display="flex" flexDirection="row" alignItems="center"> <Box gap="10px" display="flex" flexDirection="row" alignItems="center">
<SvgIcon sx={{ width: 22 }} component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox /> <SvgIcon component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox />
{!small && <Typography>{chain.name}</Typography>} {!small && <Typography>{chain.name}</Typography>}
</Box> </Box>
</MenuItem> </MenuItem>

View File

@ -2,7 +2,7 @@ import { Box, Button, SvgIcon, useMediaQuery, useTheme } from "@mui/material";
import MenuIcon from "../../assets/icons/hamburger.svg?react"; import MenuIcon from "../../assets/icons/hamburger.svg?react";
import Wallet from "./Wallet" import Wallet from "./Wallet"
import SelectNetwork from "./SelectNetwork"; import SelectNetwork from "./SelectNetwork";
import GhostChainSelect from "./GhostChainSelect";
const PREFIX = "TopBar"; const PREFIX = "TopBar";
@ -18,13 +18,12 @@ function TopBar({
address, address,
wrongNetworkToastId, wrongNetworkToastId,
connect, connect,
status,
handleDrawerToggle, handleDrawerToggle,
setWrongNetworkToastId setWrongNetworkToastId
}) { }) {
const themeColor = useTheme(); const themeColor = useTheme();
const desktop = useMediaQuery(themeColor.breakpoints.up(1130)); const desktop = useMediaQuery(themeColor.breakpoints.up(1048));
const small = useMediaQuery(themeColor.breakpoints.down(600)); const small = useMediaQuery(themeColor.breakpoints.down(400));
return ( return (
<Box <Box
display="flex" display="flex"
@ -34,20 +33,12 @@ function TopBar({
marginRight={desktop ? "33px" : "0px"} marginRight={desktop ? "33px" : "0px"}
> >
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<Box <Box display="flex" justifyContent="space-between" alignItems="center" width={small ? "calc(100vw - 78px)" : "320px"}>
display="flex"
justifyContent="space-between"
alignItems="center"
width={small ? "calc(100vw - 78px)" : "500px"}
height="40px"
>
<GhostChainSelect small={small} />
<SelectNetwork <SelectNetwork
wrongNetworkToastId={wrongNetworkToastId} wrongNetworkToastId={wrongNetworkToastId}
setWrongNetworkToastId={setWrongNetworkToastId} setWrongNetworkToastId={setWrongNetworkToastId}
chainId={chainId} chainId={chainId}
small={small} small={small}
status={status}
/> />
<Wallet address={address} connect={connect} chainId={chainId} /> <Wallet address={address} connect={connect} chainId={chainId} />
</Box> </Box>

View File

@ -24,7 +24,7 @@ import { formatCurrency, shorten } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses"; import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
import { useAccount, useDisconnect, useSwitchChain } from "wagmi"; import { useAccount, useDisconnect } from "wagmi";
const DisconnectButton = ({ onClose }) => { const DisconnectButton = ({ onClose }) => {
const { disconnect } = useDisconnect(); const { disconnect } = useDisconnect();
@ -109,11 +109,10 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const tokens = useWallet(chainId, address); const tokens = useWallet(chainId, address);
const { chains } = useSwitchChain();
const onBtnClick = (dexName, from, to) => { const onBtnClick = (dexName, from, to) => {
navigate({ navigate({
pathname: `${chains.find(chain => chain.id === chainId).name.toLowerCase()}/dex/` + dexName, pathname: "/dex/" + dexName,
search: createSearchParams({ search: createSearchParams({
pool: true, pool: true,
from: from, from: from,
@ -151,6 +150,18 @@ 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

@ -9,13 +9,13 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { ChangeEvent, useState, useMemo, useEffect } from "react"; import { ChangeEvent, useState, useEffect } from "react";
import { useNavigate, createSearchParams } from "react-router-dom"; import { useNavigate, createSearchParams } from "react-router-dom";
import { useQuery } from "react-query"; 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 { EMPTY_ADDRESS } from "../../../constants/addresses"; import { isNetworkLegacy } from "../../../constants";
import GhostStyledIcon from "../../Icon/GhostIcon"; import GhostStyledIcon from "../../Icon/GhostIcon";
import TokenStack from "../../TokenStack/TokenStack"; import TokenStack from "../../TokenStack/TokenStack";
@ -26,12 +26,11 @@ import {
useNativePrice, useNativePrice,
useReservePrice, useReservePrice,
useFtsoPrice, useFtsoPrice,
useStnkPrice,
useGhstPrice useGhstPrice
} from "../../../hooks/prices"; } from "../../../hooks/prices";
import { useLpValuation } from "../../../hooks/treasury"; import { useLpValuation } from "../../../hooks/treasury";
import { useUniswapV2PairReserves } from "../../../hooks/uniswapv2"; import { useAccount, useBalance as useNativeBalance, useConfig } from "wagmi";
import { getTokenIcons } from "../../../hooks/helpers";
import { useAccount, useBalance as useNativeBalance, useConfig, useSwitchChain, useChainId } from "wagmi";
const addTokenToWallet = async (token, userAddress) => { const addTokenToWallet = async (token, userAddress) => {
if (!window.ethereum) return; if (!window.ethereum) return;
@ -72,7 +71,6 @@ export const Token = (props) => {
const { const {
isNative, isNative,
symbol, symbol,
faucetPath,
icons, icons,
address, address,
price = 0, price = 0,
@ -86,20 +84,13 @@ export const Token = (props) => {
} = props; } = props;
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const chainId = useChainId();
const { chains } = useSwitchChain();
const chainName = useMemo(() => {
return chains.find(chain => chain.id === chainId).name.toLowerCase();
}, [chains, chainId])
const useLink = (symbol, fromAddress, toAddress, isPool) => { const useLink = (symbol, fromAddress, toAddress, isPool) => {
if (faucetPath) { if (symbol.toUpperCase() === "RESERVE") {
navigate({ pathname: faucetPath }) navigate({ pathname: "/faucet" })
} else { } else {
navigate({ navigate({
pathname: `${chainName}/dex/uniswap`, pathname: "/dex/uniswap",
search: isPool ? search: isPool ?
createSearchParams({ createSearchParams({
pool: "true", pool: "true",
@ -155,7 +146,7 @@ export const Token = (props) => {
onClick={() => useLink(symbol, reserveAddress, address, isPool)} onClick={() => useLink(symbol, reserveAddress, address, isPool)}
fullWidth fullWidth
> >
<Typography>Get {faucetPath ? "for Free" : "on DEX"}</Typography> <Typography>Get on {symbol?.toUpperCase() === "RESERVE" ? "Faucet" : "Uniswap"}</Typography>
</SecondaryButton> </SecondaryButton>
</Box> </Box>
</Box> </Box>
@ -183,6 +174,11 @@ export const useWallet = (chainId, userAddress) => {
refetch: ftsoRefetch, refetch: ftsoRefetch,
contractAddress: ftsoAddress, contractAddress: ftsoAddress,
} = useBalance(chainId, "FTSO", userAddress); } = useBalance(chainId, "FTSO", userAddress);
const {
balance: stnkBalance,
refetch: stnkRefetch,
contractAddress: stnkAddress,
} = useBalance(chainId, "STNK", userAddress);
const { const {
balance: ghstBalance, balance: ghstBalance,
refetch: ghstRefetch, refetch: ghstRefetch,
@ -193,13 +189,11 @@ export const useWallet = (chainId, userAddress) => {
refetch: lpReserveFtsoRefetch, refetch: lpReserveFtsoRefetch,
contractAddress: lpReserveFtsoBalanceAddress, contractAddress: lpReserveFtsoBalanceAddress,
} = useBalance(chainId, "RESERVE_FTSO", userAddress); } = useBalance(chainId, "RESERVE_FTSO", userAddress);
const {
tokens: lpReserveFtsoTokens,
} = useUniswapV2PairReserves(chainId, "RESERVE_FTSO");
const nativePrice = useNativePrice(chainId); const nativePrice = useNativePrice(chainId);
const reservePrice = useReservePrice(chainId); const reservePrice = useReservePrice(chainId);
const ftsoPrice = useFtsoPrice(chainId); const ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(chainId); const ghstPrice = useGhstPrice(chainId);
const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n); const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n);
@ -208,39 +202,27 @@ export const useWallet = (chainId, userAddress) => {
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol; const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE"); const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO"); const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO");
const lpReserveFtsoTokenNames = useMemo(() => {
const token0 = getTokenIcons(chainId, lpReserveFtsoTokens?.token0 ?? []);
const token1 = getTokenIcons(chainId, lpReserveFtsoTokens?.token1 ?? []);
let tokenAddresses = [lpReserveFtsoTokens?.token1, lpReserveFtsoTokens?.token0];
let tokenNames = [...token1, ...token0];
if (token0?.at(0) === reserveSymbol) {
tokenAddresses = [lpReserveFtsoTokens?.token0, lpReserveFtsoTokens?.token1];
let tokenNames = [...token0, ...token1];
}
return { tokenAddresses, tokenNames }
}, [chainId, reserveSymbol, lpReserveFtsoTokens]);
const tokens = { const tokens = {
native: { native: {
symbol: nativeSymbol, symbol: nativeSymbol,
icons: [nativeSymbol], icons: [nativeSymbol],
balance: nativeBalance, balance: nativeBalance,
price: nativePrice, price: nativePrice,
refetch: nativeBalanceRefetch, refetch: nativeBalanceRefetch
}, },
reserve: { reserve: {
symbol: reserveSymbol, symbol: reserveSymbol,
address: reserveAddress, address: reserveAddress,
balance: reserveBalance, balance: reserveBalance,
price: reservePrice, price: reservePrice,
icons: [tokenNameConverter(chainId, reserveSymbol)], icons: isNetworkLegacy(chainId) ? ["GDAI"] : [tokenNameConverter(chainId, reserveSymbol)],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg", 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",
refetch: reserveRefetch, refetch: reserveRefetch,
}, },
ftso: { ftso: {
@ -252,6 +234,15 @@ export const useWallet = (chainId, userAddress) => {
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg", externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg",
refetch: ftsoRefetch, refetch: ftsoRefetch,
}, },
stnk: {
symbol: stnkSymbol,
address: stnkAddress,
balance: stnkBalance,
price: stnkPrice,
icons: ["STNK"],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/sGHST.svg",
refetch: stnkRefetch,
},
ghst: { ghst: {
symbol: ghstSymbol, symbol: ghstSymbol,
address: ghstAddress, address: ghstAddress,
@ -264,10 +255,10 @@ export const useWallet = (chainId, userAddress) => {
reserveFtso: { reserveFtso: {
isPool: true, isPool: true,
symbol: lpReserveFtsoSymbol, symbol: lpReserveFtsoSymbol,
address: lpReserveFtsoTokenNames?.tokenAddresses.at(1) ?? "", address: lpReserveFtsoBalanceAddress,
balance: lpReserveFtsoBalance, balance: lpReserveFtsoBalance,
price: lpReserveFtsoPrice, price: lpReserveFtsoPrice,
icons: lpReserveFtsoTokenNames?.tokenNames, icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg", externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg",
refetch: lpReserveFtsoRefetch, refetch: lpReserveFtsoRefetch,
} }
@ -286,12 +277,12 @@ export const useWallet = (chainId, userAddress) => {
export const Tokens = ({ address, tokens, onClose }) => { export const Tokens = ({ address, tokens, onClose }) => {
const [expanded, setExpanded] = useState(null); const [expanded, setExpanded] = useState(null);
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.ghst, tokens.reserveFtso]; const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.stnk, tokens.ghst, tokens.reserveFtso];
const tokenProps = (token) => ({ const tokenProps = (token) => ({
...token, ...token,
expanded: expanded === token.symbol, expanded: expanded === token.symbol,
reserveAddress: EMPTY_ADDRESS, reserveAddress: tokens.reserve.address,
onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null), onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null),
onAddTokenToWallet: () => addTokenToWallet(token, address), onAddTokenToWallet: () => addTokenToWallet(token, address),
onClose: () => onClose(), onClose: () => onClose(),

View File

@ -11,7 +11,7 @@ const WalletButton = ({ openWallet, connect }) => {
const { isConnected, chain } = useAccount(); const { isConnected, chain } = useAccount();
const theme = useTheme(); const theme = useTheme();
const onClick = isConnected ? openWallet : connect; const onClick = isConnected ? openWallet : connect;
const label = isConnected ? "Wallet" : `Connect`; const label = isConnected ? "Open Wallet" : `Connect Wallet`;
return ( return (
<Button <Button
id="fatso-menu-button" id="fatso-menu-button"
@ -76,12 +76,7 @@ export function Wallet({ address, chainId, connect }) {
onOpen={openWallet} onOpen={openWallet}
onClose={closeWallet} onClose={closeWallet}
> >
<InitialWalletView <InitialWalletView isWalletOpen={isWalletOpen} address={address} chainId={chainId} onClose={closeWallet} />
isWalletOpen={isWalletOpen}
address={address}
chainId={chainId}
onClose={closeWallet}
/>
</StyledSwipeableDrawer> </StyledSwipeableDrawer>
</> </>
); );

View File

@ -11,9 +11,6 @@ export const isGovernanceAvailable = (chainId, addressChainId) => {
case 11155111: case 11155111:
exists = true exists = true
break; break;
case 63:
exists = true
break;
default: default:
break; break;
} }
@ -40,30 +37,15 @@ export const isNetworkAvailable = (chainId, addressChainId) => {
} }
export const isNetworkLegacy = (chainId) => { export const isNetworkLegacy = (chainId) => {
let isLegacy = false; let exists = false;
switch (chainId) { switch (chainId) {
case 11155111: case 560048:
isLegacy = true exists = true
break;
case 63:
isLegacy = true
break; break;
default: default:
break; break;
} }
return isLegacy; return exists;
}
export const isNetworkLegacyType = (chainId) => {
let isLegacyType = false;
switch (chainId) {
case 63:
isLegacyType = true;
break;
default:
break;
}
return isLegacyType
} }
export const networkAvgBlockSpeed = (chainId) => { export const networkAvgBlockSpeed = (chainId) => {

View File

@ -1,40 +1,38 @@
import { NetworkId } from "../constants"; import { NetworkId } from "../constants";
export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
export const STAKING_ADDRESSES = { export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506", [NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
[NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506", [NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506", [NetworkId.TESTNET_MORDOR]: "0xC25C9C56a89ebd6ef291b415d00ACfa7913c55e7",
}; };
export const BOND_DEPOSITORY_ADDRESSES = { export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0", [NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
[NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0", [NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0", [NetworkId.TESTNET_MORDOR]: "0x7C85cDEddBAd0f50453d373F7332BEa11ECa7BAf",
}; };
export const DAO_TREASURY_ADDRESSES = { export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E", [NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
[NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E", [NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E", [NetworkId.TESTNET_MORDOR]: "0x5883C8e2259556B534036c7fDF4555E09dE9f243",
}; };
export const FTSO_DAI_LP_ADDRESSES = { export const FTSO_DAI_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D", [NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
[NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D", [NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469", [NetworkId.TESTNET_MORDOR]: "0xE6546D12665dB5B22Cb92FB9e0221aE51A57aeaa",
}; };
export const FTSO_STNK_LP_ADDRESSES = { export const FTSO_STNK_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", [NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", [NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
[NetworkId.TESTNET_MORDOR]: "0x0000000000000000000000000000000000000000", [NetworkId.TESTNET_MORDOR]: "0x0000000000000000000000000000000000000000",
} }
export const RESERVE_ADDRESSES = { export const RESERVE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", [NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
[NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4", [NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC", [NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
}; };
@ -46,44 +44,41 @@ export const WETH_ADDRESSES = {
export const GHST_ADDRESSES = { export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392", [NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
[NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392", [NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392", [NetworkId.TESTNET_MORDOR]: "0x14b5787F8a1E62786F50A7998A9b14aa24298423",
}; };
export const STNK_ADDRESSES = { export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7", [NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
[NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7", [NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7", [NetworkId.TESTNET_MORDOR]: "0x137bA9403885D8ECEa95AaFBb8734F5a16121bAC",
}; };
export const FTSO_ADDRESSES = { export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A", [NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
[NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A", [NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A", [NetworkId.TESTNET_MORDOR]: "0xeA170CC0faceC531a6a9e93a28C4330Ac50343a1",
}; };
export const DISTRIBUTOR_ADDRESSES = { export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544", [NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
[NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544", [NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544", [NetworkId.TESTNET_MORDOR]: "0xaf5e76706520db7fb01096E322940206bf3fce57",
}; };
export const GHOST_GOVERNANCE_ADDRESSES = { export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1", [NetworkId.TESTNET_SEPOLIA]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
[NetworkId.TESTNET_HOODI]: "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]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3", [NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3", [NetworkId.TESTNET_MORDOR]: "0x0c4C7C49a173E2a3f9Eed93125F3F146D8e17bCb",
} }
export const GATEKEEPER_ADDRESSES = { export const GATEKEEPER_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B", [NetworkId.TESTNET_SEPOLIA]: "0xc85129A097773B7F8970a7364c928C05f265E6A1",
[NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4", [NetworkId.TESTNET_MORDOR]: "0xA59cB4ff90bE2206121aE61eEB68d0AeC7BA095f",
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
} }
export const UNISWAP_V2_ROUTER = { export const UNISWAP_V2_ROUTER = {
@ -118,10 +113,6 @@ 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,9 +1,8 @@
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, useCallback } from "react"; import { useEffect, useState } from "react";
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom"; import { Link as RouterLink, useLocation, useNavigate, useParams } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi"; import { useAccount, useChainId } 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";
@ -18,92 +17,64 @@ import BondSettingsModal from "./components/BondSettingsModal";
import NotFound from "../NotFound/NotFound"; import NotFound from "../NotFound/NotFound";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { isNetworkLegacy } 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";
const BondModalContainer = ({ chainId, address, config, connect }) => { const BondModalContainer = ({ chainId, address, connect }) => {
const { id, network } = useParams(); const { id } = useParams();
const { liveBonds } = useLiveBonds(chainId, network); const { liveBonds } = useLiveBonds(chainId);
const bond = liveBonds.find(bond => bond.id === Number(id)); const bond = liveBonds.find(bond => bond.id === Number(id));
if (!bond) return <NotFound />; if (!bond) return <NotFound />;
return <BondModal config={config} chainId={chainId} bond={bond} address={address} connect={connect} />; return <BondModal chainId={chainId} bond={bond} address={address} connect={connect} />;
}; };
export const BondModal = ({ bond, chainId, address, config, connect }) => { export const BondModal = ({ bond, chainId, address, connect }) => {
const navigate = useNavigate(); const navigate = useNavigate();
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 { getStorageValue, setStorageValue } = useLocalStorage(); const [recipientAddress, setRecipientAddress] = useState(address);
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 setRecipientAddress = useCallback((value) => { const setSlippageInner = (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);
setStorageValue(chainId, address, "bond-slippage", value); localStorage.setItem("bond-slippage", value);
}
} }
}, [chainId, address]);
const setFormatDecimalsInner = useCallback((value) => { const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) { if (Number(value) <= 17) {
setFormatDecimals(value); setFormatDecimals(value);
setStorageValue(chainId, address, "bond-decimals", value); localStorage.setItem("bond-decimals", value);
}
} }
}, [chainId, address]);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event) => { const handleKeyDown = (event) => {
if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate(`/${network}/bonds`); if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate("/bonds");
}; };
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown);
}, [network, navigate, isSettingsOpen]); }, [navigate, isSettingsOpen]);
const chainSymbol = useMemo(() => {
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
if (chainSymbol) return chainSymbol;
return "WTF";
}, [config]);
const preparedQuoteToken = useMemo(() => {
if (isNetworkLegacy(chainId)) {
return {
address: bond.quoteToken.quoteTokenAddress,
icons: bond.quoteToken.icons,
name: bond.quoteToken.name,
}
}
return { address: undefined, icons: [chainSymbol], name: chainSymbol };
}, [bond, chainSymbol, chainId])
return ( return (
<Box> <Box>
<PageTitle <PageTitle
name={ name={
<Box display="flex" flexDirection="row" alignItems="center"> <Box display="flex" flexDirection="row" alignItems="center">
<Link component={RouterLink} to={`/${network}/bonds`}> <Link component={RouterLink} to="/bonds">
<Box display="flex" flexDirection="row"> <Box display="flex" flexDirection="row">
<ArrowBack /> <ArrowBack />
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px"> <Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
@ -112,10 +83,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
</Box> </Box>
</Link> </Link>
<TokenStack tokens={preparedQuoteToken.icons} sx={{ fontSize: "27px" }} /> <TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center"> <Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
<Typography variant="h4" fontWeight={500}> <Typography variant="h4" fontWeight={500}>
{preparedQuoteToken.name} {bond.quoteToken.name}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -161,12 +132,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
<BondInputArea <BondInputArea
chainId={chainId} chainId={chainId}
bond={bond} bond={bond}
config={config}
connect={connect} connect={connect}
address={address} address={address}
slippage={slippage} slippage={slippage}
recipientAddress={recipientAddress} recipientAddress={recipientAddress}
preparedQuoteToken={preparedQuoteToken}
handleSettingsOpen={() => setSettingsOpen(true)} handleSettingsOpen={() => setSettingsOpen(true)}
formatDecimals={formatDecimals} formatDecimals={formatDecimals}
/> />

View File

@ -1,6 +1,5 @@
import { Box, Tab, Tabs, Typography, Container, useMediaQuery } from "@mui/material"; import { Box, Tab, Tabs, Typography, Container, useMediaQuery } from "@mui/material";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import Paper from "../../components/Paper/Paper"; import Paper from "../../components/Paper/Paper";
@ -20,7 +19,6 @@ import { useFtsoPrice } from "../../hooks/prices";
import { useTokenSymbol } from "../../hooks/tokens"; import { useTokenSymbol } from "../../hooks/tokens";
const Bonds = ({ chainId, address, connect }) => { const Bonds = ({ chainId, address, connect }) => {
const { network } = useParams();
const [isZoomed] = useState(false); const [isZoomed] = useState(false);
const [secondsTo, setSecondsTo] = useState(0); const [secondsTo, setSecondsTo] = useState(0);
@ -31,7 +29,7 @@ const Bonds = ({ chainId, address, connect }) => {
ReactGA.send({ hitType: "pageview", page: "/bonds" }); ReactGA.send({ hitType: "pageview", page: "/bonds" });
}, []); }, []);
const { liveBonds } = useLiveBonds(chainId, network); const { liveBonds } = useLiveBonds(chainId);
const totalReserves = useTotalReserves(chainId); const totalReserves = useTotalReserves(chainId);
const ftsoPrice = useFtsoPrice(chainId); const ftsoPrice = useFtsoPrice(chainId);

View File

@ -37,9 +37,6 @@ const BondConfirmModal = ({
sender, sender,
handleSettingsOpen, handleSettingsOpen,
isOpen, isOpen,
isNative,
bondQuoteTokenName,
bondQuoteTokenIcons,
handleConfirmClose handleConfirmClose
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
@ -56,16 +53,15 @@ const BondConfirmModal = ({
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one; const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS; const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
await purchaseBond({ await purchaseBond(
chainId, chainId,
bondId: bond.id, bond.id,
amount: spendAmountValue._value.toBigInt(), spendAmountValue._value.toBigInt(),
maxPrice, maxPrice,
user: recipientAddress, recipientAddress,
sender, sender,
referral, referral
isNative );
});
setIsPending(false); setIsPending(false);
handleConfirmClose(); handleConfirmClose();
@ -78,9 +74,9 @@ const BondConfirmModal = ({
open={isOpen} open={isOpen}
headerContent={ headerContent={
<Box display="flex" flexDirection="row"> <Box display="flex" flexDirection="row">
<TokenStack tokens={bondQuoteTokenIcons} sx={{ fontSize: "27px" }} /> <TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
<Typography variant="h4" sx={{ marginLeft: "6px" }}> <Typography variant="h4" sx={{ marginLeft: "6px" }}>
{bondQuoteTokenName} {bond.quoteToken.name}
</Typography> </Typography>
</Box> </Box>
} }
@ -95,7 +91,7 @@ const BondConfirmModal = ({
metric={spendAmount} metric={spendAmount}
/> />
<Box display="flex" flexDirection="row" justifyContent="center"> <Box display="flex" flexDirection="row" justifyContent="center">
<Typography>{bondQuoteTokenName}</Typography> <Typography>{bond.quoteToken.name}</Typography>
</Box> </Box>
</Box> </Box>
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} /> <GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />

View File

@ -1,5 +1,5 @@
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material"; import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material"; import { Box, Checkbox, FormControlLabel } from "@mui/material";
import { useState, useMemo } from "react"; import { useState, useMemo } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
@ -30,15 +30,12 @@ const BondInputArea = ({
formatDecimals, formatDecimals,
handleSettingsOpen, handleSettingsOpen,
address, address,
preparedQuoteToken,
connect connect
}) => { }) => {
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { pathname } = useLocation(); const { pathname } = useLocation();
const { currentIndex } = useCurrentIndex(chainId); const { currentIndex } = useCurrentIndex(chainId);
const { balance, refetch } = useBalance(chainId, preparedQuoteToken.address, address); const { balance, refetch } = useBalance(chainId, bond.quoteToken.quoteTokenAddress, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
@ -98,21 +95,22 @@ const BondInputArea = ({
UpperSwapCard={ UpperSwapCard={
<SwapCard <SwapCard
maxWidth="476px" maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"} inputWidth="280px"
id="from" id="from"
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />} token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={preparedQuoteToken.name} tokenName={bond.quoteToken.name}
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)} info={formatCurrency(balance, formatDecimals, bond.quoteToken.name)}
endString={preparedQuoteToken.address && "Max"} endString="Max"
endStringOnClick={setMax} endStringOnClick={setMax}
value={amount} value={amount}
onChange={event => setAmount(event.currentTarget.value)} onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }} inputProps={{ "data-testid": "fromInput" }}
/>} />
}
LowerSwapCard={ LowerSwapCard={
<SwapCard <SwapCard
maxWidth="476px" maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"} inputWidth="280px"
id="to" id="to"
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />} token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={bond.baseToken.name} tokenName={bond.baseToken.name}
@ -134,7 +132,6 @@ const BondInputArea = ({
connect={connect} connect={connect}
width="100%" width="100%"
height="60px" height="60px"
isNative={preparedQuoteToken.address === undefined}
isVertical isVertical
message={ message={
<> <>
@ -180,12 +177,12 @@ const BondInputArea = ({
)} )}
</span> </span>
} }
tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`} tooltip={`The total amount of payout asset you will receive from this bond purchase`}
/> />
<DataRow <DataRow
title="Max You Can Buy" title="Max You Can Buy"
tooltip={`Maximum ${ftsoSymbol} that can be purchased in a single transaction. Prevents whales from buying entire bond supply at once.`} tooltip={`The maximum quantity of payout token offered via bonds at this moment in time`}
balance={ balance={
<span> <span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase() {bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
@ -198,13 +195,13 @@ const BondInputArea = ({
<DataRow <DataRow
title="Discount" title="Discount"
balance={<BondDiscount discount={bond.discount} textOnly />} balance={<BondDiscount discount={bond.discount} textOnly />}
tooltip={`The discount (or premium) between ${ftsoSymbol} market price and bond price. Higher discount = better deal for bond buyers.`} tooltip={`The bond discount is the percentage difference between ${ftsoSymbol} market value and the bond's price`}
/> />
<DataRow <DataRow
title={`Vesting Term`} title={`Vesting Term`}
balance={<BondVesting vesting={bond.vesting} />} balance={<BondVesting vesting={bond.vesting} />}
tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."} tooltip={"The duration of the Bond whereby the bond can be claimed in its entirety"}
/> />
{recipientAddress !== address && ( {recipientAddress !== address && (
@ -222,9 +219,6 @@ const BondInputArea = ({
spendAmountValue={parsedAmount} spendAmountValue={parsedAmount}
spendAmount={formatNumber(parsedAmount, formatDecimals)} spendAmount={formatNumber(parsedAmount, formatDecimals)}
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)} receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
bondQuoteTokenName={preparedQuoteToken.name}
bondQuoteTokenIcons={preparedQuoteToken.icons}
isNative={preparedQuoteToken.address === undefined}
handleSettingsOpen={handleSettingsOpen} handleSettingsOpen={handleSettingsOpen}
isOpen={confirmOpen} isOpen={confirmOpen}
sender={address} sender={address}

View File

@ -11,7 +11,7 @@ import {
Typography, Typography,
useMediaQuery useMediaQuery
} from "@mui/material"; } from "@mui/material";
import { NavLink, useParams } from "react-router-dom"; import { NavLink } from "react-router-dom";
import ArrowUp from "../../../assets/icons/arrow-up.svg?react"; import ArrowUp from "../../../assets/icons/arrow-up.svg?react";
import BondDiscount from "./BondDiscount"; import BondDiscount from "./BondDiscount";
@ -26,7 +26,6 @@ import { TertiaryButton } from "../../../components/Button";
export const BondList = ({ bonds, secondsTo, chainId }) => { export const BondList = ({ bonds, secondsTo, chainId }) => {
const isSmallScreen = useScreenSize("md"); const isSmallScreen = useScreenSize("md");
const { network } = useParams();
if (bonds.length === 0) { if (bonds.length === 0) {
return ( return (
@ -44,7 +43,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
</Box> </Box>
{sortBondsByDiscount(bonds).map(bond => ( {sortBondsByDiscount(bonds).map(bond => (
<BondCard key={bond.id} secondsTo={secondsTo} bond={bond} chainName={network} /> <BondCard key={bond.id} secondsTo={secondsTo} bond={bond} />
))} ))}
</> </>
); );
@ -54,7 +53,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
<> <>
<BondTable> <BondTable>
{sortBondsByDiscount(bonds).map(bond => ( {sortBondsByDiscount(bonds).map(bond => (
<BondRow key={bond.id} bond={bond} secondsTo={secondsTo} chainName={network} /> <BondRow key={bond.id} bond={bond} secondsTo={secondsTo} />
))} ))}
</BondTable> </BondTable>
@ -65,7 +64,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
); );
}; };
const BondCard = ({ bond, secondsTo, chainName }) => { const BondCard = ({ bond, secondsTo }) => {
const quoteTokenName = bond.quoteToken.name; const quoteTokenName = bond.quoteToken.name;
const baseTokenName = bond.baseToken.name; const baseTokenName = bond.baseToken.name;
@ -131,11 +130,10 @@ const BondCard = ({ bond, secondsTo, chainName }) => {
<Box mt="16px"> <Box mt="16px">
<Link <Link
component={NavLink} component={NavLink}
to={`/${chainName}/bonds/${bond.id}`} to={`/bonds/${bond.id}`}
sx={{ pointerEvents: bond.isSoldOut ? 'none' : 'auto' }}
> >
<TertiaryButton fullWidth disabled={bond.isSoldOut}> <TertiaryButton fullWidth>
{bond.isSoldOut ? "Sold Out" : `Bond`} Bond
</TertiaryButton> </TertiaryButton>
</Link> </Link>
</Box> </Box>
@ -175,7 +173,7 @@ const payoutTokenCapacity = (bond) => {
return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`; return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`;
}; };
const BondRow = ({ bond, secondsTo, chainName }) => { const BondRow = ({ bond, secondsTo }) => {
const quoteTokenName = bond.quoteToken.name; const quoteTokenName = bond.quoteToken.name;
const baseTokenName = bond.baseToken.name; const baseTokenName = bond.baseToken.name;
@ -223,8 +221,7 @@ const BondRow = ({ bond, secondsTo, chainName }) => {
<TableCell style={{ padding: "8px 0" }}> <TableCell style={{ padding: "8px 0" }}>
<Link <Link
component={NavLink} component={NavLink}
to={`/${chainName}/bonds/${bond.id}`} to={`/bonds/${bond.id}`}
sx={{ pointerEvents: bond.isSoldOut ? 'none' : 'auto' }}
> >
<TertiaryButton fullWidth disabled={bond.isSoldOut}> <TertiaryButton fullWidth disabled={bond.isSoldOut}>
{bond.isSoldOut ? "Sold Out" : `Bond`} {bond.isSoldOut ? "Sold Out" : `Bond`}

View File

@ -20,16 +20,17 @@ 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");
const [isPending, setIsPending] = useState(false); const [isPending, setIsPending] = useState(false);
const [isWarmup, setIsWapmup] = useState(false); const [isWarmup, setIsWapmup] = useState(false);
const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false); const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false);
const [isPayoutGhst, _] = useState(true); const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("bond-isGhstPayout")
? localStorage.getItem("bond-isGhstPayout") === "true"
: 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]);
@ -38,6 +39,7 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const { notes, refetch: notesRefetch } = useNotes(chainId, address); const { notes, refetch: notesRefetch } = useNotes(chainId, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
if (!notes || notes.length === 0) return null; if (!notes || notes.length === 0) return null;
@ -50,18 +52,23 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
18 18
); );
const setIsPayoutGhstInner = (value) => {
setIsPayoutGhst(value);
localStorage.setItem("bond-isGhstPayout", value);
}
const onSubmit = async (indexes) => { const onSubmit = async (indexes) => {
const isFundsInWarmup = warmupInfo.deposit._value > 0n; const isFundsInWarmup = warmupInfo.deposit._value > 0n;
if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) { if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
setIsWapmup(true); setIsWapmup(true);
} else { } else {
setIsPending(true); setIsPending(true);
await redeem({ await redeem(
chainId, chainId,
user: address, address,
isGhst: isPayoutGhst, isPayoutGhst,
indexes indexes
}); );
await notesRefetch(); await notesRefetch();
setIsPending(false); setIsPending(false);
} }
@ -87,6 +94,24 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
</Box> </Box>
} }
> >
<Box display="flex" flexDirection="column" alignItems="center">
<Typography variant="h4" align="center" color="textSecondary">
Payout Options
</Typography>
<Tabs
centered
textColor="primary"
indicatorColor="primary"
value={isPayoutGhst ? 1 : 0}
aria-label="Payout token tabs"
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
</Tabs>
</Box>
<Box display="flex" justifyContent="center"> <Box display="flex" justifyContent="center">
<Box display="flex" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}> <Box display="flex" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}>
<Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}> <Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}>
@ -100,9 +125,6 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol) : formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
} }
</Typography> </Typography>
<Typography variant="subtitle1" align="center">
{formatCurrency(totalClaimableBalance * ghstPrice, 2)}
</Typography>
</Box> </Box>
<PrimaryButton <PrimaryButton
@ -144,15 +166,12 @@ 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>
<Box display="flex" flexDirection="column" alignItems="flex-end">
<Typography> <Typography>
{isPayoutGhst {isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol) ? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol) : 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">
@ -203,15 +222,12 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 0" }}> <TableCell style={{ padding: "8px 0" }}>
<Box display="flex" flexDirection="column" alignItems="flex-start">
<Typography> <Typography>
{isPayoutGhst {isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol) ? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol) : 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

@ -44,7 +44,7 @@ const WarmupConfirmModal = ({
minHeight="150px" minHeight="150px"
open={isOpen} open={isOpen}
headerText={ headerText={
warmupLength <= 0 warmupLength < 0
? "Bond Notification" ? "Bond Notification"
: "Bond in Warm-up" : "Bond in Warm-up"
} }
@ -52,7 +52,7 @@ const WarmupConfirmModal = ({
> >
<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 display="flex" flexDirection="column"> <Box display="flex" flexDirection="column">
{warmupLength <= 0 {warmupLength < 0
? <FormControlLabel ? <FormControlLabel
control={ control={
<Checkbox <Checkbox
@ -69,7 +69,7 @@ const WarmupConfirmModal = ({
} }
</Box> </Box>
{warmupLength <= 0 && <PrimaryButton fullWidth disabled={!isChecked} onClick={onSubmit}> {warmupLength < 0 && <PrimaryButton fullWidth disabled={!isChecked} onClick={onSubmit}>
Confirm Confirm
</PrimaryButton>} </PrimaryButton>}
</Box> </Box>

View File

@ -10,7 +10,6 @@ import {
useMediaQuery, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { decodeAddress } from "@polkadot/util-crypto"; import { decodeAddress } from "@polkadot/util-crypto";
import { fromHex } from "@polkadot-api/utils";
import { getBlockNumber } from "@wagmi/core"; import { getBlockNumber } from "@wagmi/core";
import { useTransaction } from "wagmi"; import { useTransaction } from "wagmi";
import { keccak256 } from "viem"; import { keccak256 } from "viem";
@ -43,16 +42,15 @@ import {
useCurrentSlot, useCurrentSlot,
useGenesisSlot, useGenesisSlot,
useErasTotalStake, useErasTotalStake,
useLatestBlockNumber,
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)");
@ -66,15 +64,14 @@ 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 [storedTransactions, setStoredTransactions] = useState(() => const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX);
getStorageValue(chainId, address, "bridge-txs", []) const [storedTransactions, setStoredTransactions] = useState(
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
); );
const { gatekeeperAddress } = useGatekeeperAddress(chainId); const { gatekeeperAddress } = useGatekeeperAddress(chainId);
@ -105,28 +102,24 @@ const Bridge = ({ chainId, address, config, connect }) => {
const networkIdEncoded = u64.enc(BigInt(chainId)); const networkIdEncoded = u64.enc(BigInt(chainId));
const amountEncoded = u128.enc(BigInt(watchTransaction.amount)); const amountEncoded = u128.enc(BigInt(watchTransaction.amount));
const addressEncoded = decodeAddress(watchTransaction.receiverAddress, false, 1996); const addressEncoded = decodeAddress(watchTransaction.receiverAddress, false, 1996);
const transactionHashEncoded = fromHex(watchTransaction.transactionHash);
const blockNumber = u64.enc(watchTransactionInfo?.blockNumber ?? 0n); const blockNumber = u64.enc(watchTransactionInfo?.blockNumber ?? 0n);
const clapArgsStr = new Uint8Array([ const clapArgsStr = new Uint8Array([
...addressEncoded, ...addressEncoded,
...amountEncoded, ...amountEncoded,
...blockNumber, ...blockNumber,
...transactionHashEncoded,
...networkIdEncoded ...networkIdEncoded
]); ]);
return keccak256(clapArgsStr) return keccak256(clapArgsStr)
}, [watchTransaction, watchTransactionInfo]) }, [watchTransaction, watchTransactionInfo])
const latestBlockNumber = useLatestBlockNumber();
const eraIndex = useEraIndex();
const currentSlot = useCurrentSlot(); const currentSlot = useCurrentSlot();
const genesisSlot = useGenesisSlot(); const genesisSlot = useGenesisSlot();
const currentSession = useCurrentIndex(); const currentSession = useCurrentIndex();
const applauseThreshold = useApplauseThreshold(); const applauseThreshold = useApplauseThreshold();
const evmNetwork = useEvmNetwork({ evmChainId: chainId }); const evmNetwork = useEvmNetwork({ evmChainId: chainId });
const totalStakedAmount = useErasTotalStake({ const totalStakedAmount = useErasTotalStake({
epochIndex: eraIndex?.index ?? 0, eraIndex: Math.floor((watchTransaction?.sessionIndex ?? currentSession) / 6)
}); });
const authorities = useAuthorities({ const authorities = useAuthorities({
currentSession: watchTransaction?.sessionIndex ?? currentSession currentSession: watchTransaction?.sessionIndex ?? currentSession
@ -212,20 +205,16 @@ const Bridge = ({ chainId, address, config, connect }) => {
const latestCommits = useMemo(() => { const latestCommits = useMemo(() => {
return validators?.map((validator, index) => { return validators?.map((validator, index) => {
const lastUpdatedNumber = Number(blockCommitments?.at(index)?.last_updated ?? 0);
const timestampDelta = (latestBlockNumber - lastUpdatedNumber) * 6000; // ideal 6 seconds for block
const lastUpdatedTimestamp = Math.floor(currentTime - timestampDelta);
return { return {
validator: validator, validator: validator,
lastActive: timestampDelta, lastActive: currentTime - Number(blockCommitments?.at(index)?.last_updated ?? 0),
lastUpdated: lastUpdatedTimestamp, lastUpdated: blockCommitments?.at(index)?.last_updated,
lastStoredBlock: blockCommitments?.at(index)?.last_stored_block, lastStoredBlock: blockCommitments?.at(index)?.last_stored_block,
storedBlockTime: (blockCommitments?.at(index)?.last_stored_block ?? 0n) * networkAvgBlockSpeed(chainId), storedBlockTime: (blockCommitments?.at(index)?.last_stored_block ?? 0n) * networkAvgBlockSpeed(chainId),
disabled: disabledValidators?.includes(index), disabled: disabledValidators?.includes(index),
} }
}) })
}, [blockCommitments, disabledValidators, validators, latestBlockNumber, chainId]); }, [blockCommitments, disabledValidators, validators, chainId]);
const latestUpdate = useMemo(() => { const latestUpdate = useMemo(() => {
const validCommits = latestCommits?.filter(commit => const validCommits = latestCommits?.filter(commit =>
@ -281,7 +270,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);
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions); localStorage.setItem(storagePrefix, JSON.stringify(newStoredTransactions));
setActiveTxIndex(-1); setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]); }, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
@ -307,8 +296,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
const newStoredTransactions = [transaction, ...storedTransactions]; const newStoredTransactions = [transaction, ...storedTransactions];
setStoredTransactions(newStoredTransactions); setStoredTransactions(newStoredTransactions);
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions); localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(0); setActiveTxIndex(0)
} }
return ( return (

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect } 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,11 +19,10 @@ 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, SecondaryButton } from "../../components/Button"; import { PrimaryButton, TertiaryButton } 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,
@ -51,12 +50,6 @@ 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"
@ -96,43 +89,15 @@ 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 {!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
width="90%" <TertiaryButton
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
sx={{ onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}
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
</SecondaryButton> </TertiaryButton>
</Box>} </Box>}
<Box {providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
sx={{ filter: providerDetail ? '' : 'blur(5px)' }}
>
{currentRecord?.finalization > 0 && ( {currentRecord?.finalization > 0 && (
<> <>
<Box <Box
@ -320,7 +285,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">
@ -392,21 +357,13 @@ export const BridgeModal = ({
</Box> </Box>
<Box display="flex" flexDirection="column" gap="5px"> <Box display="flex" flexDirection="column" gap="5px">
{currentRecord && currentRecord.finalization < 1 && currentRecord.applaused && <PrimaryButton <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
</TertiaryButton> </PrimaryButton>
<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.
@ -442,12 +399,6 @@ 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

View File

@ -10,10 +10,9 @@ 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, useCallback } from "react"; import { useEffect, useState } 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";
@ -29,23 +28,18 @@ import {
UNISWAP_V2_FACTORY, UNISWAP_V2_FACTORY,
RESERVE_ADDRESSES, RESERVE_ADDRESSES,
FTSO_ADDRESSES, FTSO_ADDRESSES,
EMPTY_ADDRESS,
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 PoolContainer from "./PoolContainer"; import PoolContainer from "./PoolContainer";
import SwapContainer from "./SwapContainer"; import SwapContainer from "./SwapContainer";
import TokenModal from "./TokenModal"; import TokenModal from "./TokenModal";
const Dex = ({ chainId, address, connect, config }) => { const Dex = ({ chainId, address, connect }) => {
const location = useLocation(); const location = useLocation();
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)");
@ -58,50 +52,15 @@ 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(() => getStorageValue(chainId, address, "dex-deadline", "60")); const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "dex-slippage", "5")); const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "dex-decimals", "5")); const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
const [actualDestinationAddress, setActualDestinationAddress] = useState(() => getStorageValue(chainId, address, "dex-destination", address));
const [destinationAddress, setDestinationAddress] = useState(actualDestinationAddress);
const [tokenAddressTop, setTokenAddressTop] = useState(EMPTY_ADDRESS); const [tokenAddressTop, setTokenAddressTop] = useState(RESERVE_ADDRESSES[chainId]);
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]); const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
const { symbol: tokenNameTopInner } = useTokenSymbol(chainId, tokenAddressTop); const { symbol: tokenNameTop } = useTokenSymbol(chainId, tokenAddressTop);
const { symbol: tokenNameBottomInner } = useTokenSymbol(chainId, tokenAddressBottom); const { symbol: tokenNameBottom } = useTokenSymbol(chainId, tokenAddressBottom);
const chainSymbol = useMemo(() => {
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
if (chainSymbol) return chainSymbol;
return "WTF";
}, [config])
const tokenNameTop = useMemo(() => {
if (chainSymbol && tokenAddressTop === EMPTY_ADDRESS) {
return chainSymbol;
}
return tokenNameTopInner;
}, [tokenAddressTop, tokenNameTopInner, chainSymbol]);
const tokenNameBottom = useMemo(() => {
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
if (chainSymbol && tokenAddressBottom === EMPTY_ADDRESS) {
return chainSymbol;
}
return tokenNameBottomInner;
}, [tokenAddressBottom, tokenNameBottomInner, config]);
const isWrapping = useMemo(() => {
const isNative = tokenAddressTop === EMPTY_ADDRESS;
const isWrappedNative = tokenAddressBottom === WETH_ADDRESSES[chainId];
return isNative && isWrappedNative;
}, [chainId, tokenAddressTop, tokenAddressBottom]);
const isUnwrapping = useMemo(() => {
const isWrappedNative = tokenAddressTop === WETH_ADDRESSES[chainId];
const isNative = tokenAddressBottom === EMPTY_ADDRESS;
return isNative && isWrappedNative;
}, [chainId, tokenAddressTop, tokenAddressBottom]);
useEffect(() => { useEffect(() => {
if (currentQueryParameters.has("pool")) { if (currentQueryParameters.has("pool")) {
@ -116,8 +75,8 @@ const Dex = ({ chainId, address, connect, config }) => {
setTokenAddressTop(currentQueryParameters.get("from")); setTokenAddressTop(currentQueryParameters.get("from"));
newQueryParameters.set("from", currentQueryParameters.get("from")); newQueryParameters.set("from", currentQueryParameters.get("from"));
} else { } else {
setTokenAddressTop(EMPTY_ADDRESS); setTokenAddressTop(RESERVE_ADDRESSES[chainId]);
newQueryParameters.set("from", EMPTY_ADDRESS); newQueryParameters.set("from", RESERVE_ADDRESSES[chainId]);
} }
if (currentQueryParameters.has("to")) { if (currentQueryParameters.has("to")) {
@ -133,7 +92,7 @@ const Dex = ({ chainId, address, connect, config }) => {
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname + location.search }); ReactGA.send({ hitType: "pageview", page: location.pathname + location.search });
}, [location]); }, [location])
const dexAddresses = { const dexAddresses = {
router: UNISWAP_V2_ROUTER[chainId], router: UNISWAP_V2_ROUTER[chainId],
@ -154,7 +113,7 @@ const Dex = ({ chainId, address, connect, config }) => {
} }
const changeSwapTab = (swap) => { const changeSwapTab = (swap) => {
if (swap || (isWrapping || isUnwrapping)) newQueryParameters.delete("pool"); if (swap) newQueryParameters.delete("pool");
else newQueryParameters.set("pool", true); else newQueryParameters.set("pool", true);
newQueryParameters.set("from", currentQueryParameters.get("from")); newQueryParameters.set("from", currentQueryParameters.get("from"));
newQueryParameters.set("to", currentQueryParameters.get("to")); newQueryParameters.set("to", currentQueryParameters.get("to"));
@ -183,38 +142,24 @@ const Dex = ({ chainId, address, connect, config }) => {
setSearchParams(newQueryParameters); setSearchParams(newQueryParameters);
} }
const setSlippageInner = useCallback((value) => { const setSlippageInner = (value) => {
const maybeValue = parseFloat(value); const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) { if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value); setSlippage(value);
setStorageValue(chainId, address, "dex-slippage", value); localStorage.setItem("dex-slippage", value);
}
} }
}, [chainId, address]);
const setSecondsToWaitInner = useCallback((value) => { const setSecondsToWaitInner = (value) => {
localStorage.setItem("dex-deadline", value);
setSecondsToWait(value); setSecondsToWait(value);
setStorageValue(chainId, address, "dex-deadline", value); }
}, [chainId, address]);
const setFormatDecimalsInner = useCallback((value) => { const setFormatDecimalsInner = (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 = useCallback((value) => {
const cleanedValue = value.trim();
setDestinationAddress(value);
if (isAddress(cleanedValue)) {
setActualDestinationAddress(value);
setStorageValue(chainId, address, "dex-destination", value);
}
}, [chainId, address]);
const handleCloseSetting = () => {
setDestinationAddress(undefined);
handleSettingsOpen(false);
} }
return ( return (
@ -254,11 +199,11 @@ const Dex = ({ chainId, address, connect, config }) => {
}} }}
> >
<Modal <Modal
maxWidth="450px" maxWidth="376px"
minHeight="200px" minHeight="200px"
open={settingsOpen} open={settingsOpen}
headerText={"Settings"} headerText={"Settings"}
onClose={() => handleCloseSetting()} onClose={() => handleSettingsOpen(false)}
> >
<Box> <Box>
<InputLabel htmlFor="slippage">Slippage</InputLabel> <InputLabel htmlFor="slippage">Slippage</InputLabel>
@ -322,35 +267,9 @@ const Dex = ({ chainId, address, connect, config }) => {
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
<Box mt="32px">
<InputLabel htmlFor="recipient">
{`${actualDestinationAddress ? "Custom" : "Default"} destination address`}
</InputLabel>
<Box mt="8px">
<FormControl variant="outlined" color="primary" fullWidth>
<OutlinedInput
inputProps={{ "data-testid": "decimals-to-wait" }}
type="text"
id="destination-to-wait"
value={destinationAddress
? destinationAddress
: actualDestinationAddress ?? address
}
onChange={event => setDestinationAddressInner(event.currentTarget.value)}
/>
</FormControl>
</Box>
<Box mt="8px">
<Typography variant="body2" color="textSecondary">
Recipient address of swapped assets and liquidity tokens
</Typography>
</Box>
</Box>
</Modal> </Modal>
<TokenModal <TokenModal
chainSymbol={chainSymbol}
account={address} account={address}
chainId={chainId} chainId={chainId}
listOpen={topTokenListOpen} listOpen={topTokenListOpen}
@ -358,7 +277,6 @@ const Dex = ({ chainId, address, connect, config }) => {
setTokenAddress={setInnerTokenAddressTop} setTokenAddress={setInnerTokenAddressTop}
/> />
<TokenModal <TokenModal
chainSymbol={chainSymbol}
account={address} account={address}
chainId={chainId} chainId={chainId}
listOpen={bottomTokenListOpen} listOpen={bottomTokenListOpen}
@ -404,14 +322,11 @@ const Dex = ({ chainId, address, connect, config }) => {
dexAddresses={dexAddresses} dexAddresses={dexAddresses}
connect={connect} connect={connect}
slippage={slippage} slippage={slippage}
destination={actualDestinationAddress ? actualDestinationAddress : address}
secondsToWait={secondsToWait} secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen} setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen} setBottomTokenListOpen={setBottomTokenListOpen}
setIsSwap={setIsSwap} setIsSwap={setIsSwap}
formatDecimals={formatDecimals} formatDecimals={formatDecimals}
isWrapping={isWrapping}
isUnwrapping={isUnwrapping}
/> />
: :
<PoolContainer <PoolContainer
@ -423,7 +338,6 @@ const Dex = ({ chainId, address, connect, config }) => {
dexAddresses={dexAddresses} dexAddresses={dexAddresses}
connect={connect} connect={connect}
slippage={slippage} slippage={slippage}
destination={actualDestinationAddress ? actualDestinationAddress : address}
secondsToWait={secondsToWait} secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen} setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen} setBottomTokenListOpen={setBottomTokenListOpen}

View File

@ -9,15 +9,10 @@ import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenA
import { SecondaryButton } from "../../components/Button"; import { SecondaryButton } from "../../components/Button";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, formatCurrency, bigIntSqrt } from "../../helpers"; import { formatNumber, formatCurrency } from "../../helpers";
import { useBalance, useTotalSupply } from "../../hooks/tokens"; import { useBalance, useTotalSupply } from "../../hooks/tokens";
import { import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2";
useUniswapV2Pair,
useUniswapV2PairReserves,
addLiquidity,
addLiquidityETH,
} from "../../hooks/uniswapv2";
const PoolContainer = ({ const PoolContainer = ({
tokenNameTop, tokenNameTop,
@ -28,11 +23,10 @@ const PoolContainer = ({
dexAddresses, dexAddresses,
connect, connect,
slippage, slippage,
destination,
secondsToWait, secondsToWait,
setTopTokenListOpen, setTopTokenListOpen,
setBottomTokenListOpen, setBottomTokenListOpen,
formatDecimals, formatDecimals
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)"); const isSmallScreen = useMediaQuery("(max-width: 456px)");
@ -45,13 +39,11 @@ const PoolContainer = ({
balance: balanceTop, balance: balanceTop,
refetch: balanceRefetchTop, refetch: balanceRefetchTop,
contractAddress: addressTop, contractAddress: addressTop,
isNative: topIsNative,
} = useBalance(chainId, tokenNameTop, address); } = useBalance(chainId, tokenNameTop, address);
const { const {
balance: balanceBottom, balance: balanceBottom,
refetch: balanceRefetchBottom, refetch: balanceRefetchBottom,
contractAddress: addressBottom, contractAddress: addressBottom,
isNative: bottomIsNative,
} = useBalance(chainId, tokenNameBottom, address); } = useBalance(chainId, tokenNameBottom, address);
const { const {
@ -83,6 +75,34 @@ const PoolContainer = ({
const setMaxTop = () => setAmountTop(balanceTop.toString()); const setMaxTop = () => setAmountTop(balanceTop.toString());
const setMaxBottom = () => setAmountBottom(balanceBottom.toString()); const setMaxBottom = () => setAmountBottom(balanceBottom.toString());
const bigIntSqrt = (n) => {
if (n < 0n) {
throw new Error("Cannot compute the square root of a negative number.");
}
if (n < 2n) {
return n; // The square root of 0 or 1 is the number itself
}
let low = 0n;
let high = n;
let mid;
while (low <= high) {
mid = (low + high) / 2n;
const midSquared = mid * mid;
if (midSquared === n) {
return mid; // Found the exact square root
} else if (midSquared < n) {
low = mid + 1n; // Move to the right half
} else {
high = mid - 1n; // Move to the left half
}
}
return high; // The integer part of the square root
}
const estimatedAmountOut = useMemo(() => { const estimatedAmountOut = useMemo(() => {
const pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() const pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
? pairReserves.reserve0 ? pairReserves.reserve0
@ -131,40 +151,14 @@ const PoolContainer = ({
} }
} }
}, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves]); }, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves])
const poolShares = useMemo(() => {
const hundred = new DecimalBigNumber(1n, 2);
if (lpTotalSupply?._value == 0n) {
return { currentShares: lpTotalSupply, nextShares: lpTotalSupply };
}
const currentShares = lpBalance.div(lpTotalSupply).div(hundred);
const nextShares = (lpBalance.add(estimatedAmountOut)).div(lpTotalSupply.add(estimatedAmountOut)).div(hundred);
return { currentShares, nextShares };
}, [lpBalance, lpTotalSupply, estimatedAmountOut]);
const poolPrices = useMemo(() => {
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
? pairReserves.reserve0
: pairReserves.reserve1;
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase()
? pairReserves.reserve1
: pairReserves.reserve0;
let priceIn = "0";
let priceOut = "0";
if (amountIn?._value > 0n) priceIn = (amountOut.div(amountIn)).toString();
if (amountOut?._value > 0n) priceOut = (amountIn.div(amountOut)).toString();
return { priceIn , priceOut }
}, [addressTop, addressBottom, balanceTop, tokenAddresses, pairReserves]);
const addLiquidityInner = async () => { const addLiquidityInner = async () => {
setIsPending(true); setIsPending(true);
const deadline = Math.floor(Date.now() / 1000) + secondsToWait; const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
const destination = address;
const shares = 100000; const shares = 100000;
const one = BigInt(shares * 100); const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage); const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
@ -177,7 +171,7 @@ const PoolContainer = ({
const amountAMin = amountADesired * bigIntSlippage / one; const amountAMin = amountADesired * bigIntSlippage / one;
const amountBMin = amountBDesired * bigIntSlippage / one; const amountBMin = amountBDesired * bigIntSlippage / one;
const params = { await addLiquidity(
chainId, chainId,
tokenNameTop, tokenNameTop,
tokenNameBottom, tokenNameBottom,
@ -185,16 +179,9 @@ const PoolContainer = ({
amountBDesired, amountBDesired,
amountAMin, amountAMin,
amountBMin, amountBMin,
address,
destination, destination,
deadline, deadline,
} );
if (topIsNative || bottomIsNative) {
await addLiquidityETH(params)
} else {
await addLiquidity(params);
}
await balanceRefetchTop(); await balanceRefetchTop();
await balanceRefetchBottom(); await balanceRefetchBottom();
@ -246,7 +233,7 @@ const PoolContainer = ({
} }
arrowOnClick={onSwap} arrowOnClick={onSwap}
/> />
<Box {!isSmallScreen && <Box
m="10px 0" m="10px 0"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
@ -257,29 +244,24 @@ const PoolContainer = ({
style={{ fontSize: "12px", color: theme.colors.gray[40] }} style={{ fontSize: "12px", color: theme.colors.gray[40] }}
> >
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameTop}`}</Typography> <Typography fontSize="12px" lineHeight="15px">Current Balance:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceIn, formatDecimals, tokenNameBottom)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatNumber(lpBalance, formatDecimals)} LP</Typography>
</Box> </Box>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom}`}</Typography> <Typography fontSize="12px" lineHeight="15px">Total Supply:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceOut, formatDecimals, tokenNameTop)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatNumber(lpTotalSupply, formatDecimals)} LP</Typography>
</Box> </Box>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Current Pool Share:</Typography> <Typography fontSize="12px" lineHeight="15px">Extra Balance:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.currentShares, formatDecimals)}%</Typography> <Typography fontSize="12px" lineHeight="15px">~{formatNumber(estimatedAmountOut, formatDecimals)} LP</Typography>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Next Pool Share:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.nextShares, formatDecimals)}%</Typography>
</Box>
</Box> </Box>
</Box>}
<TokenAllowanceGuard <TokenAllowanceGuard
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)} spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
tokenName={tokenNameTop} tokenName={tokenNameTop}
owner={address} owner={address}
spender={dexAddresses.router} spender={dexAddresses.router}
decimals={balanceTop._decimals} decimals={balanceTop._decimals}
isNative={topIsNative}
approvalText={"Approve " + tokenNameTop} approvalText={"Approve " + tokenNameTop}
approvalPendingText={"Approving..."} approvalPendingText={"Approving..."}
connect={connect} connect={connect}
@ -292,7 +274,6 @@ const PoolContainer = ({
owner={address} owner={address}
spender={dexAddresses.router} spender={dexAddresses.router}
decimals={balanceBottom._decimals} decimals={balanceBottom._decimals}
isNative={bottomIsNative}
approvalText={"Approve " + tokenNameBottom} approvalText={"Approve " + tokenNameBottom}
approvalPendingText={"Approving..."} approvalPendingText={"Approving..."}
connect={connect} connect={connect}

View File

@ -1,4 +1,4 @@
import { useState, useMemo, useEffect } from "react"; import { useState, useEffect } from "react";
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material"; import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -13,16 +13,8 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../helpers/timeUtil"; import { prettifySecondsInDays } from "../../helpers/timeUtil";
import { getTokenAddress } from "../../hooks/helpers"; import { getTokenAddress } from "../../hooks/helpers";
import { useBalance, depositNative, withdrawWeth } from "../../hooks/tokens"; import { useBalance } from "../../hooks/tokens";
import { import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens } from "../../hooks/uniswapv2";
useUniswapV2Pair,
useUniswapV2PairReserves,
swapExactTokensForTokens,
swapExactETHForTokens,
swapExactTokensForETH,
} from "../../hooks/uniswapv2";
import { EMPTY_ADDRESS } from "../../constants/addresses";
const SwapContainer = ({ const SwapContainer = ({
tokenNameTop, tokenNameTop,
@ -35,12 +27,9 @@ const SwapContainer = ({
setTopTokenListOpen, setTopTokenListOpen,
setBottomTokenListOpen, setBottomTokenListOpen,
slippage, slippage,
destination,
secondsToWait, secondsToWait,
setIsSwap, setIsSwap,
formatDecimals, formatDecimals
isWrapping,
isUnwrapping,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)"); const isSmallScreen = useMediaQuery("(max-width: 456px)");
@ -56,14 +45,12 @@ const SwapContainer = ({
balance: balanceTop, balance: balanceTop,
refetch: balanceRefetchTop, refetch: balanceRefetchTop,
contractAddress: addressTop, contractAddress: addressTop,
isNative: topIsNative,
} = useBalance(chainId, tokenNameTop, address); } = useBalance(chainId, tokenNameTop, address);
const { const {
balance: balanceBottom, balance: balanceBottom,
refetch: balanceRefetchBottom, refetch: balanceRefetchBottom,
contractAddress: addressBottom, contractAddress: addressBottom,
isNative: bottomIsNative,
} = useBalance(chainId, tokenNameBottom, address); } = useBalance(chainId, tokenNameBottom, address);
const { const {
@ -87,73 +74,42 @@ const SwapContainer = ({
const setMax = () => setAmountTop(balanceTop.toString()); const setMax = () => setAmountTop(balanceTop.toString());
useEffect(() => { useEffect(() => {
if (isWrapping || isUnwrapping) {
setAmountBottom(amountTop.toString());
setNextPrice("1");
setCurrentPrice("1");
return;
}
const zero = new DecimalBigNumber(0n, 0); const zero = new DecimalBigNumber(0n, 0);
const raw = new DecimalBigNumber(amountTop, balanceTop._decimals); const raw = new DecimalBigNumber(amountTop, balanceTop._decimals);
const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals); const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals);
const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3)); const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3));
const topAddress = getTokenAddress(chainId, tokenNameTop);
const bottomAddress = getTokenAddress(chainId, tokenNameBottom);
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1; const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1;
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0; const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0;
if (amountOut.eq(zero)) { if (amountIn.eq(zero)) {
setCurrentPrice(""); setCurrentPrice("");
} else { } else {
setCurrentPrice(amountIn.div(amountOut).toString()); setCurrentPrice(amountIn.div(amountOut).toString());
} }
if (amountOut.eq(zero) || amountInWithFee.eq(zero)) { if (amountIn.eq(zero) || amountInWithFee.eq(zero)) {
setAmountBottom(""); setAmountBottom("");
setNextPrice(""); setNextPrice("");
} else { } else {
const nominator = amountOut.mul(amountInWithFee); const nominator = amountOut.mul(amountIn);
const denominator = amountIn.add(amountInWithFee); const denominator = amountIn.add(amountInWithFee);
const newAmountOut = nominator.div(denominator); const newAmountOut = nominator.div(denominator);
const newReserveIn = amountIn.add(amountInWithFee); setAmountBottom(amountOut.sub(newAmountOut).toString());
const newReserveOut = amountOut.sub(newAmountOut); setNextPrice(denominator.div(newAmountOut).toString())
const nextPrice = newReserveIn.div(newReserveOut);
setAmountBottom(newAmountOut.toString());
setNextPrice(nextPrice.toString());
} }
}, [pairReserves, addressBottom, amountTop, addressTop, isWrapping, isUnwrapping]); }, [amountTop, addressTop]);
const minReceived = useMemo(() => {
const decimals = 7;
const shares = Math.pow(10, decimals);
const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));
const slippageDecimalBigNumber = new DecimalBigNumber(bigIntSlippage, 2);
const bigIntAmount = BigInt(Math.round(amountBottom * shares));
const amountDecimalBigNumber = new DecimalBigNumber(bigIntAmount, decimals);
const tmpResult = amountDecimalBigNumber.mul(slippageDecimalBigNumber);
const result = new DecimalBigNumber(tmpResult?._value, tmpResult?._decimals + decimals);
return result?.toString();
}, [amountBottom, amountBottom, slippage, balanceBottom]);
const buttonText = useMemo(() => {
let text = "Swap";
if (isWrapping) text = "Wrap";
else if (isUnwrapping) text = "Unwrap";
else if (pairAddress === EMPTY_ADDRESS) text = "Create Pool";
return text;
}, [isWrapping, isUnwrapping, pairAddress]);
const swapTokens = async () => { const swapTokens = async () => {
setIsPending(true); setIsPending(true);
const deadline = Math.floor(Date.now() / 1000) + secondsToWait; const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
const destination = address;
const shares = 100000; const shares = 100000;
const one = BigInt(shares * 100); const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage); const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
@ -165,30 +121,14 @@ const SwapContainer = ({
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals))); const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
const amountBMin = amountBDesired * bigIntSlippage / one; const amountBMin = amountBDesired * bigIntSlippage / one;
if (isWrapping) { await swapExactTokensForTokens(
await depositNative(chainId, address, amountADesired);
} else if (isUnwrapping) {
await withdrawWeth(chainId, address, amountADesired);
} else {
const params = {
chainId, chainId,
amountADesired, amountADesired,
amountBMin, amountBMin,
tokenNameTop, [tokenNameTop, tokenNameBottom],
tokenNameBottom,
destination, destination,
address,
deadline deadline
}; );
if (topIsNative) {
await swapExactETHForTokens(params)
} else if (bottomIsNative) {
await swapExactTokensForETH(params)
} else {
await swapExactTokensForTokens(params);
}
}
await balanceRefetchTop(); await balanceRefetchTop();
await balanceRefetchBottom(); await balanceRefetchBottom();
@ -236,7 +176,7 @@ const SwapContainer = ({
} }
arrowOnClick={onSwap} arrowOnClick={onSwap}
/> />
<Box {!isSmallScreen && <Box
m="10px 0" m="10px 0"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
@ -247,29 +187,24 @@ const SwapContainer = ({
style={{ fontSize: "12px", color: theme.colors.gray[40] }} style={{ fontSize: "12px", color: theme.colors.gray[40] }}
> >
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Current)`}</Typography> <Typography fontSize="12px" lineHeight="15px">Current price:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals, tokenNameTop)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals, tokenNameTop)}</Typography>
</Box> </Box>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Next)`}</Typography> <Typography fontSize="12px" lineHeight="15px">Next price:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)}</Typography> <Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)}</Typography>
</Box> </Box>
<Box width="100%" display="flex" justifyContent="space-between"> <Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Min. Receive:</Typography> <Typography fontSize="12px" lineHeight="15px">Transaction deadline:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(minReceived, formatDecimals, tokenNameBottom)}</Typography>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Tx. Deadline:</Typography>
<Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography> <Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
</Box> </Box>
</Box> </Box>}
<TokenAllowanceGuard <TokenAllowanceGuard
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)} spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
tokenName={tokenNameTop} tokenName={tokenNameTop}
owner={address} owner={address}
spender={dexAddresses.router} spender={dexAddresses.router}
decimals={balanceTop._decimals} decimals={balanceTop._decimals}
isNative={topIsNative}
approvalText={"Approve " + tokenNameTop} approvalText={"Approve " + tokenNameTop}
approvalPendingText={"Approving..."} approvalPendingText={"Approving..."}
connect={connect} connect={connect}
@ -289,10 +224,17 @@ const SwapContainer = ({
onClick={() => address === "" ? onClick={() => address === "" ?
connect() connect()
: :
(!isWrapping && !isUnwrapping) && pairAddress === EMPTY_ADDRESS ? setIsSwap(false) : swapTokens() pairAddress === "0x0000000000000000000000000000000000000000" ? setIsSwap(false) : swapTokens()
} }
> >
{address === "" ? "Connect" : buttonText } {address === "" ?
"Connect"
:
pairAddress === "0x0000000000000000000000000000000000000000" ?
"Create Pool"
:
"Swap"
}
</SecondaryButton> </SecondaryButton>
</TokenAllowanceGuard> </TokenAllowanceGuard>
</Box> </Box>

View File

@ -20,14 +20,10 @@ 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 { import { isNetworkLegacy } from "../../constants";
RESERVE_ADDRESSES, import { RESERVE_ADDRESSES, FTSO_ADDRESSES, STNK_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
FTSO_ADDRESSES,
GHST_ADDRESSES,
EMPTY_ADDRESS
} from "../../constants/addresses";
const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setTokenAddress }) => { const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }) => {
const isSmallScreen = useMediaQuery("(max-width: 599px)"); const isSmallScreen = useMediaQuery("(max-width: 599px)");
const isVerySmallScreen = useMediaQuery("(max-width: 425px)"); const isVerySmallScreen = useMediaQuery("(max-width: 425px)");
@ -43,15 +39,19 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
const { symbol: searchSymbol } = useTokenSymbol(chainId, address); const { symbol: searchSymbol } = useTokenSymbol(chainId, address);
const { balance: searchBalance } = useBalance(chainId, address, account); const { balance: searchBalance } = useBalance(chainId, address, account);
const { balance: nativeBalance } = useBalance(chainId, chainSymbol, account);
const { balance: reserveBalance } = useBalance(chainId, "RESERVE", account); const { balance: reserveBalance } = useBalance(chainId, "RESERVE", account);
const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account); const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account);
const { balance: stnkBalance } = useBalance(chainId, "STNK", account);
const { balance: ghstBalance } = useBalance(chainId, "GHST", account); const { balance: ghstBalance } = useBalance(chainId, "GHST", account);
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE"); const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const config = useConfig();
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const searchToken = useMemo(() => { const searchToken = useMemo(() => {
return [{ return [{
name: searchSymbol, name: searchSymbol,
@ -63,15 +63,9 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
const knownTokens = useMemo(() => { const knownTokens = useMemo(() => {
return [ return [
{
name: chainSymbol,
icons: [chainSymbol],
balance: nativeBalance,
address: EMPTY_ADDRESS,
},
{ {
name: reserveSymbol, name: reserveSymbol,
icons: [chainSymbol], icons: isNetworkLegacy(chainId) ? ["GDAI"] : [nativeSymbol],
balance: reserveBalance, balance: reserveBalance,
address: RESERVE_ADDRESSES[chainId] address: RESERVE_ADDRESSES[chainId]
}, },
@ -81,6 +75,12 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
balance: ftsoBalance, balance: ftsoBalance,
address: FTSO_ADDRESSES[chainId] address: FTSO_ADDRESSES[chainId]
}, },
{
name: stnkSymbol,
icons: ["STNK"],
balance: stnkBalance,
address: STNK_ADDRESSES[chainId]
},
{ {
name: ghstSymbol, name: ghstSymbol,
icons: ["GHST"], icons: ["GHST"],
@ -88,7 +88,7 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
address: GHST_ADDRESSES[chainId] address: GHST_ADDRESSES[chainId]
} }
] ]
}, [reserveSymbol, ftsoSymbol, ghstSymbol, reserveBalance, ftsoBalance, ghstBalance]); }, [reserveSymbol, ftsoSymbol, stnkSymbol, ghstSymbol, reserveBalance, ftsoBalance, stnkBalance, ghstBalance]);
useEffect(() => { useEffect(() => {
if (isAddress(userInput)) { if (isAddress(userInput)) {

View File

@ -1,6 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Box, Container, Grid, Divider, Typography, useMediaQuery } from "@mui/material"; import { Box, Container, Grid, Divider, Typography, useMediaQuery } from "@mui/material";
@ -19,10 +19,13 @@ const Governance = ({ connect, config, address, chainId }) => {
const isSmallScreen = useMediaQuery("(max-width: 650px)"); const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)"); const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const navigate = useNavigate(); const navigate = useNavigate();
const { network } = useParams();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const handleModal = () => {
const navigate = useNavigate();
}
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/governance" }); ReactGA.send({ hitType: "pageview", page: "/governance" });
}, []); }, []);
@ -67,7 +70,7 @@ const Governance = ({ connect, config, address, chainId }) => {
<Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center"> <Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
<PrimaryButton <PrimaryButton
fullWidth fullWidth
onClick={() => navigate(`/${network}/governance/create`)} onClick={() => navigate(`/governance/create`)}
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }} sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
> >
Create Proposal Create Proposal

View File

@ -28,7 +28,6 @@ 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";
@ -43,10 +42,11 @@ 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 = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []); const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
const [myProposals, setMyProposalsInner] = useState(() => myStoredProposals.map(id => BigInt(id))); const [myProposals, setMyProposals] = useState(
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);
const setMyProposals = useCallback((proposals) => { useEffect(() => {
setMyProposalsInner(proposals); const toStore = JSON.stringify(myProposals.map(id => id.toString()));
setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString())) localStorage.setItem(MY_PROPOSALS_PREFIX, toStore);
}, [chainId, address]); }, [myProposals]);
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/governance/create" }); ReactGA.send({ hitType: "pageview", page: "/governance/create" });
@ -163,14 +163,6 @@ const NewProposal = ({ config, address, connect, chainId }) => {
isVertical isVertical
> >
<Box display="flex" flexDirection="column" alignItems="center"> <Box display="flex" flexDirection="column" alignItems="center">
<TertiaryButton
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
fullWidth
disabled={isPending}
onClick={() => setIsModalOpened(true)}
>
Add New Function
</TertiaryButton>
<PrimaryButton <PrimaryButton
disabled={ disabled={
proposalFunctions.length === 0 || proposalFunctions.length === 0 ||
@ -182,6 +174,14 @@ const NewProposal = ({ config, address, connect, chainId }) => {
> >
{isPending ? "Submitting..." : "Submit Proposal"} {isPending ? "Submitting..." : "Submit Proposal"}
</PrimaryButton> </PrimaryButton>
<TertiaryButton
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
fullWidth
disabled={isPending}
onClick={() => setIsModalOpened(true)}
>
Add New
</TertiaryButton>
</Box> </Box>
</TokenAllowanceGuard> </TokenAllowanceGuard>
</Box> </Box>
@ -200,20 +200,7 @@ const NewProposal = ({ config, address, connect, chainId }) => {
</Box> </Box>
} }
> >
{isSmallScreen <TableContainer>
? <Box display="flex" flexDirection="column">
{proposalFunctions?.map((metadata, index) => {
return parseFunctionCalldata({
metadata,
index,
chainId,
removeCalldata,
nativeCoin: nativeCurrency,
isTable: false,
});
})}
</Box>
: <TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}> <Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -223,18 +210,11 @@ const NewProposal = ({ config, address, connect, chainId }) => {
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell> <TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody>{proposalFunctions?.map((metadata, index) => { <TableBody>{proposalFunctions.map((metadata, index) => {
return parseFunctionCalldata({ return parseFunctionCalldata(metadata, index, chainId, nativeCurrency, removeCalldata);
metadata,
index,
chainId,
removeCalldata,
nativeCoin: nativeCurrency,
});
})}</TableBody> })}</TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
}
</Paper>} </Paper>}
</Box> </Box>
</Container> </Container>

View File

@ -1,8 +1,7 @@
import { useEffect, useState, useMemo, useCallback } from "react"; import { useEffect, useState, useMemo } 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 { useBlock, useBlockNumber } from 'wagmi'
import { useBlock, useBlockNumber } from "wagmi";
import { import {
Box, Box,
@ -33,16 +32,13 @@ import { formatNumber, formatCurrency, shorten } from "../../helpers";
import { prettifySecondsInDays } from "../../helpers/timeUtil"; import { prettifySecondsInDays } from "../../helpers/timeUtil";
import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber";
import { convertStatusToColor, convertStatusToLabel } from "./helpers"; import { convertStatusToTemplate, convertStatusToLabel } from "./helpers";
import { parseFunctionCalldata } from "./components/functions"; 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,
getVoteTarget,
useProposalState, useProposalState,
useProposalProposer, useProposalProposer,
useProposalLocked, useProposalLocked,
@ -88,8 +84,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const isVerySmallScreen = useMediaQuery("(max-width: 379px)"); const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const theme = useTheme(); const theme = useTheme();
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);
@ -118,24 +112,48 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const votePercentage = useMemo(() => { const votePercentage = useMemo(() => {
if (totalSupply._value === 0n) return 0; if (totalSupply._value === 0n) return 0;
return totalVotes * HUNDRED / totalSupply; const value = (totalVotes?._value ?? 0n) * 100n / (totalSupply?._value ?? 1n);
return new DecimalBigNumber(value, 0);
}, [totalVotes, totalSupply]); }, [totalVotes, totalSupply]);
const forPercentage = useMemo(() => { const forPercentage = useMemo(() => {
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2); if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
return forVotes * HUNDRED / totalSupply; const value = (forVotes?._value ?? 0n) * 10000n / (totalSupply?._value ?? 1n);
return new DecimalBigNumber(value, 2);
}, [forVotes, totalSupply]); }, [forVotes, totalSupply]);
const againstPercentage= useMemo(() => { const againstPercentage= useMemo(() => {
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2); if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
return againstVotes * HUNDRED / totalSupply; const value = (againstVotes?._value ?? 0n) * 10000n / (totalSupply?._value ?? 1n);
return new DecimalBigNumber(value, 2);
}, [againstVotes, totalSupply]); }, [againstVotes, totalSupply]);
const voteWeightPercentage = useMemo(() => { const voteWeightPercentage = useMemo(() => {
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2); if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
return pastVotes * HUNDRED / totalSupply; const value = (pastVotes?._value ?? 0n) * 10000n / (totalSupply?._value ?? 1n);
return new DecimalBigNumber(value, 2);
}, [pastVotes, totalSupply]); }, [pastVotes, totalSupply]);
const voteValue = useMemo(() => {
if (totalVotes?._value == 0n) {
return 0;
}
return Number(forVotes._value * 100n / totalVotes._value);
}, [forVotes, totalVotes]);
const voteTarget = useMemo(() => {
const first = (5n * againstVotes._value + forVotes._value);
const second = BigInt(Math.floor(Math.sqrt(Number(totalVotes._value))));
const bias = 3n * first + second;
const denominator = totalVotes._value + bias;
if (denominator === 0n) {
return 80;
}
return Number(totalVotes?._value * 100n / denominator);
}, [againstVotes, forVotes, totalVotes]);
const nativeCurrency = useMemo(() => { const nativeCurrency = useMemo(() => {
const client = config?.getClient(); const client = config?.getClient();
return client?.chain?.nativeCurrency?.symbol; return client?.chain?.nativeCurrency?.symbol;
@ -150,41 +168,38 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
return url; return url;
}, [proposalProposer, config]); }, [proposalProposer, config]);
const handleVote = useCallback(async (support) => { const handleVote = async (against) => {
setIsPending(true); setIsPending(true);
const support = against ? 0 : 1;
const result = await castVote(chainId, address, proposalId, support); const result = await castVote(chainId, address, proposalId, support);
if (result) { if (result) {
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []); const toStore = JSON.stringify(proposalId.toString());
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id)); localStorage.setItem(VOTED_PROPOSALS_PREFIX, toStore);
proposals.push(proposalId); }
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
await queryClient.invalidateQueries(); setIsPending(true);
} }
setIsPending(false);
}, [chainId, address, proposalId]);
const handleExecute = async () => { const handleExecute = async () => {
setIsPending(true); setIsPending(true);
await executeProposal(chainId, address, proposalId); await executeProposal(chainId, address, proposalId);
await queryClient.invalidateQueries(); setIsPending(true);
setIsPending(false);
} }
const handleRelease = async () => { const handleRelease = async (proposalId) => {
setIsPending(true); setIsPending(true);
await releaseLocked(chainId, address, proposalId); await releaseLocked(chainId, address, proposalId);
await queryClient.invalidateQueries(); setIsPending(true);
setIsPending(false);
} }
return ( return (
<Box> <Box>
<PageTitle <PageTitle
name={`GDP-${id.slice(-5)}`} name={`GBP-${id.slice(-5)}`}
subtitle={ subtitle={
<Typography component="span"> <Typography component="span">
{`Cast $${ghstSymbol} to shape this proposal's outcome`} Proposal details, need more in-depth description
</Typography> </Typography>
} }
/> />
@ -199,24 +214,19 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
}} }}
> >
<Box sx={{ mt: "15px" }}> <Box sx={{ mt: "15px" }}>
<Box <Box display="flex" justifyContent="space-between" gap="20px">
gap="20px"
display="flex"
justifyContent={"space-between"}
flexDirection={isSmallScreen ? "column" : "row"}
>
<Paper <Paper
fullWidth fullWidth
enableBackground enableBackground
headerContent={ headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="10px"> <Box display="flex" alignItems="center" flexDirection="row" gap="10px">
{!isSmallScreen &&<Typography variant="h6"> <Typography variant="h6">
Progress Progress
</Typography>} </Typography>
<Chip <Chip
sx={{ marginTop: "4px", width: "88px" }} sx={{ marginTop: "4px", width: "88px" }}
label={convertStatusToLabel(proposalState)} label={convertStatusToLabel(proposalState)}
background={convertStatusToColor(proposalState)} template={convertStatusToTemplate(proposalState)}
/> />
</Box> </Box>
} }
@ -240,8 +250,8 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
barColor={theme.colors.feedback.success} barColor={theme.colors.feedback.success}
barBackground={theme.colors.feedback.error} barBackground={theme.colors.feedback.error}
variant="determinate" variant="determinate"
value={getVoteValue(forVotes?._value ?? 0n, totalVotes?._value ?? 0n)} value={voteValue}
target={getVoteTarget(totalVotes?._value ?? 0n, totalSupply?._value ?? 0n)} target={voteTarget}
/> />
</Box> </Box>
@ -294,14 +304,14 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending} disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(1)} onClick={() => handleVote(1)}
> >
{isPending ? "Voting..." : "For"} For
</SecondaryButton> </SecondaryButton>
<SecondaryButton <SecondaryButton
fullWidth fullWidth
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending} disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(0)} onClick={() => handleVote(0)}
> >
{isPending ? "Voting..." : "Against"} Against
</SecondaryButton> </SecondaryButton>
</> </>
: <SecondaryButton : <SecondaryButton
@ -336,7 +346,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
isProposer={proposalProposer === address} isProposer={proposalProposer === address}
chainId={chainId} chainId={chainId}
proposalId={id} proposalId={id}
isPending={isPending}
/> />
</Paper> </Paper>
</Box> </Box>
@ -353,19 +362,7 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
</Box> </Box>
} }
> >
{isSmallScreen <TableContainer>
? <Box display="flex" flexDirection="column">
{proposalDetails?.map((metadata, index) => {
return parseFunctionCalldata({
metadata,
index,
chainId,
nativeCoin: nativeCurrency,
isTable: false,
});
})}
</Box>
: <TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}> <Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -376,23 +373,17 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody>{proposalDetails?.map((metadata, index) => { <TableBody>{proposalDetails?.map((metadata, index) => {
return parseFunctionCalldata({ return parseFunctionCalldata(metadata, index, chainId, nativeCurrency);
metadata,
index,
chainId,
nativeCoin: nativeCurrency,
});
})}</TableBody> })}</TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
}
</Paper> </Paper>
</Container> </Container>
</Box> </Box>
) )
} }
const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer, isPending }) => { const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer }) => {
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId); const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId); const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId); const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
@ -412,24 +403,19 @@ const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked,
<VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" /> <VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" />
</Timeline> </Timeline>
<Box width="100%" display="flex" gap="10px"> <Box width="100%" display="flex" gap="10px">
{(isProposer && (proposalLocked?._value ?? 0n) > 0n) && <SecondaryButton {isProposer && <SecondaryButton
fullWidth fullWidth
disabled={isPending || state < 2} disabled={(proposalLocked?._value ?? 0n) === 0n || state < 2}
onClick={() => address === "" ? connect() : handleRelease()} onClick={() => address === "" ? connect() : handleRelease()}
> >
{address === "" ? "Connect" : "Release"} {address === "" ? "Connect" : "Release"}
</SecondaryButton>} </SecondaryButton>}
<SecondaryButton <SecondaryButton
fullWidth fullWidth
disabled={isPending || state !== 4} disabled={state !== 4}
onClick={() => address === "" ? connect() : handleExecute()} onClick={() => address === "" ? connect() : handleExecute()}
> >
{address === "" {address === "" ? "Connect" : "Execute"}
? "Connect"
: state !== 4
? convertStatusToLabel(state)
: isPending ? "Executing..." : "Execute"
}
</SecondaryButton> </SecondaryButton>
</Box> </Box>
</Box> </Box>

View File

@ -12,7 +12,7 @@ const GovernanceInfoText = () => {
ghostDAOs adaptive governance system algorithmically sets minimum collateral based on activity. ghostDAOs adaptive governance system algorithmically sets minimum collateral based on activity.
&nbsp;<Link &nbsp;<Link
color={theme.colors.primary[300]} color={theme.colors.primary[300]}
href="http://forum.ghostchain.io/" href="https://docs.dao.ghostchain.io/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
>Learn more here.</Link> >Learn more here.</Link>

View File

@ -103,11 +103,7 @@ const InitialStep = ({ selectedOption, handleChange, handleCalldata, handleProce
return allPossibleFunctions.find(opt => opt.value === selected)?.label || selected; return allPossibleFunctions.find(opt => opt.value === selected)?.label || selected;
}} }}
/> />
<Typography align="center" variant="body2"> <Typography align="center" variant="body2">{functionDescription}</Typography>
<b>{selectedOption}</b>
{" "}
{functionDescription}
</Typography>
{ready {ready
? <TertiaryButton disabled={!selectedOption} onClick={() => handleProceed()} fullWidth>Proceed</TertiaryButton> ? <TertiaryButton disabled={!selectedOption} onClick={() => handleProceed()} fullWidth>Proceed</TertiaryButton>
: <PrimaryButton disabled={!selectedOption} onClick={() => handleCalldata()} fullWidth>Create Function</PrimaryButton> : <PrimaryButton disabled={!selectedOption} onClick={() => handleCalldata()} fullWidth>Create Function</PrimaryButton>

View File

@ -1,5 +1,5 @@
import { useState, useMemo } from "react"; import { useState, useMemo } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { import {
Box, Box,
@ -23,7 +23,6 @@ import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
import { networkAvgBlockSpeed } from "../../../constants"; import { networkAvgBlockSpeed } from "../../../constants";
import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil"; import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import Chip from "../../../components/Chip/Chip"; import Chip from "../../../components/Chip/Chip";
import Modal from "../../../components/Modal/Modal"; import Modal from "../../../components/Modal/Modal";
@ -33,7 +32,7 @@ import { PrimaryButton, TertiaryButton } from "../../../components/Button";
import ProposalInfoText from "./ProposalInfoText"; import ProposalInfoText from "./ProposalInfoText";
import { import {
convertStatusToColor, convertStatusToTemplate,
convertStatusToLabel, convertStatusToLabel,
MY_PROPOSALS_PREFIX, MY_PROPOSALS_PREFIX,
VOTED_PROPOSALS_PREFIX VOTED_PROPOSALS_PREFIX
@ -41,8 +40,9 @@ import {
import { useScreenSize } from "../../../hooks/useScreenSize"; import { useScreenSize } from "../../../hooks/useScreenSize";
import { useProposals } from "../../../hooks/governance"; import {
import { useLocalStorage } from "../../../hooks/localstorage"; useProposals,
} from "../../../hooks/governance";
const MAX_PROPOSALS_TO_SHOW = 10; const MAX_PROPOSALS_TO_SHOW = 10;
@ -51,16 +51,17 @@ const ProposalsList = ({ chainId, address, config }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme(); const theme = useTheme();
const { network } = useParams();
const { getStorageValue, setStorageValue } = useLocalStorage();
const [proposalsFilter, setProposalFilter] = useState("active"); const [proposalsFilter, setProposalFilter] = useState("active");
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []); const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
const [myProposals, setMyProposals] = useState(() => myStoredProposals.map(id => BigInt(id))); const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []); const storedVotedProposals = localStorage.getItem(VOTED_PROPOSALS_PREFIX);
const [votedProposals, setVotedProposals] = useState(() => storedVotedProposals.map(id => BigInt(id))); const [votedProposals, setVotedProposals] = useState(
storedVotedProposals ? JSON.parse(storedVotedProposals).map(id => BigInt(id)) : []
);
const searchedIndexes = useMemo(() => { const searchedIndexes = useMemo(() => {
switch (proposalsFilter) { switch (proposalsFilter) {
@ -106,7 +107,7 @@ const ProposalsList = ({ chainId, address, config }) => {
proposal={proposal} proposal={proposal}
blockNumber={blockNumber} blockNumber={blockNumber}
chainId={chainId} chainId={chainId}
openProposal={() => navigate(`${network}/governance/${proposal.hashes.full}`)} openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
/> />
))} ))}
</Box> </Box>
@ -153,7 +154,7 @@ const ProposalsList = ({ chainId, address, config }) => {
proposal={proposal} proposal={proposal}
blockNumber={blockNumber} blockNumber={blockNumber}
chainId={chainId} chainId={chainId}
openProposal={() => navigate(`/${network}/governance/${proposal.hashes.full}`)} openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
/> />
))} ))}
</ProposalTable> </ProposalTable>
@ -174,7 +175,7 @@ const ProposalTable = ({ children }) => (
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Status</TableCell> <TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Status</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell> <TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell> <TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
<TableCell align="center" style={{ width: "180px", padding: "8px 0" }}></TableCell> <TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -185,6 +186,34 @@ const ProposalTable = ({ children }) => (
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => { const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
const theme = useTheme(); const theme = useTheme();
const voteValue = useMemo(() => {
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
const totalVotes = againstVotes + forVotes;
if (totalVotes == 0) {
return 0;
}
return Number(forVotes * 100n / totalVotes);
}, [proposal]);
const voteTarget = useMemo(() => {
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
const totalVotes = againstVotes + forVotes;
const first = (5n * againstVotes + forVotes);
const second = BigInt(Math.floor(Math.sqrt(Number(totalVotes))));
const bias = 3n * first + second;
const denominator = totalVotes + bias;
if (denominator === 0n) {
return 80;
}
return Number(totalVotes * 100n / denominator);
}, [proposal]);
return ( return (
<TableRow id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}> <TableRow id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
<TableCell align="center" style={{ padding: "8px 0" }}> <TableCell align="center" style={{ padding: "8px 0" }}>
@ -195,7 +224,7 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
<Chip <Chip
sx={{ width: "100px" }} sx={{ width: "100px" }}
label={convertStatusToLabel(proposal.state)} label={convertStatusToLabel(proposal.state)}
background={convertStatusToColor(proposal.state)} template={convertStatusToTemplate(proposal.state)}
/> />
</TableCell> </TableCell>
@ -215,8 +244,8 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
barColor={theme.colors.feedback.success} barColor={theme.colors.feedback.success}
barBackground={theme.colors.feedback.error} barBackground={theme.colors.feedback.error}
variant="determinate" variant="determinate"
value={proposal?.voteValue ?? 0} value={voteValue}
target={proposal?.voteTarget ?? 50} target={voteTarget}
/> />
</Box> </Box>
</TableCell> </TableCell>
@ -244,6 +273,7 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => { const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
const theme = useTheme(); const theme = useTheme();
const isSmallScreen = useMediaQuery('(max-width: 450px)'); const isSmallScreen = useMediaQuery('(max-width: 450px)');
return ( return (
<Box id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}> <Box id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
<Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between"> <Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between">
@ -253,7 +283,7 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
<Chip <Chip
sx={{ width: "88px" }} sx={{ width: "88px" }}
label={convertStatusToLabel(proposal.state)} label={convertStatusToLabel(proposal.state)}
background={convertStatusToColor(proposal.state)} template={convertStatusToTemplate(proposal.state)}
/> />
</Box> </Box>
<Typography> <Typography>
@ -271,8 +301,8 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
barColor={theme.colors.feedback.success} barColor={theme.colors.feedback.success}
barBackground={theme.colors.feedback.error} barBackground={theme.colors.feedback.error}
variant="determinate" variant="determinate"
value={proposal?.voteValue ?? 0} value={69}
target={proposal?.voteTarget ?? 50} target={Math.floor(Math.random() * 101)}
/> />
</Box> </Box>
<Box marginBottom="20px"> <Box marginBottom="20px">
@ -312,11 +342,10 @@ const ProposalFilterTrigger = ({ trigger, setTrigger }) => {
} }
const convertDeadline = (deadline, blockNumber, chainId) => { const convertDeadline = (deadline, blockNumber, chainId) => {
const alreadyHappened = blockNumber > deadline; const diff = blockNumber > deadline ? blockNumber - deadline : deadline - blockNumber;
const diff = alreadyHappened ? blockNumber - deadline : deadline - blockNumber;
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId)); const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
const result = prettifySeconds(voteSeconds, "min"); const result = prettifySeconds(voteSeconds, "mins");
if (result === "now") { if (result === "now") {
return new Date(Date.now()).toLocaleDateString('en-US', { return new Date(Date.now()).toLocaleDateString('en-US', {
year: 'numeric', year: 'numeric',
@ -325,10 +354,7 @@ const convertDeadline = (deadline, blockNumber, chainId) => {
}); });
} }
const prefix = alreadyHappened ? "" : "in "; return `in ${result}`;
const postfix = alreadyHappened ? " ago" : "";
return `${prefix}${result}${postfix}`;
} }
export default ProposalsList; export default ProposalsList;

View File

@ -20,12 +20,20 @@ export const prepareAuditReservesCalldata = (chainId) => {
return { label, target, calldata, value }; return { label, target, calldata, value };
} }
export const prepareAuditReservesDescription = "function audits and updates the protocol's total reserve value. It sums the value of all approved reserve and liquidity tokens, then stores and logs the new total."; export const prepareAuditReservesDescription = "Audit Reserves function audits and updates the protocol's total reserve value. It sums the value of all approved reserve and liquidity tokens, then stores and logs the new total.";
export const AuditReservesSteps = () => { export const AuditReservesSteps = () => {
return null; return null;
} }
export const AuditReservesParsed = (props) => { export const AuditReservesParsed = (props) => {
return (
<>
{props.isTable && <AuditReservesParsedCell {...props} />}
</>
)
}
const AuditReservesParsedCell = (props) => {
return <ParsedCell {...props} /> return <ParsedCell {...props} />
} }

View File

@ -29,7 +29,7 @@ export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, i
return { label, target, calldata, value }; return { label, target, calldata, value };
} }
export const prepareCreateBondDescription = "function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol."; export const prepareCreateBondDescription = "Create Bond function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol.";
export const CreateBondParsed = (props) => { export const CreateBondParsed = (props) => {
const [isOpened, setIsOpened] = useState(false); const [isOpened, setIsOpened] = useState(false);
@ -51,11 +51,15 @@ export const CreateBondParsed = (props) => {
<CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} /> <CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} />
</Box> </Box>
</Modal> </Modal>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} /> {props.isTable && <CreateBondParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props}/>}
</> </>
) )
} }
const CreateBondParsedCell = (props) => {
return <ParsedCell {...props} />
}
export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata, args }) => { export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata, args }) => {
const createMode = args === undefined; const createMode = args === undefined;
@ -114,7 +118,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]); }, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
const possibleTokens = [ const possibleTokens = [
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` }, { value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${nativeCurrency} LP`, label: `${ftsoSymbol}-${nativeCurrency} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` }, { value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
]; ];
@ -125,7 +129,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
createMode={createMode} createMode={createMode}
possibleTokens={possibleTokens} possibleTokens={possibleTokens}
setTokenAddress={createMode ? setTokenAddress : empty} setTokenAddress={createMode ? setTokenAddress : empty}
nativeCurrency={reserveSymbol} nativeCurrency={nativeCurrency}
ftsoSymbol={ftsoSymbol} ftsoSymbol={ftsoSymbol}
capacityInQuote={capacityInQuote} capacityInQuote={capacityInQuote}
setCapacityInQuote={createMode ? setCapacityInQuote : empty} setCapacityInQuote={createMode ? setCapacityInQuote : empty}
@ -222,7 +226,7 @@ const IntervalsArguments = ({
/> />
<ArgumentInput <ArgumentInput
endString="seconds" endString="seconds"
label="_intervals[1]" label="_intervals[0]"
value={tuneInterval ?? ""} value={tuneInterval ?? ""}
setValue={setTuneInterval} setValue={setTuneInterval}
tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval." tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval."
@ -292,7 +296,7 @@ const TokenAndBooleansArguments = ({
rightText={"False"} rightText={"False"}
setLeftValue={() => setCapacityInQuote(true)} setLeftValue={() => setCapacityInQuote(true)}
setRightValue={() => setCapacityInQuote(false)} setRightValue={() => setCapacityInQuote(false)}
tooltip={`Determines how the bond market capacity is measured. True = measured in _quoteToken, False = measured in ${ftsoSymbol}.`} tooltip={`Determines how the bond market capacity is measured. True = measured in ${nativeCurrency}, False = measured in ${ftsoSymbol}.`}
/> />
<BooleanTrigger <BooleanTrigger
value={fixedTerm} value={fixedTerm}
@ -311,7 +315,6 @@ const TokenAndBooleansArguments = ({
value={selectedOption ?? ""} value={selectedOption ?? ""}
onChange={handleChange} onChange={handleChange}
options={possibleTokens} options={possibleTokens}
maxWidth="calc(100vw - 70px)"
inputWidth="100%" inputWidth="100%"
renderValue={(selected) => { renderValue={(selected) => {
if (!selected || selected.length === 0) { if (!selected || selected.length === 0) {

View File

@ -1,85 +0,0 @@
import { useState } from "react";
import { encodeFunctionData } from 'viem';
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
import Modal from "../../../../components/Modal/Modal";
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
import { GHOST_GOVERNANCE_ADDRESSES } from "../../../../constants/addresses";
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
import { ArgumentInput, ParsedCell } from "./index";
export const prepareSetLateQuorumExtensionCalldata = (chainId, lateQuorumVoteExtension) => {
const value = 0n;
const label = "setLateQuorumVoteExtension";
const target = GHOST_GOVERNANCE_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: GovernorAbi,
functionName: 'setLateQuorumVoteExtension',
args: [lateQuorumVoteExtension]
});
return { label, target, calldata, value };
}
export const prepareSetLateQuorumExtensionDescription = "updates the current value of the vote extension parameter: the number of blocks that are required to pass from the time a proposal reaches quorum until its voting period ends.";
export const SetLateQuorumExtensionParsed = (props) => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<Modal
headerContent={
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
<Typography variant="h4">View setLateQuorumVoteExtension</Typography>
</Box>
}
open={isOpened}
onClose={() => setIsOpened(false)}
maxWidth="460px"
minHeight="80px"
>
<Box minHeight="100px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
<SetLateQuorumExtensionSteps {...props} />
</Box>
</Modal>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
</>
)
}
export const SetLateQuorumExtensionSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
const createMode = args === undefined;
const [lateQuorumVoteExtension, setlateQuorumVoteExtension] = useState(args?.at(0));
const handleProceed = () => {
addCalldata(prepareSetLateQuorumExtensionCalldata(chainId, lateQuorumVoteExtension));
}
return (
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent={"space-between"}>
<ArgumentInput
disabled={!createMode}
endString="blocks"
label="lateQuorumVoteExtension"
value={lateQuorumVoteExtension ?? ""}
setValue={createMode ? setlateQuorumVoteExtension : () => {}}
tooltip="Vote extension defines extension in blocks if a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at least a specified time has passed"
/>
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
<Box display="flex" gap="10px">
<TertiaryButton onClick={toInitialStep} fullWidth>Back</TertiaryButton>
<TertiaryButton disabled fullWidth>Next</TertiaryButton>
</Box>
<PrimaryButton
disabled={!lateQuorumVoteExtension}
onClick={() => handleProceed()}
fullWidth
>
Create Function
</PrimaryButton>
</Box>}
</Box>
);
}

View File

@ -1,85 +0,0 @@
import { useState } from "react";
import { encodeFunctionData } from 'viem';
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
import Modal from "../../../../components/Modal/Modal";
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
import { GHOST_GOVERNANCE_ADDRESSES } from "../../../../constants/addresses";
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
import { ArgumentInput, ParsedCell } from "./index";
export const prepareSetProposalThresholdCalldata = (chainId, newThreshold) => {
const value = 0n;
const label = "setProposalThreshold";
const target = GHOST_GOVERNANCE_ADDRESSES[chainId];
const calldata = encodeFunctionData({
abi: GovernorAbi,
functionName: 'setProposalThreshold',
args: [newThreshold]
});
return { label, target, calldata, value };
}
export const prepareSetProposalThresholdDescription = "updates the minimum amount required to create a proposal. This threshold applies only when there are no Active or Pending proposals; otherwise, a higher threshold is enforced.";
export const SetProposalThresholdParsed = (props) => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<Modal
headerContent={
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
<Typography variant="h4">View setProposalThreshold</Typography>
</Box>
}
open={isOpened}
onClose={() => setIsOpened(false)}
maxWidth="460px"
minHeight="80px"
>
<Box minHeight="100px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
<SetProposalThresholdSteps {...props} />
</Box>
</Modal>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
</>
)
}
export const SetProposalThresholdSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
const createMode = args === undefined;
const [newThreshold, setNewThreshold] = useState(args?.at(0));
const handleProceed = () => {
addCalldata(prepareSetProposalThresholdCalldata(chainId, newThreshold));
}
return (
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent={"space-between"}>
<ArgumentInput
disabled={!createMode}
endString="!CSPR units"
label="newProposalThreshold"
value={newThreshold ?? ""}
setValue={createMode ? setNewThreshold : () => {}}
tooltip="The new proposal threshold defines the minimum amount each proposer must lock for the duration of the proposal. This locked amount serves as the required deposit to create a proposal."
/>
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
<Box display="flex" gap="10px">
<TertiaryButton onClick={toInitialStep} fullWidth>Back</TertiaryButton>
<TertiaryButton disabled fullWidth>Next</TertiaryButton>
</Box>
<PrimaryButton
disabled={!newThreshold}
onClick={() => handleProceed()}
fullWidth
>
Create Function
</PrimaryButton>
</Box>}
</Box>
);
}

View File

@ -24,7 +24,7 @@ export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, in
return { label, target, calldata, value }; return { label, target, calldata, value };
} }
export const prepareSetAdjustmentDescription = "function schedules a gradual change to the staking APY. It increases or decreases the staking APY by a specified rate per epoch until a target rate reached."; export const prepareSetAdjustmentDescription = "Set Adjustment function schedules a gradual change to the staking APY. It increases or decreases the staking APY by a specified rate per epoch until a target rate reached.";
export const SetAdjustmentParsed = (props) => { export const SetAdjustmentParsed = (props) => {
const [isOpened, setIsOpened] = useState(false); const [isOpened, setIsOpened] = useState(false);
@ -45,17 +45,21 @@ export const SetAdjustmentParsed = (props) => {
<SetAdjustmentSteps {...props} /> <SetAdjustmentSteps {...props} />
</Box> </Box>
</Modal> </Modal>
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} /> {props.isTable && <SetAdjustmentParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />}
</> </>
) )
} }
const SetAdjustmentParsedCell = (props) => {
return <ParsedCell {...props} />
}
export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }) => { export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
const createMode = args === undefined; const createMode = args === undefined;
const [rate, setRate] = useState(args?.at(0)); const [rate, setRate] = useState(args?.at(0));
const [target, setTarget] = useState(args?.at(1)); const [target, setTarget] = useState(args?.at(1));
const [increase, setIncrease] = useState(args === undefined ? true : args.at(2)); const [increase, setIncrease] = useState(args?.at(1) ?? true);
const handleProceed = () => { const handleProceed = () => {
addCalldata(prepareSetAdjustmentCalldata(chainId, rate, target, increase)); addCalldata(prepareSetAdjustmentCalldata(chainId, rate, target, increase));

View File

@ -10,7 +10,6 @@ import InfoTooltip from "../../../../components/Tooltip/InfoTooltip";
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json"; import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json"; import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json"; import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
import { config } from "../../../../config"; import { config } from "../../../../config";
import { shorten, formatCurrency } from "../../../../helpers"; import { shorten, formatCurrency } from "../../../../helpers";
@ -18,8 +17,6 @@ import { shorten, formatCurrency } from "../../../../helpers";
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves"; import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment"; import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond"; import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond";
import { prepareSetProposalThresholdDescription, prepareSetProposalThresholdCalldata, SetProposalThresholdSteps, SetProposalThresholdParsed } from "./ProposalThreshold";
import { prepareSetLateQuorumExtensionDescription, prepareSetLateQuorumExtensionCalldata, SetLateQuorumExtensionSteps, SetLateQuorumExtensionParsed } from "./LateQuorumExtension";
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified." const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
@ -27,11 +24,9 @@ export const allPossibleFunctions = [
{ value: "auditReserves", label: "auditReserves" }, { value: "auditReserves", label: "auditReserves" },
{ value: "setAdjustment", label: "setAdjustment" }, { value: "setAdjustment", label: "setAdjustment" },
{ value: "create", label: "create" }, { value: "create", label: "create" },
{ value: "setProposalThreshold", label: "setProposalThreshold" },
{ value: "setLateQuorumVoteExtension", label: "setLateQuorumVoteExtension" },
]; ];
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi, GovernorAbi]; const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
const identifyAction = (calldata) => { const identifyAction = (calldata) => {
let decoded = { functionName: "Unknown", args: [] }; let decoded = { functionName: "Unknown", args: [] };
@ -49,58 +44,78 @@ const identifyAction = (calldata) => {
return decoded; return decoded;
} }
export const parseFunctionCalldata = ({ export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
metadata,
index,
chainId,
nativeCoin,
removeCalldata,
isTable = true,
}) => {
const { label, calldata, target, value } = metadata; const { label, calldata, target, value } = metadata;
const { functionName, args } = identifyAction(calldata); const { functionName, args } = identifyAction(calldata);
const labelOrName = label ?? (functionName ?? "Unknown"); const labelOrName = label ?? (functionName ?? "Unknown");
const remove = removeCalldata && (() => removeCalldata(index)); const remove = removeCalldata && (() => removeCalldata(index));
const props = {
isTable,
calldata,
label: labelOrName,
chainId: chainId,
remove: removeCalldata,
nativeCoin,
value,
target,
args,
id: index,
};
switch (functionName) { switch (functionName) {
case allPossibleFunctions[0].value: case "auditReserves":
return <AuditReservesParsed key={index} {...props} />; return <AuditReservesParsed
case allPossibleFunctions[1].value: isTable
return <SetAdjustmentParsed key={index} {...props} />; key={index}
case allPossibleFunctions[2].value: calldata={calldata}
return <CreateBondParsed {...props} />; label={labelOrName}
case allPossibleFunctions[3].value: chainId={chainId}
return <SetProposalThresholdParsed key={index} {...props} />; remove={remove}
case allPossibleFunctions[4].value: nativeCoin={nativeCoin}
return <SetLateQuorumExtensionParsed key={index} {...props} />; value={value}
target={target}
id={index}
/>;
case "setAdjustment":
return <SetAdjustmentParsed
isTable
args={args}
key={index}
calldata={calldata}
label={labelOrName}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
case "create":
return <CreateBondParsed
isTable
args={args}
key={index}
calldata={calldata}
label={labelOrName}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
default: default:
return <ParsedCell key={index} {...props} />; return <ParsedCell
isTable
key={index}
calldata={calldata}
label="Unknown"
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
} }
} }
export const getFunctionArguments = (functionName) => { export const getFunctionArguments = (functionName) => {
switch (functionName) { switch (functionName) {
case allPossibleFunctions[1].value: case "auditReserves":
return null;
case "setAdjustment":
return SetAdjustmentSteps; return SetAdjustmentSteps;
case allPossibleFunctions[2].value: case "create":
return CreateBondSteps; return CreateBondSteps;
case allPossibleFunctions[3].value:
return SetProposalThresholdSteps;
case allPossibleFunctions[4].value:
return SetLateQuorumExtensionSteps;
default: default:
return null; return null;
} }
@ -108,16 +123,12 @@ export const getFunctionArguments = (functionName) => {
export const getFunctionCalldata = (functionName, chainId) => { export const getFunctionCalldata = (functionName, chainId) => {
switch (functionName) { switch (functionName) {
case allPossibleFunctions[0].value: case "auditReserves":
return prepareAuditReservesCalldata(chainId); return prepareAuditReservesCalldata(chainId);
case allPossibleFunctions[1].value: case "setAdjustment":
return prepareSetAdjustmentCalldata(chainId); return prepareSetAdjustmentCalldata(chainId);
case allPossibleFunctions[2].value: case "create":
return prepareCreateBondCalldata(chainId); return prepareCreateBondCalldata(chainId);
case allPossibleFunctions[3].value:
return prepareSetProposalThresholdCalldata(chainId);
case allPossibleFunctions[4].value:
return prepareSetLateQuorumExtensionCalldata(chainId);
default: default:
return null; return null;
} }
@ -125,16 +136,12 @@ export const getFunctionCalldata = (functionName, chainId) => {
export const getFunctionDescription = (functionName) => { export const getFunctionDescription = (functionName) => {
switch (functionName) { switch (functionName) {
case allPossibleFunctions[0].value: case "auditReserves":
return prepareAuditReservesDescription; return prepareAuditReservesDescription;
case allPossibleFunctions[1].value: case "setAdjustment":
return prepareSetAdjustmentDescription; return prepareSetAdjustmentDescription;
case allPossibleFunctions[2].value: case "create":
return prepareCreateBondDescription; return prepareCreateBondDescription;
case allPossibleFunctions[3].value:
return prepareSetProposalThresholdDescription;
case allPossibleFunctions[4].value:
return prepareSetLateQuorumExtensionDescription;
default: default:
return DEFAULT_DESCRIPTION; return DEFAULT_DESCRIPTION;
} }
@ -268,7 +275,6 @@ export const ParsedCell = (props) => {
} }
}; };
if (props.isTable) {
return ( return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}> <TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}> <TableCell align="center" style={{ padding: "8px 0" }}>
@ -299,55 +305,4 @@ export const ParsedCell = (props) => {
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
}
return (
<Box
id={props.id + `--proposalFunction`}
data-testid={props.id + `--proposalFunction`}
display="flex"
flexDirection="column"
>
<Box display="flex" justifyContent="space-between">
<Typography>{`Function ${props.id + 1}`}</Typography>
<Typography>{props.label}</Typography>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Target</Typography>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.target)}</Typography>
</Link>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Calldata</Typography>
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.calldata)}</Typography>
</Link>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Value</Typography>
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
</Link>
</Box>
{(props.args || props.remove) && <Box
display="flex"
flexDirection="row"
justifyContent="space-between"
gap="10px"
marginTop="10px"
>
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
</Box>}
<hr width="100%" style={{ margin: "20px 0" }} />
</Box>
)
} }

View File

@ -1,24 +1,18 @@
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS"; export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS"; export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
export const convertStatusToColor = (status) => { export const convertStatusToTemplate = (status) => {
switch (status) { switch (status) {
case 1:
return "#f2e370";
case 2:
return "#f06f73";
case 3:
return "#f06f73";
case 4:
return "#60c45b";
case 5:
return "#60c45b";
case 6:
return "#f06f73";
case 7: case 7:
return "#f2e370"; return 'darkGray';
case 2:
return 'warning';
case 4:
return 'success';
case 3:
return 'error';
default: default:
return "#a2b7ce"; return 'darkGray';
} }
} }

View File

@ -31,6 +31,7 @@ export default function NotFound({
}); });
setWrongNetworkToastId(toastId); setWrongNetworkToastId(toastId);
setNetworkId(newChainId); setNetworkId(newChainId);
switchChain({ chainId: newChainId }); switchChain({ chainId: newChainId });
} }

View File

@ -19,6 +19,7 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [settingsModalOpen, setSettingsModalOpen] = useState(false);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const [upperToken, setUpperToken] = useState(ftsoSymbol); const [upperToken, setUpperToken] = useState(ftsoSymbol);
@ -30,16 +31,28 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
useEffect(() => { useEffect(() => {
switch (true) { switch (true) {
case (upperToken === ftsoSymbol && bottomToken === stnkSymbol):
setAction("STAKE")
break;
case (upperToken === ftsoSymbol && bottomToken === ghstSymbol): case (upperToken === ftsoSymbol && bottomToken === ghstSymbol):
setAction("STAKE") setAction("STAKE")
break; break;
case (upperToken === stnkSymbol && bottomToken === ftsoSymbol):
setAction("UNSTAKE")
break;
case (upperToken === ghstSymbol && bottomToken === ftsoSymbol): case (upperToken === ghstSymbol && bottomToken === ftsoSymbol):
setAction("UNSTAKE") setAction("UNSTAKE")
break; break;
case (upperToken === stnkSymbol && bottomToken === ghstSymbol):
setAction("WRAP")
break;
case (upperToken === ghstSymbol && bottomToken === stnkSymbol):
setAction("UNWRAP")
break;
default: default:
setAction("STAKE") setAction("STAKE")
} }
}, [ftsoSymbol, ghstSymbol, upperToken, bottomToken]) }, [upperToken, bottomToken])
return ( return (
<Modal <Modal

View File

@ -21,15 +21,14 @@ import InfoTooltip from "../../../components/Tooltip/InfoTooltip";
import Paper from "../../../components/Paper/Paper"; import Paper from "../../../components/Paper/Paper";
import Token from "../../../components/Token/Token"; import Token from "../../../components/Token/Token";
import { PrimaryButton, SecondaryButton } from "../../../components/Button"; import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { Tab, Tabs } from "../../../components/Tabs/Tabs";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../../helpers/timeUtil"; import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { formatNumber, formatCurrency } from "../../../helpers"; import { formatNumber } 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, useStnkPrice } from "../../../hooks/prices";
import { isNetworkLegacy } from "../../../constants";
import ClaimConfirmationModal from "./ClaimConfirmationModal"; import ClaimConfirmationModal from "./ClaimConfirmationModal";
@ -53,10 +52,10 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const isSmallScreen = useMediaQuery("(max-width: 745px)"); const isSmallScreen = useMediaQuery("(max-width: 745px)");
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false); const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [isPayoutGhst, _] = useState(true); const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("stake-isGhstPayout")
? localStorage.getItem("stake-isGhstPayout")
const ghstPrice = useGhstPrice(chainId); : false
const stnkPrice = useStnkPrice(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);
@ -66,14 +65,6 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
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 claimableBalance = useMemo(() => {
if (isNetworkLegacy(chainId)) {
return isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares;
}
const toClaim = new DecimalBigNumber(claim.shares, 18);
return isPayoutGhst ? toClaim : toClaim.mul(currentIndex);
}, [chainId, claim, currentIndex, balanceForShares]);
const closeConfirmationModal = () => { const closeConfirmationModal = () => {
setConfirmationModalOpen(false); setConfirmationModalOpen(false);
claimRefetch(); claimRefetch();
@ -82,6 +73,11 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
if (claim.shares === 0n) return <></>; if (claim.shares === 0n) return <></>;
const setIsPayoutGhstInner = (value) => {
setIsPayoutGhst(value);
localStorage.setitem("bond-isGhstPayout", value);
}
const warmupTooltip = `Your claim earns rebases during warmup. You can emergency withdraw, but this forfeits the rebases`; const warmupTooltip = `Your claim earns rebases during warmup. You can emergency withdraw, but this forfeits the rebases`;
return ( return (
@ -91,7 +87,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
onClose={() => closeConfirmationModal()} onClose={() => closeConfirmationModal()}
chainid={chainId} chainid={chainId}
receiver={address} receiver={address}
receiveAmount={claim.expiry > epoch.number ? claim.deposit : claimableBalance} receiveAmount={claim.expiry > epoch.number ? claim.deposit : isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol} outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
@ -110,6 +106,18 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
flexDirection={isSmallScreen ? "column" : "row"} flexDirection={isSmallScreen ? "column" : "row"}
> >
<Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography> <Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography>
<Tabs
centered
textColor="primary"
indicatorColor="primary"
value={isPayoutGhst ? 1 : 0}
aria-label="Claim token tabs"
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
</Tabs>
</Box> </Box>
} }
tooltip={warmupTooltip} tooltip={warmupTooltip}
@ -118,35 +126,33 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
{isSmallScreen ? ( {isSmallScreen ? (
<MobileClaimInfo <MobileClaimInfo
setConfirmationModalOpen={setConfirmationModalOpen} setConfirmationModalOpen={setConfirmationModalOpen}
prepareBalance={claimableBalance} prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
isPayoutGhst={isPayoutGhst} isPayoutGhst={isPayoutGhst}
claim={claim} claim={claim}
epoch={epoch} epoch={epoch}
isClaimable={claim.expiry > epoch.number} isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
/> />
) : ( ) : (
<Table> <Table>
<StyledTableHeader className={classes.stakePoolHeaderText}> <StyledTableHeader className={classes.stakePoolHeaderText}>
<TableRow> <TableRow>
<TableCell style={{ width: "200px", padding: "8px 0" }}>Asset</TableCell> <TableCell style={{ width: "200px", padding: "8px 0" }}>Asset</TableCell>
<TableCell style={{ width: "200px", padding: "8px 0" }}>Staked Amount</TableCell> <TableCell style={{ width: "200px", padding: "8px 0" }}>Amount</TableCell>
<TableCell style={{ width: "150px", padding: "8px 0" }}>Claimable In</TableCell> <TableCell style={{ width: "150px", padding: "8px 0" }}>Claimable In</TableCell>
<TableCell></TableCell> <TableCell></TableCell>
</TableRow> </TableRow>
</StyledTableHeader> </StyledTableHeader>
<ClaimInfo <ClaimInfo
setConfirmationModalOpen={setConfirmationModalOpen} setConfirmationModalOpen={setConfirmationModalOpen}
prepareBalance={claimableBalance} prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
isPayoutGhst={isPayoutGhst} isPayoutGhst={isPayoutGhst}
claim={claim} claim={claim}
epoch={epoch} epoch={epoch}
isClaimable={claim.expiry > epoch.number} isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol} stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
/> />
</Table> </Table>
@ -156,17 +162,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
); );
}; };
const ClaimInfo = ({ const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst, stnkSymbol, ghstSymbol }) => {
setConfirmationModalOpen,
prepareBalance,
claim,
epoch,
isClaimable,
isPayoutGhst,
stnkSymbol,
ghstSymbol,
tokenPrice
}) => {
return ( return (
<TableBody> <TableBody>
<TableRow> <TableRow>
@ -180,10 +176,7 @@ const ClaimInfo = ({
</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 }}>
{formatCurrency(prepareBalance, 5, isPayoutGhst ? ghstSymbol : stnkSymbol)} {`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? ghstSymbol : stnkSymbol}`}
</Typography>
<Typography variant="body2" gutterBottom={false} style={{ lineHeight: 1.4 }}>
{formatCurrency(prepareBalance * tokenPrice, 2)}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}> <TableCell style={{ padding: "8px 8px 8px 0" }}>
@ -200,17 +193,7 @@ const ClaimInfo = ({
); );
}; };
const MobileClaimInfo = ({ const MobileClaimInfo = ({ setConfirmationModalOpen, prepareBalance, epoch, claim, isPayoutGhst, isClaimable, ghstSymbol, stnkSymbol }) => {
setConfirmationModalOpen,
prepareBalance,
epoch,
claim,
isPayoutGhst,
isClaimable,
ghstSymbol,
stnkSymbol,
tokenPrice,
}) => {
return ( return (
<Box mt="10px"> <Box mt="10px">
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}> <Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
@ -221,15 +204,10 @@ const MobileClaimInfo = ({
</Box> </Box>
<DataRow <DataRow
title="Staked Amount" title="Amount"
balance={formatNumber(prepareBalance, 7)} balance={formatNumber(prepareBalance, 7)}
/> />
<DataRow
title="Price Estimation"
balance={formatCurrency(prepareBalance * tokenPrice, 2)}
/>
<DataRow <DataRow
title="Claimed in" title="Claimed in"
balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))} balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))}

View File

@ -9,7 +9,7 @@ import {
Box, Box,
useMediaQuery, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { useNavigate, useParams, createSearchParams } from "react-router-dom"; import { useNavigate, createSearchParams } from "react-router-dom";
import Paper from "../../../components/Paper/Paper"; import Paper from "../../../components/Paper/Paper";
import { SecondaryButton } from "../../../components/Button"; import { SecondaryButton } from "../../../components/Button";
@ -17,15 +17,15 @@ 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";
import { EMPTY_ADDRESS, FTSO_ADDRESSES } from "../../../constants/addresses"; import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
const FarmPools = ({ chainId }) => { const FarmPools = ({ chainId }) => {
const isSmallScreen = useMediaQuery("(max-width: 775px)"); const isSmallScreen = useMediaQuery("(max-width: 775px)");
const { network } = useParams();
const { totalSupply: reserveFtsoUniTotalSupply } = useTotalSupply(chainId, "RESERVE_FTSO"); const { totalSupply: reserveFtsoUniTotalSupply } = useTotalSupply(chainId, "RESERVE_FTSO");
const reserveFtsoUniValuation = useLpValuation(chainId, "RESERVE_FTSO", reserveFtsoUniTotalSupply._value); const reserveFtsoUniValuation = useLpValuation(chainId, "RESERVE_FTSO", reserveFtsoUniTotalSupply._value);
@ -35,14 +35,14 @@ const FarmPools = ({ chainId }) => {
const pools = [ const pools = [
{ {
icons: ["FTSO", tokenNameConverter(chainId, reserveSymbol)], icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
name: `${ftsoSymbol}-${reserveSymbol}`, name: `${ftsoSymbol}-${reserveSymbol}`,
dex: "Uniswap V2", dex: "Uniswap V2",
url: `/${network}/dex/uniswap`, url: "/dex/uniswap",
tvl: reserveFtsoUniValuation, tvl: reserveFtsoUniValuation,
params: createSearchParams({ params: createSearchParams({
pool: "true", pool: "true",
from: `${EMPTY_ADDRESS}`, from: `${RESERVE_ADDRESSES[chainId]}`,
to: `${FTSO_ADDRESSES[chainId]}`, to: `${FTSO_ADDRESSES[chainId]}`,
}) })
}, },

View File

@ -52,7 +52,7 @@ export const TotalDeposit = props => {
const _props = { const _props = {
...props, ...props,
label: "Total Deposit", label: "Total Deposit",
tooltip: `Total reserves of native coins, LP tokens, and other assets in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`, tooltip: `The total stablecoin reserves in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`,
}; };
if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`; if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`;

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, useCallback } from "react"; import React, { useEffect, useState } 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,7 +23,6 @@ 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";
@ -83,11 +82,20 @@ export const StakeInputArea = ({
const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false); const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false);
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false); const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const { getStorageValue, setStorageValue } = useLocalStorage(); const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("stake-decimals")
? localStorage.getItem("stake-decimals")
: "5"
);
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "stake-decimals", "5")); const [isClaim, setIsClaim] = useState(localStorage.getItem("stake-isClaim")
const [isClaim, setIsClaim] = useState(() => getStorageValue(chainId, address, "stake-isClaim", true)); ? localStorage.getItem("stake-isClaim") === 'true'
const [isTrigger, setIsTrigger] = useState(() => getStorageValue(chainId, address, "stake-isTrigger", true)); : 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("");
@ -102,22 +110,23 @@ 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 = useCallback((value) => { const setIsClaimInner = (value) => {
setIsClaim(value); setIsClaim(value);
setStorageValue(chainId, address, "stake-isClaim", value); localStorage.setItem("stake-isClaim", value);
}, [chainId, address]);
const setIsTriggerInner = useCallback((value) => { }
const setIsTriggerInner = (value) => {
setIsTrigger(value); setIsTrigger(value);
setStorageValue(chainId, address, "stake-isTrigger", value); localStorage.setItem("stake-isTrigger", value);
}, [chainId, address]); }
const setFormatDecimalsInner = useCallback((value) => { const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) { if (Number(value) <= 17) {
setFormatDecimals(value); setFormatDecimals(value);
setStorageValue(chainId, address, "stake-decimals", value); localStorage.setItem("staking-decimals", value);
}
} }
}, [chainId, address]);
useEffect(() => { useEffect(() => {
const innerBalance = upperToken === ghstSymbol ? const innerBalance = upperToken === ghstSymbol ?
@ -233,6 +242,36 @@ 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

@ -12,7 +12,6 @@ import {
CurrentIndex, CurrentIndex,
} from "./components/Metric"; } from "./components/Metric";
import TokenInfo from "./components/TokenInfo"; import TokenInfo from "./components/TokenInfo";
import ProtocolDetails from "./components/ProtocolDetails";
import Paper from "../../components/Paper/Paper"; import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle"; import PageTitle from "../../components/PageTitle/PageTitle";
@ -61,7 +60,7 @@ const MetricsDashboard = ({ chainId }) => {
</Paper> </Paper>
<Paper style={{ padding: "0px" }} fullWidth={true} > <Paper style={{ padding: "0px" }} fullWidth={true} >
<ProtocolDetails theme={theme} isMobileScreen={isMobileScreen} chainId={chainId} /> <TokenInfo isMobileScreen={isMobileScreen} chainId={chainId} />
</Paper> </Paper>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -84,7 +84,7 @@ export const FatsoBacking = props => {
const _props = { const _props = {
...props, ...props,
label: `Backing per ${props.ftsoSymbol}`, label: `Backing per ${props.ftsoSymbol}`,
tooltip: `The total amount of native coins, LP tokens, and other assets held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.` tooltip: `The total amount of stablecoins held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.`
}; };
if (backing) _props.metric = formatCurrency(backing, 2); if (backing) _props.metric = formatCurrency(backing, 2);

View File

@ -1,137 +0,0 @@
import { useMemo } from "react";
import { Grid, Box, Typography } from "@mui/material";
import { useConfig } from "wagmi";
import { useNavigate, createSearchParams } from "react-router-dom";
import { formatNumber } from "../../../helpers";
import { useEpoch, useGatekeeperApy } from "../../../hooks/staking";
import { useTokenSymbol, useBalance, useCirculatingSupply } from "../../../hooks/tokens";
import { SecondaryButton } from "../../../components/Button";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { GATEKEEPER_ADDRESSES, EMPTY_ADDRESS } from "../../../constants/addresses";
const ProtocolDetail = ({ isMobileScreen, theme, name, sideName, url, urlParams, description }) => {
const navigate = useNavigate();
return (
<Box position="relative" width={`${isMobileScreen ? "100%" : "48%"}`}>
<Box
borderRadius="9px"
padding="12px"
sx={{ backgroundColor: theme.colors.paper.card }}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box display="flex" gap={"3px"} alignItems="center" justifyContent="space-between">
<Typography fontSize="20px" fontWeight="bold" lineHeight="33px">
{name}
</Typography>
<Typography color={theme.colors.primary[300]} fontSize="20px" fontWeight="bold" lineHeight="33px">
{sideName}
</Typography>
</Box>
<>
<Box my="18px">
<Typography color={theme.colors.gray[40]} mt="9px">
{description}
</Typography>
</Box>
<Box display="flex" justifyContent="center" width="100%">
<SecondaryButton
onClick={() => urlParams
? navigate({
pathname: url,
search: urlParams.toString()
})
: navigate(url, { replace: true })
}
fullWidth
>
{name}
</SecondaryButton>
</Box>
</>
</Box>
</Box>
)
}
const ProtocolDetails = ({ chainId, isMobileScreen, theme, }) => {
const config = useConfig();
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const networkName = config?.getClient()?.chain?.name;
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: csprSymbol } = useTokenSymbol(chainId, "CSPR");
const { contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", EMPTY_ADDRESS);
const circulatingSupply = useCirculatingSupply(chainId, "STNK");
const gatekeepedApy = useGatekeeperApy(chainId);
const { epoch } = useEpoch(chainId);
const apyInner = useMemo(() => {
let apy = Infinity;
if (circulatingSupply._value > 0n) {
const value = epoch.distribute.div(circulatingSupply);
apy = 100 * (Math.pow(1 + parseFloat(value.toString()), 1095) - 1);
if (apy === 0) apy = Infinity;
}
return apy;
}, [circulatingSupply, epoch]);
const bridgeNumbers = useMemo(() => {
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
const number = 1 + connectedNetworks * 3;
return `(${number}, ${number})`;
}, [chainId]);
return (
<Grid container spacing={0} justifyContent={"center"}>
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/dex/uniswap`}
urlParams={createSearchParams({
from: EMPTY_ADDRESS,
to: `${ftsoAddress}`,
})}
name="(3, 3) Swap"
sideName="Unlock Magic"
description={`Buying strategy expands bond capacity triggering a positive loop to strengthen the Treasury and attract more stakers. Swap ${nativeSymbol} for ${ftsoSymbol} to unlock (3, 3) Stake.`}
/>
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/bonds`}
name="(1, 1) Bond"
sideName="Up to 40% Discount"
description={`Bonding strategy grows Treasury and builds Protocol Owned Liquidity (POL) by allowing usersto acquire ${csprSymbol} at a discount. Take advantage of the next available bond.`}
/>
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/stake`}
name="(3, 3) Stake"
sideName={`${formatNumber(apyInner, 0)}% APY`}
description={`Staking enables (3, 3) coordination by aligning long-term incentives, sustainably backed (1, 1) Bonds, LP fees, and Farming. Stake ${ftsoSymbol} to earn rewards and unlock ${bridgeNumbers} Stake\u00B2.`}
/>
<ProtocolDetail
isMobileScreen={isMobileScreen}
theme={theme}
url={`/${networkName.toLowerCase()}/bridge`}
name={`${bridgeNumbers} Stake\u00B2`}
sideName={`${formatNumber(apyInner * gatekeepedApy, 0)}% APY`}
description={`Staking\u00B2 strategy further deepens long-term incentives powered by sustainable cross-chain bridging revenue. ${bridgeNumbers} Stake\u00B2 your ${csprSymbol} to receive organic APY with no warmup and dillution.`}
/>
</Box>
</Grid>
)
}
export default ProtocolDetails;

View File

@ -1,23 +1,23 @@
import { Grid, Box, Typography, useTheme } from "@mui/material"; import { Grid, Box, Typography, useTheme } from "@mui/material";
import { useAccount, useConfig, useBalance as useBalanceNative } from "wagmi"; import { useAccount, useConfig, useBalance as useBalanceNative } from "wagmi";
import { useNavigate, useParams, createSearchParams } from "react-router-dom"; import { useNavigate, createSearchParams } from "react-router-dom";
import Token from "../../../components/Token/Token"; 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 {
useFtsoPrice, useFtsoPrice,
useStnkPrice,
useGhstPrice, useGhstPrice,
useReservePrice, useReservePrice,
useNativePrice, useNativePrice,
} from "../../../hooks/prices"; } from "../../../hooks/prices";
import { tokenNameConverter } from "../../../helpers/tokenConverter"; import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { EMPTY_ADDRESS, WETH_ADDRESSES } from "../../../constants/addresses";
const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => { const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const actualBalance = balance ? balance : new DecimalBigNumber(0, 0); const actualBalance = balance ? balance : new DecimalBigNumber(0, 0);
@ -58,13 +58,10 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
<Box display="flex" justifyContent="center" width="100%"> <Box display="flex" justifyContent="center" width="100%">
<SecondaryButton <SecondaryButton
onClick={() => tokenUrlParams onClick={() => navigate({
? navigate({
pathname: tokenUrl, pathname: tokenUrl,
search: tokenUrlParams.toString() search: tokenUrlParams.toString()
}) })}
: window.open(tokenUrl, '_blank')
}
fullWidth fullWidth
> >
Get {tokenName} Get {tokenName}
@ -78,7 +75,6 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
const TokenInfo = ({ chainId, isMobileScreen }) => { const TokenInfo = ({ chainId, isMobileScreen }) => {
const theme = useTheme(); const theme = useTheme();
const { network } = useParams();
const { address } = useAccount(); const { address } = useAccount();
const config = useConfig(); const config = useConfig();
@ -87,15 +83,18 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
const nativePrice = useNativePrice(chainId); const nativePrice = useNativePrice(chainId);
const ftsoPrice = useFtsoPrice(chainId); const ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(chainId); const ghstPrice = useGhstPrice(chainId);
const reservePrice = useReservePrice(chainId); const reservePrice = useReservePrice(chainId);
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE"); const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO"); const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST"); const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { data: nativeBalance } = useBalanceNative({ address }); const { data: nativeBalance } = useBalanceNative({ address });
const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", address); const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", address);
const { balance: stnkBalance, contractAddress: stnkAddress } = useBalance(chainId, "STNK", address);
const { balance: ghstBalance, contractAddress: ghstAddress } = useBalance(chainId, "GHST", address); const { balance: ghstBalance, contractAddress: ghstAddress } = useBalance(chainId, "GHST", address);
const { balance: reserveBalance, contractAddress: reserveAddress } = useBalance(chainId, "RESERVE", address); const { balance: reserveBalance, contractAddress: reserveAddress } = useBalance(chainId, "RESERVE", address);
@ -104,7 +103,7 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px"> <Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
<TokenTab <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`} tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({ tokenUrlParams={createSearchParams({
from: `${reserveAddress}`, from: `${reserveAddress}`,
to: `${ftsoAddress}`, to: `${ftsoAddress}`,
@ -117,7 +116,20 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
/> />
<TokenTab <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`} tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({
from: `${reserveAddress}`,
to: `${stnkAddress}`,
})}
theme={theme}
tokenName={stnkSymbol}
balance={stnkBalance}
price={stnkPrice}
description={`${stnkSymbol} is a receipt for staked ${ftsoSymbol}, growing with staking rewards. When unstaked, its burned for ${ftsoSymbol} at a 1:1 ratio.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({ tokenUrlParams={createSearchParams({
from: `${reserveAddress}`, from: `${reserveAddress}`,
to: `${ghstAddress}`, to: `${ghstAddress}`,
@ -126,31 +138,36 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
tokenName={ghstSymbol} tokenName={ghstSymbol}
balance={ghstBalance} balance={ghstBalance}
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} enables ghostDAO to have on-chain governance and to be truly cross-chain. ${ghstSymbol} Price = ${ftsoSymbol} Price x Current Index.`}
/> />
<TokenTab <TokenTab
isMobileScreen={isMobileScreen} isMobileScreen={isMobileScreen}
tokenUrl={"https://ghostchain.io/faucet/"} tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
tokenUrlParams=""
theme={theme}
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}.`
}
/>
</Box>
{!isNetworkLegacy(chainId) && (
<Box width="100%" mt="25px">
<TokenTab
isMobileScreen={true}
tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
tokenUrlParams=""
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
isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`}
tokenUrlParams={createSearchParams({
from: `${EMPTY_ADDRESS}`,
to: `${WETH_ADDRESSES[chainId]}`,
})}
theme={theme}
tokenName={reserveSymbol}
balance={reserveBalance}
price={reservePrice}
description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`}
/>
</Box> </Box>
)}
</Grid> </Grid>
) )
} }

View File

@ -52,16 +52,3 @@ export const timeConverter = (time, max = 7200, maxText = "long ago") => {
return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`; return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`;
} }
} }
export const bigIntSqrt = (n) => {
if (n < 0n) return 0n;
if (n < 2n) return n;
let x = n / 2n + 1n;
let y = (x + n / x) / 2n;
while (y < x) {
x = y;
y = (x + n / x) / 2n;
}
return x;
}

View File

@ -1,11 +1,9 @@
export const tokenNameConverter = (chainId, name, address) => { export const tokenNameConverter = (chainId, name) => {
if (name?.toUpperCase() === "WETH") { if (name?.toUpperCase() === "WETH") {
switch (chainId) { switch (chainId) {
case 63: case 63:
name = "wmETC" name = "wmETC"
break; break;
default:
name = "wETH";
} }
} }
return name; return name;

View File

@ -2,7 +2,6 @@ import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core"; import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { isNetworkLegacyType } from "../../constants";
import { config } from "../../config"; import { config } from "../../config";
import { import {
@ -14,18 +13,14 @@ import { abi as BondAbi } from "../../abi/GhostBondDepository.json";
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json"; import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json"; import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json";
import { useReservePrice, useFtsoPrice } from "../prices"; import { useFtsoPrice } from "../prices";
import { useOrinalCoefficient } from "../treasury";
import { useTokenSymbol, useTokenSymbols } from "../tokens"; import { useTokenSymbol, useTokenSymbols } from "../tokens";
import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers"; import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers"; import { shorten } from "../../helpers";
import { tokenNameConverter } from "../../helpers/tokenConverter";
export const useLiveBonds = (chainId, chainName) => { export const useLiveBonds = (chainId) => {
const ftsoPrice = useFtsoPrice(chainId); const baseTokenPerUsd = useFtsoPrice(chainId);
const baseTokenPerUsd = useReservePrice(chainId);
const originalCoefficient = useOrinalCoefficient(chainId);
const { data: liveIndexesRaw, refetch } = useReadContract({ const { data: liveIndexesRaw, refetch } = useReadContract({
abi: BondAbi, abi: BondAbi,
@ -131,11 +126,10 @@ export const useLiveBonds = (chainId, chainName) => {
const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : ""; const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";
const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9); const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9);
const priceInUsd = quoteTokenPerBaseToken.mul(baseTokenPerUsd).mul(quoteTokenPerUsd).mul(markdown).div(originalCoefficient); const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken).mul(markdown);
const discount = baseTokenPerUsd._value > 0n
const discount = ftsoPrice._value > 0n ? baseTokenPerUsd.sub(priceInUsd).div(baseTokenPerUsd)
? ftsoPrice.sub(priceInUsd).div(ftsoPrice) : new DecimalBigNumber("0", baseTokenPerUsd._decimals);
: new DecimalBigNumber("0", ftsoPrice._decimals);
const capacityInBaseToken = capacityInQuote const capacityInBaseToken = capacityInQuote
? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9) ? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9)
@ -151,20 +145,21 @@ export const useLiveBonds = (chainId, chainName) => {
); );
const zero = new DecimalBigNumber(0n, 0); const zero = new DecimalBigNumber(0n, 0);
const bondName = `${baseTokenSymbol}/${quoteTokenSymbol}`;
return { return {
id, id,
discount, discount,
displayName: getBondNameDisplayName(chainId, quoteTokenAddress, baseTokenSymbol), displayName: getBondNameDisplayName(chainId, bondName, quoteTokenAddress),
baseToken: { baseToken: {
name: baseTokenSymbol, name: baseTokenSymbol,
purchaseUrl: getTokenPurchaseLink(chainId, "", chainName), purchaseUrl: getTokenPurchaseLink(chainId, ""),
icons: ["FTSO"], icons: ["FTSO"],
tokenAddress: getTokenAddress(chainId, "FTSO") tokenAddress: getTokenAddress(chainId, "FTSO")
}, },
quoteToken: { quoteToken: {
name: tokenNameConverter(chainId, quoteTokenSymbol), name: quoteTokenSymbol,
purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress, chainName), purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress),
icons: getTokenIcons(chainId, quoteTokenAddress), icons: getTokenIcons(chainId, quoteTokenAddress),
decimals: quoteTokenDecimals, decimals: quoteTokenDecimals,
quoteTokenAddress, quoteTokenAddress,
@ -172,11 +167,11 @@ export const useLiveBonds = (chainId, chainName) => {
duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0, duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0,
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0, vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true, isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true,
isSoldOut: capacityInBaseToken.eq(zero), isSoldOut: capacityInBaseToken.eq(zero) || capacityInBaseToken.eq(zero),
price: { price: {
inUsd: priceInUsd, inUsd: priceInUsd,
inBaseToken: quoteTokenPerBaseToken, inBaseToken: quoteTokenPerBaseToken,
marketPriceInUsd: ftsoPrice marketPriceInUsd: baseTokenPerUsd
}, },
capacity: { capacity: {
inBaseToken: capacityInBaseToken, inBaseToken: capacityInBaseToken,
@ -250,7 +245,7 @@ export const useNotes = (chainId, address) => {
return { return {
id, id,
quoteToken: { quoteToken: {
name: tokenNameConverter(chainId, quoteTokenSymbol), name: quoteTokenSymbol,
icons: getTokenIcons(chainId, quoteTokenAddress), icons: getTokenIcons(chainId, quoteTokenAddress),
}, },
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0, vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
@ -266,7 +261,7 @@ export const useNotes = (chainId, address) => {
return { notes, refetch }; return { notes, refetch };
} }
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => { export const purchaseBond = async (chainId, bondId, amount, maxPrice, user, sender, referral) => {
const args = [ const args = [
bondId, bondId,
amount, amount,
@ -284,12 +279,11 @@ export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, se
"deposit", "deposit",
args, args,
sender, sender,
messages, messages
isNative ? amount : undefined
); );
} }
export const redeem = async ({ chainId, user, isGhst, indexes }) => { export const redeem = async (chainId, user, isGhst, indexes) => {
const args = [ const args = [
user, user,
isGhst, isGhst,
@ -314,8 +308,7 @@ const executeOnChainTransaction = async (
functionName, functionName,
args, args,
account, account,
messages, messages
value
) => { ) => {
try { try {
const { request } = await simulateContract(config, { const { request } = await simulateContract(config, {
@ -324,9 +317,7 @@ const executeOnChainTransaction = async (
functionName, functionName,
args, args,
account, account,
chainId, chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
value: value,
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -8,24 +8,15 @@ import { useUnstableProvider } from "./UnstableProvider"
const MetadataProvider = createContext(null) const MetadataProvider = createContext(null)
export const useMetadata = () => useContext(MetadataProvider) export const useMetadata = () => useContext(MetadataProvider)
const CACHE_VERSION = "v2"
export const MetadataProviderProvider = ({ children }) => { export const MetadataProviderProvider = ({ children }) => {
const { client, chainId } = useUnstableProvider() const { client, chainId } = useUnstableProvider()
const { data: metadata } = useSWR( const { data: metadata } = useSWR(
client && chainId ? ["metadata", client, chainId] : null, client && chainId ? ["metadata", client, chainId] : null,
async ([_, client]) => { async ([_, client]) => {
const storageKey = `metadata-${chainId}-${CACHE_VERSION}` const storageKey = `metadata-${chainId}`
const storedMetadata = sessionStorage.getItem(storageKey) const storedMetadata = sessionStorage.getItem(storageKey)
if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata)) if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata))
Object.keys(sessionStorage).forEach(key => {
if (key.startsWith("metadata-")) {
sessionStorage.removeItem(key);
}
});
const metadata = await new Promise((resolve, reject) => const metadata = await new Promise((resolve, reject) =>
client._request("state_getMetadata", [], { client._request("state_getMetadata", [], {
onSuccess: resolve, onSuccess: resolve,

View File

@ -1,19 +1,14 @@
import { createContext, useEffect, useContext, useState, useMemo, useCallback, useRef } from "react" import { createContext, useContext, useState, useMemo } from "react"
import { Unstable } from "@substrate/connect-discovery" import { Unstable } from "@substrate/connect-discovery"
import { createClient } from "@polkadot-api/substrate-client" import { createClient } from "@polkadot-api/substrate-client"
import { getObservableClient } from "@polkadot-api/observable-client" import { getObservableClient } from "@polkadot-api/observable-client"
import useSWR from "swr" import useSWR from "swr"
const MAX_BLOCK_TIMEOUT = 15000
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3" const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
const UnstableProvider = createContext(null) const UnstableProvider = createContext(null)
export const useUnstableProvider = () => useContext(UnstableProvider) export const useUnstableProvider = () => useContext(UnstableProvider)
export const UnstableProviderProvider = ({ children }) => { export const UnstableProviderProvider = ({ children }) => {
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
const [isConnected, setIsConnected] = useState(false);
const [reconnectTicket, setReconnectTicket] = useState(0);
const { data: providerDetails } = useSWR("getGhostProviders", () => const { data: providerDetails } = useSWR("getGhostProviders", () =>
Unstable.getSubstrateConnectExtensionProviders() Unstable.getSubstrateConnectExtensionProviders()
); );
@ -24,66 +19,36 @@ export const UnstableProviderProvider = ({ children }) => {
() => providerDetail ? providerDetail.provider : null () => providerDetail ? providerDetail.provider : null
); );
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
const client = useMemo(() => { const client = useMemo(() => {
if (!provider || !chainId) return undefined; if (!provider || !chainId) return undefined;
const chain = provider.getChains()[chainId]; const chain = provider.getChains()[chainId];
if (!chain) return undefined; if (!chain) return undefined;
return createClient(chain.connect);
}, [provider, chainId]);
return createClient(chain.connect) const observableClient = useMemo(() => {
}, [provider, chainId, reconnectTicket]); return client ? getObservableClient(client) : undefined;
}, [client]);
const observableClient = useMemo(() => client ? getObservableClient(client) : undefined, [client]); const chainHead$ = useMemo(() => {
const chainHead$ = useMemo(() => observableClient?.chainHead$(), [observableClient]); return observableClient ? observableClient.chainHead$() : undefined;
}, [observableClient]);
const lastBlockNumber = useRef(0); return (
<UnstableProvider.Provider
useEffect(() => { value={{
if (!chainHead$) return;
lastBlockNumber.current = 0;
let timeoutId;
const sub = chainHead$.bestBlocks$.subscribe({
next: (blocks) => {
const currentHeight = blocks.at(0)?.number ?? -1;
if (currentHeight > lastBlockNumber.current) {
lastBlockNumber.current = currentHeight;
setIsConnected(true);
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
setIsConnected(false);
setReconnectTicket(t => t + 1);
}, MAX_BLOCK_TIMEOUT);
}
},
error: (err) => {
setIsConnected(false);
setTimeout(() => setReconnectTicket(t => t + 1), 1000);
}
});
return () => {
sub.unsubscribe();
clearTimeout(timeoutId);
};
}, [chainHead$]);
const value = useMemo(() => ({
isConnected,
providerDetails, providerDetails,
providerDetail, providerDetail,
connectProviderDetail: setProviderDetail, connectProviderDetail: setProviderDetail,
provider,
chainId, chainId,
client, client,
setChainId, setChainId,
chainHead$ chainHead$
}), [isConnected, providerDetails, providerDetail, chainId, client, chainHead$]); }}
>
return (
<UnstableProvider.Provider value={value}>
{children} {children}
</UnstableProvider.Provider> </UnstableProvider.Provider>
); );

View File

@ -12,5 +12,3 @@ export * from "./useBlockCommitments";
export * from "./useApplauseDetails"; export * from "./useApplauseDetails";
export * from "./useBabeSlots"; export * from "./useBabeSlots";
export * from "./useErasTotalStaked"; export * from "./useErasTotalStaked";
export * from "./useLatestBlockNumber";
export * from "./useEraIndex";

View File

@ -1,41 +0,0 @@
import useSWRSubscription from "swr/subscription"
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
import { distinct, filter, map, mergeMap } from "rxjs"
import { useUnstableProvider } from "./UnstableProvider"
import { useMetadata } from "./MetadataProvider"
export const useEraIndex = () => {
const { chainHead$, chainId } = useUnstableProvider()
const metadata = useMetadata()
const { data: eraIndex } = useSWRSubscription(
chainHead$ && chainId && metadata
? ["eraIndex", chainHead$, chainId, metadata]
: null,
([_, chainHead$, chainId, metadata], { next }) => {
const { finalized$, storage$ } = chainHead$
const subscription = finalized$.pipe(
filter(Boolean),
mergeMap((blockInfo) => {
const builder = getDynamicBuilder(getLookupFn(metadata))
const eraIndex = builder.buildStorage("Staking", "ActiveEra")
return storage$(blockInfo?.hash, "value", () =>
eraIndex?.keys.enc()
).pipe(
filter(Boolean),
distinct(),
map((value) => eraIndex?.value.dec(value))
)
}),
)
.subscribe({
next(eraIndex) {
next(null, eraIndex)
},
error: next,
})
return () => subscription.unsubscribe()
}
)
return eraIndex;
}

View File

@ -5,14 +5,14 @@ import { distinct, filter, map, mergeMap } from "rxjs"
import { useUnstableProvider } from "./UnstableProvider" import { useUnstableProvider } from "./UnstableProvider"
import { useMetadata } from "./MetadataProvider" import { useMetadata } from "./MetadataProvider"
export const useErasTotalStake = ({ epochIndex }) => { export const useErasTotalStake = ({ eraIndex }) => {
const { chainHead$, chainId } = useUnstableProvider() const { chainHead$, chainId } = useUnstableProvider()
const metadata = useMetadata() const metadata = useMetadata()
const { data: eraTotalStake } = useSWRSubscription( const { data: eraTotalStake } = useSWRSubscription(
chainHead$ && chainId && metadata chainHead$ && chainId && metadata
? ["eraTotalStake", chainHead$, epochIndex, chainId, metadata] ? ["eraTotalStake", chainHead$, eraIndex, chainId, metadata]
: null, : null,
([_, chainHead$, epochIndex, chainId, metadata], { next }) => { ([_, chainHead$, eraIndex, chainId, metadata], { next }) => {
const { finalized$, storage$ } = chainHead$ const { finalized$, storage$ } = chainHead$
const subscription = finalized$.pipe( const subscription = finalized$.pipe(
filter(Boolean), filter(Boolean),
@ -20,7 +20,7 @@ export const useErasTotalStake = ({ epochIndex }) => {
const builder = getDynamicBuilder(getLookupFn(metadata)) const builder = getDynamicBuilder(getLookupFn(metadata))
const eraTotalStake = builder.buildStorage("Staking", "ErasTotalStake") const eraTotalStake = builder.buildStorage("Staking", "ErasTotalStake")
return storage$(blockInfo?.hash, "value", () => return storage$(blockInfo?.hash, "value", () =>
eraTotalStake?.keys.enc(epochIndex) eraTotalStake?.keys.enc(eraIndex)
).pipe( ).pipe(
filter(Boolean), filter(Boolean),
distinct(), distinct(),

View File

@ -1,18 +0,0 @@
import { useState, useEffect } from "react";
import useSWRSubscription from "swr/subscription"
import { useUnstableProvider } from "./UnstableProvider"
export const useLatestBlockNumber = () => {
const { chainHead$ } = useUnstableProvider();
const [blockNumber, setBlockNumber] = useState(null);
useEffect(() => {
if (!chainHead$) return;
const subscription = chainHead$.best$.subscribe((block) => {
setBlockNumber(block.number);
});
return () => subscription.unsubscribe();
}, [chainHead$]);
return blockNumber;
}

View File

@ -1,10 +1,8 @@
import { useMemo } from "react";
import { useReadContract, useReadContracts } from "wagmi"; import { useReadContract, useReadContracts } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core"; import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { keccak256, stringToBytes } from 'viem' import { keccak256, stringToBytes } from 'viem'
import { isNetworkLegacyType } from "../../constants";
import { config } from "../../config"; import { config } from "../../config";
import { import {
@ -18,23 +16,6 @@ import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQu
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenDecimals, getTokenAbi, getTokenAddress } from "../helpers"; import { getTokenDecimals, getTokenAbi, getTokenAddress } from "../helpers";
export const getVoteValue = (forVotes, totalVotes) => {
if (totalVotes == 0n) return 0;
const value = forVotes * 100n / totalVotes;
return Math.floor(Number(value.toString()));
}
export const getVoteTarget = (totalVotes, totalSupply) => {
if (totalSupply == 0n) return 80;
const precision = 10n ** 3n;
const valueRaw = (totalVotes * precision) / totalSupply;
const value = Number(valueRaw) / Number(precision);
const result = (5 - Math.sqrt(1 + 80/9 * (value - 0.1) )) / 4;
return Math.floor(result * 100);
}
export const useProposalVoteOf = (chainId, proposalId, who) => { export const useProposalVoteOf = (chainId, proposalId, who) => {
const { data, error } = useReadContract({ const { data, error } = useReadContract({
abi: GovernorAbi, abi: GovernorAbi,
@ -115,7 +96,6 @@ export const useMinQuorum = (chainId) => {
export const useProposalThreshold = (chainId, name) => { export const useProposalThreshold = (chainId, name) => {
const decimals = getTokenDecimals(name); const decimals = getTokenDecimals(name);
const { proposalCount } = useProposalCount(chainId);
const { data } = useReadContract({ const { data } = useReadContract({
abi: GovernorStorageAbi, abi: GovernorStorageAbi,
@ -133,17 +113,13 @@ export const useProposalThreshold = (chainId, name) => {
chainId: chainId, chainId: chainId,
}); });
const lastIndex = proposalCount === 0n ? 0n : proposalCount - 1n;
const { proposalId } = useProposalDetailsAt(chainId, lastIndex, {
enabled: proposalCount !== 0n
});
const { state } = useProposalState(chainId, proposalId, {
enabled: proposalCount !== 0n && !!proposalId
});
let threshold = new DecimalBigNumber(data ?? 0n, decimals); let threshold = new DecimalBigNumber(data ?? 0n, decimals);
if (proposalCount !== 0n && state !== undefined && state < 2) { const { proposalCount } = useProposalCount(chainId);
const { proposalId } = useProposalDetailsAt(chainId, proposalCount === 0n ? 0n : proposalCount - 1n);
const { state } = useProposalState(chainId, proposalId);
if (state < 2) {
threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals); threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals);
} }
@ -344,22 +320,22 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: proposalDetails } = useReadContracts({ const { data: proposalDetails } = useReadContracts({
contracts: searchedIndexes?.map(proposalId => { contracts: searchedIndexes?.map(index => {
return { return {
abi: GovernorStorageAbi, abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId], address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails", functionName: "proposalDetails",
args: [proposalId], args: [index],
scopeKey: `proposalDetails-${chainId}-${proposalId}`, scopeKey: `proposalDetails-${chainId}-${index}`,
chainId: chainId, chainId: chainId,
} }
}) })
}); });
const { data: proposalDeadlines } = useReadContracts({ const { data: proposalDeadlines } = useReadContracts({
contracts: indexes?.map((_, index) => { contracts: indexes?.map(index => {
const proposalId = searchedIndexes const proposalId = searchedIndexes
? searchedIndexes?.at(index) ? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0); : proposalsDetailsAt?.at(index)?.result?.at(0);
return { return {
@ -374,9 +350,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: proposalVotes } = useReadContracts({ const { data: proposalVotes } = useReadContracts({
contracts: indexes?.map((_, index) => { contracts: indexes?.map(index => {
const proposalId = searchedIndexes const proposalId = searchedIndexes
? searchedIndexes?.at(index) ? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0); : proposalsDetailsAt?.at(index)?.result?.at(0);
return { return {
@ -391,9 +367,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: proposalStates } = useReadContracts({ const { data: proposalStates } = useReadContracts({
contracts: indexes?.map((_, index) => { contracts: indexes?.map(index => {
const proposalId = searchedIndexes const proposalId = searchedIndexes
? searchedIndexes?.at(index) ? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0); : proposalsDetailsAt?.at(index)?.result?.at(0);
return { return {
@ -408,9 +384,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: proposalSnapshots } = useReadContracts({ const { data: proposalSnapshots } = useReadContracts({
contracts: indexes?.map((_, index) => { contracts: indexes?.map(index => {
const proposalId = searchedIndexes const proposalId = searchedIndexes
? searchedIndexes?.at(index) ? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0); : proposalsDetailsAt?.at(index)?.result?.at(0);
return { return {
@ -425,8 +401,8 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: proposalQuorums } = useReadContracts({ const { data: proposalQuorums } = useReadContracts({
contracts: proposalSnapshots?.map((proposal, index) => { contracts: indexes?.map(index => {
const timepoint = proposal?.result; const timepoint = proposalSnapshots?.at(index)?.result;
return { return {
abi: GovernorAbi, abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId], address: GHOST_GOVERNANCE_ADDRESSES[chainId],
@ -439,8 +415,8 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: pastTotalSupplies } = useReadContracts({ const { data: pastTotalSupplies } = useReadContracts({
contracts: proposalSnapshots?.map((proposal, index) => { contracts: indexes?.map(index => {
const timepoint = proposal?.result; const timepoint = proposalSnapshots?.at(index)?.result;
return { return {
abi: ghstAbi, abi: ghstAbi,
address: ghstAddress, address: ghstAddress,
@ -453,9 +429,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}); });
const { data: proposalProposer } = useReadContracts({ const { data: proposalProposer } = useReadContracts({
contracts: indexes?.map((_, index) => { contracts: indexes?.map(index => {
const proposalId = searchedIndexes const proposalId = searchedIndexes
? searchedIndexes?.at(index) ? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0); : proposalsDetailsAt?.at(index)?.result?.at(0);
return { return {
@ -469,11 +445,10 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
}) })
}); });
const hashes = useMemo(() => { const hashes = indexes?.map(index => {
return indexes?.map((_, index) => {
let result = { short: index + 1, full: undefined }; let result = { short: index + 1, full: undefined };
const proposalId = searchedIndexes const proposalId = searchedIndexes
? searchedIndexes?.at(index) ? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0); : proposalsDetailsAt?.at(index)?.result?.at(0);
if (proposalId) { if (proposalId) {
@ -483,55 +458,19 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
} }
return result; return result;
}); });
}, [indexes, searchedIndexes, proposalsDetailsAt]);
const voteValues = useMemo(() => { const proposals = indexes?.map(index => ({
return indexes?.map((_, idx) => { hashes: hashes?.at(index),
const index = indexes.length - idx - 1;
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
const totalVotes = againstVotes + forVotes;
return getVoteValue(forVotes, totalVotes);
}) ?? [];
}, [indexes, proposalVotes]);
const voteTargets = useMemo(() => {
return indexes?.map((_, idx) => {
const index = indexes.length - idx - 1;
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
const totalSupply = pastTotalSupplies?.at(index)?.result ?? 0n;
const totalVotes = againstVotes + forVotes;
return getVoteTarget(totalVotes, totalSupply);
}) ?? [];
}, [indexes, proposalVotes, pastTotalSupplies]);
const proposalDetailsPrepared = useMemo(() => {
if (!searchedIndexes) return proposalsDetailsAt;
return proposalDetails?.map((obj, index) => {
if (!obj?.result) return obj;
const proposalId = searchedIndexes.at(index);
return {
...obj,
result: [proposalId, ...obj.result],
};
}) ?? [];
}, [searchedIndexes, proposalsDetailsAt, proposalDetails]);
const proposals = indexes?.map((_, index) => ({
hashes: hashes?.at(index) ?? { short: "", full: "" },
proposer: proposalProposer?.at(index)?.result, proposer: proposalProposer?.at(index)?.result,
details: proposalDetailsPrepared?.at(index)?.result, details: proposalsDetailsAt?.at(index)?.result,
deadline: proposalDeadlines?.at(index)?.result ?? 0n, deadline: proposalDeadlines?.at(index)?.result ?? 0n,
snapshot: proposalSnapshots?.at(index)?.result ?? 0n,
state: proposalStates?.at(index)?.result ?? 0, state: proposalStates?.at(index)?.result ?? 0,
pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.at(index)?.result ?? 0n, decimals), pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.at(index)?.result ?? 0n, decimals),
quorum: new DecimalBigNumber(proposalQuorums?.at(index)?.result ?? 0n, decimals), quorum: new DecimalBigNumber(proposalQuorums?.at(index)?.result ?? 0n, decimals),
votes: proposalVotes?.at(index)?.result?.map(vote => new DecimalBigNumber(vote ?? 0n, decimals)), snapshot: new DecimalBigNumber(proposalSnapshots?.at(index)?.result ?? 0n, decimals),
voteValue: voteValues?.at(index), votes: proposalVotes?.at(index)?.result?.map(
voteTarget: voteTargets?.at(index), vote => new DecimalBigNumber(vote ?? 0n, decimals),
),
})); }));
return { proposals }; return { proposals };
@ -545,8 +484,7 @@ export const releaseLocked = async (chainId, account, proposalId) => {
functionName: 'releaseLocked', functionName: 'releaseLocked',
args: [proposalId], args: [proposalId],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -573,8 +511,7 @@ export const executeProposal = async (chainId, account, proposalId) => {
functionName: 'execute', functionName: 'execute',
args: [proposalId], args: [proposalId],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -601,8 +538,7 @@ export const castVote = async (chainId, account, proposalId, support) => {
functionName: 'castVote', functionName: 'castVote',
args: [proposalId, support], args: [proposalId, support],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -633,8 +569,7 @@ export const propose = async (chainId, account, functions, description) => {
functionName: 'propose', functionName: 'propose',
args: [targets, values, calldatas, description], args: [targets, values, calldatas, description],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -7,8 +7,6 @@ import {
WETH_ADDRESSES, WETH_ADDRESSES,
} from "../constants/addresses"; } from "../constants/addresses";
import { tokenNameConverter } from "../helpers/tokenConverter";
import { abi as DaiAbi } from "../abi/Reserve.json"; import { abi as DaiAbi } from "../abi/Reserve.json";
import { abi as FatsoAbi } from "../abi/Fatso.json"; import { abi as FatsoAbi } from "../abi/Fatso.json";
import { abi as StinkyAbi } from "../abi/Stinky.json"; import { abi as StinkyAbi } from "../abi/Stinky.json";
@ -144,12 +142,6 @@ export const getTokenAddress = (chainId, name) => {
case "WMETC": case "WMETC":
address = WETH_ADDRESSES[chainId]; address = WETH_ADDRESSES[chainId];
break; break;
case "ETH":
address = undefined;
break;
case "METC":
address = undefined;
break;
} }
return address; return address;
} }
@ -158,7 +150,7 @@ export const getTokenIcons = (chainId, address) => {
let icons = [""]; let icons = [""];
switch (address) { switch (address) {
case RESERVE_ADDRESSES[chainId]: case RESERVE_ADDRESSES[chainId]:
icons = [tokenNameConverter(chainId, "WETH")]; icons = ["WETH"];
break; break;
case FTSO_ADDRESSES[chainId]: case FTSO_ADDRESSES[chainId]:
icons = ["FTSO"]; icons = ["FTSO"];
@ -172,27 +164,24 @@ export const getTokenIcons = (chainId, address) => {
case FTSO_DAI_LP_ADDRESSES[chainId]: case FTSO_DAI_LP_ADDRESSES[chainId]:
icons = ["FTSO", "WETH"]; icons = ["FTSO", "WETH"];
break; break;
default:
icons = [""]
} }
return icons; return icons;
} }
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => { export const getBondNameDisplayName = (chainId, stringValue, tokenAddress) => {
let stringValue = tokenNameConverter(chainId, "WETH")
if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) { if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
stringValue = `${baseTokenSymbol}-${stringValue} LP`; stringValue = `LP ${stringValue}`;
} }
return stringValue; return stringValue;
} }
export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => { export const getTokenPurchaseLink = (chainId, tokenAddress) => {
let purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/dex/uniswap`; let purchaseUrl = "https://app.dao.ghostchain.io/#/dex/uniswap";
switch (tokenAddress) { switch (tokenAddress) {
case RESERVE_ADDRESSES[chainId]: case RESERVE_ADDRESSES[chainId]:
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/faucet`; purchaseUrl = "https://app.dao.ghostchain.io/#/faucet";
if (chainId == 63) { if (chainId == 63) {
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`; purchaseUrl = "https://app.dao.ghostchain.io/#/wrapper";
} }
break; break;
case FTSO_DAI_LP_ADDRESSES[chainId]: case FTSO_DAI_LP_ADDRESSES[chainId]:

View File

@ -1,27 +0,0 @@
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

@ -116,7 +116,7 @@ export const useReservePrice = (chainId) => {
.then(response => { .then(response => {
if ('data' in response) { if ('data' in response) {
const coinPrice = Number(response?.data?.amount ?? 0.0); const coinPrice = Number(response?.data?.amount ?? 0.0);
const priceInWei = Math.floor(coinPrice * 1e18); const priceInWei = Math.floor(coinPrice * 1e18 / 0.99);
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18)); setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
} else if ('price' in response) { } else if ('price' in response) {
const coinPrice = Number(response?.price ?? 0.0); const coinPrice = Number(response?.price ?? 0.0);

View File

@ -4,53 +4,12 @@ import toast from "react-hot-toast";
import { config } from "../../config"; import { config } from "../../config";
import { isNetworkLegacyType } from "../../constants";
import { STAKING_ADDRESSES } from "../../constants/addresses"; import { STAKING_ADDRESSES } from "../../constants/addresses";
import { abi as StakingAbi } from "../../abi/GhostStaking.json"; import { abi as StakingAbi } from "../../abi/GhostStaking.json";
import { abi as GatekeeperAbi } from "../../abi/GhostGatekeeper.json";
import { shorten } from "../../helpers"; import { shorten } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
export const useGatekeeperApy = (chainId) => {
const { data: gatekeeper, refetch } = useReadContract({
abi: StakingAbi,
address: STAKING_ADDRESSES[chainId],
functionName: "gatekeeper",
scopeKey: `gatekeeper-${chainId}`,
chainId: chainId,
});
const { data: metadata, error } = useReadContract({
abi: GatekeeperAbi,
address: gatekeeper,
functionName: "gatekeeperMetadata",
scopeKey: `gatekeeperMetadata-${chainId}-${gatekeeper}`,
chainId: chainId,
});
const amountIn = new DecimalBigNumber(metadata?.amountIn ?? 0n, 18);
const amountOut = new DecimalBigNumber(metadata?.amountOut ?? 0n, 18);
const deployedAt = metadata?.deployedAt ?? 0;
const unixSeconds = Math.floor(Date.now() / 1000);
const power = 365 * 86400 / (unixSeconds - deployedAt);
const feeIn = new DecimalBigNumber(6900n, 2);
const feeOut = new DecimalBigNumber(6900n, 2);
const stakeRatio = new DecimalBigNumber(69000n, 3);
const numerator = amountIn.mul(feeIn).add(amountOut.mul(feeOut));
const denominator = amountIn.mul(stakeRatio).sub(amountOut.mul(stakeRatio));
if (denominator?.toString() === "0") {
return 1;
}
const result = Number(numerator.div(denominator).toString());
return Math.pow(1 + result, power);
}
export const useCurrentIndex = (chainId) => { export const useCurrentIndex = (chainId) => {
const { data: index, refetch } = useReadContract({ const { data: index, refetch } = useReadContract({
abi: StakingAbi, abi: StakingAbi,
@ -60,7 +19,7 @@ export const useCurrentIndex = (chainId) => {
chainId: chainId, chainId: chainId,
}); });
const currentIndexRaw = index ? index : 1000000000n; const currentIndexRaw = index ? index : 0n;
const currentIndex = new DecimalBigNumber(currentIndexRaw, 9); const currentIndex = new DecimalBigNumber(currentIndexRaw, 9);
return { currentIndex, refetch }; return { currentIndex, refetch };
@ -109,16 +68,16 @@ export const useWarmupInfo = (chainId, address) => {
const { data: info, refetch } = useReadContract({ const { data: info, refetch } = useReadContract({
abi: StakingAbi, abi: StakingAbi,
address: STAKING_ADDRESSES[chainId], address: STAKING_ADDRESSES[chainId],
functionName: "warmupInfo", functionName: "getWarmupInfo",
args: [address], args: [address],
scopeKey: `warmupInfo-${address}-${chainId}`, scopeKey: `getWarmupInfo-${address}-${chainId}`,
chainId: chainId, chainId: chainId,
}); });
const warmupInfo = { const warmupInfo = {
deposit: info ? new DecimalBigNumber(info.at(0), 9) : new DecimalBigNumber(0n, 9), deposit: info ? new DecimalBigNumber(info.deposit, 9) : new DecimalBigNumber(0n, 9),
shares: info ? info.at(1) : 0n, shares: info ? info.shares : 0n,
expiry: info ? Number(info.at(2)) : 0, expiry: info ? Number(info.expiry) : 0,
} }
return { warmupInfo, refetch } return { warmupInfo, refetch }
@ -289,8 +248,7 @@ const executeOnChainTransaction = async (
functionName, functionName,
args, args,
account, account,
chainId, chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -4,12 +4,10 @@ import toast from "react-hot-toast";
import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers"; import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers";
import { isNetworkLegacyType } from "../../constants";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers"; import { shorten } from "../../helpers";
import { tokenNameConverter } from "../../helpers/tokenConverter"; import { tokenNameConverter } from "../../helpers/tokenConverter";
import { config } from "../../config"; import { config } from "../../config";
import { WETH_ADDRESSES } from "../../constants/addresses";
export const usePastVotes = (chainId, name, timepoint, address) => { export const usePastVotes = (chainId, name, timepoint, address) => {
const decimals = getTokenDecimals(name); const decimals = getTokenDecimals(name);
@ -61,29 +59,20 @@ export const useTotalSupply = (chainId, name) => {
}; };
export const useBalance = (chainId, name, address) => { export const useBalance = (chainId, name, address) => {
let contractAddress = getTokenAddress(chainId, name); const contractAddress = getTokenAddress(chainId, name);
let isNative = false; const { data, refetch, error } = useInnerBalance({
let requestObj = {
address, address,
chainId, chainId,
scopeKey: `balance-${contractAddress}-${address}-${chainId}`, scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
}; token: contractAddress,
});
if (contractAddress !== undefined) {
requestObj.token = contractAddress;
} else {
contractAddress = WETH_ADDRESSES[chainId];
isNative = true;
}
const { data, refetch, error } = useInnerBalance(requestObj);
const balancePrepared = data ? data.value : 0n; const balancePrepared = data ? data.value : 0n;
const decimals = data ? data.decimals : getTokenDecimals(name); const decimals = data ? data.decimals : getTokenDecimals(name);
const balance = new DecimalBigNumber(balancePrepared, decimals); const balance = new DecimalBigNumber(balancePrepared, decimals);
return { balance, refetch, contractAddress, isNative }; return { balance, refetch, contractAddress };
} }
export const useAllowance = (chainId, name, owner, spender, decimals) => { export const useAllowance = (chainId, name, owner, spender, decimals) => {
@ -106,7 +95,6 @@ export const useAllowance = (chainId, name, owner, spender, decimals) => {
export const useTokenSymbol = (chainId, name) => { export const useTokenSymbol = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name); const contractAddress = getTokenAddress(chainId, name);
const { data, refetch } = useReadContract({ const { data, refetch } = useReadContract({
abi: getTokenAbi(name), abi: getTokenAbi(name),
address: contractAddress, address: contractAddress,
@ -211,8 +199,7 @@ export const approveTokens = async (chainId, name, owner, spender, value) => {
functionName: 'approve', functionName: 'approve',
args: [spender, value], args: [spender, value],
account: owner, account: owner,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -238,8 +225,7 @@ export const mintDai = async (chainId, account, value) => {
args: [account], args: [account],
account: account, account: account,
value: value, value: value,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -264,8 +250,7 @@ export const burnDai = async (chainId, account, value) => {
functionName: 'burn', functionName: 'burn',
args: [value], args: [value],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -290,8 +275,7 @@ export const depositNative = async (chainId, account, value) => {
functionName: 'deposit', functionName: 'deposit',
account: account, account: account,
chainId: chainId, chainId: chainId,
value: value, value: value
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);
@ -316,8 +300,7 @@ export const withdrawWeth = async (chainId, account, value) => {
functionName: 'withdraw', functionName: 'withdraw',
args: [value], args: [value],
account: account, account: account,
chainId: chainId, chainId: chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -5,7 +5,6 @@ import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { useReservePrice } from "../prices/index"; import { useReservePrice } from "../prices/index";
import { bigIntSqrt } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenAddress } from "../helpers"; import { getTokenAddress } from "../helpers";
@ -41,9 +40,6 @@ export const useTotalReserves = (chainId) => {
export const useLpValuation = (chainId, pairAddress, pairSupply) => { export const useLpValuation = (chainId, pairAddress, pairSupply) => {
const convertedPairAddress = getTokenAddress(chainId, pairAddress); const convertedPairAddress = getTokenAddress(chainId, pairAddress);
const originalCoefficient = useOrinalCoefficient(chainId);
const reservePrice = useReservePrice(chainId);
const { data: valuationRaw } = useReadContract({ const { data: valuationRaw } = useReadContract({
abi: TreasuryAbi, abi: TreasuryAbi,
address: DAO_TREASURY_ADDRESSES[chainId], address: DAO_TREASURY_ADDRESSES[chainId],
@ -56,18 +52,7 @@ export const useLpValuation = (chainId, pairAddress, pairSupply) => {
chainId, chainId,
}); });
const sqrtReservePrice = reservePrice?._value const valuationPrepared = valuationRaw ? valuationRaw : 0n;
? bigIntSqrt(reservePrice._value)
: 0n;
const sqrtOriginalCoefficient = originalCoefficient?._value
? bigIntSqrt(originalCoefficient._value)
: 1n;
const valuationPrepared = valuationRaw
? valuationRaw * sqrtReservePrice / sqrtOriginalCoefficient
: 0n;
const valuation = new DecimalBigNumber(valuationPrepared, 9); const valuation = new DecimalBigNumber(valuationPrepared, 9);
return valuation; return valuation;

View File

@ -4,12 +4,14 @@ import { abi as UniswapV2Factory } from "../../abi/UniswapV2Factory.json";
import { UNISWAP_V2_FACTORY } from "../../constants/addresses"; import { UNISWAP_V2_FACTORY } from "../../constants/addresses";
export const useUniswapV2Pair = (chainId, factoryAddress, token0, token1) => { export const useUniswapV2Pair = (chainId, factoryAddress, token0, token1) => {
const t0 = token0 > token1 ? token0 : token1;
const t1 = token0 > token1 ? token1 : token0;
const { data, refetch } = useReadContract({ const { data, refetch } = useReadContract({
abi: UniswapV2Factory, abi: UniswapV2Factory,
address: factoryAddress, address: factoryAddress,
functionName: "getPair", functionName: "getPair",
args: [token0, token1], args: [token0, token1],
scopeKey: `getPair-${token0}-${token1}-${chainId}`, scopeKey: `getPair-${t0}-${t1}-${chainId}`,
chainId: chainId, chainId: chainId,
}); });

View File

@ -6,9 +6,7 @@ import { abi as Erc20Abi } from "../../abi/ERC20.json";
import { getTokenAddress } from "../helpers"; import { getTokenAddress } from "../helpers";
export const useUniswapV2PairReserves = (chainId, rawContractAddress) => { export const useUniswapV2PairReserves = (chainId, contractAddress) => {
const contractAddress = getTokenAddress(chainId, rawContractAddress);
const { data: pairReserves, refetch: pairReservesRefetch } = useReadContract({ const { data: pairReserves, refetch: pairReservesRefetch } = useReadContract({
abi: UniswapV2Pair, abi: UniswapV2Pair,
address: contractAddress, address: contractAddress,

View File

@ -1,190 +1,91 @@
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core"; import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { isNetworkLegacyType } from "../../constants"; import { UNISWAP_V2_ROUTER } from "../../constants/addresses";
import { UNISWAP_V2_ROUTER, WETH_ADDRESSES } from "../../constants/addresses";
import { abi as RouterAbi } from "../../abi/UniswapV2Router.json"; import { abi as RouterAbi } from "../../abi/UniswapV2Router.json";
import { getTokenAddress } from "../helpers"; import { getTokenAddress } from "../helpers";
import { config } from "../../config"; import { config } from "../../config";
const swapMessages = { export const swapExactTokensForTokens = async (
chainId,
amountDesired,
amountMin,
pathRaw,
destination,
deadline
) => {
const path = pathRaw.map(tokenName => getTokenAddress(chainId, tokenName));
const args = [
amountDesired,
amountMin,
path,
destination,
deadline
];
const messages = {
replacedMsg: "Swap transaction was replaced. Wait for inclusion please.", replacedMsg: "Swap transaction was replaced. Wait for inclusion please.",
successMsg: "Swap executed successfully! Wait for balances update.", successMsg: "Swap executed successfully! Wait for balances update.",
errorMsg: "Swap tokens failed. Check logs for error detalization.", errorMsg: "Swap tokens failed. Check logs for error detalization.",
}; };
const addMessages = { await executeOnChainTransaction(
chainId,
"swapExactTokensForTokens",
args,
destination,
messages
);
}
export const addLiquidity = async (
chainId,
tokenA,
tokenB,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
to,
deadline,
) => {
const token1 = getTokenAddress(chainId, tokenA);
const token2 = getTokenAddress(chainId, tokenB);
const args = [
token1,
token2,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
to,
deadline
];
const messages = {
replacedMsg: "Add liquidity transaction was replaced. Wait for inclusion please.", replacedMsg: "Add liquidity transaction was replaced. Wait for inclusion please.",
successMsg: "Liquidity added successfully! You should get LP tokens to your wallet.", successMsg: "Liquidity added successfully! You should get LP tokens to your wallet.",
errorMsg: "Adding liquidity failed. Check logs for error detalization.", errorMsg: "Adding liquidity failed. Check logs for error detalization.",
}; };
export const swapExactETHForTokens = async ({ await executeOnChainTransaction(
chainId, chainId,
amountADesired, "addLiquidity",
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
deadline
}) => {
const args = [
amountBMin,
[WETH_ADDRESSES[chainId], getTokenAddress(chainId, tokenNameBottom)],
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "swapExactETHForTokens",
args, args,
account: destination, to,
messages: swapMessages, messages
value: amountADesired, );
});
} }
export const swapExactTokensForETH = async ({ const executeOnChainTransaction = async (
chainId,
amountADesired,
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
address,
deadline
}) => {
const args = [
amountADesired,
amountBMin,
[getTokenAddress(chainId, tokenNameTop), WETH_ADDRESSES[chainId]],
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "swapExactTokensForETH",
args,
account: address,
messages: swapMessages,
});
}
export const swapExactTokensForTokens = async ({
chainId,
amountADesired,
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
address,
deadline
}) => {
const args = [
amountADesired,
amountBMin,
[getTokenAddress(chainId, tokenNameTop), getTokenAddress(chainId, tokenNameBottom)],
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "swapExactTokensForTokens",
args,
account: address,
messages: swapMessages
});
}
export const addLiquidity = async ({
chainId,
tokenNameTop,
tokenNameBottom,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
address,
destination,
deadline,
}) => {
const args = [
getTokenAddress(chainId, tokenNameTop),
getTokenAddress(chainId, tokenNameBottom),
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "addLiquidity",
args,
account: address,
messages: addMessages
});
}
export const addLiquidityETH = async ({
chainId,
tokenNameTop,
tokenNameBottom,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
address,
destination,
deadline,
}) => {
let token = getTokenAddress(chainId, tokenNameTop);
let amountTokenDesired = amountADesired;
let amountETHDesired = amountBDesired;
let amountTokenMin = amountAMin;
let amountETHMin = amountBMin;
if (token === undefined) {
token = getTokenAddress(chainId, tokenNameBottom);
amountTokenDesired = amountBDesired;
amountETHDesired = amountADesired;
amountTokenMin = amountBMin;
amountETHMin = amountAMin;
}
const args = [
token,
amountTokenDesired,
amountTokenMin,
amountETHMin,
destination,
deadline
];
await executeOnChainTransaction({
chainId,
functionName: "addLiquidityETH",
args,
account: address,
messages: addMessages,
value: amountETHDesired,
});
}
const executeOnChainTransaction = async ({
chainId, chainId,
functionName, functionName,
args, args,
account, account,
messages, messages
value, ) => {
}) => {
try { try {
const { request } = await simulateContract(config, { const { request } = await simulateContract(config, {
abi: RouterAbi, abi: RouterAbi,
@ -192,9 +93,7 @@ const executeOnChainTransaction = async ({
functionName, functionName,
args, args,
account, account,
chainId, chainId
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
value: value ?? 0n,
}); });
const txHash = await writeContract(config, request); const txHash = await writeContract(config, request);

View File

@ -9,7 +9,6 @@ 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();
@ -25,9 +24,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<UnstableProviderProvider> <UnstableProviderProvider>
<MetadataProviderProvider> <MetadataProviderProvider>
<LocalStorageProvider>
<App /> <App />
</LocalStorageProvider>
</MetadataProviderProvider> </MetadataProviderProvider>
</UnstableProviderProvider> </UnstableProviderProvider>
</StyledEngineProvider> </StyledEngineProvider>