356 lines
16 KiB
JavaScript
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;
|