ghost-dao-interface/src/containers/Dex/Dex.jsx
Uncle Fatso d4446f6fb1
version 0.0.22
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-04-28 14:03:56 +03:00

356 lines
16 KiB
JavaScript

import {
Box,
Container,
Divider,
Typography,
InputLabel,
FormControl,
OutlinedInput,
useMediaQuery,
useTheme,
} from "@mui/material";
import SettingsIcon from '@mui/icons-material/Settings';
import { useEffect, useState } from "react";
import { useParams, useLocation, useSearchParams } from "react-router-dom";
import { Helmet } from "react-helmet";
import ReactGA from "react-ga4";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import Modal from "../../components/Modal/Modal";
import PageTitle from "../../components/PageTitle/PageTitle";
import Paper from "../../components/Paper/Paper";
import SwapCard from "../../components/Swap/SwapCard";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { Tab, Tabs } from "../../components/Tabs/Tabs";
import {
UNISWAP_V2_ROUTER,
UNISWAP_V2_FACTORY,
DAI_ADDRESSES,
FTSO_ADDRESSES,
} from "../../constants/addresses";
import { useTokenSymbol } from "../../hooks/tokens";
import PoolContainer from "./PoolContainer";
import SwapContainer from "./SwapContainer";
import TokenModal from "./TokenModal";
const Dex = ({ chainId, address, connect }) => {
const location = useLocation();
const pathname = useParams();
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const [currentQueryParameters, setSearchParams] = useSearchParams();
const newQueryParameters = new URLSearchParams();
const [isSwap, setIsSwap] = useState(false);
const [settingsOpen, handleSettingsOpen] = useState(false);
const [topTokenListOpen, setTopTokenListOpen] = useState(false);
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
const [tokenAddressTop, setTokenAddressTop] = useState(DAI_ADDRESSES[chainId]);
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
const { symbol: tokenNameTop } = useTokenSymbol(chainId, tokenAddressTop);
const { symbol: tokenNameBottom } = useTokenSymbol(chainId, tokenAddressBottom);
useEffect(() => {
if (currentQueryParameters.has("pool")) {
setIsSwap(false);
newQueryParameters.set("pool", true);
} else {
setIsSwap(true);
newQueryParameters.delete("pool");
}
if (currentQueryParameters.has("from")) {
setTokenAddressTop(currentQueryParameters.get("from"));
newQueryParameters.set("from", currentQueryParameters.get("from"));
} else {
setTokenAddressTop(DAI_ADDRESSES[chainId]);
newQueryParameters.set("from", DAI_ADDRESSES[chainId]);
}
if (currentQueryParameters.has("to")) {
setTokenAddressBottom(currentQueryParameters.get("to"));
newQueryParameters.set("to", currentQueryParameters.get("to"));
} else {
setTokenAddressBottom(FTSO_ADDRESSES[chainId]);
newQueryParameters.set("to", FTSO_ADDRESSES[chainId]);
}
setSearchParams(newQueryParameters)
}, [currentQueryParameters])
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname + location.search });
}, [location])
const dexAddresses = {
router: UNISWAP_V2_ROUTER[chainId],
factory: UNISWAP_V2_FACTORY[chainId],
}
const onCardsSwap = () => {
const tmpFrom = currentQueryParameters.get("from");
const tmpTo = currentQueryParameters.get("to");
if (currentQueryParameters.has("pool")) newQueryParameters.set("pool", true);
else newQueryParameters.delete("pool");
newQueryParameters.set("from", tmpTo);
newQueryParameters.set("to", tmpFrom);
setSearchParams(newQueryParameters);
}
const changeSwapTab = (swap) => {
if (swap) newQueryParameters.delete("pool");
else newQueryParameters.set("pool", true);
newQueryParameters.set("from", currentQueryParameters.get("from"));
newQueryParameters.set("to", currentQueryParameters.get("to"));
setSearchParams(newQueryParameters);
}
const setInnerTokenAddressTop = (tokenAddress) => {
if (currentQueryParameters.has("pool")) newQueryParameters.set("pool", true);
else newQueryParameters.delete("pool");
if (currentQueryParameters.get("to") === tokenAddress) {
newQueryParameters.set("from", currentQueryParameters.get("from"));
} else newQueryParameters.set("from", tokenAddress);
newQueryParameters.set("to", currentQueryParameters.get("to"));
setSearchParams(newQueryParameters);
}
const setInnerTokenAddressBottom = (tokenAddress) => {
if (currentQueryParameters.has("pool")) newQueryParameters.set("pool", true);
else newQueryParameters.delete("pool");
newQueryParameters.set("from", currentQueryParameters.get("from"));
if (currentQueryParameters.get("from") === tokenAddress) {
newQueryParameters.set("to", currentQueryParameters.get("to"));
} else newQueryParameters.set("to", tokenAddress);
setSearchParams(newQueryParameters);
}
const setSlippageInner = (value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value);
localStorage.setItem("dex-slippage", value);
}
}
const setSecondsToWaitInner = (value) => {
localStorage.setItem("dex-deadline", value);
setSecondsToWait(value);
}
const setFormatDecimalsInner = (value) => {
if (Number(value) <= 17) {
localStorage.setItem("dex-decimals", value);
setFormatDecimals(value);
}
}
return (
<Box height="calc(100vh - 43px)">
<Helmet>
<title>ghostSwap | The pure web3 legacy v2 swap</title>
<meta name="description" content="ghostSwap carries the legacy of V2 DEX interfaces - no KYC, no bloatware, just pure decentralized swapping. Enjoy gas-efficient trades, deep liquidity, and full self-custody, enabling token swaps as DeFi intended." />
<meta name="keywords" content="ghostSwap, Uniswap, Uniswap V2, Sushiswap, Sushiswap v2, Pancakeswap, Pancakeswap v2, Bancor, DEX, swap, web3, LP, liquidity provider, decentralized exchange, legacy v2, legacy v2 swap" />
<meta property="og:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostSwap-Featured_Image.png" />
<meta property="og:title" content="ghostDAO | The DeFi 2.0 cross-chain reserve currency" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:type" content="website" />
<meta property="og:description" content="ghostSwap carries the legacy of V2 DEX interfaces - no KYC, no bloatware, just pure decentralized swapping. Enjoy gas-efficient trades, deep liquidity, and full self-custody, enabling token swaps as DeFi intended." />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@realGhostChain" />
<meta name="twitter:title" content="ghostDAO | The DeFi 2.0 cross-chain reserve currency" />
<meta name="twitter:description" content="ghostSwap carries the legacy of V2 DEX interfaces - no KYC, no bloatware, just pure decentralized swapping. Enjoy gas-efficient trades, deep liquidity, and full self-custody, enabling token swaps as DeFi intended." />
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostSwap-Featured_Image.png" />
</Helmet>
<PageTitle
name={`${pathname.name.charAt(0).toUpperCase() + pathname.name.slice(1).toLowerCase()} V2 Classic`}
subtitle="Classic interface to V2 smart contracts."
/>
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
height: "calc(100vh - 153px)",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<Modal
maxWidth="376px"
minHeight="200px"
open={settingsOpen}
headerText={"Settings"}
onClose={() => handleSettingsOpen(false)}
>
<Box>
<InputLabel htmlFor="slippage">Slippage</InputLabel>
<Box mt="8px">
<FormControl variant="outlined" color="primary" fullWidth>
<OutlinedInput
inputProps={{ "data-testid": "slippage-dex" }}
type="text"
id="slippage-dex"
value={slippage}
onChange={event => setSlippageInner(event.currentTarget.value)}
endAdornment="%"
/>
</FormControl>
</Box>
<Box mt="8px">
<Typography variant="body2" color="textSecondary">
Transaction may revert if price changes by more than slippage %
</Typography>
</Box>
</Box>
<Box mt="32px">
<InputLabel htmlFor="recipient">Transaction deadline</InputLabel>
<Box mt="8px">
<FormControl variant="outlined" color="primary" fullWidth>
<OutlinedInput
inputProps={{ "data-testid": "seconds-to-wait" }}
type="number"
id="seconds-to-wait"
value={secondsToWait}
onChange={event => setSecondsToWaitInner(event.currentTarget.value)}
endAdornment="seconds"
/>
</FormControl>
</Box>
<Box mt="8px">
<Typography variant="body2" color="textSecondary">
How long transaction is valid in the transaction pool
</Typography>
</Box>
</Box>
<Box mt="32px">
<InputLabel htmlFor="recipient">Decimal representation</InputLabel>
<Box mt="8px">
<FormControl variant="outlined" color="primary" fullWidth>
<OutlinedInput
inputProps={{ "data-testid": "decimals-to-wait" }}
type="number"
id="decimals-to-wait"
value={formatDecimals}
onChange={event => setFormatDecimalsInner(event.currentTarget.value)}
endAdornment="decimals"
/>
</FormControl>
</Box>
<Box mt="8px">
<Typography variant="body2" color="textSecondary">
Number of decimals to be shown in token balances
</Typography>
</Box>
</Box>
</Modal>
<TokenModal
account={address}
chainId={chainId}
listOpen={topTokenListOpen}
setListOpen={setTopTokenListOpen}
setTokenAddress={setInnerTokenAddressTop}
/>
<TokenModal
account={address}
chainId={chainId}
listOpen={bottomTokenListOpen}
setListOpen={setBottomTokenListOpen}
setTokenAddress={setInnerTokenAddressBottom}
/>
<Box sx={{ width: "100%", maxWidth: "420px", mt: "15px" }}>
<Paper
fullWidth
enableBackground
topRight={
<GhostStyledIcon
component={SettingsIcon}
viewBox="0 0 23 23"
style={{ display: "flex", alignItems: "center", cursor: "pointer" }}
onClick={handleSettingsOpen}
/>
}
headerContent={
<Tabs
centered
textColor="primary"
indicatorColor="primary"
value={isSwap ? 0 : 1}
aria-label="Dex swap menu"
onChange={(_, view) => changeSwapTab(view === 0)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="dex-swap-button" label="Swap" style={{ fontSize: "1.5rem" }} />
<Tab aria-label="dex-add-button" label="Pool" style={{ fontSize: "1.5rem" }} />
</Tabs>
}
>
<Box height="350px">
{isSwap ?
<SwapContainer
tokenNameTop={tokenNameTop}
tokenNameBottom={tokenNameBottom}
onCardsSwap={onCardsSwap}
chainId={chainId}
address={address}
dexAddresses={dexAddresses}
connect={connect}
slippage={slippage}
secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen}
setIsSwap={setIsSwap}
formatDecimals={formatDecimals}
/>
:
<PoolContainer
tokenNameTop={tokenNameTop}
tokenNameBottom={tokenNameBottom}
onCardsSwap={onCardsSwap}
chainId={chainId}
address={address}
dexAddresses={dexAddresses}
connect={connect}
slippage={slippage}
secondsToWait={secondsToWait}
setTopTokenListOpen={setTopTokenListOpen}
setBottomTokenListOpen={setBottomTokenListOpen}
formatDecimals={formatDecimals}
/>
}
</Box>
</Paper>
</Box>
</Container>
</Box>
)
}
export default Dex;