Compare commits

..

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

67 changed files with 1101 additions and 1946 deletions

View File

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

View File

@ -5,16 +5,8 @@ import CssBaseline from "@mui/material/CssBaseline";
import { styled, ThemeProvider } from "@mui/material/styles";
import { lazy, Suspense, useCallback, useEffect, useState } from "react";
import toast, { Toaster } from "react-hot-toast";
import { Outlet, Navigate, Route, Routes, useLocation, useParams } from "react-router-dom";
import {
useAccount,
useConnect,
useChainId,
useConfig,
usePublicClient,
useSwitchChain,
injected
} from "wagmi";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { useAccount, useConnect, useChainId, useConfig, usePublicClient, injected } from "wagmi";
import Messages from "./components/Messages/Messages";
import NavDrawer from "./components/Sidebar/NavDrawer";
@ -22,7 +14,7 @@ import Sidebar from "./components/Sidebar/Sidebar";
import TopBar from "./components/TopBar/TopBar";
import { shouldTriggerSafetyCheck } from "./helpers";
import { isNetworkAvailable, isGovernanceAvailable } from "./constants";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "./constants";
import useTheme from "./hooks/useTheme";
import { useUnstableProvider } from "./hooks/ghost";
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 StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
const Faucet = lazy(() => import("./containers/Faucet/Faucet"));
const Wrapper = lazy(() => import("./containers/WethWrapper/WethWrapper"));
const Dex = lazy(() => import("./containers/Dex/Dex"));
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
@ -106,7 +100,6 @@ function App() {
const [theme, toggleTheme] = useTheme();
const config = useConfig();
const { chains } = useSwitchChain();
const { connect, error: errorMessage } = useConnect();
const tryConnectInjected = () => connect({ connector: injected() });
@ -116,7 +109,7 @@ function App() {
const [isSidebarExpanded, setIsSidebarExpanded] = 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 chainId = useChainId();
@ -160,7 +153,7 @@ function App() {
setWrongNetworkToastId(null);
}
}
}, [chainId, addressChainId, isConnected, wrongNetworkToastId])
}, [chainId, addressChainId, isConnected])
useEffect(() => {
if (errorMessage) {
@ -177,6 +170,7 @@ function App() {
};
const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme;
const chainExists = isNetworkAvailable(chainId, addressChainId);
useEffect(() => {
@ -193,7 +187,6 @@ function App() {
wrongNetworkToastId={wrongNetworkToastId}
setWrongNetworkToastId={setWrongNetworkToastId}
address={address}
status={status}
chainId={addressChainId ? addressChainId : chainId}
chainExists={chainExists}
handleDrawerToggle={handleDrawerToggle}
@ -210,19 +203,23 @@ function App() {
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
<Suspense fallback={<div></div>}>
<Routes>
<Route path="/" element={<Navigate to={chainExists ? `/${chains.at(0).name.toLowerCase()}/dashboard` : "/empty"} />} />
<Route path="/" element={<Navigate to={chainExists ? "/dashboard" : "/empty"} />} />
{chainExists &&
<Route path="/:network" element={<AvailableNetworkGuard allowedNetworks={chains.map(chain => chain.name.toLowerCase())} /> }>
<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/:id" element={<BondModalContainer config={config} 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} />} />
<Route path="dex/:name" element={<Dex config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance/: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>
<>
<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/:id" element={<BondModalContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId}/>} />
{isNetworkLegacy(chainId)
? <Route path="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
: <Route path="/wrapper" element={<Wrapper config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
}
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/dex/:name" element={<Dex 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
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;

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { useMemo } from "react";
import React from "react";
import "./Sidebar.scss";
@ -16,7 +16,6 @@ import {
} from "@mui/material";
import { styled } from "@mui/material/styles";
import { NavLink } from "react-router-dom";
import { useSwitchChain } from "wagmi";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
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 ForkRightIcon from '@mui/icons-material/ForkRight';
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 BookIcon from '@mui/icons-material/Book';
import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange';
@ -40,20 +39,19 @@ import BondIcon from "../Icon/BondIcon";
import StakeIcon from "../Icon/StakeIcon";
import WrapIcon from "../Icon/WrapIcon";
import { isNetworkAvailable, isGovernanceAvailable } from "../../constants";
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants";
import { AVAILABLE_DEXES } from "../../constants/dexes";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
import { ECOSYSTEM } from "../../constants/ecosystem";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { sortBondsByDiscount, formatCurrency } from "../../helpers";
import BondDiscount from "../../containers/Bond/components/BondDiscount";
import Chip from "../Chip/Chip";
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShowerIcon from '@mui/icons-material/Shower';
import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup';
import { useTokenSymbol } from "../../hooks/tokens";
import { useFtsoPrice, useGhstPrice, useGhostedSupplyPrice } from "../../hooks/prices";
import { useLiveBonds } from "../../hooks/bonds/index";
import pckg from "../../../package.json"
@ -70,21 +68,14 @@ const StyledBox = styled(Box)(({ theme }) => ({
}));
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 ftsoPrice = useFtsoPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const ghostedSupplyPrice = useGhostedSupplyPrice(chainId);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
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 (
<Paper className="dapp-sidebar">
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
@ -101,6 +92,17 @@ const NavContent = ({ chainId, addressChainId }) => {
Version {pckg.version}
</Box>
</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 className="menu-divider">
@ -111,18 +113,58 @@ const NavContent = ({ chainId, addressChainId }) => {
<div className="dapp-nav" id="navbarNav">
{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
icon={CurrencyExchangeIcon}
label={`(3, 3) Swap`}
to={`/${chainName}/dex/uniswap`}
label={`Dex`}
to={'/dex/uniswap'}
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) => {
return (
<Link
component={NavLink}
to={`/${chainName}/dex/${dex.name.toLowerCase()}`}
to={`/dex/${dex.name.toLowerCase()}`}
key={index}
style={{ textDecoration: "none" }}
>
@ -136,54 +178,11 @@ const NavContent = ({ chainId, addressChainId }) => {
</AccordionDetails>
}
/>
<NavItem icon={StakeIcon} label={`(3, 3) Stake`} to={`/${chainName}/stake`} />
<NavItem
defaultExpanded
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`} />}
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
<NavItem icon={ForkRightIcon} label={`Bridge`} to="/bridge" />
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to="/governance" />}
<Box className="menu-divider">
<Divider />
<Divider />
</Box>
</>
}
@ -192,9 +191,10 @@ const NavContent = ({ chainId, addressChainId }) => {
</div>
<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="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">
<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} />

View File

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

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";
@ -12,62 +12,28 @@ import { isNetworkAvailable } from "../../constants";
import { parseKnownToken } from "../../components/Token/Token";
import { useSwitchChain } from 'wagmi';
import { useNavigate, useLocation } from 'react-router-dom';
import toast from "react-hot-toast";
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small, status }) {
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small }) {
const theme = useTheme();
const navigate = useNavigate();
const hasAttemptedSwitch = useRef(false);
const { chains, switchChain } = useSwitchChain();
const { pathname } = useLocation();
const [networkId, setNetworkId] = useState(chainId);
useEffect(() => {
if (status === 'reconnecting' || status === 'connecting') return;
if (chainId !== networkId) setNetworkId(chainId);
}, [chainId])
const parts = pathname.split('/');
if (!hasAttemptedSwitch.current && parts.length > 2) {
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) => {
const handleChange = (event) => {
const chainName = chains.find((chain) => chain.id === event.target.value).name;
toast.dismiss(wrongNetworkToastId);
const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, {
position: 'bottom-right'
});
setWrongNetworkToastId(toastId);
setNetworkId(newNetworkId);
switchChain({
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 });
}
setNetworkId(event.target.value);
switchChain({ chainId: event.target.value });
}
return(
@ -76,7 +42,6 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
labelId="network-select-helper-label"
id="network-select-helper"
value={networkId}
disabled={!hasAttemptedSwitch.current}
onChange={handleChange}
sx={{
boxShadow: 'none',

View File

@ -18,7 +18,6 @@ function TopBar({
address,
wrongNetworkToastId,
connect,
status,
handleDrawerToggle,
setWrongNetworkToastId
}) {
@ -40,7 +39,6 @@ function TopBar({
setWrongNetworkToastId={setWrongNetworkToastId}
chainId={chainId}
small={small}
status={status}
/>
<Wallet address={address} connect={connect} chainId={chainId} />
</Box>

View File

@ -24,7 +24,7 @@ import { formatCurrency, shorten } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
import { useAccount, useDisconnect, useSwitchChain } from "wagmi";
import { useAccount, useDisconnect } from "wagmi";
const DisconnectButton = ({ onClose }) => {
const { disconnect } = useDisconnect();
@ -109,11 +109,10 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
const theme = useTheme();
const navigate = useNavigate();
const tokens = useWallet(chainId, address);
const { chains } = useSwitchChain();
const onBtnClick = (dexName, from, to) => {
navigate({
pathname: `${chains.find(chain => chain.id === chainId).name.toLowerCase()}/dex/` + dexName,
pathname: "/dex/" + dexName,
search: createSearchParams({
pool: true,
from: from,
@ -151,6 +150,18 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
<Divider />
</Box>
<Box
sx={{ display: "flex", flexDirection: "column" }}
style={{ gap: theme.spacing(1.5) }}
>
<SecondaryButton
fullWidth
onClick={() => onBtnClick("uniswap", RESERVE_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
>
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.reserve?.symbol} on Uniswap`}</Typography>
</SecondaryButton>
</Box>
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
<DisconnectButton onClose={onClose} />
</Box>

View File

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

View File

@ -76,12 +76,7 @@ export function Wallet({ address, chainId, connect }) {
onOpen={openWallet}
onClose={closeWallet}
>
<InitialWalletView
isWalletOpen={isWalletOpen}
address={address}
chainId={chainId}
onClose={closeWallet}
/>
<InitialWalletView isWalletOpen={isWalletOpen} address={address} chainId={chainId} onClose={closeWallet} />
</StyledSwipeableDrawer>
</>
);

View File

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

View File

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

View File

@ -1,9 +1,8 @@
import { ArrowBack } from "@mui/icons-material";
import { Box, Link, Skeleton, Typography } from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react";
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi";
import { isAddress } from "viem";
import { useEffect, useState } from "react";
import { Link as RouterLink, useLocation, useNavigate, useParams } from "react-router-dom";
import { useAccount, useChainId } from "wagmi";
import ReactGA from "react-ga4";
import PageTitle from "../../components/PageTitle/PageTitle";
@ -18,92 +17,64 @@ import BondSettingsModal from "./components/BondSettingsModal";
import NotFound from "../NotFound/NotFound";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { isNetworkLegacy } from "../../constants";
import { NetworkId } from "../../constants";
import { formatCurrency } from "../../helpers";
import { useLocalStorage } from "../../hooks/localstorage";
import { useLiveBonds } from "../../hooks/bonds";
import { useFtsoPrice } from "../../hooks/prices";
const BondModalContainer = ({ chainId, address, config, connect }) => {
const { id, network } = useParams();
const { liveBonds } = useLiveBonds(chainId, network);
const BondModalContainer = ({ chainId, address, connect }) => {
const { id } = useParams();
const { liveBonds } = useLiveBonds(chainId);
const bond = liveBonds.find(bond => bond.id === Number(id));
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 { network } = useParams();
const { pathname } = useLocation();
const [slippage, setSlippage] = useState(localStorage.getItem("bond-slippage") || "10");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("bond-decimals") || "5");
const [isSettingsOpen, setSettingsOpen] = useState(false);
const { getStorageValue, setStorageValue } = useLocalStorage();
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "bond-slippage", "10"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "bond-decimals", "5"));
const [recipientAddress, setRecipientAddressInner] = useState(() => getStorageValue(chainId, address, "bond-recipient", address));
const [recipientAddress, setRecipientAddress] = useState(address);
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: pathname });
}, [])
const setRecipientAddress = useCallback((value) => {
setRecipientAddressInner(value);
if (isAddress(value)) {
setStorageValue(chainId, address, "bond-recipient", value);
}
}, [chainId, address]);
const setSlippageInner = useCallback((value) => {
const setSlippageInner = (value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
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) {
setFormatDecimals(value);
setStorageValue(chainId, address, "bond-decimals", value);
localStorage.setItem("bond-decimals", value);
}
}, [chainId, address]);
}
useEffect(() => {
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);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [network, 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])
}, [navigate, isSettingsOpen]);
return (
<Box>
<PageTitle
name={
<Box display="flex" flexDirection="row" alignItems="center">
<Link component={RouterLink} to={`/${network}/bonds`}>
<Link component={RouterLink} to="/bonds">
<Box display="flex" flexDirection="row">
<ArrowBack />
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
@ -112,10 +83,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
</Box>
</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">
<Typography variant="h4" fontWeight={500}>
{preparedQuoteToken.name}
{bond.quoteToken.name}
</Typography>
</Box>
</Box>
@ -161,12 +132,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
<BondInputArea
chainId={chainId}
bond={bond}
config={config}
connect={connect}
address={address}
slippage={slippage}
recipientAddress={recipientAddress}
preparedQuoteToken={preparedQuoteToken}
handleSettingsOpen={() => setSettingsOpen(true)}
formatDecimals={formatDecimals}
/>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 { useLocation } from "react-router-dom";
@ -30,15 +30,12 @@ const BondInputArea = ({
formatDecimals,
handleSettingsOpen,
address,
preparedQuoteToken,
connect
}) => {
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { pathname } = useLocation();
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: ghstSymbol } = useTokenSymbol(chainId, "GHST");
@ -98,21 +95,22 @@ const BondInputArea = ({
UpperSwapCard={
<SwapCard
maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
inputWidth="280px"
id="from"
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={preparedQuoteToken.name}
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
endString={preparedQuoteToken.address && "Max"}
tokenName={bond.quoteToken.name}
info={formatCurrency(balance, formatDecimals, bond.quoteToken.name)}
endString="Max"
endStringOnClick={setMax}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
/>}
/>
}
LowerSwapCard={
<SwapCard
maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
inputWidth="280px"
id="to"
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
tokenName={bond.baseToken.name}
@ -134,7 +132,6 @@ const BondInputArea = ({
connect={connect}
width="100%"
height="60px"
isNative={preparedQuoteToken.address === undefined}
isVertical
message={
<>
@ -180,12 +177,12 @@ const BondInputArea = ({
)}
</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
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={
<span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
@ -198,14 +195,14 @@ const BondInputArea = ({
<DataRow
title="Discount"
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
title={`Vesting Term`}
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 && (
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
@ -222,9 +219,6 @@ const BondInputArea = ({
spendAmountValue={parsedAmount}
spendAmount={formatNumber(parsedAmount, formatDecimals)}
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
bondQuoteTokenName={preparedQuoteToken.name}
bondQuoteTokenIcons={preparedQuoteToken.icons}
isNative={preparedQuoteToken.address === undefined}
handleSettingsOpen={handleSettingsOpen}
isOpen={confirmOpen}
sender={address}

View File

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

View File

@ -20,16 +20,17 @@ import { formatCurrency } from "../../../helpers";
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
import { useNotes, redeem } from "../../../hooks/bonds";
import { useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice } from "../../../hooks/prices";
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const isSmallScreen = useScreenSize("md");
const [isPending, setIsPending] = useState(false);
const [isWarmup, setIsWapmup] = 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 { warmupExists } = useWarmupLength(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 { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
if (!notes || notes.length === 0) return null;
@ -50,18 +52,23 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
18
);
const setIsPayoutGhstInner = (value) => {
setIsPayoutGhst(value);
localStorage.setItem("bond-isGhstPayout", value);
}
const onSubmit = async (indexes) => {
const isFundsInWarmup = warmupInfo.deposit._value > 0n;
if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
setIsWapmup(true);
} else {
setIsPending(true);
await redeem({
await redeem(
chainId,
user: address,
isGhst: isPayoutGhst,
address,
isPayoutGhst,
indexes
});
);
await notesRefetch();
setIsPending(false);
}
@ -87,6 +94,24 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
</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" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}>
<Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}>
@ -99,137 +124,128 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
? formatCurrency(totalClaimableBalance, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
}
</Typography>
<Typography variant="subtitle1" align="center">
{formatCurrency(totalClaimableBalance * ghstPrice, 2)}
</Typography>
</Typography>
</Box>
<PrimaryButton
disabled={isPending || totalClaimableBalance._value === 0n}
fullWidth
className=""
onClick={() => onSubmit(notes.filter((note) => secondsTo > note.matured).map(note => note.id))}
>
Claim All
</PrimaryButton>
</Box>
<PrimaryButton
disabled={isPending || totalClaimableBalance._value === 0n}
fullWidth
className=""
onClick={() => onSubmit(notes.filter((note) => secondsTo > note.matured).map(note => note.id))}
>
Claim All
</PrimaryButton>
</Box>
</Box>
<Box mt="48px">
{isSmallScreen ? (
<>
{notes.map((note, index) => (
<Box key={index} mt="32px">
<Box display="flex" alignItems="center">
<TokenStack tokens={note.quoteToken.icons} />
<Box ml="8px">
<Typography>{note.quoteToken.name}</Typography>
<Box mt="48px">
{isSmallScreen ? (
<>
{notes.map((note, index) => (
<Box key={index} mt="32px">
<Box display="flex" alignItems="center">
<TokenStack tokens={note.quoteToken.icons} />
<Box ml="8px">
<Typography>{note.quoteToken.name}</Typography>
</Box>
</Box>
</Box>
<Box display="flex" justifyContent="space-between" mt="16px">
<Typography>Duration</Typography>
<Typography>
<BondVesting vesting={note.vesting} />
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mt="16px">
<Typography>Duration</Typography>
<Typography>
<BondVesting vesting={note.vesting} />
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mt="8px">
<Typography>Remaining</Typography>
<Typography>
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mt="8px">
<Typography>Remaining</Typography>
<Typography>
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mt="8px">
<Typography>Payout</Typography>
<Box display="flex" flexDirection="column" alignItems="flex-end">
<Box display="flex" justifyContent="space-between" mt="8px">
<Typography>Payout</Typography>
<Typography>
{isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
}
</Typography>
<Typography>{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
</Box>
<Box mt="16px">
<TertiaryButton
fullWidth
disabled={isPending || secondsTo < note.matured}
onClick={() => onSubmit([note.id])}
>
Claim
</TertiaryButton>
</Box>
</Box>
<Box mt="16px">
<TertiaryButton
fullWidth
disabled={isPending || secondsTo < note.matured}
onClick={() => onSubmit([note.id])}
>
Claim
</TertiaryButton>
</Box>
</Box>
))}
</>
) : (
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell style={{ width: "180px", padding: "8px 0" }}>Bond</TableCell>
<TableCell style={{ padding: "8px 0" }}>Duration</TableCell>
<TableCell style={{ padding: "8px 0" }}>Remaining</TableCell>
<TableCell style={{ padding: "8px 0" }}>Payout</TableCell>
</TableRow>
</TableHead>
<TableBody>
{notes.map((note, index) => (
<TableRow key={index}>
<TableCell style={{ padding: "8px 0" }}>
<Box display="flex" alignItems="center">
<TokenStack tokens={note.quoteToken.icons} />
<Box display="flex" flexDirection="column" ml="16px">
<Typography>{note.quoteToken.name}</Typography>
))}
</>
) : (
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell style={{ width: "180px", padding: "8px 0" }}>Bond</TableCell>
<TableCell style={{ padding: "8px 0" }}>Duration</TableCell>
<TableCell style={{ padding: "8px 0" }}>Remaining</TableCell>
<TableCell style={{ padding: "8px 0" }}>Payout</TableCell>
</TableRow>
</TableHead>
<TableBody>
{notes.map((note, index) => (
<TableRow key={index}>
<TableCell style={{ padding: "8px 0" }}>
<Box display="flex" alignItems="center">
<TokenStack tokens={note.quoteToken.icons} />
<Box display="flex" flexDirection="column" ml="16px">
<Typography>{note.quoteToken.name}</Typography>
</Box>
</Box>
</Box>
</TableCell>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<Typography>
<BondVesting vesting={note.vesting} />
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<Typography>
<BondVesting vesting={note.vesting} />
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<Typography>
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<Typography>
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<Box display="flex" flexDirection="column" alignItems="flex-start">
<TableCell style={{ padding: "8px 0" }}>
<Typography>
{isPayoutGhst
? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
}
</Typography>
<Typography variant="body2">{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
</Box>
</TableCell>
</TableCell>
<TableCell style={{ padding: "8px 0" }}>
<TertiaryButton
fullWidth
disabled={isPending || secondsTo < note.matured}
onClick={() => onSubmit([note.id])}
>
Claim
</TertiaryButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</Box>
<TableCell style={{ padding: "8px 0" }}>
<TertiaryButton
fullWidth
disabled={isPending || secondsTo < note.matured}
onClick={() => onSubmit([note.id])}
>
Claim
</TertiaryButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</Box>
</Paper>
</>
);

View File

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

View File

@ -10,7 +10,6 @@ import {
useMediaQuery,
} from "@mui/material";
import { decodeAddress } from "@polkadot/util-crypto";
import { fromHex } from "@polkadot-api/utils";
import { getBlockNumber } from "@wagmi/core";
import { useTransaction } from "wagmi";
import { keccak256 } from "viem";
@ -43,16 +42,15 @@ import {
useCurrentSlot,
useGenesisSlot,
useErasTotalStake,
useLatestBlockNumber,
useEraIndex,
} from "../../hooks/ghost";
import { useLocalStorage } from "../../hooks/localstorage";
import { ValidatorTable } from "./ValidatorTable";
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
import { BridgeHeader } from "./BridgeHeader";
import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard";
const STORAGE_PREFIX = "storedTransactions"
const Bridge = ({ chainId, address, config, connect }) => {
const isBigScreen = useMediaQuery("(max-width: 980px)")
const isSmallScreen = useMediaQuery("(max-width: 650px)");
@ -66,15 +64,14 @@ const Bridge = ({ chainId, address, config, connect }) => {
const [bridgeAction, setBridgeAction] = useState(true);
const [currentTime, setCurrentTime] = useState(Date.now());
const { getStorageValue, setStorageValue } = useLocalStorage();
useEffect(() => {
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
return () => clearInterval(interval);
}, []);
const [storedTransactions, setStoredTransactions] = useState(() =>
getStorageValue(chainId, address, "bridge-txs", [])
const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX);
const [storedTransactions, setStoredTransactions] = useState(
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
);
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
@ -105,28 +102,24 @@ const Bridge = ({ chainId, address, config, connect }) => {
const networkIdEncoded = u64.enc(BigInt(chainId));
const amountEncoded = u128.enc(BigInt(watchTransaction.amount));
const addressEncoded = decodeAddress(watchTransaction.receiverAddress, false, 1996);
const transactionHashEncoded = fromHex(watchTransaction.transactionHash);
const blockNumber = u64.enc(watchTransactionInfo?.blockNumber ?? 0n);
const clapArgsStr = new Uint8Array([
...addressEncoded,
...amountEncoded,
...blockNumber,
...transactionHashEncoded,
...networkIdEncoded
]);
return keccak256(clapArgsStr)
}, [watchTransaction, watchTransactionInfo])
const latestBlockNumber = useLatestBlockNumber();
const eraIndex = useEraIndex();
const currentSlot = useCurrentSlot();
const genesisSlot = useGenesisSlot();
const currentSession = useCurrentIndex();
const applauseThreshold = useApplauseThreshold();
const evmNetwork = useEvmNetwork({ evmChainId: chainId });
const totalStakedAmount = useErasTotalStake({
epochIndex: eraIndex?.index ?? 0,
eraIndex: Math.floor((watchTransaction?.sessionIndex ?? currentSession) / 6)
});
const authorities = useAuthorities({
currentSession: watchTransaction?.sessionIndex ?? currentSession
@ -212,20 +205,16 @@ const Bridge = ({ chainId, address, config, connect }) => {
const latestCommits = useMemo(() => {
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 {
validator: validator,
lastActive: timestampDelta,
lastUpdated: lastUpdatedTimestamp,
lastActive: currentTime - Number(blockCommitments?.at(index)?.last_updated ?? 0),
lastUpdated: blockCommitments?.at(index)?.last_updated,
lastStoredBlock: blockCommitments?.at(index)?.last_stored_block,
storedBlockTime: (blockCommitments?.at(index)?.last_stored_block ?? 0n) * networkAvgBlockSpeed(chainId),
disabled: disabledValidators?.includes(index),
}
})
}, [blockCommitments, disabledValidators, validators, latestBlockNumber, chainId]);
}, [blockCommitments, disabledValidators, validators, chainId]);
const latestUpdate = useMemo(() => {
const validCommits = latestCommits?.filter(commit =>
@ -281,7 +270,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
const removeStoredRecord = useCallback(() => {
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
setStoredTransactions(newStoredTransactions);
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
localStorage.setItem(storagePrefix, JSON.stringify(newStoredTransactions));
setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
@ -307,8 +296,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
const newStoredTransactions = [transaction, ...storedTransactions];
setStoredTransactions(newStoredTransactions);
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(0);
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(0)
}
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 { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
@ -19,11 +19,10 @@ import ContentPasteIcon from '@mui/icons-material/ContentPaste';
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import Modal from "../../components/Modal/Modal";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { PrimaryButton, TertiaryButton, SecondaryButton } from "../../components/Button";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { formatCurrency } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
export const BridgeModal = ({
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 (
<Modal
data-testid="transaction-details-modal"
@ -96,43 +89,15 @@ export const BridgeModal = ({
minHeight={"100px"}
>
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
{!providerDetail && <Box
width="90%"
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
backgroundColor="#1f4771"
sx={{
position: 'absolute',
top: '130px',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1,
}}
>
<SecondaryButton
{!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<TertiaryButton
fullWidth
sx={{
marginTop: "0 !important",
marginBottom: "0 !important"
}}
onClick={() => window.open(
'https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases',
'_blank',
'noopener,noreferrer'
)}
onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}
>
Get GHOST Connect
</SecondaryButton>
</TertiaryButton>
</Box>}
<Box
display="flex"
flexDirection="row"
justifyContent="space-between"
alignItems="center"
sx={{ filter: providerDetail ? '' : 'blur(5px)' }}
>
{providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
{currentRecord?.finalization > 0 && (
<>
<Box
@ -320,7 +285,7 @@ export const BridgeModal = ({
</Box>
</>
)}
</Box>
</Box>}
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
<Box display="flex" flexDirection="row" justifyContent="space-between">
@ -392,21 +357,13 @@ export const BridgeModal = ({
</Box>
<Box display="flex" flexDirection="column" gap="5px">
{currentRecord && currentRecord.finalization < 1 && currentRecord.applaused && <PrimaryButton
fullWidth
loading={false}
onClick={() => removeStoredRecord()}
>
{`Get ${bridgeNumbers()} Stake\u00B2`}
</PrimaryButton>}
<TertiaryButton
<PrimaryButton
fullWidth
loading={false}
onClick={() => removeStoredRecord()}
>
Erase Record
</TertiaryButton>
</PrimaryButton>
<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.
@ -442,12 +399,6 @@ export const BridgeConfirmModal = ({
>
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
<Box width="100%" display="flex" flexDirection="column" alignItems="start">
<Box>
<Typography variant="subtitle1">
You are bridging to GHOST Chain. We will guide you towards (10, 10) Stake<sup>2</sup> rewards right after that.
</Typography>
</Box>
<hr style={{ margin: "10px 0", width: "100%" }} />
<FormControlLabel
control={
<Checkbox
@ -483,7 +434,7 @@ export const BridgeConfirmModal = ({
</span>
}
/>
<hr style={{ margin: "10px 0", width: "100%" }} />
<hr style={{ margin: "10px 0", width: "100%" }} />
<FormControlLabel
control={
<Checkbox

View File

@ -10,10 +10,9 @@ import {
useTheme,
} from "@mui/material";
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 { Helmet } from "react-helmet";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
@ -29,23 +28,18 @@ import {
UNISWAP_V2_FACTORY,
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
EMPTY_ADDRESS,
WETH_ADDRESSES,
} from "../../constants/addresses";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol } from "../../hooks/tokens";
import { getTokenAddress } from "../../hooks/helpers";
import PoolContainer from "./PoolContainer";
import SwapContainer from "./SwapContainer";
import TokenModal from "./TokenModal";
const Dex = ({ chainId, address, connect, config }) => {
const Dex = ({ chainId, address, connect }) => {
const location = useLocation();
const pathname = useParams();
const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
@ -58,50 +52,15 @@ const Dex = ({ chainId, address, connect, config }) => {
const [topTokenListOpen, setTopTokenListOpen] = useState(false);
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
const [secondsToWait, setSecondsToWait] = useState(() => getStorageValue(chainId, address, "dex-deadline", "60"));
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "dex-slippage", "5"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "dex-decimals", "5"));
const [actualDestinationAddress, setActualDestinationAddress] = useState(() => getStorageValue(chainId, address, "dex-destination", address));
const [destinationAddress, setDestinationAddress] = useState(actualDestinationAddress);
const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
const [tokenAddressTop, setTokenAddressTop] = useState(EMPTY_ADDRESS);
const [tokenAddressTop, setTokenAddressTop] = useState(RESERVE_ADDRESSES[chainId]);
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
const { symbol: tokenNameTopInner } = useTokenSymbol(chainId, tokenAddressTop);
const { symbol: tokenNameBottomInner } = 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]);
const { symbol: tokenNameTop } = useTokenSymbol(chainId, tokenAddressTop);
const { symbol: tokenNameBottom } = useTokenSymbol(chainId, tokenAddressBottom);
useEffect(() => {
if (currentQueryParameters.has("pool")) {
@ -116,8 +75,8 @@ const Dex = ({ chainId, address, connect, config }) => {
setTokenAddressTop(currentQueryParameters.get("from"));
newQueryParameters.set("from", currentQueryParameters.get("from"));
} else {
setTokenAddressTop(EMPTY_ADDRESS);
newQueryParameters.set("from", EMPTY_ADDRESS);
setTokenAddressTop(RESERVE_ADDRESSES[chainId]);
newQueryParameters.set("from", RESERVE_ADDRESSES[chainId]);
}
if (currentQueryParameters.has("to")) {
@ -133,7 +92,7 @@ const Dex = ({ chainId, address, connect, config }) => {
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname + location.search });
}, [location]);
}, [location])
const dexAddresses = {
router: UNISWAP_V2_ROUTER[chainId],
@ -154,7 +113,7 @@ const Dex = ({ chainId, address, connect, config }) => {
}
const changeSwapTab = (swap) => {
if (swap || (isWrapping || isUnwrapping)) newQueryParameters.delete("pool");
if (swap) newQueryParameters.delete("pool");
else newQueryParameters.set("pool", true);
newQueryParameters.set("from", currentQueryParameters.get("from"));
newQueryParameters.set("to", currentQueryParameters.get("to"));
@ -183,38 +142,24 @@ const Dex = ({ chainId, address, connect, config }) => {
setSearchParams(newQueryParameters);
}
const setSlippageInner = useCallback((value) => {
const setSlippageInner = (value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
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);
setStorageValue(chainId, address, "dex-deadline", value);
}, [chainId, address]);
}
const setFormatDecimalsInner = useCallback((value) => {
const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) {
localStorage.setItem("dex-decimals", 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 (
@ -254,11 +199,11 @@ const Dex = ({ chainId, address, connect, config }) => {
}}
>
<Modal
maxWidth="450px"
maxWidth="376px"
minHeight="200px"
open={settingsOpen}
headerText={"Settings"}
onClose={() => handleCloseSetting()}
onClose={() => handleSettingsOpen(false)}
>
<Box>
<InputLabel htmlFor="slippage">Slippage</InputLabel>
@ -322,35 +267,9 @@ const Dex = ({ chainId, address, connect, config }) => {
</Typography>
</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>
<TokenModal
chainSymbol={chainSymbol}
account={address}
chainId={chainId}
listOpen={topTokenListOpen}
@ -358,7 +277,6 @@ const Dex = ({ chainId, address, connect, config }) => {
setTokenAddress={setInnerTokenAddressTop}
/>
<TokenModal
chainSymbol={chainSymbol}
account={address}
chainId={chainId}
listOpen={bottomTokenListOpen}
@ -404,14 +322,11 @@ const Dex = ({ chainId, address, connect, config }) => {
dexAddresses={dexAddresses}
connect={connect}
slippage={slippage}
destination={actualDestinationAddress ? actualDestinationAddress : address}
secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen}
setIsSwap={setIsSwap}
formatDecimals={formatDecimals}
isWrapping={isWrapping}
isUnwrapping={isUnwrapping}
/>
:
<PoolContainer
@ -423,7 +338,6 @@ const Dex = ({ chainId, address, connect, config }) => {
dexAddresses={dexAddresses}
connect={connect}
slippage={slippage}
destination={actualDestinationAddress ? actualDestinationAddress : address}
secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen}

View File

@ -9,15 +9,10 @@ import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenA
import { SecondaryButton } from "../../components/Button";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, formatCurrency, bigIntSqrt } from "../../helpers";
import { formatNumber, formatCurrency } from "../../helpers";
import { useBalance, useTotalSupply } from "../../hooks/tokens";
import {
useUniswapV2Pair,
useUniswapV2PairReserves,
addLiquidity,
addLiquidityETH,
} from "../../hooks/uniswapv2";
import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2";
const PoolContainer = ({
tokenNameTop,
@ -28,11 +23,10 @@ const PoolContainer = ({
dexAddresses,
connect,
slippage,
destination,
secondsToWait,
setTopTokenListOpen,
setBottomTokenListOpen,
formatDecimals,
formatDecimals
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)");
@ -45,13 +39,11 @@ const PoolContainer = ({
balance: balanceTop,
refetch: balanceRefetchTop,
contractAddress: addressTop,
isNative: topIsNative,
} = useBalance(chainId, tokenNameTop, address);
const {
balance: balanceBottom,
refetch: balanceRefetchBottom,
contractAddress: addressBottom,
isNative: bottomIsNative,
} = useBalance(chainId, tokenNameBottom, address);
const {
@ -83,6 +75,34 @@ const PoolContainer = ({
const setMaxTop = () => setAmountTop(balanceTop.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 pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
? pairReserves.reserve0
@ -131,40 +151,14 @@ const PoolContainer = ({
}
}
}, [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]);
}, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves])
const addLiquidityInner = async () => {
setIsPending(true);
const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
const destination = address;
const shares = 100000;
const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
@ -177,7 +171,7 @@ const PoolContainer = ({
const amountAMin = amountADesired * bigIntSlippage / one;
const amountBMin = amountBDesired * bigIntSlippage / one;
const params = {
await addLiquidity(
chainId,
tokenNameTop,
tokenNameBottom,
@ -185,16 +179,9 @@ const PoolContainer = ({
amountBDesired,
amountAMin,
amountBMin,
address,
destination,
deadline,
}
if (topIsNative || bottomIsNative) {
await addLiquidityETH(params)
} else {
await addLiquidity(params);
}
);
await balanceRefetchTop();
await balanceRefetchBottom();
@ -246,7 +233,7 @@ const PoolContainer = ({
}
arrowOnClick={onSwap}
/>
<Box
{!isSmallScreen && <Box
m="10px 0"
display="flex"
flexDirection="column"
@ -257,29 +244,24 @@ const PoolContainer = ({
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameTop}`}</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceIn, formatDecimals, tokenNameBottom)}</Typography>
<Typography fontSize="12px" lineHeight="15px">Current Balance:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(lpBalance, formatDecimals)} LP</Typography>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom}`}</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceOut, formatDecimals, tokenNameTop)}</Typography>
<Typography fontSize="12px" lineHeight="15px">Total Supply:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(lpTotalSupply, formatDecimals)} LP</Typography>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Current Pool Share:</Typography>
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.currentShares, formatDecimals)}%</Typography>
<Typography fontSize="12px" lineHeight="15px">Extra Balance:</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>}
<TokenAllowanceGuard
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
tokenName={tokenNameTop}
owner={address}
spender={dexAddresses.router}
decimals={balanceTop._decimals}
isNative={topIsNative}
approvalText={"Approve " + tokenNameTop}
approvalPendingText={"Approving..."}
connect={connect}
@ -292,7 +274,6 @@ const PoolContainer = ({
owner={address}
spender={dexAddresses.router}
decimals={balanceBottom._decimals}
isNative={bottomIsNative}
approvalText={"Approve " + tokenNameBottom}
approvalPendingText={"Approving..."}
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 toast from "react-hot-toast";
@ -13,16 +13,8 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../helpers/timeUtil";
import { getTokenAddress } from "../../hooks/helpers";
import { useBalance, depositNative, withdrawWeth } from "../../hooks/tokens";
import {
useUniswapV2Pair,
useUniswapV2PairReserves,
swapExactTokensForTokens,
swapExactETHForTokens,
swapExactTokensForETH,
} from "../../hooks/uniswapv2";
import { EMPTY_ADDRESS } from "../../constants/addresses";
import { useBalance } from "../../hooks/tokens";
import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens } from "../../hooks/uniswapv2";
const SwapContainer = ({
tokenNameTop,
@ -35,12 +27,9 @@ const SwapContainer = ({
setTopTokenListOpen,
setBottomTokenListOpen,
slippage,
destination,
secondsToWait,
setIsSwap,
formatDecimals,
isWrapping,
isUnwrapping,
formatDecimals
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)");
@ -56,14 +45,12 @@ const SwapContainer = ({
balance: balanceTop,
refetch: balanceRefetchTop,
contractAddress: addressTop,
isNative: topIsNative,
} = useBalance(chainId, tokenNameTop, address);
const {
balance: balanceBottom,
refetch: balanceRefetchBottom,
contractAddress: addressBottom,
isNative: bottomIsNative,
} = useBalance(chainId, tokenNameBottom, address);
const {
@ -87,73 +74,42 @@ const SwapContainer = ({
const setMax = () => setAmountTop(balanceTop.toString());
useEffect(() => {
if (isWrapping || isUnwrapping) {
setAmountBottom(amountTop.toString());
setNextPrice("1");
setCurrentPrice("1");
return;
}
const zero = new DecimalBigNumber(0n, 0);
const raw = new DecimalBigNumber(amountTop, balanceTop._decimals);
const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals);
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 amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0;
if (amountOut.eq(zero)) {
if (amountIn.eq(zero)) {
setCurrentPrice("");
} else {
setCurrentPrice(amountIn.div(amountOut).toString());
}
if (amountOut.eq(zero) || amountInWithFee.eq(zero)) {
if (amountIn.eq(zero) || amountInWithFee.eq(zero)) {
setAmountBottom("");
setNextPrice("");
} else {
const nominator = amountOut.mul(amountInWithFee);
const nominator = amountOut.mul(amountIn);
const denominator = amountIn.add(amountInWithFee);
const newAmountOut = nominator.div(denominator);
const newReserveIn = amountIn.add(amountInWithFee);
const newReserveOut = amountOut.sub(newAmountOut);
const nextPrice = newReserveIn.div(newReserveOut);
setAmountBottom(newAmountOut.toString());
setNextPrice(nextPrice.toString());
setAmountBottom(amountOut.sub(newAmountOut).toString());
setNextPrice(denominator.div(newAmountOut).toString())
}
}, [pairReserves, addressBottom, amountTop, addressTop, isWrapping, isUnwrapping]);
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]);
}, [amountTop, addressTop]);
const swapTokens = async () => {
setIsPending(true);
const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
const destination = address;
const shares = 100000;
const one = BigInt(shares * 100);
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 amountBMin = amountBDesired * bigIntSlippage / one;
if (isWrapping) {
await depositNative(chainId, address, amountADesired);
} else if (isUnwrapping) {
await withdrawWeth(chainId, address, amountADesired);
} else {
const params = {
chainId,
amountADesired,
amountBMin,
tokenNameTop,
tokenNameBottom,
destination,
address,
deadline
};
if (topIsNative) {
await swapExactETHForTokens(params)
} else if (bottomIsNative) {
await swapExactTokensForETH(params)
} else {
await swapExactTokensForTokens(params);
}
}
await swapExactTokensForTokens(
chainId,
amountADesired,
amountBMin,
[tokenNameTop, tokenNameBottom],
destination,
deadline
);
await balanceRefetchTop();
await balanceRefetchBottom();
@ -236,7 +176,7 @@ const SwapContainer = ({
}
arrowOnClick={onSwap}
/>
<Box
{!isSmallScreen && <Box
m="10px 0"
display="flex"
flexDirection="column"
@ -247,29 +187,24 @@ const SwapContainer = ({
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
>
<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>
</Box>
<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>
</Box>
<Box width="100%" display="flex" justifyContent="space-between">
<Typography fontSize="12px" lineHeight="15px">Min. Receive:</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">Transaction deadline:</Typography>
<Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
</Box>
</Box>
</Box>}
<TokenAllowanceGuard
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
tokenName={tokenNameTop}
owner={address}
spender={dexAddresses.router}
decimals={balanceTop._decimals}
isNative={topIsNative}
approvalText={"Approve " + tokenNameTop}
approvalPendingText={"Approving..."}
connect={connect}
@ -289,10 +224,17 @@ const SwapContainer = ({
onClick={() => address === "" ?
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>
</TokenAllowanceGuard>
</Box>

View File

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

View File

@ -1,6 +1,6 @@
import { useEffect } from "react";
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";
@ -19,10 +19,13 @@ const Governance = ({ connect, config, address, chainId }) => {
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const navigate = useNavigate();
const { network } = useParams();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const handleModal = () => {
const navigate = useNavigate();
}
useEffect(() => {
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">
<PrimaryButton
fullWidth
onClick={() => navigate(`/${network}/governance/create`)}
onClick={() => navigate(`/governance/create`)}
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
>
Create Proposal

View File

@ -28,7 +28,6 @@ import PageTitle from "../../components/PageTitle/PageTitle";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
@ -43,10 +42,11 @@ const NewProposal = ({ config, address, connect, chainId }) => {
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposalsInner] = useState(() => myStoredProposals.map(id => BigInt(id)));
const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const [isPending, setIsPending] = useState(false);
const [isModalOpened, setIsModalOpened] = useState(false);
@ -61,10 +61,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
proposalDescription
} = useProposalHash(chainId, proposalFunctions);
const setMyProposals = useCallback((proposals) => {
setMyProposalsInner(proposals);
setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString()))
}, [chainId, address]);
useEffect(() => {
const toStore = JSON.stringify(myProposals.map(id => id.toString()));
localStorage.setItem(MY_PROPOSALS_PREFIX, toStore);
}, [myProposals]);
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/governance/create" });
@ -163,14 +163,6 @@ const NewProposal = ({ config, address, connect, chainId }) => {
isVertical
>
<Box display="flex" flexDirection="column" alignItems="center">
<TertiaryButton
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
fullWidth
disabled={isPending}
onClick={() => setIsModalOpened(true)}
>
Add New Function
</TertiaryButton>
<PrimaryButton
disabled={
proposalFunctions.length === 0 ||
@ -182,6 +174,14 @@ const NewProposal = ({ config, address, connect, chainId }) => {
>
{isPending ? "Submitting..." : "Submit Proposal"}
</PrimaryButton>
<TertiaryButton
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
fullWidth
disabled={isPending}
onClick={() => setIsModalOpened(true)}
>
Add New
</TertiaryButton>
</Box>
</TokenAllowanceGuard>
</Box>
@ -200,41 +200,21 @@ const NewProposal = ({ config, address, connect, chainId }) => {
</Box>
}
>
{isSmallScreen
? <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" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>{proposalFunctions?.map((metadata, index) => {
return parseFunctionCalldata({
metadata,
index,
chainId,
removeCalldata,
nativeCoin: nativeCurrency,
});
})}</TableBody>
</Table>
</TableContainer>
}
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>{proposalFunctions.map((metadata, index) => {
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency, removeCalldata);
})}</TableBody>
</Table>
</TableContainer>
</Paper>}
</Box>
</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 { useParams } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import { useBlock, useBlockNumber } from "wagmi";
import { useBlock, useBlockNumber } from 'wagmi'
import {
Box,
@ -33,16 +32,13 @@ import { formatNumber, formatCurrency, shorten } from "../../helpers";
import { prettifySecondsInDays } from "../../helpers/timeUtil";
import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber";
import { convertStatusToColor, convertStatusToLabel } from "./helpers";
import { convertStatusToTemplate, convertStatusToLabel } from "./helpers";
import { parseFunctionCalldata } from "./components/functions";
import { networkAvgBlockSpeed } from "../../constants";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
import {
getVoteValue,
getVoteTarget,
useProposalState,
useProposalProposer,
useProposalLocked,
@ -88,8 +84,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const theme = useTheme();
const queryClient = useQueryClient();
const { getStorageValue, setStorageValue } = useLocalStorage();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance } = useBalance(chainId, "GHST", address);
@ -118,24 +112,48 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const votePercentage = useMemo(() => {
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]);
const forPercentage = useMemo(() => {
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]);
const againstPercentage= useMemo(() => {
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]);
const voteWeightPercentage = useMemo(() => {
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]);
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 client = config?.getClient();
return client?.chain?.nativeCurrency?.symbol;
@ -150,41 +168,38 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
return url;
}, [proposalProposer, config]);
const handleVote = useCallback(async (support) => {
const handleVote = async (against) => {
setIsPending(true);
const support = against ? 0 : 1;
const result = await castVote(chainId, address, proposalId, support);
if (result) {
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
proposals.push(proposalId);
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
await queryClient.invalidateQueries();
const toStore = JSON.stringify(proposalId.toString());
localStorage.setItem(VOTED_PROPOSALS_PREFIX, toStore);
}
setIsPending(false);
}, [chainId, address, proposalId]);
setIsPending(true);
}
const handleExecute = async () => {
setIsPending(true);
await executeProposal(chainId, address, proposalId);
await queryClient.invalidateQueries();
setIsPending(false);
setIsPending(true);
}
const handleRelease = async () => {
const handleRelease = async (proposalId) => {
setIsPending(true);
await releaseLocked(chainId, address, proposalId);
await queryClient.invalidateQueries();
setIsPending(false);
setIsPending(true);
}
return (
<Box>
<PageTitle
name={`GDP-${id.slice(-5)}`}
name={`GBP-${id.slice(-5)}`}
subtitle={
<Typography component="span">
{`Cast $${ghstSymbol} to shape this proposal's outcome`}
Proposal details, need more in-depth description
</Typography>
}
/>
@ -199,24 +214,19 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
}}
>
<Box sx={{ mt: "15px" }}>
<Box
gap="20px"
display="flex"
justifyContent={"space-between"}
flexDirection={isSmallScreen ? "column" : "row"}
>
<Box display="flex" justifyContent="space-between" gap="20px">
<Paper
fullWidth
enableBackground
headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="10px">
{!isSmallScreen &&<Typography variant="h6">
<Typography variant="h6">
Progress
</Typography>}
</Typography>
<Chip
sx={{ marginTop: "4px", width: "88px" }}
label={convertStatusToLabel(proposalState)}
background={convertStatusToColor(proposalState)}
template={convertStatusToTemplate(proposalState)}
/>
</Box>
}
@ -240,8 +250,8 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
barColor={theme.colors.feedback.success}
barBackground={theme.colors.feedback.error}
variant="determinate"
value={getVoteValue(forVotes?._value ?? 0n, totalVotes?._value ?? 0n)}
target={getVoteTarget(totalVotes?._value ?? 0n, totalSupply?._value ?? 0n)}
value={voteValue}
target={voteTarget}
/>
</Box>
@ -294,14 +304,14 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(1)}
>
{isPending ? "Voting..." : "For"}
For
</SecondaryButton>
<SecondaryButton
fullWidth
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
onClick={() => handleVote(0)}
>
{isPending ? "Voting..." : "Against"}
Against
</SecondaryButton>
</>
: <SecondaryButton
@ -336,7 +346,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
isProposer={proposalProposer === address}
chainId={chainId}
proposalId={id}
isPending={isPending}
/>
</Paper>
</Box>
@ -353,46 +362,28 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
</Box>
}
>
{isSmallScreen
? <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" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>{proposalDetails?.map((metadata, index) => {
return parseFunctionCalldata({
metadata,
index,
chainId,
nativeCoin: nativeCurrency,
});
})}</TableBody>
</Table>
</TableContainer>
}
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>{proposalDetails?.map((metadata, index) => {
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency);
})}</TableBody>
</Table>
</TableContainer>
</Paper>
</Container>
</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 { snapshot: proposalSnapshot } = useProposalSnapshot(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" />
</Timeline>
<Box width="100%" display="flex" gap="10px">
{(isProposer && (proposalLocked?._value ?? 0n) > 0n) && <SecondaryButton
{isProposer && <SecondaryButton
fullWidth
disabled={isPending || state < 2}
disabled={(proposalLocked?._value ?? 0n) === 0n || state < 2}
onClick={() => address === "" ? connect() : handleRelease()}
>
{address === "" ? "Connect" : "Release"}
</SecondaryButton>}
<SecondaryButton
fullWidth
disabled={isPending || state !== 4}
disabled={state !== 4}
onClick={() => address === "" ? connect() : handleExecute()}
>
{address === ""
? "Connect"
: state !== 4
? convertStatusToLabel(state)
: isPending ? "Executing..." : "Execute"
}
{address === "" ? "Connect" : "Execute"}
</SecondaryButton>
</Box>
</Box>

View File

@ -12,7 +12,7 @@ const GovernanceInfoText = () => {
ghostDAOs adaptive governance system algorithmically sets minimum collateral based on activity.
&nbsp;<Link
color={theme.colors.primary[300]}
href="http://forum.ghostchain.io/"
href="https://docs.dao.ghostchain.io/"
target="_blank"
rel="noopener noreferrer"
>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;
}}
/>
<Typography align="center" variant="body2">
<b>{selectedOption}</b>
{" "}
{functionDescription}
</Typography>
<Typography align="center" variant="body2">{functionDescription}</Typography>
{ready
? <TertiaryButton disabled={!selectedOption} onClick={() => handleProceed()} fullWidth>Proceed</TertiaryButton>
: <PrimaryButton disabled={!selectedOption} onClick={() => handleCalldata()} fullWidth>Create Function</PrimaryButton>

View File

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

View File

@ -20,12 +20,20 @@ export const prepareAuditReservesCalldata = (chainId) => {
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 = () => {
return null;
}
export const AuditReservesParsed = (props) => {
return (
<>
{props.isTable && <AuditReservesParsedCell {...props} />}
</>
)
}
const AuditReservesParsedCell = (props) => {
return <ParsedCell {...props} />
}

View File

@ -29,7 +29,7 @@ export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, i
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) => {
const [isOpened, setIsOpened] = useState(false);
@ -51,11 +51,15 @@ export const CreateBondParsed = (props) => {
<CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} />
</Box>
</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 }) => {
const createMode = args === undefined;
@ -114,7 +118,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
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])}` },
];
@ -125,7 +129,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
createMode={createMode}
possibleTokens={possibleTokens}
setTokenAddress={createMode ? setTokenAddress : empty}
nativeCurrency={reserveSymbol}
nativeCurrency={nativeCurrency}
ftsoSymbol={ftsoSymbol}
capacityInQuote={capacityInQuote}
setCapacityInQuote={createMode ? setCapacityInQuote : empty}
@ -222,7 +226,7 @@ const IntervalsArguments = ({
/>
<ArgumentInput
endString="seconds"
label="_intervals[1]"
label="_intervals[0]"
value={tuneInterval ?? ""}
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."
@ -292,7 +296,7 @@ const TokenAndBooleansArguments = ({
rightText={"False"}
setLeftValue={() => setCapacityInQuote(true)}
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
value={fixedTerm}
@ -311,7 +315,6 @@ const TokenAndBooleansArguments = ({
value={selectedOption ?? ""}
onChange={handleChange}
options={possibleTokens}
maxWidth="calc(100vw - 70px)"
inputWidth="100%"
renderValue={(selected) => {
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 };
}
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) => {
const [isOpened, setIsOpened] = useState(false);
@ -45,17 +45,21 @@ export const SetAdjustmentParsed = (props) => {
<SetAdjustmentSteps {...props} />
</Box>
</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 }) => {
const createMode = args === undefined;
const [rate, setRate] = useState(args?.at(0));
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 = () => {
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 DistributorAbi } from "../../../../abi/GhostDistributor.json";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
import { config } from "../../../../config";
import { shorten, formatCurrency } from "../../../../helpers";
@ -18,20 +17,16 @@ import { shorten, formatCurrency } from "../../../../helpers";
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
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."
export const allPossibleFunctions = [
{ value: "auditReserves", label: "auditReserves" },
{ value: "setAdjustment", label: "setAdjustment" },
{ value: "create", label: "create" },
{ value: "setProposalThreshold", label: "setProposalThreshold" },
{ value: "setLateQuorumVoteExtension", label: "setLateQuorumVoteExtension" },
{ value: "auditReserves", label: "auditReserves" },
{ value: "setAdjustment", label: "setAdjustment" },
{ value: "create", label: "create" },
];
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi, GovernorAbi];
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
const identifyAction = (calldata) => {
let decoded = { functionName: "Unknown", args: [] };
@ -49,58 +44,78 @@ const identifyAction = (calldata) => {
return decoded;
}
export const parseFunctionCalldata = ({
metadata,
index,
chainId,
nativeCoin,
removeCalldata,
isTable = true,
}) => {
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
const { label, calldata, target, value } = metadata;
const { functionName, args } = identifyAction(calldata);
const labelOrName = label ?? (functionName ?? "Unknown");
const remove = removeCalldata && (() => removeCalldata(index));
const props = {
isTable,
calldata,
label: labelOrName,
chainId: chainId,
remove: removeCalldata,
nativeCoin,
value,
target,
args,
id: index,
};
switch (functionName) {
case allPossibleFunctions[0].value:
return <AuditReservesParsed key={index} {...props} />;
case allPossibleFunctions[1].value:
return <SetAdjustmentParsed key={index} {...props} />;
case allPossibleFunctions[2].value:
return <CreateBondParsed {...props} />;
case allPossibleFunctions[3].value:
return <SetProposalThresholdParsed key={index} {...props} />;
case allPossibleFunctions[4].value:
return <SetLateQuorumExtensionParsed key={index} {...props} />;
case "auditReserves":
return <AuditReservesParsed
isTable
key={index}
calldata={calldata}
label={labelOrName}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
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:
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) => {
switch (functionName) {
case allPossibleFunctions[1].value:
case "auditReserves":
return null;
case "setAdjustment":
return SetAdjustmentSteps;
case allPossibleFunctions[2].value:
case "create":
return CreateBondSteps;
case allPossibleFunctions[3].value:
return SetProposalThresholdSteps;
case allPossibleFunctions[4].value:
return SetLateQuorumExtensionSteps;
default:
return null;
}
@ -108,16 +123,12 @@ export const getFunctionArguments = (functionName) => {
export const getFunctionCalldata = (functionName, chainId) => {
switch (functionName) {
case allPossibleFunctions[0].value:
case "auditReserves":
return prepareAuditReservesCalldata(chainId);
case allPossibleFunctions[1].value:
case "setAdjustment":
return prepareSetAdjustmentCalldata(chainId);
case allPossibleFunctions[2].value:
case "create":
return prepareCreateBondCalldata(chainId);
case allPossibleFunctions[3].value:
return prepareSetProposalThresholdCalldata(chainId);
case allPossibleFunctions[4].value:
return prepareSetLateQuorumExtensionCalldata(chainId);
default:
return null;
}
@ -125,16 +136,12 @@ export const getFunctionCalldata = (functionName, chainId) => {
export const getFunctionDescription = (functionName) => {
switch (functionName) {
case allPossibleFunctions[0].value:
case "auditReserves":
return prepareAuditReservesDescription;
case allPossibleFunctions[1].value:
case "setAdjustment":
return prepareSetAdjustmentDescription;
case allPossibleFunctions[2].value:
case "create":
return prepareCreateBondDescription;
case allPossibleFunctions[3].value:
return prepareSetProposalThresholdDescription;
case allPossibleFunctions[4].value:
return prepareSetLateQuorumExtensionDescription;
default:
return DEFAULT_DESCRIPTION;
}
@ -268,86 +275,34 @@ export const ParsedCell = (props) => {
}
};
if (props.isTable) {
return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.label}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.target)}</Typography>
</Link>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.calldata)}</Typography>
</Link>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
</TableCell>
<TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
</div>
</TableCell>
</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>
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.label}</Typography>
</Box>
</TableCell>
<Box display="flex" justifyContent="space-between">
<Typography>Target</Typography>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.target)}</Typography>
</Link>
</Box>
</TableCell>
<Box display="flex" justifyContent="space-between">
<Typography>Calldata</Typography>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.calldata)}</Typography>
</Link>
</Box>
</TableCell>
<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>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
</TableCell>
{(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>
<TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
</div>
</TableCell>
</TableRow>
)
}

View File

@ -1,24 +1,18 @@
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
export const convertStatusToColor = (status) => {
export const convertStatusToTemplate = (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:
return "#f2e370";
return 'darkGray';
case 2:
return 'warning';
case 4:
return 'success';
case 3:
return 'error';
default:
return "#a2b7ce";
return 'darkGray';
}
}

View File

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

View File

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

View File

@ -21,15 +21,14 @@ import InfoTooltip from "../../../components/Tooltip/InfoTooltip";
import Paper from "../../../components/Paper/Paper";
import Token from "../../../components/Token/Token";
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { Tab, Tabs } from "../../../components/Tabs/Tabs";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { formatNumber, formatCurrency } from "../../../helpers";
import { formatNumber } from "../../../helpers";
import { STAKING_ADDRESSES } from "../../../constants/addresses";
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
import { useGhstPrice, useStnkPrice } from "../../../hooks/prices";
import { isNetworkLegacy } from "../../../constants";
import ClaimConfirmationModal from "./ClaimConfirmationModal";
@ -53,10 +52,10 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const isSmallScreen = useMediaQuery("(max-width: 745px)");
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [isPayoutGhst, _] = useState(true);
const ghstPrice = useGhstPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("stake-isGhstPayout")
? localStorage.getItem("stake-isGhstPayout")
: false
);
const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address);
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
@ -66,14 +65,6 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
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 = () => {
setConfirmationModalOpen(false);
claimRefetch();
@ -82,6 +73,11 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
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`;
return (
@ -91,7 +87,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
onClose={() => closeConfirmationModal()}
chainid={chainId}
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}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
@ -110,6 +106,18 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
flexDirection={isSmallScreen ? "column" : "row"}
>
<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>
}
tooltip={warmupTooltip}
@ -118,35 +126,33 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
{isSmallScreen ? (
<MobileClaimInfo
setConfirmationModalOpen={setConfirmationModalOpen}
prepareBalance={claimableBalance}
prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
isPayoutGhst={isPayoutGhst}
claim={claim}
epoch={epoch}
isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
/>
) : (
<Table>
<StyledTableHeader className={classes.stakePoolHeaderText}>
<TableRow>
<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></TableCell>
</TableRow>
</StyledTableHeader>
<ClaimInfo
setConfirmationModalOpen={setConfirmationModalOpen}
prepareBalance={claimableBalance}
prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
isPayoutGhst={isPayoutGhst}
claim={claim}
epoch={epoch}
isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
/>
</Table>
@ -156,17 +162,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
);
};
const ClaimInfo = ({
setConfirmationModalOpen,
prepareBalance,
claim,
epoch,
isClaimable,
isPayoutGhst,
stnkSymbol,
ghstSymbol,
tokenPrice
}) => {
const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst, stnkSymbol, ghstSymbol }) => {
return (
<TableBody>
<TableRow>
@ -180,10 +176,7 @@ const ClaimInfo = ({
</TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}>
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}>
{formatCurrency(prepareBalance, 5, isPayoutGhst ? ghstSymbol : stnkSymbol)}
</Typography>
<Typography variant="body2" gutterBottom={false} style={{ lineHeight: 1.4 }}>
{formatCurrency(prepareBalance * tokenPrice, 2)}
{`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? ghstSymbol : stnkSymbol}`}
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}>
@ -200,17 +193,7 @@ const ClaimInfo = ({
);
};
const MobileClaimInfo = ({
setConfirmationModalOpen,
prepareBalance,
epoch,
claim,
isPayoutGhst,
isClaimable,
ghstSymbol,
stnkSymbol,
tokenPrice,
}) => {
const MobileClaimInfo = ({ setConfirmationModalOpen, prepareBalance, epoch, claim, isPayoutGhst, isClaimable, ghstSymbol, stnkSymbol }) => {
return (
<Box mt="10px">
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
@ -221,15 +204,10 @@ const MobileClaimInfo = ({
</Box>
<DataRow
title="Staked Amount"
title="Amount"
balance={formatNumber(prepareBalance, 7)}
/>
<DataRow
title="Price Estimation"
balance={formatCurrency(prepareBalance * tokenPrice, 2)}
/>
<DataRow
title="Claimed in"
balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))}

View File

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

View File

@ -52,7 +52,7 @@ export const TotalDeposit = props => {
const _props = {
...props,
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)}`;

View File

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

View File

@ -84,7 +84,7 @@ export const FatsoBacking = props => {
const _props = {
...props,
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);

View File

@ -1,23 +1,23 @@
import { Grid, Box, Typography, useTheme } from "@mui/material";
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 { SecondaryButton } from "../../../components/Button";
import { formatNumber, formatCurrency } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { isNetworkLegacy } from "../../../constants"
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import {
useFtsoPrice,
useStnkPrice,
useGhstPrice,
useReservePrice,
useNativePrice,
} from "../../../hooks/prices";
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { EMPTY_ADDRESS, WETH_ADDRESSES } from "../../../constants/addresses";
const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => {
const navigate = useNavigate();
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%">
<SecondaryButton
onClick={() => tokenUrlParams
? navigate({
pathname: tokenUrl,
search: tokenUrlParams.toString()
})
: window.open(tokenUrl, '_blank')
}
onClick={() => navigate({
pathname: tokenUrl,
search: tokenUrlParams.toString()
})}
fullWidth
>
Get {tokenName}
@ -78,7 +75,6 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
const TokenInfo = ({ chainId, isMobileScreen }) => {
const theme = useTheme();
const { network } = useParams();
const { address } = useAccount();
const config = useConfig();
@ -87,15 +83,18 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
const nativePrice = useNativePrice(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const reservePrice = useReservePrice(chainId);
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { data: nativeBalance } = useBalanceNative({ 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: 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">
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`}
tokenUrl="/dex/uniswap"
tokenUrlParams={createSearchParams({
from: `${reserveAddress}`,
to: `${ftsoAddress}`,
@ -117,7 +116,20 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
/>
<TokenTab
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({
from: `${reserveAddress}`,
to: `${ghstAddress}`,
@ -126,31 +138,36 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
tokenName={ghstSymbol}
balance={ghstBalance}
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
isMobileScreen={isMobileScreen}
tokenUrl={"https://ghostchain.io/faucet/"}
theme={theme}
tokenName={nativeSymbol}
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)}
price={reservePrice}
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl={`/${network}/dex/uniswap`}
tokenUrlParams={createSearchParams({
from: `${EMPTY_ADDRESS}`,
to: `${WETH_ADDRESSES[chainId]}`,
})}
tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
tokenUrlParams=""
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.`}
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}
tokenName={nativeSymbol}
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)}
price={reservePrice}
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
/>
</Box>
)}
</Grid>
)
}

View File

@ -52,16 +52,3 @@ export const timeConverter = (time, max = 7200, maxText = "long ago") => {
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") {
switch (chainId) {
case 63:
name = "wmETC"
break;
default:
name = "wETH";
}
}
return name;

View File

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

View File

@ -8,24 +8,15 @@ import { useUnstableProvider } from "./UnstableProvider"
const MetadataProvider = createContext(null)
export const useMetadata = () => useContext(MetadataProvider)
const CACHE_VERSION = "v2"
export const MetadataProviderProvider = ({ children }) => {
const { client, chainId } = useUnstableProvider()
const { data: metadata } = useSWR(
client && chainId ? ["metadata", client, chainId] : null,
async ([_, client]) => {
const storageKey = `metadata-${chainId}-${CACHE_VERSION}`
const storageKey = `metadata-${chainId}`
const storedMetadata = sessionStorage.getItem(storageKey)
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) =>
client._request("state_getMetadata", [], {
onSuccess: resolve,

View File

@ -12,5 +12,3 @@ export * from "./useBlockCommitments";
export * from "./useApplauseDetails";
export * from "./useBabeSlots";
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 { useMetadata } from "./MetadataProvider"
export const useErasTotalStake = ({ epochIndex }) => {
export const useErasTotalStake = ({ eraIndex }) => {
const { chainHead$, chainId } = useUnstableProvider()
const metadata = useMetadata()
const { data: eraTotalStake } = useSWRSubscription(
chainHead$ && chainId && metadata
? ["eraTotalStake", chainHead$, epochIndex, chainId, metadata]
? ["eraTotalStake", chainHead$, eraIndex, chainId, metadata]
: null,
([_, chainHead$, epochIndex, chainId, metadata], { next }) => {
([_, chainHead$, eraIndex, chainId, metadata], { next }) => {
const { finalized$, storage$ } = chainHead$
const subscription = finalized$.pipe(
filter(Boolean),
@ -20,7 +20,7 @@ export const useErasTotalStake = ({ epochIndex }) => {
const builder = getDynamicBuilder(getLookupFn(metadata))
const eraTotalStake = builder.buildStorage("Staking", "ErasTotalStake")
return storage$(blockInfo?.hash, "value", () =>
eraTotalStake?.keys.enc(epochIndex)
eraTotalStake?.keys.enc(eraIndex)
).pipe(
filter(Boolean),
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 { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";
import { keccak256, stringToBytes } from 'viem'
import { isNetworkLegacyType } from "../../constants";
import { config } from "../../config";
import {
@ -18,23 +16,6 @@ import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQu
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
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) => {
const { data, error } = useReadContract({
abi: GovernorAbi,
@ -115,7 +96,6 @@ export const useMinQuorum = (chainId) => {
export const useProposalThreshold = (chainId, name) => {
const decimals = getTokenDecimals(name);
const { proposalCount } = useProposalCount(chainId);
const { data } = useReadContract({
abi: GovernorStorageAbi,
@ -133,17 +113,13 @@ export const useProposalThreshold = (chainId, name) => {
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);
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);
}
@ -344,22 +320,22 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: proposalDetails } = useReadContracts({
contracts: searchedIndexes?.map(proposalId => {
contracts: searchedIndexes?.map(index => {
return {
abi: GovernorStorageAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
functionName: "proposalDetails",
args: [proposalId],
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
args: [index],
scopeKey: `proposalDetails-${chainId}-${index}`,
chainId: chainId,
}
})
});
const { data: proposalDeadlines } = useReadContracts({
contracts: indexes?.map((_, index) => {
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
@ -374,9 +350,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: proposalVotes } = useReadContracts({
contracts: indexes?.map((_, index) => {
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
@ -391,9 +367,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: proposalStates } = useReadContracts({
contracts: indexes?.map((_, index) => {
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
@ -408,9 +384,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: proposalSnapshots } = useReadContracts({
contracts: indexes?.map((_, index) => {
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
@ -425,8 +401,8 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: proposalQuorums } = useReadContracts({
contracts: proposalSnapshots?.map((proposal, index) => {
const timepoint = proposal?.result;
contracts: indexes?.map(index => {
const timepoint = proposalSnapshots?.at(index)?.result;
return {
abi: GovernorAbi,
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
@ -439,8 +415,8 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: pastTotalSupplies } = useReadContracts({
contracts: proposalSnapshots?.map((proposal, index) => {
const timepoint = proposal?.result;
contracts: indexes?.map(index => {
const timepoint = proposalSnapshots?.at(index)?.result;
return {
abi: ghstAbi,
address: ghstAddress,
@ -453,9 +429,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
});
const { data: proposalProposer } = useReadContracts({
contracts: indexes?.map((_, index) => {
contracts: indexes?.map(index => {
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
return {
@ -469,69 +445,32 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
})
});
const hashes = useMemo(() => {
return indexes?.map((_, index) => {
let result = { short: index + 1, full: undefined };
const proposalId = searchedIndexes
? searchedIndexes?.at(index)
: proposalsDetailsAt?.at(index)?.result?.at(0);
const hashes = indexes?.map(index => {
let result = { short: index + 1, full: undefined };
const proposalId = searchedIndexes
? searchedIndexes?.at(0)
: proposalsDetailsAt?.at(index)?.result?.at(0);
if (proposalId) {
const hash = "0x" + proposalId.toString(16);
result.short = hash.slice(-5);
result.full = hash;
}
return result;
});
}, [indexes, searchedIndexes, proposalsDetailsAt]);
if (proposalId) {
const hash = "0x" + proposalId.toString(16);
result.short = hash.slice(-5);
result.full = hash;
}
return result;
});
const voteValues = 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 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: "" },
const proposals = indexes?.map(index => ({
hashes: hashes?.at(index),
proposer: proposalProposer?.at(index)?.result,
details: proposalDetailsPrepared?.at(index)?.result,
details: proposalsDetailsAt?.at(index)?.result,
deadline: proposalDeadlines?.at(index)?.result ?? 0n,
snapshot: proposalSnapshots?.at(index)?.result ?? 0n,
state: proposalStates?.at(index)?.result ?? 0,
pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.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)),
voteValue: voteValues?.at(index),
voteTarget: voteTargets?.at(index),
snapshot: new DecimalBigNumber(proposalSnapshots?.at(index)?.result ?? 0n, decimals),
votes: proposalVotes?.at(index)?.result?.map(
vote => new DecimalBigNumber(vote ?? 0n, decimals),
),
}));
return { proposals };
@ -545,8 +484,7 @@ export const releaseLocked = async (chainId, account, proposalId) => {
functionName: 'releaseLocked',
args: [proposalId],
account: account,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);
@ -573,8 +511,7 @@ export const executeProposal = async (chainId, account, proposalId) => {
functionName: 'execute',
args: [proposalId],
account: account,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);
@ -601,8 +538,7 @@ export const castVote = async (chainId, account, proposalId, support) => {
functionName: 'castVote',
args: [proposalId, support],
account: account,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);
@ -633,8 +569,7 @@ export const propose = async (chainId, account, functions, description) => {
functionName: 'propose',
args: [targets, values, calldatas, description],
account: account,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);

View File

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

View File

@ -4,7 +4,6 @@ import toast from "react-hot-toast";
import { config } from "../../config";
import { isNetworkLegacyType } from "../../constants";
import { STAKING_ADDRESSES } from "../../constants/addresses";
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
@ -20,7 +19,7 @@ export const useCurrentIndex = (chainId) => {
chainId: chainId,
});
const currentIndexRaw = index ? index : 1000000000n;
const currentIndexRaw = index ? index : 0n;
const currentIndex = new DecimalBigNumber(currentIndexRaw, 9);
return { currentIndex, refetch };
@ -69,16 +68,16 @@ export const useWarmupInfo = (chainId, address) => {
const { data: info, refetch } = useReadContract({
abi: StakingAbi,
address: STAKING_ADDRESSES[chainId],
functionName: "warmupInfo",
functionName: "getWarmupInfo",
args: [address],
scopeKey: `warmupInfo-${address}-${chainId}`,
scopeKey: `getWarmupInfo-${address}-${chainId}`,
chainId: chainId,
});
const warmupInfo = {
deposit: info ? new DecimalBigNumber(info.at(0), 9) : new DecimalBigNumber(0n, 9),
shares: info ? info.at(1) : 0n,
expiry: info ? Number(info.at(2)) : 0,
deposit: info ? new DecimalBigNumber(info.deposit, 9) : new DecimalBigNumber(0n, 9),
shares: info ? info.shares : 0n,
expiry: info ? Number(info.expiry) : 0,
}
return { warmupInfo, refetch }
@ -249,8 +248,7 @@ const executeOnChainTransaction = async (
functionName,
args,
account,
chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId
});
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 { isNetworkLegacyType } from "../../constants";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers";
import { tokenNameConverter } from "../../helpers/tokenConverter";
import { config } from "../../config";
import { WETH_ADDRESSES } from "../../constants/addresses";
export const usePastVotes = (chainId, name, timepoint, address) => {
const decimals = getTokenDecimals(name);
@ -61,29 +59,20 @@ export const useTotalSupply = (chainId, name) => {
};
export const useBalance = (chainId, name, address) => {
let contractAddress = getTokenAddress(chainId, name);
let isNative = false;
let requestObj = {
const contractAddress = getTokenAddress(chainId, name);
const { data, refetch, error } = useInnerBalance({
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 decimals = data ? data.decimals : getTokenDecimals(name);
const balance = new DecimalBigNumber(balancePrepared, decimals);
return { balance, refetch, contractAddress, isNative };
return { balance, refetch, contractAddress };
}
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) => {
const contractAddress = getTokenAddress(chainId, name);
const { data, refetch } = useReadContract({
abi: getTokenAbi(name),
address: contractAddress,
@ -211,8 +199,7 @@ export const approveTokens = async (chainId, name, owner, spender, value) => {
functionName: 'approve',
args: [spender, value],
account: owner,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);
@ -238,8 +225,7 @@ export const mintDai = async (chainId, account, value) => {
args: [account],
account: account,
value: value,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);
@ -264,8 +250,7 @@ export const burnDai = async (chainId, account, value) => {
functionName: 'burn',
args: [value],
account: account,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
const txHash = await writeContract(config, request);
@ -290,8 +275,7 @@ export const depositNative = async (chainId, account, value) => {
functionName: 'deposit',
account: account,
chainId: chainId,
value: value,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
value: value
});
const txHash = await writeContract(config, request);
@ -316,8 +300,7 @@ export const withdrawWeth = async (chainId, account, value) => {
functionName: 'withdraw',
args: [value],
account: account,
chainId: chainId,
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
chainId: chainId
});
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 { bigIntSqrt } from "../../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenAddress } from "../helpers";
@ -41,9 +40,6 @@ export const useTotalReserves = (chainId) => {
export const useLpValuation = (chainId, pairAddress, pairSupply) => {
const convertedPairAddress = getTokenAddress(chainId, pairAddress);
const originalCoefficient = useOrinalCoefficient(chainId);
const reservePrice = useReservePrice(chainId);
const { data: valuationRaw } = useReadContract({
abi: TreasuryAbi,
address: DAO_TREASURY_ADDRESSES[chainId],
@ -56,18 +52,7 @@ export const useLpValuation = (chainId, pairAddress, pairSupply) => {
chainId,
});
const sqrtReservePrice = reservePrice?._value
? bigIntSqrt(reservePrice._value)
: 0n;
const sqrtOriginalCoefficient = originalCoefficient?._value
? bigIntSqrt(originalCoefficient._value)
: 1n;
const valuationPrepared = valuationRaw
? valuationRaw * sqrtReservePrice / sqrtOriginalCoefficient
: 0n;
const valuationPrepared = valuationRaw ? valuationRaw : 0n;
const valuation = new DecimalBigNumber(valuationPrepared, 9);
return valuation;

View File

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

View File

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

View File

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

View File

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