version 0.0.22
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2
.env.template
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
VITE_APP_TRACKING_ID=
|
||||||
|
VITE_APP_REFERRAL_ADDRESS=
|
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
33
eslint.config.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
ecmaFeatures: { jsx: true },
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...js.configs.recommended.rules,
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
32
index.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="https://ipfs.io/ipfs/QmRgJ2UbzpM58te1R91bC2neWsS28F2QatBtzerM1JekyC" type="image/png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;700&display=swap" rel="stylesheet">
|
||||||
|
<title>ghostDAO | The DeFi 2.0 cross-chain reserve currency</title>
|
||||||
|
<meta name="description" content="ghostDAO is pioneering a decentralized cross-chain reserve currency powered by DeFi 2.0 protocol." />
|
||||||
|
<meta name="keywords" content="DAO, Defi, Cross-chain arbitrage, Blockchain, Crypto, Ethereum, ghostDAO, ghostchain, freedom, privacy, Sepolia" />
|
||||||
|
|
||||||
|
<meta property="og:image" content="https://ghostchain.io/wp-content/uploads/2025/01/ghostDAO-Featured_Image-1.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="ghostDAO is pioneering a decentralized cross-chain reserve currency powered by DeFi 2.0 protocol." />
|
||||||
|
|
||||||
|
<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="ghostDAO is pioneering a decentralized cross-chain reserve currency powered by DeFi 2.0 protocol." />
|
||||||
|
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/01/ghostDAO-Featured_Image-1.png" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
47
package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "ghost-dao-interface",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.22",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "set \"GENERATE_SOURCEMAP=false\" && vite build",
|
||||||
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@ethersproject/address": "^5.8.0",
|
||||||
|
"@ethersproject/bignumber": "^5.8.0",
|
||||||
|
"@ethersproject/units": "^5.8.0",
|
||||||
|
"@mui/icons-material": "^6.4.7",
|
||||||
|
"@mui/material": "^6.4.7",
|
||||||
|
"@mui/utils": "^6.4.6",
|
||||||
|
"@tanstack/react-query": "^5.67.2",
|
||||||
|
"@tanstack/react-query-devtools": "^5.67.2",
|
||||||
|
"@wagmi/core": "^2.16.7",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-ga4": "^2.1.0",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-hot-toast": "^2.5.2",
|
||||||
|
"react-router-dom": "^7.3.0",
|
||||||
|
"scss": "^0.2.4",
|
||||||
|
"viem": "^2.26.5",
|
||||||
|
"wagmi": "^2.14.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.21.0",
|
||||||
|
"@types/react": "^19.0.10",
|
||||||
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"eslint": "^9.21.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
|
"globals": "^15.15.0",
|
||||||
|
"sass-embedded": "^1.85.1",
|
||||||
|
"vite": "^6.2.0",
|
||||||
|
"vite-plugin-svgr": "^4.3.0"
|
||||||
|
}
|
||||||
|
}
|
5935
pnpm-lock.yaml
Normal file
218
src/App.jsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import "./style.scss";
|
||||||
|
|
||||||
|
import { useMediaQuery } from "@mui/material";
|
||||||
|
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 { 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";
|
||||||
|
import Sidebar from "./components/Sidebar/Sidebar";
|
||||||
|
import TopBar from "./components/TopBar/TopBar";
|
||||||
|
|
||||||
|
import { shouldTriggerSafetyCheck } from "./helpers";
|
||||||
|
import { isNetworkAvailable } from "./constants";
|
||||||
|
import useTheme from "./hooks/useTheme";
|
||||||
|
import { dark as darkTheme } from "./themes/dark.js";
|
||||||
|
import { girth as gTheme } from "./themes/girth.js";
|
||||||
|
import { light as lightTheme } from "./themes/light.js";
|
||||||
|
|
||||||
|
// Dynamic Imports for code splitting
|
||||||
|
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 Dex = lazy(() => import("./containers/Dex/Dex"));
|
||||||
|
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
|
||||||
|
|
||||||
|
const PREFIX = "App";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
drawer: `${PREFIX}-drawer`,
|
||||||
|
content: `${PREFIX}-content`,
|
||||||
|
contentShift: `${PREFIX}-contentShift`,
|
||||||
|
toolbar: `${PREFIX}-toolbar`,
|
||||||
|
drawerPaper: `${PREFIX}-drawerPaper`,
|
||||||
|
notification: `${PREFIX}-notification`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledDiv = styled("div")(({ theme }) => ({
|
||||||
|
[`& .${classes.drawer}`]: {
|
||||||
|
[theme.breakpoints.up("md")]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.content}`]: {
|
||||||
|
flexGrow: 1,
|
||||||
|
padding: "15px",
|
||||||
|
transition: theme.transitions.create("margin", {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: transitionDuration,
|
||||||
|
}),
|
||||||
|
marginLeft: drawerWidth,
|
||||||
|
marginTop: "-48.5px",
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.contentShift}`]: {
|
||||||
|
transition: theme.transitions.create("margin", {
|
||||||
|
easing: theme.transitions.easing.easeOut,
|
||||||
|
duration: transitionDuration,
|
||||||
|
}),
|
||||||
|
marginLeft: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// necessary for content to be below app bar
|
||||||
|
[`& .${classes.toolbar}`]: theme.mixins.toolbar,
|
||||||
|
|
||||||
|
[`& .${classes.drawerPaper}`]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.notification}`]: {
|
||||||
|
marginLeft: "264px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 😬 Sorry for all the console logging
|
||||||
|
const DEBUG = false;
|
||||||
|
|
||||||
|
// 🛰 providers
|
||||||
|
if (DEBUG) console.log("📡 Connecting to Mainnet Ethereum");
|
||||||
|
// 🔭 block explorer URL
|
||||||
|
// const blockExplorer = targetNetwork.blockExplorer;
|
||||||
|
|
||||||
|
const drawerWidth = 264;
|
||||||
|
const transitionDuration = 969;
|
||||||
|
function App() {
|
||||||
|
const location = useLocation();
|
||||||
|
const [theme, toggleTheme] = useTheme();
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
const { connect, error: errorMessage } = useConnect();
|
||||||
|
const tryConnectInjected = () => connect({ connector: injected() });
|
||||||
|
|
||||||
|
const bondIndexes = [];
|
||||||
|
|
||||||
|
const [wrongNetworkToastId, setWrongNetworkToastId] = useState(null);
|
||||||
|
const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|
||||||
|
const { address = "", chainId: addressChainId, isConnected, isReconnecting } = useAccount();
|
||||||
|
|
||||||
|
const provider = usePublicClient();
|
||||||
|
const chainId = useChainId();
|
||||||
|
|
||||||
|
const [migrationModalOpen, setMigrationModalOpen] = useState(false);
|
||||||
|
const migModalClose = () => {
|
||||||
|
setMigrationModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSmallerScreen = useMediaQuery("(max-width: 1047px)");
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 600px)");
|
||||||
|
|
||||||
|
async function loadDetails(whichDetails) {
|
||||||
|
const loadProvider = provider;
|
||||||
|
|
||||||
|
if (whichDetails === "app") {
|
||||||
|
loadApp(loadProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whichDetails === "account" && address && isConnected) {
|
||||||
|
loadAccount(loadProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldTriggerSafetyCheck()) {
|
||||||
|
toast.success("Safety Check: Always verify you're on app.dao.ghostchain.io!", { duration: 5000 });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isConnected && chainId !== addressChainId) {
|
||||||
|
const toastId = toast.loading("You are connected to wrong network. Use Sepolia Testnet please.", {
|
||||||
|
position: 'bottom-right'
|
||||||
|
});
|
||||||
|
setWrongNetworkToastId(toastId);
|
||||||
|
} else {
|
||||||
|
if (wrongNetworkToastId) {
|
||||||
|
toast.dismiss(wrongNetworkToastId);
|
||||||
|
setWrongNetworkToastId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [chainId, addressChainId, isConnected])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errorMessage) {
|
||||||
|
toast.error("No injected connectors found in your browser. Download Metamask extension or visit EIP-1193 for more details.", { duration: 5000 });
|
||||||
|
}
|
||||||
|
}, [errorMessage]);
|
||||||
|
|
||||||
|
const handleDrawerToggle = () => {
|
||||||
|
setMobileOpen(!mobileOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSidebarClose = () => {
|
||||||
|
setIsSidebarExpanded(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme;
|
||||||
|
|
||||||
|
const chainExists = isNetworkAvailable(chainId, addressChainId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSidebarExpanded) handleSidebarClose();
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledDiv>
|
||||||
|
<ThemeProvider theme={themeMode}>
|
||||||
|
<CssBaseline />
|
||||||
|
<div className={`app ${isSmallerScreen && "tablet"} ${isSmallScreen && "mobile"} ${theme}`}>
|
||||||
|
<Toaster>{t => <Messages toast={t} />}</Toaster>
|
||||||
|
<TopBar
|
||||||
|
address={address}
|
||||||
|
chainId={addressChainId ? addressChainId : chainId}
|
||||||
|
handleDrawerToggle={handleDrawerToggle}
|
||||||
|
connect={tryConnectInjected}
|
||||||
|
/>
|
||||||
|
<nav className={classes.drawer}>
|
||||||
|
{isSmallerScreen ? (
|
||||||
|
<NavDrawer chainId={chainId} addressChainId={addressChainId} mobileOpen={mobileOpen} handleDrawerToggle={handleDrawerToggle} />
|
||||||
|
) : (
|
||||||
|
<Sidebar chainId={chainId} addressChainId={addressChainId} />
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
|
||||||
|
<Suspense fallback={<div></div>}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Navigate to={chainExists ? "/dashboard" : "/empty"} />} />
|
||||||
|
{chainExists &&
|
||||||
|
<>
|
||||||
|
<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}/>} />
|
||||||
|
<Route path="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
|
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
<Route path="/empty" element={<NotFound />} />
|
||||||
|
<Route path="*" element={<NotFound />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ThemeProvider>
|
||||||
|
</StyledDiv>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
1
src/abi/ERC20.json
Normal file
1
src/abi/Fatso.json
Normal file
1
src/abi/Ghost.json
Normal file
1
src/abi/GhostBondDepository.json
Normal file
1
src/abi/GhostStaking.json
Normal file
1
src/abi/GhostTreasury.json
Normal file
1
src/abi/Reserve.json
Normal file
1
src/abi/Stinky.json
Normal file
1
src/abi/UniswapV2Factory.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"abi":[{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":false,"internalType":"address","name":"pair","type":"address"},{"indexed":false,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_feeTo","type":"address"}],"name":"setFeeTo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"name":"setFeeToSetter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]}
|
1
src/abi/UniswapV2Pair.json
Normal file
1
src/abi/UniswapV2Router.json
Normal file
3
src/assets/icons/arrow-up.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.29688 17.4453H13.8359C15.3594 17.4453 16.1406 16.6641 16.1406 15.1641V5.58594C16.1406 4.07812 15.3594 3.29688 13.8359 3.29688H4.29688C2.77344 3.29688 1.99219 4.07031 1.99219 5.58594V15.1641C1.99219 16.6719 2.77344 17.4453 4.29688 17.4453ZM4.3125 16.4766C3.4375 16.4766 2.96094 16.0156 2.96094 15.1094V5.63281C2.96094 4.73438 3.4375 4.26562 4.3125 4.26562H13.8125C14.6797 4.26562 15.1719 4.73438 15.1719 5.63281V15.1094C15.1719 16.0156 14.6797 16.4766 13.8125 16.4766H4.3125ZM11.6094 12.2422C11.875 12.2422 12.0469 12.0469 12.0469 11.7656V7.86719C12.0469 7.52344 11.8594 7.375 11.5547 7.375H7.64062C7.35156 7.375 7.17188 7.54688 7.17188 7.8125C7.17188 8.07812 7.35938 8.25781 7.64844 8.25781H9.53125L10.6641 8.17969L9.60938 9.17188L6.22656 12.5547C6.14062 12.6406 6.07812 12.7578 6.07812 12.8828C6.07812 13.1562 6.25781 13.3359 6.53125 13.3359C6.67969 13.3359 6.78906 13.2734 6.875 13.1875L10.25 9.82031L11.2344 8.77344L11.1562 10.0547V11.7734C11.1562 12.0625 11.3359 12.2422 11.6094 12.2422Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
27
src/assets/icons/ghost-nav-header.svg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-f0cbd7f5-2d57-4633-b669-dfc2c2c885d2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 382.48 299.46">
|
||||||
|
<g id="uuid-67077388-8ea2-4b9c-9826-134503e6674f" data-name="Layer 1">
|
||||||
|
<g id="uuid-05037fc0-3679-4d97-8cd0-d8a139f69a5d" data-name="Logo Vertical Black">
|
||||||
|
<g>
|
||||||
|
<path d="m38.8,278.89c0,6.97-1.77,12.14-5.31,15.51-3.54,3.37-9,5.06-16.4,5.06-2.59,0-5.18-.23-7.78-.69-2.59-.46-5-1.07-7.21-1.82l2.11-10.12c1.89.76,3.87,1.35,5.95,1.78,2.08.43,4.44.65,7.09.65,3.46,0,5.9-.76,7.33-2.27,1.43-1.51,2.15-3.46,2.15-5.83v-1.54c-1.3.6-2.63,1.04-4.01,1.34-1.38.3-2.88.45-4.5.45-5.89,0-10.4-1.74-13.53-5.22s-4.7-8.36-4.7-14.62c0-3.13.49-5.98,1.46-8.55.97-2.56,2.39-4.77,4.25-6.6,1.86-1.83,4.14-3.25,6.84-4.25,2.7-1,5.75-1.5,9.15-1.5,1.46,0,2.96.07,4.5.2,1.54.14,3.06.31,4.58.53,1.51.22,2.96.47,4.33.77,1.38.3,2.6.61,3.69.93v35.8Zm-26.49-17.33c0,6.75,2.73,10.12,8.18,10.12,1.24,0,2.4-.16,3.48-.49,1.08-.32,2-.7,2.75-1.13v-19.28c-.59-.11-1.3-.2-2.11-.28-.81-.08-1.75-.12-2.83-.12-3.19,0-5.56,1.05-7.13,3.16-1.57,2.11-2.35,4.78-2.35,8.02Z"/>
|
||||||
|
<path d="m50.14,284.24v-60.91l12.07-1.94v20.41c.81-.27,1.85-.53,3.12-.77,1.27-.24,2.5-.36,3.69-.36,3.46,0,6.33.47,8.63,1.42,2.29.95,4.13,2.28,5.51,4.01,1.38,1.73,2.35,3.78,2.92,6.16.57,2.38.85,5.02.85,7.94v24.06h-12.07v-22.6c0-3.89-.5-6.64-1.5-8.26-1-1.62-2.85-2.43-5.55-2.43-1.08,0-2.09.09-3.04.28-.95.19-1.8.39-2.55.61v32.4h-12.07Z"/>
|
||||||
|
<path d="m137.7,262.85c0,3.35-.49,6.41-1.46,9.19-.97,2.78-2.38,5.16-4.21,7.13-1.84,1.97-4.04,3.5-6.6,4.58-2.57,1.08-5.44,1.62-8.63,1.62s-5.98-.54-8.55-1.62c-2.57-1.08-4.77-2.6-6.6-4.58-1.84-1.97-3.27-4.35-4.29-7.13-1.03-2.78-1.54-5.84-1.54-9.19s.53-6.4,1.58-9.15,2.51-5.1,4.37-7.05,4.08-3.46,6.64-4.54c2.56-1.08,5.36-1.62,8.38-1.62s5.9.54,8.46,1.62c2.56,1.08,4.76,2.59,6.6,4.54,1.83,1.94,3.27,4.29,4.29,7.05,1.02,2.75,1.54,5.81,1.54,9.15Zm-12.31,0c0-3.73-.74-6.65-2.23-8.79-1.49-2.13-3.6-3.2-6.36-3.2s-4.89,1.07-6.4,3.2c-1.51,2.13-2.27,5.06-2.27,8.79s.76,6.68,2.27,8.87,3.65,3.28,6.4,3.28,4.87-1.09,6.36-3.28c1.48-2.19,2.23-5.14,2.23-8.87Z"/>
|
||||||
|
<path d="m159.25,275.57c2.21,0,3.78-.22,4.7-.65.92-.43,1.38-1.27,1.38-2.51,0-.97-.59-1.82-1.78-2.55-1.19-.73-3-1.55-5.43-2.47-1.89-.7-3.6-1.43-5.14-2.19-1.54-.76-2.85-1.66-3.93-2.71-1.08-1.05-1.92-2.31-2.51-3.77-.59-1.46-.89-3.21-.89-5.26,0-4,1.48-7.15,4.46-9.48,2.97-2.32,7.05-3.48,12.23-3.48,2.59,0,5.08.23,7.45.69,2.38.46,4.27.96,5.67,1.5l-2.11,9.4c-1.41-.49-2.93-.92-4.58-1.3-1.65-.38-3.5-.57-5.55-.57-3.78,0-5.67,1.05-5.67,3.16,0,.49.08.92.24,1.3.16.38.49.74.97,1.09.49.35,1.15.73,1.98,1.14.84.41,1.9.86,3.2,1.35,2.65.98,4.83,1.95,6.56,2.9,1.73.95,3.09,1.98,4.09,3.09,1,1.1,1.7,2.33,2.11,3.68s.61,2.91.61,4.69c0,4.2-1.58,7.38-4.74,9.54-3.16,2.15-7.63,3.23-13.41,3.23-3.78,0-6.93-.32-9.44-.97-2.51-.65-4.25-1.19-5.22-1.62l2.02-9.8c2.05.81,4.16,1.45,6.32,1.9,2.16.46,4.29.69,6.4.69Z"/>
|
||||||
|
<path d="m185.89,231.02l12.07-1.94v12.56h14.5v10.04h-14.5v14.98c0,2.54.45,4.56,1.34,6.08.89,1.51,2.69,2.27,5.39,2.27,1.3,0,2.63-.12,4.01-.36,1.38-.24,2.63-.58,3.77-1.01l1.7,9.4c-1.46.59-3.08,1.11-4.86,1.54-1.78.43-3.97.65-6.56.65-3.29,0-6.02-.45-8.18-1.34-2.16-.89-3.89-2.13-5.18-3.73-1.3-1.59-2.2-3.52-2.71-5.79-.51-2.27-.77-4.78-.77-7.53v-35.8Z"/>
|
||||||
|
<path d="m272.24,256.13c0,4.86-.76,9.1-2.27,12.72-1.51,3.62-3.66,6.62-6.44,8.99-2.78,2.38-6.17,4.16-10.17,5.35-4,1.19-8.48,1.78-13.45,1.78-2.27,0-4.91-.09-7.94-.28-3.02-.19-5.99-.58-8.91-1.17v-54.67c2.92-.54,5.95-.9,9.11-1.09,3.16-.19,5.87-.28,8.14-.28,4.81,0,9.17.54,13.08,1.62,3.92,1.08,7.28,2.78,10.08,5.1,2.81,2.32,4.97,5.29,6.48,8.91,1.51,3.62,2.27,7.97,2.27,13.04Zm-36.53,17.82c.59.05,1.28.1,2.07.12.78.03,1.71.04,2.79.04,6.32,0,11-1.59,14.05-4.78,3.05-3.19,4.58-7.59,4.58-13.2s-1.46-10.34-4.37-13.37c-2.92-3.02-7.53-4.54-13.85-4.54-.86,0-1.75.01-2.67.04-.92.03-1.78.09-2.59.2v35.48Z"/>
|
||||||
|
<path d="m314.35,284.24c-.62-1.95-1.3-3.95-2.01-6.01-.72-2.06-1.44-4.1-2.15-6.14h-21.92c-.71,2.05-1.42,4.1-2.13,6.16-.71,2.06-1.38,4.06-1.99,5.99h-13.12c2.11-6.05,4.12-11.64,6.01-16.77,1.9-5.13,3.75-9.96,5.57-14.5,1.81-4.54,3.6-8.84,5.36-12.92,1.76-4.08,3.59-8.06,5.49-11.95h11.98c1.84,3.89,3.66,7.87,5.45,11.95,1.79,4.08,3.59,8.38,5.41,12.92,1.82,4.54,3.68,9.37,5.57,14.5,1.9,5.13,3.91,10.72,6.02,16.77h-13.53Zm-15.18-43.42c-.28.81-.69,1.92-1.24,3.33-.54,1.4-1.16,3.02-1.85,4.86-.69,1.84-1.47,3.86-2.33,6.08-.86,2.21-1.73,4.53-2.61,6.96h16.05c-.86-2.43-1.69-4.76-2.5-6.98s-1.57-4.25-2.29-6.09c-.72-1.84-1.36-3.46-1.91-4.86-.55-1.4-.99-2.5-1.32-3.3Z"/>
|
||||||
|
<path d="m382.48,256.13c0,4.81-.72,9.03-2.15,12.68-1.43,3.65-3.39,6.7-5.87,9.15-2.48,2.46-5.44,4.31-8.87,5.55-3.43,1.24-7.12,1.86-11.06,1.86s-7.45-.62-10.85-1.86c-3.4-1.24-6.37-3.09-8.91-5.55-2.54-2.46-4.54-5.51-5.99-9.15s-2.19-7.87-2.19-12.68.76-9.03,2.27-12.68c1.51-3.65,3.55-6.71,6.12-9.19,2.56-2.48,5.53-4.35,8.91-5.59,3.37-1.24,6.92-1.86,10.65-1.86s7.45.62,10.85,1.86c3.4,1.24,6.37,3.11,8.91,5.59,2.54,2.48,4.54,5.55,5.99,9.19s2.19,7.87,2.19,12.68Zm-42.85,0c0,2.75.34,5.24,1.01,7.45.67,2.21,1.65,4.12,2.92,5.71,1.27,1.59,2.82,2.82,4.66,3.69,1.83.86,3.94,1.3,6.32,1.3s4.41-.43,6.28-1.3c1.86-.86,3.43-2.09,4.7-3.69,1.27-1.59,2.24-3.5,2.92-5.71.67-2.21,1.01-4.7,1.01-7.45s-.34-5.25-1.01-7.49c-.68-2.24-1.65-4.16-2.92-5.75-1.27-1.59-2.83-2.82-4.7-3.69-1.86-.86-3.96-1.3-6.28-1.3s-4.48.45-6.32,1.34c-1.84.89-3.39,2.13-4.66,3.73-1.27,1.59-2.24,3.51-2.92,5.75-.68,2.24-1.01,4.71-1.01,7.41Z"/>
|
||||||
|
</g>
|
||||||
|
<g id="uuid-0330e3a3-d42f-4ace-b81a-6057926f3413" data-name="Inner">
|
||||||
|
<path d="m182.41,176.98c-.13,0-.27,0-.4-.02l-.36-.05-5.28-1.06-1.67-.44c-.41-.11-.79-.21-1.17-.31-.77-.2-1.53-.4-2.29-.62l-.09-.03-3.37-1.11c-4.93-1.77-9.24-3.78-13.19-6.15-8.55-5.19-15.79-12.3-20.95-20.58-5.08-8.09-8.49-17.66-9.84-27.64-.69-5.18-.85-10.17-.47-14.83.4-4.83,1.23-9.01,2.53-12.78.52-1.51,1.67-2.74,3.13-3.37,1.47-.63,3.15-.63,4.61.03,2.76,1.24,4.08,4.42,3.01,7.24-1.07,2.82-1.82,6.16-2.24,9.93-.43,3.88-.42,8.09.04,12.51.91,8.55,3.57,16.78,7.69,23.79,4.16,7.13,10.14,13.35,17.29,17.98,3.34,2.14,7.07,4.01,11.38,5.7l3.1,1.12c.63.21,1.27.39,1.9.58.4.12.81.23,1.21.36l1.5.44,5.03,1.17c1.99.56,3.21,2.49,2.9,4.6-.3,2.06-2.03,3.56-4.03,3.56Z"/>
|
||||||
|
<path d="m239.91,76.73c-1.85,0-3.65-.89-4.74-2.53-1.94-2.91-4.37-5.6-7.22-7.99-3.26-2.77-6.94-5.22-10.96-7.28-7.92-4.07-16.74-6.38-25.52-6.68-4.45-.14-8.88.24-13.17,1.15-4.26.91-8.46,2.38-12.49,4.37-3.86,1.91-7.66,4.35-11.3,7.26-3.25,2.69-6.43,5.84-9.66,9.6l-.29.3c-1.49,1.38-3.83,1.4-5.44.03-1.57-1.33-1.97-3.53-.95-5.23l.3-.43c3.44-4.23,6.86-7.81,10.45-10.95,4.09-3.46,8.36-6.38,12.74-8.69,4.64-2.46,9.53-4.32,14.53-5.53,5.02-1.21,10.2-1.84,15.47-1.78,10.27.06,20.67,2.48,30.08,6.99,4.83,2.31,9.29,5.11,13.27,8.31,3.72,2.96,6.94,6.34,9.55,10.03.93,1.31,1.27,2.95.94,4.51-.33,1.56-1.3,2.92-2.67,3.73-.92.55-1.92.81-2.92.81Z"/>
|
||||||
|
<path d="m207.46,177.41c-.99,0-1.98-.26-2.85-.75-1.36-.78-2.35-2.09-2.72-3.61-.73-2.99,1.09-6.07,4.05-6.86,6.84-1.84,13.63-5.08,19.61-9.37,6.59-4.73,12.2-10.69,16.24-17.24,4.17-6.73,6.82-14.44,7.67-22.29.86-7.39.11-15.45-2.23-23.99l-.07-.29c-.38-2.01.83-4.01,2.82-4.65,1.96-.64,4.06.21,4.99,2.02l.15.29.1.31c2.92,9.52,4.04,18.66,3.31,27.18-.71,9.23-3.55,18.4-8.22,26.49-4.49,7.82-10.85,15.01-18.38,20.77-6.98,5.34-14.65,9.29-22.8,11.75-.54.16-1.1.25-1.66.25Z"/>
|
||||||
|
</g>
|
||||||
|
<g id="uuid-f7f60885-c04a-4843-bb71-7cfee722f2fa" data-name="Outer">
|
||||||
|
<path d="m272.86,140.76c-9.87-.09-17.97,7.87-18.07,17.74-.03,3.53.97,6.83,2.71,9.62-4.46,5.23-9.5,10-14.97,14.13-10.06,7.64-21.76,13.09-33.82,15.74-12.09,2.69-25.04,2.54-37.47-.44-11.4-2.64-23.15-7.97-34.93-15.85l-.1-.07c-2.06-1.29-4.79-.72-6.22,1.3-1.45,2.05-1.07,4.83.88,6.34l.11.08c12.45,9,25.1,15.28,37.58,18.66,8.14,2.27,16.55,3.45,24.91,3.52,5.95.05,11.88-.45,17.66-1.52,13.95-2.54,27.6-8.3,39.48-16.66,6.97-4.89,13.41-10.66,19.09-17.06.92.15,1.85.24,2.81.25,9.87.09,17.97-7.87,18.06-17.74.09-9.87-7.87-17.97-17.74-18.07Z"/>
|
||||||
|
<path d="m132.47,158.26c-1.77-4.44-5.17-7.93-9.56-9.81-3.27-1.4-6.72-1.75-9.99-1.2-3.68-7.45-6.35-15.61-7.88-24.17-2.6-14.4-1.83-29.01,2.22-42.26,2.09-6.84,5.06-13.35,8.83-19.36,3.73-5.95,8.33-11.5,13.65-16.51,5.03-4.74,10.89-9.13,17.34-13.02,5.98-3.48,12.52-6.58,19.97-9.47l.59-.23.49-.39c1.58-1.26,2.18-3.4,1.49-5.32-.8-2.21-3.1-3.48-5.36-2.95l-.42.12c-8.04,2.82-15.13,5.92-21.75,9.49-7.27,4.07-13.88,8.71-19.62,13.78-6.19,5.45-11.59,11.58-16.05,18.2-4.54,6.74-8.19,14.1-10.84,21.87-5.14,15.03-6.56,31.72-4.11,48.27,1.56,10.65,4.68,20.85,9.16,30.18-.46.74-.87,1.52-1.22,2.35-3.9,9.07.31,19.62,9.39,23.51,2.12.91,4.32,1.38,6.5,1.44,7.14.22,14.03-3.88,17.02-10.83,1.89-4.39,1.95-9.26.18-13.7Z"/>
|
||||||
|
<path d="m188.22,35.18c1.44.4,2.91.61,4.37.64,3.2.07,6.37-.73,9.23-2.35,2.92-1.67,5.25-4.07,6.82-6.95,7.24,1.49,14.46,3.9,21.38,7.19,12.16,5.82,23,14.16,31.34,24.13,8.54,10.16,14.66,22.39,17.7,35.41,2.93,12.08,3.22,25.66.87,40.35l-.05.52c-.07,2.31,1.61,4.33,3.91,4.7.22.04.44.05.66.06,1.83.04,3.52-.96,4.33-2.63l.24-.48.1-.53c3.11-15.89,3.27-30.81.5-44.31-2.92-14.81-9.39-28.93-18.71-40.82-9.1-11.67-21.11-21.6-34.75-28.72-8.45-4.37-17.37-7.59-26.38-9.58-.34-.94-.75-1.87-1.26-2.76-2.37-4.15-6.21-7.14-10.82-8.4-4.61-1.26-9.44-.66-13.59,1.71-4.15,2.37-7.14,6.21-8.4,10.82-1.26,4.61-.66,9.44,1.71,13.59s6.21,7.14,10.82,8.4Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
3
src/assets/icons/hamburger.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1.31738 6.05078H17.6826C18.21 6.05078 18.6143 5.62012 18.6143 5.10156C18.6143 4.57422 18.21 4.15234 17.6826 4.15234H1.31738C0.798828 4.15234 0.385742 4.58301 0.385742 5.10156C0.385742 5.61133 0.798828 6.05078 1.31738 6.05078ZM3.11035 10.6035H15.916C16.4434 10.6035 16.8477 10.1641 16.8477 9.6543C16.8477 9.12695 16.4434 8.70508 15.916 8.70508H3.11035C2.5918 8.70508 2.17871 9.13574 2.17871 9.6543C2.17871 10.1641 2.5918 10.6035 3.11035 10.6035ZM4.87695 15.1562H14.1494C14.6768 15.1562 15.0811 14.7168 15.0811 14.207C15.0811 13.6797 14.6768 13.249 14.1494 13.249H4.87695C4.3584 13.249 3.94531 13.6885 3.94531 14.207C3.94531 14.708 4.3584 15.1562 4.87695 15.1562Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 778 B |
5
src/assets/icons/sushiswap.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-2bb224c1-aa4b-43bb-bfe5-b0a52c71a8a4" data-name="Sushiswap" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2499.67 2291.55">
|
||||||
|
<path d="m1611.46,614.12c-251-168-502-219-575-115s73,323,313,490c251,168,502,219,575,115s-73-323-313-490Z"/>
|
||||||
|
<path d="m1778.46,362.12C1246.46-14.88,682.46-107.88,515.46,132.12L38.46,834.12c-150,254,152,725,676,1095,527,373,1085,469,1258,237l485-707c157-241-136-742-679-1097Zm606,1056c-147,209-658,104-1149-240C734.46,833.12,441.46,395.12,587.46,186.12s658-104,1149,240c490,345,783,783,648,992Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 586 B |
27
src/assets/icons/uniswap.svg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 168.3 193.8" style="enable-background:new 0 0 168.3 193.8;" xml:space="preserve" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba, .uuid-d6f8202d-a51b-403f-94f5-aeb3a111aab4 {
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-d6f8202d-a51b-403f-94f5-aeb3a111aab4 {
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-e6defac3-7099-4fa9-ba34-9db5d15b5563" data-name="Uniswap">
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m66,44.1c-2.1-.3-2.2-.4-1.2-.5,1.9-.3,6.3.1,9.4.8,7.2,1.7,13.7,6.1,20.6,13.8l1.8,2.1,2.6-.4c11.1-1.8,22.5-.4,32,4,2.6,1.2,6.7,3.6,7.2,4.2.2.2.5,1.5.7,2.8.7,4.7.4,8.2-1.1,10.9-.8,1.5-.8,1.9-.3,3.2.4,1,1.6,1.7,2.7,1.7,2.4,0,4.9-3.8,6.1-9.1l.5-2.1.9,1c5.1,5.7,9.1,13.6,9.7,19.2l.2,1.5-.9-1.3c-1.5-2.3-2.9-3.8-4.8-5.1-3.4-2.3-7-3-16.5-3.5-8.6-.5-13.5-1.2-18.3-2.8-8.2-2.7-12.4-6.2-22.1-19.1-4.3-5.7-7-8.8-9.7-11.4-5.9-5.7-11.8-8.7-19.5-9.9Z"/>
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m140.5,56.8c.2-3.8.7-6.3,1.8-8.6.4-.9.8-1.7.9-1.7s-.1.7-.4,1.5c-.8,2.2-.9,5.3-.4,8.8.7,4.5,1,5.1,5.8,10,2.2,2.3,4.8,5.2,5.8,6.4l1.7,2.2-1.7-1.6c-2.1-2-6.9-5.8-8-6.3-.7-.4-.8-.4-1.3.1-.4.4-.5,1-.5,3.9-.1,4.5-.7,7.3-2.2,10.2-.8,1.5-.9,1.2-.2-.5.5-1.3.6-1.9.6-6.2,0-8.7-1-10.8-7.1-14.3-1.5-.9-4.1-2.2-5.6-2.9-1.6-.7-2.8-1.3-2.7-1.3.2-.2,6.1,1.5,8.4,2.5,3.5,1.4,4.1,1.5,4.5,1.4.3-.3.5-1.1.6-3.6Z"/>
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m70.1,71.7c-4.2-5.8-6.9-14.8-6.3-21.5l.2-2.1,1,.2c1.8.3,4.9,1.5,6.4,2.4,4,2.4,5.8,5.7,7.5,13.9.5,2.4,1.2,5.2,1.5,6.1.5,1.5,2.4,5,4,7.2,1.1,1.6.4,2.4-2.1,2.2-3.8-.4-8.9-3.9-12.2-8.4Z"/>
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m135.4,115.2c-19.8-8-26.8-14.9-26.8-26.6,0-1.7.1-3.1.1-3.1.1,0,.8.6,1.7,1.3,4,3.2,8.5,4.6,21,6.4,7.3,1.1,11.5,1.9,15.3,3.2,12.1,4,19.6,12.2,21.4,23.3.5,3.2.2,9.3-.6,12.5-.7,2.5-2.7,7.1-3.2,7.2-.1,0-.3-.5-.3-1.3-.2-4.2-2.3-8.2-5.8-11.3-4.2-3.6-9.6-6.3-22.8-11.6Z"/>
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m121.4,118.5c-.2-1.5-.7-3.4-1-4.2l-.5-1.5.9,1.1c1.3,1.5,2.3,3.3,3.2,5.8.7,1.9.7,2.5.7,5.6s-.1,3.7-.7,5.4c-1,2.7-2.2,4.6-4.2,6.7-3.6,3.7-8.3,5.7-15,6.6-1.2.1-4.6.4-7.6.6-7.5.4-12.5,1.2-17,2.8-.6.2-1.2.4-1.3.3-.2-.2,2.9-2,5.4-3.2,3.5-1.7,7.1-2.6,15-4,3.9-.6,7.9-1.4,8.9-1.8,9.9-3.1,14.8-10.8,13.2-20.2Z"/>
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m130.5,134.6c-2.6-5.7-3.2-11.1-1.8-16.2.2-.5.4-1,.6-1s.8.3,1.4.7c1.2.8,3.7,2.2,10.1,5.7,8.1,4.4,12.7,7.8,15.9,11.7,2.8,3.4,4.5,7.3,5.3,12.1.5,2.7.2,9.2-.5,11.9-2.2,8.5-7.2,15.3-14.5,19.2-1.1.6-2,1-2.1,1s.3-1,.9-2.2c2.4-5.1,2.7-10,.9-15.5-1.1-3.4-3.4-7.5-8-14.4-5.5-8-6.8-10.1-8.2-13Z"/>
|
||||||
|
<path class="uuid-eeeda88e-94f9-459e-9a3d-2b134be4d3ba" d="m56,165.2c7.4-6.2,16.5-10.6,24.9-12,3.6-.6,9.6-.4,12.9.5,5.3,1.4,10.1,4.4,12.6,8.1,2.4,3.6,3.5,6.7,4.6,13.6.4,2.7.9,5.5,1,6.1.8,3.6,2.4,6.4,4.4,7.9,3.1,2.3,8.5,2.4,13.8.4.9-.3,1.7-.6,1.7-.5.2.2-2.5,2-4.3,2.9-2.5,1.3-4.5,1.7-7.2,1.7-4.8,0-8.9-2.5-12.2-7.5-.7-1-2.1-3.9-3.3-6.6-3.5-8.1-5.3-10.5-9.4-13.2-3.6-2.3-8.2-2.8-11.7-1.1-4.6,2.2-5.8,8.1-2.6,11.7,1.3,1.5,3.7,2.7,5.7,3,3.7.5,6.9-2.4,6.9-6.1,0-2.4-.9-3.8-3.3-4.9-3.2-1.4-6.7.2-6.6,3.3,0,1.3.6,2.1,1.9,2.7q.8.4.2.3c-2.9-.6-3.6-4.2-1.3-6.5,2.8-2.8,8.7-1.6,10.7,2.3.8,1.6.9,4.8.2,6.8-1.7,4.4-6.5,6.7-11.4,5.4-3.3-.9-4.7-1.8-8.7-5.9-7-7.2-9.7-8.6-19.7-10.1l-1.9-.3,2.1-2Z"/>
|
||||||
|
<path class="uuid-d6f8202d-a51b-403f-94f5-aeb3a111aab4" d="m3.4,4.3c23.3,28.3,59.2,72.3,61,74.7,1.5,2,.9,3.9-1.6,5.3-1.4.8-4.3,1.6-5.7,1.6-1.6,0-3.5-.8-4.8-2.1-.9-.9-4.8-6.6-13.6-20.3-6.7-10.5-12.4-19.2-12.5-19.3q-.4-.2,11.8,21.6c7.7,13.7,10.2,18.6,10.2,19.2,0,1.3-.4,2-2,3.8-2.7,3-3.9,6.4-4.8,13.5-1,7.9-3.7,13.5-11.4,23-4.5,5.6-5.2,6.6-6.3,8.9-1.4,2.8-1.8,4.4-2,8-.2,3.8.2,6.2,1.3,9.8,1,3.2,2.1,5.3,4.8,9.4,2.3,3.6,3.7,6.3,3.7,7.3,0,.8.2.8,3.8,0,8.6-2,15.7-5.4,19.6-9.6,2.4-2.6,3-4,3-7.6,0-2.3-.1-2.8-.7-4.2-1-2.2-2.9-4-7-6.8-5.4-3.7-7.7-6.7-8.3-10.7-.5-3.4.1-5.7,3.1-12,3.1-6.5,3.9-9.2,4.4-15.8.3-4.2.8-5.9,2-7.2,1.3-1.4,2.4-1.9,5.5-2.3,5.1-.7,8.4-2,11-4.5,2.3-2.1,3.3-4.2,3.4-7.3l.1-2.3-1.3-1.4C65.4,71.6.3,0,0,0c-.1,0,1.5,1.9,3.4,4.3Zm30.7,142.2c1.1-1.9.5-4.3-1.3-5.5-1.7-1.1-4.3-.6-4.3.9,0,.4.2.8.8,1,.9.5,1,1,.3,2.1s-.7,2.1.2,2.8c1.4,1.1,3.3.5,4.3-1.3Z"/>
|
||||||
|
<path class="uuid-d6f8202d-a51b-403f-94f5-aeb3a111aab4" d="m74.6,93.9c-2.4.7-4.7,3.3-5.4,5.9-.4,1.6-.2,4.5.5,5.4,1.1,1.4,2.1,1.8,4.9,1.8,5.5,0,10.2-2.4,10.7-5.3.5-2.4-1.6-5.7-4.5-7.2-1.5-.8-4.6-1.1-6.2-.6Zm6.4,5c.8-1.2.5-2.5-1-3.4-2.7-1.7-6.8-.3-6.8,2.3,0,1.3,2.1,2.7,4.1,2.7,1.3,0,3.1-.8,3.7-1.6Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
3
src/assets/icons/wallet.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.5 11H13.5C13.2239 11 13 11.2239 13 11.5C13 11.7761 13.2239 12 13.5 12H14.5C14.7761 12 15 11.7761 15 11.5C15 11.2239 14.7761 11 14.5 11ZM4.5 3C3.67157 3 3 3.67157 3 4.5V14.5C3 15.8807 4.11929 17 5.5 17H15.5C16.3284 17 17 16.3284 17 15.5V6.5C17 5.84689 16.5826 5.29127 16 5.08535V4.5C16 3.67157 15.3284 3 14.5 3H4.5ZM4 14.5V5.91465C4.15639 5.96992 4.32468 6 4.5 6H15.5C15.7761 6 16 6.22386 16 6.5V15.5C16 15.7761 15.7761 16 15.5 16H5.5C4.67157 16 4 15.3284 4 14.5ZM4.5 4H14.5C14.7761 4 15 4.22386 15 4.5V5H4.5C4.22386 5 4 4.77614 4 4.5C4 4.22386 4.22386 4 4.5 4Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 681 B |
21
src/assets/tokens/DAI.svg
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-d0168025-b667-41b6-8421-ee763cc95339" data-name="gDAI" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-86475da6-a6bb-46b1-a1ba-25a52c3111f8 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-33e67a14-1464-40a4-baf5-808856f4743a {
|
||||||
|
fill: #345b87;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-b53aee60-fbcd-470c-8030-b468043ab38e" data-name="gDAI">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-33e67a14-1464-40a4-baf5-808856f4743a" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-86475da6-a6bb-46b1-a1ba-25a52c3111f8" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.67,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85c-22.67,22.66-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<path class="uuid-86475da6-a6bb-46b1-a1ba-25a52c3111f8" d="m209.68,115.27v-13.53c0-.56-.45-1.01-1.01-1.01h-16.69c-6.03-16.54-19.07-33.68-43.97-41.61-8.16-2.6-16.69-3.87-25.25-3.87h-42.64s0,0-.01,0h-13.53c-.56,0-1.01.45-1.01,1.01v44.47h-18.68c-.56,0-1.01.45-1.01,1.01v13.53c0,.56.45,1.01,1.01,1.01h18.68v17.44h-18.68c-.56,0-1.01.45-1.01,1.01v14.13c0,.56.45,1.01,1.01,1.01h18.68v43.62c0,.56.45,1.01,1.01,1.01h0,0s56.19,0,56.19,0c8.57,0,17.09-1.27,25.25-3.87,24.47-7.79,37.49-24.49,43.65-40.76h17.01c.56,0,1.01-.45,1.01-1.01v-14.13c0-.56-.45-1.01-1.01-1.01h-12.92c.42-3.1.61-6.04.61-8.74v-.21c0-2.63-.18-5.48-.57-8.49h12.88c.56,0,1.01-.45,1.01-1.01Zm-85.07-45.21c6.74,0,13.46,1,19.89,3.04,16.11,6.14,25.14,16.73,29.95,27.63h-93.03l-.02-30.67h43.22Zm19.89,106.59c-6.43,2.05-12.97,3.52-19.71,3.52h-43.3l-.02-30.3h92.8c-4.82,10.69-13.78,20.95-29.76,26.78Zm34.89-51.86v.17c0,2.69-.17,5.65-.59,8.76h-97.35v-17.44s97.35,0,97.35,0c.42,3.02.59,5.89.59,8.51Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
29
src/assets/tokens/GHST.svg
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 254.751 254.751">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-67f42f03-098b-49dc-a7ed-dc13bd713387 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-61807842-393a-4128-a086-d5229f2542da {
|
||||||
|
fill: #345b87;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-fb84f985-acc6-4529-8860-a7d17e43f533" data-name="GHST">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-61807842-393a-4128-a086-d5229f2542da" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m125,5c32.05,0,62.19,12.48,84.85,35.15s35.15,52.8,35.15,84.85-12.48,62.19-35.15,84.85c-22.67,22.67-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<g id="uuid-1a93fc72-ded1-41af-8e7c-010273c35667" data-name="Inner">
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m135.65,68.54c.11,0,.22,0,.33.02l.22.04,4.48,1.05,1.43.42c.34.1.67.2,1,.3.65.19,1.29.38,1.93.59l2.86,1.04c4.22,1.67,7.84,3.5,11.14,5.63,7.13,4.65,13.1,10.9,17.26,18.08,4.11,7.04,6.75,15.28,7.63,23.83.45,4.44.44,8.69,0,12.65-.47,4.09-1.28,7.61-2.49,10.77-.41,1.07-1.25,1.92-2.32,2.34-1.07.42-2.27.38-3.3-.12-1.95-.95-2.82-3.25-1.99-5.25,1.03-2.47,1.8-5.41,2.27-8.73.49-3.4.6-7.1.32-11-.56-7.54-2.66-14.85-6.09-21.13-3.46-6.4-8.55-12.04-14.71-16.32-2.89-1.99-6.12-3.74-9.87-5.34l-2.67-1.06c-.5-.19-1.07-.37-1.64-.55-.34-.11-.69-.22-1.03-.34l-1.32-.43-4.31-1.14c-1.1-.35-2.07-1.52-1.79-3.1.26-1.47,1.5-2.26,2.65-2.26Z"/>
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m83.09,153.6c1.37,0,2.69.69,3.46,1.92,1.64,2.63,3.72,5.09,6.17,7.29,2.78,2.53,5.95,4.78,9.42,6.71,6.84,3.8,14.53,6.08,22.23,6.58,3.91.25,7.82.04,11.62-.64,3.78-.68,7.52-1.86,11.12-3.49,3.45-1.57,6.85-3.6,10.13-6.06,2.93-2.27,5.8-4.94,8.74-8.14l.19-.18c.87-.76,2.38-.97,3.57.11,1.15,1.04,1.14,2.52.54,3.45l-.2.26c-3.06,3.52-6.08,6.49-9.24,9.08-3.57,2.84-7.29,5.2-11.09,7.05-4.02,1.96-8.23,3.41-12.51,4.31-4.31.89-8.75,1.26-13.22,1.09-8.75-.34-17.55-2.68-25.45-6.78-4.05-2.1-7.78-4.61-11.08-7.44-3.07-2.62-5.71-5.57-7.83-8.76-.63-.95-.84-2.14-.57-3.25.27-1.11,1-2.06,2-2.61.63-.35,1.31-.51,1.98-.51Z"/>
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m113.94,67.47c.76,0,1.51.21,2.16.61.95.59,1.63,1.55,1.86,2.64.45,2.16-.92,4.32-3.05,4.82-6.07,1.42-12.11,4.08-17.49,7.68-5.92,3.97-11.02,9.05-14.75,14.69-3.86,5.81-6.41,12.52-7.38,19.41-.96,6.47-.53,13.57,1.27,21.12l.04.18c.18,1.15-.43,2.55-1.95,3-1.48.43-2.76-.39-3.25-1.43l-.09-.19-.05-.2c-2.23-8.2-2.93-16.03-2.09-23.27.86-7.84,3.53-15.57,7.72-22.32,4.04-6.55,9.66-12.5,16.23-17.2,6.1-4.36,12.75-7.52,19.76-9.4.35-.09.71-.14,1.06-.14Z"/>
|
||||||
|
</g>
|
||||||
|
<g id="uuid-977abf79-b623-4053-ab7b-a617d834d6cf" data-name="Outer">
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m178.97,83.02c1.57,3.93,4.57,7.01,8.46,8.68,2.89,1.24,5.95,1.55,8.84,1.06,3.25,6.6,5.62,13.81,6.98,21.39,2.3,12.74,1.62,25.68-1.97,37.4-1.85,6.05-4.48,11.82-7.82,17.13-3.3,5.27-7.37,10.18-12.08,14.61-4.45,4.19-9.63,8.08-15.34,11.52-5.3,3.08-11.08,5.82-17.68,8.38l-.52.2-.43.35c-1.4,1.12-1.93,3.01-1.32,4.71.71,1.95,2.75,3.08,4.74,2.61l.38-.11c7.11-2.5,13.39-5.24,19.25-8.39,6.44-3.6,12.28-7.71,17.37-12.19,5.48-4.82,10.26-10.24,14.2-16.11,4.02-5.96,7.25-12.47,9.59-19.35,4.54-13.3,5.8-28.07,3.64-42.72-1.38-9.42-4.14-18.45-8.11-26.71.41-.66.77-1.35,1.08-2.08,3.45-8.03-.28-17.36-8.31-20.81-1.88-.81-3.82-1.22-5.75-1.28-6.32-.19-12.42,3.43-15.06,9.58-1.67,3.89-1.73,8.19-.16,12.12Z"/>
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m54.73,98.51c8.74.08,15.91-6.96,15.99-15.7.03-3.12-.86-6.05-2.4-8.52,3.95-4.62,8.41-8.85,13.25-12.51,8.91-6.76,19.25-11.58,29.93-13.93,10.7-2.38,22.16-2.24,33.16.39,10.09,2.34,20.49,7.06,30.91,14.02l.09.06c1.82,1.14,4.24.63,5.51-1.15,1.29-1.81.94-4.28-.78-5.61l-.1-.07c-11.02-7.96-22.22-13.52-33.26-16.51-7.2-2.01-14.65-3.05-22.05-3.12-5.27-.05-10.51.4-15.63,1.34-12.34,2.25-24.43,7.35-34.94,14.74-6.17,4.32-11.87,9.43-16.9,15.1-.81-.14-1.64-.21-2.49-.22-8.74-.08-15.91,6.96-15.99,15.7-.08,8.74,6.96,15.91,15.7,15.99Z"/>
|
||||||
|
<path class="uuid-67f42f03-098b-49dc-a7ed-dc13bd713387" d="m129.64,191.95c-1.28-.35-2.57-.54-3.87-.57-2.83-.06-5.64.64-8.17,2.08-2.59,1.47-4.65,3.6-6.04,6.15-6.41-1.32-12.79-3.45-18.92-6.36-10.76-5.15-20.35-12.54-27.73-21.36-7.56-8.99-12.97-19.82-15.67-31.34-2.59-10.69-2.85-22.71-.77-35.71l.04-.46c.06-2.05-1.43-3.83-3.46-4.16-.2-.03-.39-.05-.58-.05-1.62-.04-3.11.85-3.83,2.33l-.21.43-.09.47c-2.75,14.06-2.9,27.26-.44,39.21,2.59,13.11,8.31,25.6,16.56,36.12,8.05,10.33,18.68,19.12,30.75,25.42,7.48,3.87,15.37,6.72,23.35,8.48.3.83.67,1.65,1.12,2.44,2.1,3.68,5.5,6.32,9.58,7.44,4.08,1.12,8.35.58,12.03-1.51,3.68-2.1,6.32-5.5,7.44-9.58,1.12-4.08.58-8.35-1.51-12.03-2.1-3.68-5.5-6.32-9.58-7.44Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
25
src/assets/tokens/Tether.svg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-ea950327-21e9-46f4-9c32-e5006f5369bf" data-name="Tether" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-5a4a7c11-cad0-40ef-9537-af8570123d18 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-1943e345-f7c5-4fea-b145-1ae170e7d8b6 {
|
||||||
|
fill: #50af95;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-9295f9c5-a5c4-404d-a18b-67d8b0674eff" data-name="Tether">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-5a4a7c11-cad0-40ef-9537-af8570123d18" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-5a4a7c11-cad0-40ef-9537-af8570123d18" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.67,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85c-22.67,22.66-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<polygon class="uuid-1943e345-f7c5-4fea-b145-1ae170e7d8b6" points="187.95 51.86 125 51.86 62.05 51.86 25.11 129.79 125 225.48 224.89 129.79 187.95 51.86"/>
|
||||||
|
<rect class="uuid-5a4a7c11-cad0-40ef-9537-af8570123d18" x="112.27" y="138.67" width="25.26" height="51.27"/>
|
||||||
|
<path class="uuid-5a4a7c11-cad0-40ef-9537-af8570123d18" d="m137.52,132.58s-3.13.51-12.52.51-12.73-.51-12.73-.51v-51.27h25.26v51.27Z"/>
|
||||||
|
<rect class="uuid-5a4a7c11-cad0-40ef-9537-af8570123d18" x="77.36" y="75.46" width="95.06" height="23.1"/>
|
||||||
|
<path class="uuid-5a4a7c11-cad0-40ef-9537-af8570123d18" d="m125,113.51c-34.33,0-62.16,6.29-62.16,14.05s27.83,14.05,62.16,14.05,62.16-6.29,62.16-14.05-27.83-14.05-62.16-14.05Zm-.08,23.88c-30.98,0-56.1-4.93-56.1-11.01s25.12-11.01,56.1-11.01,56.1,4.93,56.1,11.01-25.12,11.01-56.1,11.01Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
23
src/assets/tokens/USDC.svg
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-9c2185b8-f0f0-4550-9f05-1613d8452f3c" data-name="USDC" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-2155c26c-1e81-468a-bc43-f7b3d6408870 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-9672bf96-e250-47f9-913c-375f9bf2de1d {
|
||||||
|
fill: #2775ca;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-6c0c5c81-8bea-4d13-a85f-ce4df5b157c1" data-name="USDC">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-9672bf96-e250-47f9-913c-375f9bf2de1d" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-2155c26c-1e81-468a-bc43-f7b3d6408870" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.67,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85c-22.67,22.67-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<path class="uuid-2155c26c-1e81-468a-bc43-f7b3d6408870" d="m152.07,128.67c-6.46-4.98-13.64-7.48-24.51-11.26l-.84-.29c-6.13-1.96-12.22-4.21-15.25-7.41l-.15-.14c-1.86-1.71-2.76-3.79-2.76-6.36,0-3.1,1.44-5.83,4.21-7.93,2.99-2.38,7.39-3.74,12.07-3.74,5.58,0,9.59,2.46,11.88,4.43,3.02,2.84,4.55,5.93,4.55,9.2h0c0,1.63,1.4,2.95,3.11,2.94l12.42-.12c1.71-.02,3.08-1.36,3.05-3-.16-7.38-3.41-14.13-9.34-19.46-4.26-4.01-10.05-6.94-16.53-8.41v-9.74c0-2.03-1.73-3.68-3.86-3.68h-10.41c-2.13,0-3.86,1.65-3.86,3.68v9.54c-6.15,1.17-11.49,3.42-15.55,6.57-6.46,4.94-10.32,12.36-10.32,19.85,0,6.17,2.6,12.12,7.03,16.24,5.4,5.57,13.11,8.84,19.91,11.3,1.99.75,4.05,1.45,6.11,2.16,5.5,1.88,11.19,3.83,15.49,6.64,3.38,2.13,4.83,4.3,4.83,7.25,0,3.38-1.26,5.8-4.1,7.84-3.33,2.4-8.71,3.83-14.41,3.83s-10.59-2.07-14.34-5.85c-2.53-2.5-4.16-6.23-4.16-9.5,0-1.62-1.38-2.94-3.08-2.94h-12.12c-1.7,0-3.08,1.32-3.08,2.94,0,7.02,2.73,13.61,7.82,18.96,4.75,5.38,11.78,9.19,19.97,10.87v9.57c0,2.03,1.73,3.68,3.86,3.68h10.41c2.13,0,3.86-1.65,3.86-3.68v-9.32c5.61-.86,10.84-2.64,14.9-5.08,8.17-4.61,13.04-12.53,13.04-21.19,0-7.32-3.33-13.51-9.87-18.36Z"/>
|
||||||
|
<path class="uuid-2155c26c-1e81-468a-bc43-f7b3d6408870" d="m97.34,53.82h0c1.57-.71,4.26-1.88,4.26-5.99v-7.27c0-.31-.02-.63-.07-.94-.49-3.03-3.07-3.48-4.69-3.11-.19.06-.37.12-.56.18-2.69.86-5.35,1.84-7.97,2.95-11.23,4.73-21.3,11.51-29.96,20.14-8.66,8.63-15.45,18.68-20.2,29.88-4.92,11.6-7.41,23.91-7.41,36.6s2.49,25.01,7.41,36.61c4.75,11.2,11.55,21.25,20.2,29.88,8.65,8.63,18.73,15.4,29.96,20.14,2.37,1,4.77,1.9,7.19,2.7.32.1.63.21.95.31,1.88.41,4.62.01,5.07-3.37.05-.31.07-.62.07-.94v-6.59c0-4.1-3.3-5.82-4.88-6.52-28.94-11.3-49.48-39.41-49.48-72.21s20.84-61.31,50.1-72.45Z"/>
|
||||||
|
<path class="uuid-2155c26c-1e81-468a-bc43-f7b3d6408870" d="m203.35,126.27c0,32.8-20.55,60.91-49.48,72.21-1.57.71-4.88,2.42-4.88,6.52v6.59c0,.31.02.63.07.94.45,3.39,3.19,3.78,5.07,3.37.32-.1.63-.2.95-.31,2.43-.8,4.83-1.7,7.19-2.7,11.23-4.74,21.3-11.51,29.96-20.14,8.66-8.63,15.45-18.68,20.2-29.88,4.92-11.6,7.41-23.91,7.41-36.61,0-12.69-2.49-25.01-7.41-36.6-4.75-11.2-11.55-21.25-20.2-29.88-8.65-8.63-18.73-15.41-29.96-20.14-2.62-1.11-5.28-2.09-7.97-2.95-.19-.06-.37-.12-.56-.18-1.61-.37-4.2.08-4.69,3.11-.05.31-.07.62-.07.94v7.27c0,4.1,2.69,5.28,4.26,5.99h0c29.26,11.14,50.1,39.41,50.1,72.45Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
16
src/assets/tokens/Unknown.svg
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-0430fa1f-ea22-48b8-8dfe-2d2b6d17ffea" data-name="Blank" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-ae168512-afac-4c34-ad41-a89ac9be6f28 {
|
||||||
|
fill: #aec4dd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-19425ec5-a607-4212-8316-f54b070a0a0d" data-name="Blank">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-ae168512-afac-4c34-ad41-a89ac9be6f28" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-ae168512-afac-4c34-ad41-a89ac9be6f28" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.66,22.66,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85c-22.67,22.67-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 884 B |
24
src/assets/tokens/eGHST.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-cf283373-2eb4-44d0-88d2-93533a7f4b1d" data-name="eGHST" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-26b0e53c-73c9-4369-9725-1cee728298be {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-b5883f0e-f5cf-4203-918a-2785037b72d3 {
|
||||||
|
fill: #345b87;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-3b8b2c6a-96ca-43c2-aa22-4ab240b28646" data-name="eGHST">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-b5883f0e-f5cf-4203-918a-2785037b72d3" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-26b0e53c-73c9-4369-9725-1cee728298be" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.67,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85c-22.66,22.66-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<path class="uuid-26b0e53c-73c9-4369-9725-1cee728298be" d="m99.22,159.65v-69.3h46.8v13.1h-31.2v13.6h27.7v12.8h-27.7v16.7h33.5v13.1h-49.1Z"/>
|
||||||
|
<path class="uuid-26b0e53c-73c9-4369-9725-1cee728298be" d="m135.14,44.57c.18,0,.36,0,.54.03l.49.07,6.31,1.27,1.98.52c.48.13.93.25,1.38.36.92.24,1.84.48,2.76.75l.13.04,4.03,1.33c5.9,2.12,11.07,4.53,15.8,7.36,10.26,6.22,18.94,14.75,25.12,24.68,6.09,9.7,10.17,21.16,11.8,33.12.83,6.21,1.01,12.18.56,17.78-.48,5.8-1.47,10.82-3.05,15.36-.68,1.97-2.17,3.57-4.09,4.4-1.92.83-4.11.82-6.01-.03-3.6-1.61-5.33-5.76-3.93-9.44,1.25-3.3,2.14-7.22,2.62-11.64.51-4.57.49-9.52-.05-14.74-1.07-10.07-4.2-19.76-9.06-28.02-4.9-8.39-11.93-15.7-20.35-21.15-3.93-2.52-8.32-4.72-13.4-6.71l-3.67-1.33c-.74-.24-1.49-.46-2.24-.68-.48-.14-.96-.28-1.44-.42l-1.76-.52-6-1.39c-2.69-.75-4.33-3.36-3.92-6.2.4-2.78,2.74-4.79,5.44-4.79Z"/>
|
||||||
|
<path class="uuid-26b0e53c-73c9-4369-9725-1cee728298be" d="m66.78,163.74c2.41,0,4.76,1.17,6.19,3.3,2.27,3.4,5.12,6.55,8.46,9.36,3.84,3.27,8.17,6.14,12.9,8.57,9.33,4.8,19.72,7.51,30.06,7.86,5.23.17,10.45-.28,15.5-1.35,5.01-1.07,9.96-2.8,14.69-5.14,4.55-2.25,9.03-5.12,13.31-8.55,3.83-3.17,7.58-6.89,11.4-11.33l.39-.41c2.02-1.87,5.18-1.9,7.34-.06,2.12,1.8,2.65,4.77,1.28,7.06l-.19.31-.23.28c-4.12,5.06-8.21,9.34-12.51,13.1-4.9,4.16-10.02,7.64-15.28,10.42-5.57,2.95-11.43,5.18-17.42,6.63-6.02,1.45-12.27,2.16-18.55,2.14-12.31-.08-24.77-2.97-36.04-8.37-5.79-2.77-11.14-6.12-15.91-9.96-4.46-3.56-8.33-7.62-11.48-12.06-1.21-1.7-1.65-3.84-1.22-5.88.43-2.03,1.7-3.81,3.49-4.87,1.19-.71,2.51-1.05,3.81-1.05Z"/>
|
||||||
|
<path class="uuid-26b0e53c-73c9-4369-9725-1cee728298be" d="m105.35,44.06c1.3,0,2.58.33,3.72.98,1.77,1.01,3.06,2.73,3.54,4.71.95,3.9-1.42,7.92-5.29,8.95-8.06,2.16-16.05,5.98-23.1,11.04-7.76,5.57-14.37,12.59-19.13,20.31-4.9,7.92-8.02,16.99-9.02,26.22-1.01,8.71-.13,18.21,2.63,28.27l.09.4c.51,2.71-1.12,5.41-3.78,6.28-2.64.87-5.47-.28-6.73-2.72l-.2-.4-.13-.43c-3.5-11.4-4.83-22.35-3.97-32.56.85-11.07,4.26-22.07,9.86-31.77,5.38-9.38,13-17.99,22.02-24.89,8.36-6.39,17.55-11.13,27.31-14.08.71-.21,1.44-.32,2.17-.32Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
87
src/assets/tokens/sGHST.svg
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-0fc887e6-719b-4e51-bef8-e5c269508f6b" data-name="sGHST" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-a205ebe2-7c8f-434c-9bde-29ac218716be {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-02b6136c-c9e6-463d-bbf0-11f88994091e {
|
||||||
|
fill: #345b87;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-09c9f5fc-f985-4022-8a72-546c3a26d36b" data-name="sGHST">
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-02b6136c-c9e6-463d-bbf0-11f88994091e" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.66,22.67,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85c-22.67,22.66-52.8,35.15-84.85,35.15s-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m122.95,147.85c2.2,0,4.02-.18,5.45-.55,1.43-.37,2.58-.87,3.45-1.5.87-.63,1.47-1.38,1.8-2.25.33-.87.5-1.83.5-2.9,0-2.27-1.07-4.15-3.2-5.65-2.13-1.5-5.8-3.12-11-4.85-2.27-.8-4.53-1.72-6.8-2.75-2.27-1.03-4.3-2.33-6.1-3.9-1.8-1.57-3.27-3.47-4.4-5.7-1.13-2.23-1.7-4.95-1.7-8.15s.6-6.08,1.8-8.65c1.2-2.57,2.9-4.75,5.1-6.55,2.2-1.8,4.87-3.18,8-4.15,3.13-.97,6.67-1.45,10.6-1.45,4.67,0,8.7.5,12.1,1.5s6.2,2.1,8.4,3.3l-4.5,12.3c-1.93-1-4.08-1.88-6.45-2.65-2.37-.77-5.22-1.15-8.55-1.15-3.73,0-6.42.52-8.05,1.55-1.63,1.03-2.45,2.62-2.45,4.75,0,1.27.3,2.33.9,3.2.6.87,1.45,1.65,2.55,2.35s2.37,1.33,3.8,1.9c1.43.57,3.02,1.15,4.75,1.75,3.6,1.33,6.73,2.65,9.4,3.95,2.67,1.3,4.88,2.82,6.65,4.55,1.77,1.73,3.08,3.77,3.95,6.1.87,2.33,1.3,5.17,1.3,8.5,0,6.47-2.27,11.48-6.8,15.05-4.53,3.57-11.37,5.35-20.5,5.35-3.07,0-5.83-.18-8.3-.55-2.47-.37-4.65-.82-6.55-1.35-1.9-.53-3.53-1.1-4.9-1.7-1.37-.6-2.52-1.17-3.45-1.7l4.4-12.4c2.07,1.13,4.62,2.15,7.65,3.05,3.03.9,6.75,1.35,11.15,1.35Z"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="189.6" cy="86.25" r="11.24"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m188.76,101.97c-5.57-.31-10.81-3.57-13.37-8.95-3.73-7.84-.39-17.26,7.45-20.99,3.8-1.81,8.08-2.03,12.04-.62,3.97,1.41,7.14,4.28,8.95,8.08,1.81,3.8,2.03,8.07.62,12.04-1.41,3.97-4.28,7.14-8.08,8.95-2.46,1.17-5.07,1.64-7.61,1.5Zm1.21-22.44c-1.11-.06-2.22.15-3.25.64h0c-1.62.77-2.85,2.13-3.45,3.82-.6,1.69-.51,3.52.27,5.14,1.59,3.35,5.61,4.78,8.96,3.18,1.62-.77,2.85-2.13,3.45-3.82.6-1.69.51-3.52-.26-5.14-.77-1.63-2.13-2.85-3.82-3.45-.62-.22-1.25-.35-1.89-.38Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="162.47" cy="56.81" r="7.17"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m161.8,68.48c-1.98-.11-3.91-.73-5.63-1.83-2.63-1.68-4.44-4.29-5.11-7.34-.67-3.05-.11-6.18,1.58-8.8h0c1.68-2.63,4.29-4.44,7.34-5.11,3.04-.66,6.18-.11,8.8,1.58,2.63,1.68,4.44,4.29,5.11,7.34.67,3.05.11,6.18-1.58,8.8-1.68,2.63-4.29,4.44-7.34,5.11-1.05.23-2.12.31-3.17.26Zm.82-14.33c-.24-.01-.48,0-.72.06-.69.15-1.29.57-1.67,1.16h0c-.38.6-.51,1.31-.36,2,.15.69.57,1.29,1.16,1.67.6.38,1.31.51,2,.36.69-.15,1.29-.57,1.67-1.16.38-.6.51-1.31.36-2-.15-.69-.57-1.29-1.16-1.67-.39-.25-.83-.39-1.28-.42Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="130.69" cy="47.21" r="5.29"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m130.14,57c-5.15-.29-9.25-4.57-9.25-9.78,0-5.4,4.39-9.8,9.8-9.8,2.61-.01,5.08,1.02,6.93,2.87,1.85,1.85,2.87,4.31,2.88,6.93,0,5.41-4.39,9.8-9.8,9.81-.19,0-.37,0-.56-.02Zm.59-10.56s-.03,0-.04,0c-.43,0-.78.35-.78.78,0,.43.25.79.78.78.43,0,.78-.35.78-.78,0-.41-.32-.75-.73-.77Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="102.31" cy="50.93" r="4.68"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m101.86,58.98c-3.46-.19-6.54-2.63-7.39-6.17-1.04-4.32,1.63-8.68,5.95-9.72,2.09-.51,4.26-.16,6.09.96,1.84,1.13,3.12,2.9,3.63,4.99,1.04,4.32-1.63,8.68-5.96,9.72-.78.19-1.56.25-2.33.21Zm.52-9.34c-.11,0-.24,0-.37.03-.69.17-1.12.87-.95,1.56.17.69.85,1.12,1.56.96.69-.17,1.12-.87.95-1.56-.11-.45-.41-.7-.58-.8-.12-.08-.33-.18-.61-.19Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="80.03" cy="60.67" r="3.19"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m79.74,66.1c-1.81-.1-3.53-1.1-4.47-2.8h0c-1.45-2.63-.5-5.95,2.13-7.4,1.27-.7,2.75-.87,4.14-.47,1.4.4,2.56,1.33,3.26,2.6.71,1.27.87,2.74.47,4.14-.4,1.4-1.32,2.56-2.6,3.26-.93.51-1.94.73-2.93.67Zm-.52-4.98c.25.45.82.61,1.27.36.3-.16.41-.42.45-.56.04-.14.08-.41-.08-.71-.25-.45-.82-.62-1.27-.37-.45.25-.61.82-.36,1.27h0Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="124.38" cy="196.84" r="11.24"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m123.51,212.57c-.24-.01-.48-.03-.73-.06-4.18-.43-7.95-2.46-10.61-5.72-2.66-3.26-3.89-7.36-3.46-11.55.43-4.19,2.46-7.95,5.72-10.61,3.26-2.66,7.35-3.89,11.55-3.46,4.18.43,7.95,2.46,10.61,5.72,2.66,3.26,3.89,7.36,3.46,11.55h0c-.86,8.39-8.2,14.59-16.54,14.13Zm1.24-22.44c-1.68-.09-3.31.43-4.62,1.5-1.39,1.14-2.26,2.74-2.44,4.53-.38,3.69,2.32,7,6.01,7.37,3.66.33,7-2.32,7.37-6.01.38-3.69-2.32-6.99-6.01-7.37-.11-.01-.21-.02-.32-.02Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="166.1" cy="187.54" r="7.17"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m164.31,199.51c-4.09-.23-7.94-2.6-9.86-6.53-1.37-2.8-1.57-5.97-.56-8.92,1.01-2.95,3.11-5.33,5.92-6.7,5.78-2.84,12.79-.44,15.63,5.35h0c2.83,5.78.43,12.8-5.35,15.63-1.86.91-3.84,1.28-5.77,1.17Zm.77-14.32c-.44-.02-.89.06-1.31.27-.64.31-1.12.86-1.34,1.53s-.19,1.39.13,2.03c.64,1.32,2.23,1.86,3.56,1.22,1.32-.65,1.86-2.24,1.22-3.56-.44-.89-1.32-1.43-2.25-1.48Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="190.13" cy="164.12" r="5.29"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m189.56,173.9c-1.55-.09-3.11-.54-4.52-1.41-2.24-1.36-3.81-3.5-4.43-6.05-.62-2.54-.22-5.18,1.14-7.41,1.36-2.24,3.5-3.81,6.05-4.44,2.55-.62,5.18-.22,7.41,1.14,4.62,2.8,6.1,8.84,3.3,13.46-1.95,3.2-5.44,4.9-8.94,4.7Zm.61-10.56c-.09,0-.17,0-.23.02-.14.03-.34.12-.48.35-.14.23-.13.45-.09.59.03.14.12.34.35.48.36.23.85.1,1.07-.26.22-.36.1-.84-.26-1.07-.12-.08-.25-.11-.36-.11Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="200.65" cy="139.36" r="4.68"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m200.22,147.41c-.68-.04-1.36-.16-2.02-.38-2.05-.66-3.72-2.07-4.71-3.99-.99-1.91-1.17-4.1-.51-6.15.66-2.05,2.07-3.72,3.99-4.71,1.92-.99,4.1-1.16,6.15-.51h0c4.23,1.36,6.57,5.9,5.22,10.13-.65,2.05-2.07,3.72-3.98,4.71-1.29.67-2.71.97-4.13.89Zm.5-9.35c-.29-.02-.53.07-.66.14-.18.09-.5.31-.64.75-.14.44,0,.81.08.98.09.18.31.5.75.64.45.14.81.01.99-.08.18-.09.5-.31.64-.75.22-.68-.16-1.41-.84-1.63-.11-.04-.22-.06-.32-.06Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="203.11" cy="116.46" r="3.19"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m202.81,121.9c-1.42-.08-2.73-.7-3.7-1.75-.99-1.07-1.5-2.46-1.44-3.91.06-1.45.68-2.79,1.75-3.78,1.07-.99,2.46-1.5,3.91-1.44h.09c1.42.08,2.73.7,3.69,1.75.99,1.07,1.5,2.46,1.44,3.91-.12,3-2.66,5.34-5.66,5.22h-.08Zm.36-6.37h-.02s0,0,0,0c-.33-.01-.56.15-.67.25-.11.1-.29.31-.3.64-.01.34.15.57.24.68.1.11.2.28.65.3.33.01.56-.15.67-.25.11-.1.29-.31.3-.65.01-.33-.15-.56-.25-.67-.1-.1-.3-.28-.63-.3Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="63.36" cy="86.01" r="11.24"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m62.45,101.74c-2.93-.16-5.77-1.15-8.22-2.9h0c-3.43-2.44-5.7-6.07-6.4-10.21-.7-4.15.26-8.32,2.7-11.75,5.03-7.07,14.88-8.73,21.96-3.7,7.07,5.03,8.73,14.89,3.7,21.96-2.44,3.43-6.07,5.7-10.21,6.4-1.18.2-2.36.26-3.52.2Zm-2.99-10.25c1.46,1.04,3.24,1.45,5.02,1.15,1.77-.3,3.32-1.27,4.36-2.73,2.15-3.02,1.44-7.23-1.58-9.38-3.02-2.15-7.23-1.44-9.38,1.58-1.04,1.46-1.45,3.25-1.15,5.02s1.27,3.32,2.73,4.36h0Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="50.83" cy="126.88" r="7.17"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m50.18,138.54h-.07c-3.12-.19-5.97-1.59-8.04-3.93-2.07-2.34-3.1-5.34-2.91-8.46.19-3.11,1.58-5.97,3.92-8.03,2.32-2.05,5.3-3.08,8.38-2.91h.07c3.11.2,5.97,1.59,8.03,3.93,2.07,2.34,3.1,5.34,2.91,8.46-.4,6.41-5.91,11.3-12.31,10.95Zm.8-14.32c-.71-.04-1.38.19-1.91.66-.53.47-.85,1.12-.89,1.83-.04.71.19,1.4.66,1.93.47.53,1.12.85,1.83.89h0s.01,0,.02,0c1.46.08,2.71-1.03,2.8-2.49.04-.71-.19-1.39-.66-1.92-.47-.53-1.12-.85-1.83-.89h-.02Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="59.31" cy="159.34" r="5.29"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m58.79,169.12c-3.24-.18-6.34-1.97-8.03-5.01-2.63-4.72-.93-10.7,3.79-13.33,2.28-1.28,4.93-1.59,7.45-.87,2.52.71,4.61,2.37,5.88,4.65,2.63,4.72.93,10.7-3.79,13.33h0c-1.68.93-3.51,1.32-5.3,1.23Zm.57-10.56c-.12,0-.27.01-.41.1-.24.13-.33.33-.37.47-.04.14-.06.36.07.59.21.37.68.51,1.06.3.37-.21.51-.68.3-1.06-.13-.23-.32-.33-.46-.37-.05-.01-.11-.03-.17-.03Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="75.64" cy="180.73" r="4.68"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m75.19,188.77c-2.02-.11-4-.98-5.48-2.58-3.01-3.27-2.8-8.38.47-11.39,3.27-3.01,8.38-2.8,11.39.47,3.01,3.27,2.8,8.38-.47,11.39-1.67,1.53-3.81,2.23-5.91,2.12Zm.52-9.34c-.34-.02-.68.09-.95.34-.53.48-.56,1.3-.08,1.83.48.52,1.3.56,1.83.08.53-.48.56-1.3.08-1.83-.24-.26-.56-.4-.88-.42Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" cx="94.33" cy="194.18" r="3.19"/>
|
||||||
|
<path class="uuid-a205ebe2-7c8f-434c-9bde-29ac218716be" d="m94.02,199.62c-.88-.05-1.77-.31-2.57-.81-1.23-.77-2.09-1.97-2.42-3.39-.33-1.42-.08-2.88.68-4.11,1.59-2.55,4.95-3.33,7.5-1.74,2.55,1.59,3.33,4.96,1.74,7.5h0c-1.09,1.75-3.01,2.66-4.93,2.56Zm.37-6.37c-.11,0-.2,0-.27.02-.14.03-.4.13-.58.42-.18.29-.15.57-.12.71.03.14.13.4.42.58.44.27,1.02.14,1.29-.3s.14-1.02-.3-1.29c-.15-.1-.31-.13-.44-.14Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 10 KiB |
36
src/assets/tokens/wETH.svg
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.uuid-e080659c-91a4-4965-9905-603394020b2c {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21 {
|
||||||
|
fill: #627eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-bb37a35d-b2ce-440e-86f6-0142a60ec718 {
|
||||||
|
fill: rgba(255, 255, 255, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50 {
|
||||||
|
fill: rgba(255, 255, 255, .6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="uuid-c49657cc-831e-4816-83c0-b3ceca5eead0" data-name="Ethereum">
|
||||||
|
<g id="uuid-559a2235-ac8a-4942-be49-ac3193ebd3cd" data-name="EthereumBackground">
|
||||||
|
<circle class="uuid-ca9afedd-40fd-4006-99f9-d2c3cf46bc21" cx="125" cy="125" r="122.5"/>
|
||||||
|
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m125,5c32.05,0,62.19,12.48,84.85,35.15,22.67,22.66,35.15,52.8,35.15,84.85s-12.48,62.19-35.15,84.85-52.8,35.15-84.85,35.15-62.19-12.48-84.85-35.15C17.48,187.19,5,157.05,5,125s12.48-62.19,35.15-84.85C62.81,17.48,92.95,5,125,5m0-5C55.96,0,0,55.96,0,125s55.96,125,125,125,125-55.96,125-125S194.04,0,125,0h0Z"/>
|
||||||
|
</g>
|
||||||
|
<g id="uuid-0bd28738-f2c4-4934-8365-8668de7b64ac" data-name="EthereumIcon">
|
||||||
|
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m128.89,31.25v69.3l58.57,26.17-58.57-95.47Z"/>
|
||||||
|
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m128.89,31.25l-58.58,95.47,58.58-26.17V31.25Z"/>
|
||||||
|
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m128.89,171.62v47.09l58.61-81.09-58.61,34Z"/>
|
||||||
|
<path class="uuid-e080659c-91a4-4965-9905-603394020b2c" d="m128.89,218.71v-47.09l-58.58-33.99,58.58,81.09Z"/>
|
||||||
|
<path class="uuid-bb37a35d-b2ce-440e-86f6-0142a60ec718" d="m128.89,160.73l58.57-34.01-58.57-26.16v60.16Z"/>
|
||||||
|
<path class="uuid-933ff958-c1b5-4023-94a9-0cca9b4aab50" d="m70.31,126.72l58.58,34.01v-60.16l-58.58,26.16Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
75
src/components/Accordion/Accordion.jsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { Accordion as MuiAccordion, AccordionDetails, AccordionSummary } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
|
||||||
|
const PREFIX = "Accordion";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMuiAccordion = styled(MuiAccordion)(() => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
"& .MuiAccordionSummary-content": {
|
||||||
|
display: "initial",
|
||||||
|
margin: "initial",
|
||||||
|
},
|
||||||
|
"&.MuiAccordion-root": {
|
||||||
|
backdropFilter: "none",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionDetails-root": {
|
||||||
|
padding: "0px 0px 0px 16px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-expandIconWrapper": {
|
||||||
|
padding: "0px 11px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-root": {
|
||||||
|
minHeight: "initial",
|
||||||
|
padding: "initial",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Accordion = ({
|
||||||
|
summary,
|
||||||
|
children,
|
||||||
|
arrowOnlyCollapse = false,
|
||||||
|
defaultExpanded = true,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [expand, setExpand] = useState(false);
|
||||||
|
const toggleAcordion = () => {
|
||||||
|
setExpand(prev => !prev);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (arrowOnlyCollapse && defaultExpanded) {
|
||||||
|
setExpand(true);
|
||||||
|
}
|
||||||
|
}, [defaultExpanded]);
|
||||||
|
return (
|
||||||
|
<StyledMuiAccordion
|
||||||
|
square
|
||||||
|
elevation={0}
|
||||||
|
expanded={arrowOnlyCollapse ? expand : undefined}
|
||||||
|
defaultExpanded={defaultExpanded}
|
||||||
|
{...props}
|
||||||
|
className={`${classes.root} ${props.className}`}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={
|
||||||
|
<GhostStyledIcon
|
||||||
|
className="accordion-arrow"
|
||||||
|
name="arrow-down"
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
onClick={toggleAcordion} />
|
||||||
|
}>
|
||||||
|
{summary}
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>{children}</AccordionDetails>
|
||||||
|
</StyledMuiAccordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Accordion;
|
231
src/components/BackgroundCanvas/BackgroundCanvas.jsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
function BackgroundCanvas() {
|
||||||
|
const [err, setErr] = useState(false);
|
||||||
|
|
||||||
|
const webGlInit = (some) => {
|
||||||
|
const options = {
|
||||||
|
alpha: true,
|
||||||
|
stencil: false,
|
||||||
|
antialias: false,
|
||||||
|
depth: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
some.elem = document.querySelector("canvas");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
some.gl = (some.elem.getContext("webgl", options) || some.elem.getContext("experimental-webgl", options));
|
||||||
|
|
||||||
|
if (!some.gl) return false;
|
||||||
|
const vertexShader = some.gl.createShader(some.gl.VERTEX_SHADER);
|
||||||
|
some.gl.shaderSource(
|
||||||
|
vertexShader,
|
||||||
|
`
|
||||||
|
precision highp float;
|
||||||
|
attribute vec2 aPosition;
|
||||||
|
uniform vec3 uHSV;
|
||||||
|
uniform vec2 uResolution;
|
||||||
|
varying vec4 vColor;
|
||||||
|
vec3 hsv2rgb(vec3 c) {
|
||||||
|
vec4 K = vec4(1.0, 3.0, 1.9, 3.0);
|
||||||
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||||
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||||
|
}
|
||||||
|
void main() {
|
||||||
|
gl_PointSize = 1.0;
|
||||||
|
gl_Position = vec4(
|
||||||
|
( aPosition.x / uResolution.x * 2.0) - 1.0,
|
||||||
|
(-aPosition.y / uResolution.y * 2.0) + 1.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
vColor = vec4(hsv2rgb(uHSV), 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
some.gl.compileShader(vertexShader);
|
||||||
|
const fragmentShader = some.gl.createShader(some.gl.FRAGMENT_SHADER);
|
||||||
|
some.gl.shaderSource(
|
||||||
|
fragmentShader,
|
||||||
|
`
|
||||||
|
precision highp float;
|
||||||
|
varying vec4 vColor;
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = vColor;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
some.gl.compileShader(fragmentShader);
|
||||||
|
some.program = some.gl.createProgram();
|
||||||
|
some.gl.attachShader(some.program, vertexShader);
|
||||||
|
some.gl.attachShader(some.program, fragmentShader);
|
||||||
|
some.gl.linkProgram(some.program);
|
||||||
|
some.gl.useProgram(some.program);
|
||||||
|
some.aPosition = some.gl.getAttribLocation(some.program, "aPosition");
|
||||||
|
some.gl.enableVertexAttribArray(some.aPosition);
|
||||||
|
some.positionBuffer = some.gl.createBuffer();
|
||||||
|
some.uHSV = some.gl.getUniformLocation(some.program, "uHSV");
|
||||||
|
some.gl.enableVertexAttribArray(some.uHSV);
|
||||||
|
some = webGlResize(some);
|
||||||
|
window.addEventListener(
|
||||||
|
"resize",
|
||||||
|
() => {
|
||||||
|
some = webGlResize(some);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlResize = (some) => {
|
||||||
|
some.width = some.elem.width = some.elem.offsetWidth;
|
||||||
|
some.height = some.elem.height = some.elem.offsetHeight;
|
||||||
|
const uResolution = some.gl.getUniformLocation(some.program, "uResolution");
|
||||||
|
some.gl.enableVertexAttribArray(uResolution);
|
||||||
|
some.gl.uniform2f(uResolution, some.width, some.height);
|
||||||
|
some.gl.viewport(0, 0, some.gl.drawingBufferWidth, some.gl.drawingBufferHeight);
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlBlend = (some, s, d) => {
|
||||||
|
some.gl.blendFunc(s, d);
|
||||||
|
some.gl.enable(some.gl.BLEND);
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlCreateBuffer = (some, len) => {
|
||||||
|
some.num = len;
|
||||||
|
some.bufferLine = new Float32Array(len * 2);
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlBeginPath = (some) => {
|
||||||
|
some.k = 0;
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlStrokeStyle = (some, h, s, v) => {
|
||||||
|
some.gl.uniform3f(some.uHSV, h, s, v);
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlLineTo = (some, x, y) => {
|
||||||
|
some.bufferLine[some.k++] = x;
|
||||||
|
some.bufferLine[some.k++] = y;
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const webGlStroke = (some) => {
|
||||||
|
some.gl.bindBuffer(some.gl.ARRAY_BUFFER, some.positionBuffer);
|
||||||
|
some.gl.vertexAttribPointer(some.aPosition, 2, some.gl.FLOAT, false, 0, 0);
|
||||||
|
some.gl.bufferData(some.gl.ARRAY_BUFFER, some.bufferLine, some.gl.DYNAMIC_DRAW);
|
||||||
|
some.gl.drawArrays(some.gl.LINE_STRIP, 0, some.num);
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pelinInit = (some) => {
|
||||||
|
some.p = new Uint8Array(512);
|
||||||
|
const p = new Uint8Array(256);
|
||||||
|
for (let i = 0; i < 256; i++) p[i] = i;
|
||||||
|
for (let i = 255; i > 0; i--) {
|
||||||
|
const n = Math.floor((i + 1) * Math.random());
|
||||||
|
[p[i], p[n]] = [p[n], p[i]];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 512; i++) some.p[i] = p[i & 255];
|
||||||
|
return some;
|
||||||
|
};
|
||||||
|
|
||||||
|
const perlinFade = (t) => {
|
||||||
|
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
const perlinLerp = (t, a, b) => {
|
||||||
|
return a + t * (b - a);
|
||||||
|
};
|
||||||
|
|
||||||
|
const perlinGrad = (hash, x, y, z) => {
|
||||||
|
const h = hash & 15;
|
||||||
|
const u = h < 8 ? x : y;
|
||||||
|
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
|
||||||
|
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
||||||
|
};
|
||||||
|
|
||||||
|
const perlinNoise = (some, xi, yi, zi) => {
|
||||||
|
const X = Math.floor(xi) & 255;
|
||||||
|
const Y = Math.floor(yi) & 255;
|
||||||
|
const Z = Math.floor(zi) & 255;
|
||||||
|
const x = xi - Math.floor(xi);
|
||||||
|
const y = yi - Math.floor(yi);
|
||||||
|
const z = zi - Math.floor(zi);
|
||||||
|
const u = perlinFade(x);
|
||||||
|
const v = perlinFade(y);
|
||||||
|
const w = perlinFade(z);
|
||||||
|
const A = some.p[X] + Y;
|
||||||
|
const AA = some.p[A] + Z;
|
||||||
|
const AB = some.p[A + 1] + Z;
|
||||||
|
const B = some.p[X + 1] + Y;
|
||||||
|
const BA = some.p[B] + Z;
|
||||||
|
const BB = some.p[B + 1] + Z;
|
||||||
|
return perlinLerp(
|
||||||
|
w,
|
||||||
|
perlinLerp(
|
||||||
|
v,
|
||||||
|
perlinLerp(u, perlinGrad(some.p[AA], x, y, z), perlinGrad(some.p[BA], x - 1, y, z)),
|
||||||
|
perlinLerp(u, perlinGrad(some.p[AB], x, y - 1, z), perlinGrad(some.p[BB], x - 1, y - 1, z)),
|
||||||
|
),
|
||||||
|
perlinLerp(
|
||||||
|
v,
|
||||||
|
perlinLerp(u, perlinGrad(some.p[AA + 1], x, y, z - 1), perlinGrad(some.p[BA + 1], x - 1, y, z - 1)),
|
||||||
|
perlinLerp(u, perlinGrad(some.p[AB + 1], x, y - 1, z - 1), perlinGrad(some.p[BB + 1], x - 1, y - 1, z - 1)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = (canvas, perlin, z) => {
|
||||||
|
const sw = canvas.width / 100;
|
||||||
|
const sh = sw * 40;
|
||||||
|
for (let i = 0; i < 180; i++) {
|
||||||
|
canvas = webGlBeginPath(canvas);
|
||||||
|
canvas = webGlStrokeStyle(canvas, z % 0.5, 0.5, 0.6);
|
||||||
|
for (let j = -50; j < 50; j++) {
|
||||||
|
const h = perlinNoise(perlin, j * 0.05, z - i * 0.01, z);
|
||||||
|
canvas = webGlLineTo(
|
||||||
|
canvas,
|
||||||
|
canvas.width * 0.5 + 0.01 * (i + 180) * j * sw * 0.5,
|
||||||
|
-90 + i + canvas.height * 0.5 + h * sh,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
canvas = webGlStroke(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
z += 0.0012;
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
run(canvas, perlin, z);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const starter = () => {
|
||||||
|
//let canvas = {};
|
||||||
|
let perlin = {};
|
||||||
|
let canvas = webGlInit({});
|
||||||
|
webGlBlend(canvas, canvas.gl.ONE, canvas.gl.ONE);
|
||||||
|
perlin = pelinInit(perlin);
|
||||||
|
canvas = webGlCreateBuffer(canvas, 100);
|
||||||
|
const z = 0;
|
||||||
|
run(canvas, perlin, z);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
starter();
|
||||||
|
} catch {
|
||||||
|
setErr(true);
|
||||||
|
}
|
||||||
|
}, [starter]);
|
||||||
|
|
||||||
|
if (err === true) return (<></>)
|
||||||
|
return (<canvas className="ghost-canvas"></canvas>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BackgroundCanvas;
|
130
src/components/Button/Button.jsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { Button as MuiButton, CircularProgress } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import ArrowUp from "../../assets/icons/arrow-up.svg?react";
|
||||||
|
|
||||||
|
const PREFIX = "custom";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMuiButton = styled(MuiButton, {
|
||||||
|
shouldForwardProp: prop => prop !== "icon",
|
||||||
|
})(({ icon }) => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
fontSize: "15px",
|
||||||
|
fontWeight: 500,
|
||||||
|
height: "39px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
margin: "4.5px",
|
||||||
|
textTransform: "none",
|
||||||
|
textDecoration: "none",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
maxHeight: "39px",
|
||||||
|
padding: icon ? "9px" : "6px 16px",
|
||||||
|
"&.MuiButton-text": {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
fontSize: "22px",
|
||||||
|
},
|
||||||
|
"&.MuiButton-fullWidth": {
|
||||||
|
marginLeft: "0px",
|
||||||
|
marginRight: "0px",
|
||||||
|
},
|
||||||
|
"&.MuiButton-sizeLarge": {
|
||||||
|
minWidth: icon ? "inherit" : "250px",
|
||||||
|
height: "51px",
|
||||||
|
maxHeight: "51px",
|
||||||
|
padding: icon ? "12px" : "6px 16px",
|
||||||
|
fontSize: "18px",
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
fontSize: "29px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"&.MuiButton-sizeSmall": {
|
||||||
|
padding: icon ? "6px" : "0px 23px",
|
||||||
|
height: "33px",
|
||||||
|
fontSize: "14px",
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
fontSize: "18px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .MuiButton-endIcon": {
|
||||||
|
marginLeft: "8px",
|
||||||
|
},
|
||||||
|
"& .MuiButton-startIcon": {
|
||||||
|
marginRight: "8px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Button = ({
|
||||||
|
disableElevation = true,
|
||||||
|
disableFocusRipple = true,
|
||||||
|
disableRipple = true,
|
||||||
|
template = "primary",
|
||||||
|
startIconName,
|
||||||
|
endIconName,
|
||||||
|
className = "",
|
||||||
|
loading,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
let variant = props.variant;
|
||||||
|
let color = props.color;
|
||||||
|
let localProps = {};
|
||||||
|
//let target: HTMLAttributeAnchorTarget = undefined;
|
||||||
|
switch (template) {
|
||||||
|
case "primary":
|
||||||
|
variant = "contained";
|
||||||
|
color = "primary";
|
||||||
|
break;
|
||||||
|
case "secondary":
|
||||||
|
variant = "outlined";
|
||||||
|
color = "secondary";
|
||||||
|
break;
|
||||||
|
case "tertiary":
|
||||||
|
variant = "outlined";
|
||||||
|
color = "primary";
|
||||||
|
break;
|
||||||
|
case "text":
|
||||||
|
variant = "text";
|
||||||
|
color = "secondary";
|
||||||
|
break;
|
||||||
|
case "success":
|
||||||
|
variant = "contained";
|
||||||
|
color = "success";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (props.href) {
|
||||||
|
localProps = {
|
||||||
|
target: "_blank",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const endIcon = endIconName || (props.href && ArrowUp) || null;
|
||||||
|
return (
|
||||||
|
<StyledMuiButton
|
||||||
|
variant={variant}
|
||||||
|
color={color}
|
||||||
|
className={`${classes.root} ${className}`}
|
||||||
|
disableElevation={disableElevation}
|
||||||
|
disableFocusRipple={disableFocusRipple}
|
||||||
|
disableRipple={disableRipple}
|
||||||
|
{...props}
|
||||||
|
{...localProps}
|
||||||
|
startIcon={startIconName ? <GhostStyledIcon component={startIconName} fontSize="large" /> : null}
|
||||||
|
endIcon={endIcon ? <GhostStyledIcon component={endIcon} fontSize="large" /> : null}
|
||||||
|
>
|
||||||
|
{loading && <CircularProgress color="inherit" size="17px" sx={{ marginRight: "8px", opacity: 1 }} />}
|
||||||
|
{props.icon && !props.children ? <GhostStyledIcon component={props.icon} /> : null}
|
||||||
|
{props.children}
|
||||||
|
</StyledMuiButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
20
src/components/Button/index.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Button from "./Button";
|
||||||
|
|
||||||
|
export const PrimaryButton = (props) => {
|
||||||
|
return <Button template="primary" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecondaryButton = (props) => {
|
||||||
|
return <Button template="secondary" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TertiaryButton = (props) => {
|
||||||
|
return <Button template="tertiary" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextButton = (props) => {
|
||||||
|
return <Button template="text" {...props} />;
|
||||||
|
};
|
||||||
|
export const SuccessButton = (props) => {
|
||||||
|
return <Button template="success" {...props} />;
|
||||||
|
};
|
49
src/components/Chip/Chip.jsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Chip as MuiChip } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
const PREFIX = "Chip";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
chip: `${PREFIX}-chip`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMuiChip = styled(MuiChip, {
|
||||||
|
shouldForwardProp: prop => prop !== "template" && prop !== "strong",
|
||||||
|
})(({ theme, template, strong }) => {
|
||||||
|
return {
|
||||||
|
[`&.${classes.chip}`]: {
|
||||||
|
height: "21px",
|
||||||
|
borderRadius: "16px",
|
||||||
|
backgroundColor: template
|
||||||
|
? template === "purple"
|
||||||
|
? theme.colors.special["olyZaps"]
|
||||||
|
: template === "gray"
|
||||||
|
? theme.colors.gray[500]
|
||||||
|
: template === "darkGray"
|
||||||
|
? theme.colors.gray[600]
|
||||||
|
: theme.colors.feedback[template]
|
||||||
|
: theme.palette.mode === "light"
|
||||||
|
? theme.colors.gray[90]
|
||||||
|
: theme.colors.gray[10],
|
||||||
|
"& span": {
|
||||||
|
fontSize: "12px",
|
||||||
|
lineHeight: "18px",
|
||||||
|
color:
|
||||||
|
theme.palette.mode === "light"
|
||||||
|
? theme.colors.gray[700]
|
||||||
|
: template === "gray"
|
||||||
|
? theme.colors.gray[10]
|
||||||
|
: template === "darkGray"
|
||||||
|
? theme.colors.gray[90]
|
||||||
|
: theme.colors.gray[600],
|
||||||
|
fontWeight: strong ? 700 : 450,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const Chip = ({ template, strong = false, ...props }) => {
|
||||||
|
return <StyledMuiChip className={classes.chip} template={template} strong={strong} {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chip;
|
41
src/components/Countdown/Countdown.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const CountdownTimer = ({ otherwise, targetDate }) => {
|
||||||
|
const calculateTimeLeft = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const difference = targetDate.getTime() - now.getTime();
|
||||||
|
let timeLeft = { hours: 0, minutes: 0 };
|
||||||
|
|
||||||
|
if (difference > 0) {
|
||||||
|
timeLeft = {
|
||||||
|
hours: Math.floor(difference / (1000 * 60 * 60)),
|
||||||
|
minutes: Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)),
|
||||||
|
seconds: Math.floor(difference % (1000 * 60) / 1000),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeLeft;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setTimeLeft(calculateTimeLeft());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{timeLeft.hours > 0 || timeLeft.minutes > 0 || timeLeft.seconds > 0 ? (
|
||||||
|
<>in {timeLeft.hours} hrs, {timeLeft.minutes} mins, {timeLeft.seconds} secs</>
|
||||||
|
) : (
|
||||||
|
<>{otherwise}</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountdownTimer;
|
101
src/components/DataRow/DataRow.jsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { Box, Typography, Skeleton } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
import Accordion from "../Accordion/Accordion";
|
||||||
|
import InfoTooltip from "../Tooltip/InfoTooltip";
|
||||||
|
|
||||||
|
const PREFIX = "DataRow";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
accordion: `${PREFIX}-accordion`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledDiv = styled("div")(() => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexDirection: "row",
|
||||||
|
margin: "12px 0px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||||
|
[`&.${classes.accordion}`]: {
|
||||||
|
"&:before": {
|
||||||
|
height: "0px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-root": {
|
||||||
|
flexDirection: "row-reverse",
|
||||||
|
"& .data-row": {
|
||||||
|
margin: "0px",
|
||||||
|
},
|
||||||
|
"& .Mui-expanded": {
|
||||||
|
marginTop: "0px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-expandIconWrapper": {
|
||||||
|
paddingLeft: "0px",
|
||||||
|
paddingRight: "0px",
|
||||||
|
marginRight: "10px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionDetails-root": {
|
||||||
|
display: "block",
|
||||||
|
paddingLeft: "33px",
|
||||||
|
"& .MuiTypography-root": {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: "14px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const DataRow = props => {
|
||||||
|
const Row = () => (
|
||||||
|
<StyledDiv className={`${classes.root} data-row`} style={props.indented ? { paddingLeft: "10px" } : {}}>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
fontSize={props.indented ? "14px" : "15px"}
|
||||||
|
color={props.indented ? "textSecondary" : "primary"}
|
||||||
|
>
|
||||||
|
{typeof props.title === "string" ? (
|
||||||
|
<Typography fontSize={props.indented ? "14px" : "15px"} color={props.indented ? "textSecondary" : "primary"}>
|
||||||
|
{props.title}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
props.title
|
||||||
|
)}
|
||||||
|
{props.tooltip && <InfoTooltip message={props.tooltip} />}
|
||||||
|
</Box>
|
||||||
|
{typeof props.balance === "string" ? (
|
||||||
|
<Typography
|
||||||
|
fontSize={props.indented ? "14px" : "15px"}
|
||||||
|
id={props.id}
|
||||||
|
color={props.indented ? "textSecondary" : "primary"}
|
||||||
|
style={{ textAlign: "right" }}
|
||||||
|
>
|
||||||
|
{props.isLoading ? <Skeleton width="80px" /> : props.balance}
|
||||||
|
</Typography>
|
||||||
|
) : props.isLoading ? (
|
||||||
|
<Skeleton width="80px" />
|
||||||
|
) : (
|
||||||
|
props.balance
|
||||||
|
)}
|
||||||
|
</StyledDiv>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.children ? (
|
||||||
|
<StyledAccordion className={classes.accordion} summary={<Row />}>
|
||||||
|
{props.children}
|
||||||
|
</StyledAccordion>
|
||||||
|
) : (
|
||||||
|
<Row />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataRow;
|
10
src/components/Icon/BondIcon.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
const BondIcon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 24 24">
|
||||||
|
<path d="M5.30469 17.3984H6.91406C7.03125 17.3984 7.10156 17.4297 7.1875 17.5156L8.32812 18.6484C9.44531 19.7656 10.5469 19.7656 11.6641 18.6484L12.8047 17.5156C12.8906 17.4297 12.9609 17.3984 13.0781 17.3984H14.6875C16.2656 17.3984 17.0469 16.6172 17.0469 15.0391V13.4297C17.0469 13.3203 17.0781 13.2344 17.1641 13.1563L18.2969 12.0156C19.4141 10.8984 19.4141 9.79688 18.2969 8.6797L17.1641 7.54688C17.0703 7.45313 17.0469 7.38282 17.0469 7.27345V5.66407C17.0469 4.07813 16.2578 3.29688 14.6875 3.29688H13.0781C12.9609 3.29688 12.8828 3.26563 12.8047 3.18751L11.6641 2.0547C10.5391 0.921883 9.46094 0.937508 8.32812 2.06251L7.1875 3.18751C7.10938 3.26563 7.03125 3.29688 6.91406 3.29688H5.30469C3.72656 3.29688 2.94531 4.07032 2.94531 5.66407V7.27345C2.94531 7.38282 2.91406 7.46095 2.83594 7.54688L1.69531 8.6797C0.578125 9.79688 0.578125 10.8984 1.69531 12.0156L2.83594 13.1563C2.91406 13.2344 2.94531 13.3203 2.94531 13.4297V15.0391C2.94531 16.6172 3.73438 17.3984 5.30469 17.3984ZM5.79688 15.3438C5.13281 15.3438 5.01562 15.2266 5.00781 14.5547V12.7344C5.00781 12.5156 4.95312 12.3594 4.79688 12.2109L3.5 10.9141C3.02344 10.4375 3.02344 10.2813 3.5 9.8047L4.79688 8.50782C4.95312 8.35157 5.00781 8.19532 5.01562 7.98438V6.16407C5.01562 5.48438 5.125 5.37501 5.79688 5.37501L7.625 5.3672C7.83594 5.3672 8 5.3047 8.14844 5.15626L9.44531 3.85938C9.92188 3.39063 10.0703 3.38282 10.5469 3.85938L11.8438 5.15626C11.9922 5.3047 12.1562 5.3672 12.375 5.3672L14.1953 5.37501C14.8594 5.37501 14.9766 5.4922 14.9766 6.16407V7.98438C14.9844 8.19532 15.0391 8.35157 15.1953 8.50782L16.4922 9.8047C16.9688 10.2813 16.9688 10.4375 16.4922 10.9141L15.1953 12.2109C15.0391 12.3594 14.9844 12.5156 14.9844 12.7344L14.9766 14.5547C14.9766 15.2266 14.8594 15.3438 14.1953 15.3438L12.375 15.3516C12.1562 15.3516 12.0078 15.3906 11.8438 15.5547L10.5469 16.8516C10.0781 17.3281 9.92188 17.3281 9.44531 16.8516L8.14844 15.5547C7.98438 15.3906 7.83594 15.3516 7.625 15.3516L5.79688 15.3438Z" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BondIcon;
|
10
src/components/Icon/DiscordIcon.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
const DiscordIcon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 24 24">
|
||||||
|
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DiscordIcon;
|
23
src/components/Icon/GhostIcon.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { SvgIcon } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
const PREFIX = "Icon";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledSvgIcon = styled(SvgIcon)(() => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexDirection: "row",
|
||||||
|
margin: "12px 0px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const GhostStyledIcon = ({ component, ...props }) => {
|
||||||
|
return <StyledSvgIcon component={component} {...props}></StyledSvgIcon>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GhostStyledIcon;
|
14
src/components/Icon/LinkArrow.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
const LinkArrow = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M 1.667 3.333 C 0.746 3.333 0 2.587 0 1.667 C 0 0.746 0.746 0 1.667 0 L 18.333 0 C 19.254 0 20 0.746 20 1.667 L 20 18.333 C 20 19.254 19.254 20 18.333 20 C 17.413 20 16.667 19.254 16.667 18.333 L 16.667 5.69 L 2.845 19.512 C 2.194 20.163 1.139 20.163 0.488 19.512 C -0.163 18.861 -0.163 17.806 0.488 17.155 L 14.31 3.333 L 1.667 3.333 Z"
|
||||||
|
></path>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default LinkArrow;
|
10
src/components/Icon/StakeIcon.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
const StakeIcon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 24 24">
|
||||||
|
<path d="M11.6172 19.0859L17.6562 15.5859C18.3438 15.1875 18.6719 14.8203 18.6719 14.2656C18.6719 13.7031 18.3438 13.3438 17.6562 12.9375L16.75 12.4141L17.6562 11.8906C18.3438 11.4922 18.6719 11.125 18.6719 10.5703C18.6719 10.0078 18.3438 9.64844 17.6562 9.25L16.5781 8.625L17.6562 8C18.3438 7.59375 18.6719 7.23438 18.6719 6.67969C18.6719 6.11719 18.3438 5.75781 17.6562 5.35156L11.6172 1.85156C11.1797 1.60156 10.8438 1.46875 10.4922 1.46875C10.1484 1.46875 9.8125 1.60156 9.375 1.85156L3.33594 5.35156C2.64844 5.75781 2.32031 6.11719 2.32031 6.67969C2.32031 7.23438 2.64844 7.59375 3.33594 8L4.41406 8.625L3.33594 9.25C2.64844 9.64844 2.32031 10.0078 2.32031 10.5703C2.32031 11.125 2.64844 11.4922 3.33594 11.8906L4.24219 12.4141L3.33594 12.9375C2.64844 13.3438 2.32031 13.7031 2.32031 14.2656C2.32031 14.8203 2.64844 15.1875 3.33594 15.5859L9.375 19.0859C9.8125 19.3359 10.1484 19.4688 10.4922 19.4688C10.8438 19.4688 11.1797 19.3359 11.6172 19.0859ZM10.4922 10C10.3984 10 10.3125 9.96875 10.2031 9.90625L4.64062 6.75C4.60156 6.73438 4.58594 6.71094 4.58594 6.67969C4.58594 6.64062 4.60156 6.61719 4.64062 6.60156L10.2031 3.44531C10.3125 3.38281 10.3984 3.35156 10.4922 3.35156C10.5859 3.35156 10.6719 3.38281 10.7891 3.44531L16.3516 6.60156C16.3828 6.61719 16.4062 6.64062 16.4062 6.67969C16.4062 6.71094 16.3828 6.73438 16.3516 6.75L10.7891 9.90625C10.6719 9.96875 10.5859 10 10.4922 10ZM10.4922 11.8828C10.8438 11.8828 11.1797 11.75 11.6172 11.5L14.8359 9.63281L16.3516 10.4922C16.3828 10.5156 16.4062 10.5312 16.4062 10.5703C16.4062 10.6016 16.3828 10.625 16.3516 10.6406L10.7891 13.8047C10.6719 13.8672 10.5859 13.8984 10.4922 13.8984C10.3984 13.8984 10.3125 13.8672 10.2031 13.8047L4.64062 10.6406C4.60156 10.625 4.58594 10.6016 4.58594 10.5703C4.58594 10.5312 4.60156 10.5156 4.64062 10.4922L6.15625 9.63281L9.375 11.5C9.8125 11.75 10.1484 11.8828 10.4922 11.8828ZM10.2031 17.4922L4.64062 14.3359C4.60156 14.3203 4.58594 14.2969 4.58594 14.2656C4.58594 14.2266 4.60156 14.2109 4.64062 14.1875L5.98438 13.4297L9.375 15.3906C9.8125 15.6406 10.1484 15.7734 10.4922 15.7734C10.8438 15.7734 11.1797 15.6406 11.6172 15.3906L15.0078 13.4297L16.3516 14.1875C16.3828 14.2109 16.4062 14.2266 16.4062 14.2656C16.4062 14.2969 16.3828 14.3203 16.3516 14.3359L10.7891 17.4922C10.6719 17.5547 10.5859 17.5859 10.4922 17.5859C10.3984 17.5859 10.3125 17.5547 10.2031 17.4922Z" />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StakeIcon;
|
17
src/components/Icon/WrapIcon.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
const BondIcon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 24 24">
|
||||||
|
<>
|
||||||
|
<path d="M 17.119 9.976 C 17.119 13.338 14.855 16.142 11.808 16.951 L 11.808 17.868 C 11.772 17.97 11.752 18.079 11.752 18.194 C 11.752 18.308 11.772 18.417 11.808 18.519 L 11.808 18.924 C 11.884 18.909 11.96 18.892 12.035 18.875 C 12.208 19.047 12.448 19.154 12.712 19.154 L 19.04 19.154 C 19.57 19.154 20 18.724 20 18.194 C 20 17.663 19.57 17.233 19.04 17.233 L 15.487 17.233 C 17.647 15.565 19.04 12.935 19.04 9.976 C 19.04 4.933 14.993 0.845 10 0.845 C 5.008 0.845 0.961 4.933 0.961 9.976 C 0.961 12.935 2.354 15.565 4.514 17.233 L 0.961 17.233 C 0.43 17.233 0 17.663 0 18.194 C 0 18.724 0.43 19.154 0.961 19.154 L 7.289 19.154 C 7.553 19.154 7.792 19.047 7.966 18.875 C 8.041 18.892 8.117 18.909 8.192 18.924 L 8.192 18.519 C 8.229 18.417 8.249 18.308 8.249 18.194 C 8.249 18.08 8.229 17.97 8.192 17.868 L 8.192 16.951 C 5.146 16.142 2.882 13.338 2.882 9.976 C 2.882 5.976 6.087 2.766 10 2.766 C 13.914 2.766 17.119 5.976 17.119 9.976 Z"></path>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M 12.712 9.976 C 12.712 11.489 11.498 12.715 10 12.715 C 8.503 12.715 7.289 11.489 7.289 9.976 C 7.289 8.463 8.503 7.237 10 7.237 C 11.498 7.237 12.712 8.463 12.712 9.976 Z M 10.791 9.976 C 10.791 10.446 10.419 10.794 10 10.794 C 9.582 10.794 9.209 10.446 9.209 9.976 C 9.209 9.506 9.582 9.158 10 9.158 C 10.419 9.158 10.791 9.506 10.791 9.976 Z"
|
||||||
|
></path>
|
||||||
|
</>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BondIcon;
|
41
src/components/Messages/Messages.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Alert, Icon } from "@mui/material";
|
||||||
|
import { resolveValue, toast as hotToast } from "react-hot-toast";
|
||||||
|
|
||||||
|
// A component that displays error messages
|
||||||
|
const Messages = ({ toast }) => {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
variant="filled"
|
||||||
|
severity={toast.type === "blank" ? "info" : toast.type}
|
||||||
|
style={{ wordBreak: "break-word" }}
|
||||||
|
sx={{
|
||||||
|
maxWidth: "650px",
|
||||||
|
borderRadius: "9px",
|
||||||
|
fontSize: "15px",
|
||||||
|
fontWeight: 450,
|
||||||
|
lineHeight: "24px",
|
||||||
|
"& .MuiAlert-message": { display: "flex", alignItems: "center", padding: "0px" },
|
||||||
|
}}
|
||||||
|
action={
|
||||||
|
<Icon
|
||||||
|
name="x"
|
||||||
|
sx={{ fontSize: "20px", cursor: "pointer", paddingTop: "5px" }}
|
||||||
|
onClick={() => {
|
||||||
|
hotToast.dismiss(toast.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
iconMapping={{
|
||||||
|
success: <Icon name="check-circle" sx={{ fontSize: "18px" }} />,
|
||||||
|
error: <Icon name="alert-circle" sx={{ fontSize: "18px" }} />,
|
||||||
|
warning: <Icon name="alert-circle" sx={{ fontSize: "18px" }} />,
|
||||||
|
info: <Icon name="info" sx={{ fontSize: "18px" }} />,
|
||||||
|
}}
|
||||||
|
elevation={12}
|
||||||
|
>
|
||||||
|
{resolveValue(toast.message, toast)}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Messages;
|
62
src/components/Metric/Metric.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Box, Typography, Skeleton, styled } from "@mui/material";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
import InfoTooltip from "../Tooltip/InfoTooltip";
|
||||||
|
|
||||||
|
const PREFIX = "Metric";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
label: `${PREFIX}-label`,
|
||||||
|
metric: `${PREFIX}-metric`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Root = styled("div")(({ theme }) => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
"& h4": {
|
||||||
|
fontWeight: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.label}`]: {
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "28px",
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Metric = ({ className = "", metricVariant = "h5", loadingWidth = "100%", ...props }) => {
|
||||||
|
return (
|
||||||
|
<Root className={`${classes.root} ${className}`}>
|
||||||
|
<Box textAlign={{ xs: "center" }}>
|
||||||
|
<Box display="flex" justifyContent="center" alignItems="center">
|
||||||
|
<Typography className={classes.label} color="textSecondary">
|
||||||
|
{props.label}
|
||||||
|
</Typography>
|
||||||
|
{props.tooltip && (
|
||||||
|
<Box display="flex" className={classes.label} alignItems="center" style={{ fontSize: "14px" }}>
|
||||||
|
<InfoTooltip message={props.tooltip} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box width="100%" fontSize="24px" lineHeight="33px" fontWeight="700">
|
||||||
|
{props.isLoading ? (
|
||||||
|
<Skeleton width={loadingWidth} />
|
||||||
|
) : typeof props.metric === "string" ? (
|
||||||
|
<Typography fontSize="24px" fontWeight="700" lineHeight="33px">
|
||||||
|
{props.metric}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
props.metric
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Metric;
|
23
src/components/Metric/MetricCollection.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Box, Grid } from "@mui/material";
|
||||||
|
import { Children } from "react";
|
||||||
|
|
||||||
|
const MetricCollection = ({ children }) => {
|
||||||
|
// Based on Number of Children, determine each rows grid width
|
||||||
|
const numOfMetrics = Children.count(children);
|
||||||
|
let numPerRow = (12 / numOfMetrics);
|
||||||
|
if (numOfMetrics > 3) {
|
||||||
|
numPerRow = 4;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexWrap="wrap" justifyContent="space-between" alignItems={{ xs: "left", sm: "center" }}>
|
||||||
|
<Grid container spacing={2} alignItems="flex-end">
|
||||||
|
{Children.map(children, child => (
|
||||||
|
<Grid item xs={12} sm={numPerRow}>
|
||||||
|
{child}
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default MetricCollection;
|
132
src/components/Modal/Modal.jsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { Backdrop, IconButton, Modal as MuiModal } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { ReactElement, useState } from "react";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import Paper from "../Paper/Paper";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
|
const PREFIX = "Modal";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
paper: `${PREFIX}-paper`,
|
||||||
|
backdrop: `${PREFIX}-backdrop`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMuiModal = styled(MuiModal, {
|
||||||
|
shouldForwardProp: prop => prop !== "minHeight" && prop !== "maxWidth",
|
||||||
|
})(({ theme, minHeight, maxWidth }) => ({
|
||||||
|
[`& .${classes.paper}.Paper-root`]: {
|
||||||
|
"& .MuiIconButton-sizeSmall": {
|
||||||
|
padding: "0px",
|
||||||
|
marginRight: "-9px",
|
||||||
|
},
|
||||||
|
"& .modalDismiss": {
|
||||||
|
marginLeft: "auto",
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
position: "absolute",
|
||||||
|
minHeight: minHeight ? minHeight : "auto",
|
||||||
|
[theme.breakpoints.down("md")]: {
|
||||||
|
maxWidth: "none",
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
maxWidth: maxWidth ? maxWidth : "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.backdrop}`]: {
|
||||||
|
"&::before": {
|
||||||
|
"@supports not ((-webkit-backdrop-filter: none) or (backdrop-filter: none))": {
|
||||||
|
content: '""',
|
||||||
|
background:
|
||||||
|
theme.palette.mode === "light"
|
||||||
|
? `linear-gradient(180deg, #AFCDE9 1%, #F7FBE7 100%)`
|
||||||
|
: `linear-gradient(180deg, rgba(8, 15, 53, 0), rgba(0, 0, 10, 0.9)), linear-gradient(333deg, rgba(153, 207, 255, 0.2), rgba(180, 255, 217, 0.08)), radial-gradient(circle at 77% 89%, rgba(125, 163, 169, 0.8), rgba(125, 163, 169, 0) 50%), radial-gradient(circle at 15% 95%, rgba(125, 163, 169, 0.8), rgba(125, 163, 169, 0) 43%), radial-gradient(circle at 65% 23%, rgba(137, 151, 119, 0.4), rgba(137, 151, 119, 0) 70%), radial-gradient(circle at 10% 0%, rgba(187, 211, 204, 0.33), rgba(187,211,204,0) 35%), radial-gradient(circle at 11% 100%, rgba(131, 165, 203, 0.3), rgba(131, 165, 203, 0) 30%)`,
|
||||||
|
opacity: "1",
|
||||||
|
filter: "blur(333px)",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"@supports not ((-webkit-backdrop-filter: none) or (backdrop-filter: none))": {
|
||||||
|
background: "hsla(0,0%,39.2%,.9)",
|
||||||
|
},
|
||||||
|
background: "hsla(0,0%,39.2%,.1)",
|
||||||
|
backdropFilter: "blur(33px)",
|
||||||
|
"--webkitBackdropFilter": "blur(33px)",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function getModalStyle() {
|
||||||
|
const top = 50;
|
||||||
|
const left = 50;
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: `${top}%`,
|
||||||
|
left: `${left}%`,
|
||||||
|
transform: `translate(-${top}%, -${left}%)`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Modal = ({
|
||||||
|
open = false,
|
||||||
|
minHeight = "605px",
|
||||||
|
maxWidth = "750px",
|
||||||
|
closePosition = "right",
|
||||||
|
headerText,
|
||||||
|
headerContent,
|
||||||
|
topRight,
|
||||||
|
topLeft,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
// getModalStyle is not a pure function, we roll the style only on the first render
|
||||||
|
const [modalStyle] = useState(getModalStyle);
|
||||||
|
const closeButton = (
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={e => {
|
||||||
|
if (props.onClose) {
|
||||||
|
props.onClose(e, "escapeKeyDown");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GhostStyledIcon component={CloseIcon} />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
//modal must have a close position. Close position and topLeft or topRight cant be used for the same position.
|
||||||
|
const topRightPos = closePosition === "right" ? closeButton : topRight;
|
||||||
|
const topLeftPos = closePosition === "left" ? closeButton : topLeft;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledMuiModal
|
||||||
|
open={open}
|
||||||
|
className={props.className}
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
aria-describedby="modal-description"
|
||||||
|
BackdropComponent={Backdrop}
|
||||||
|
BackdropProps={{ className: classes.backdrop }}
|
||||||
|
{...props}
|
||||||
|
minHeight={minHeight}
|
||||||
|
maxWidth={maxWidth}
|
||||||
|
>
|
||||||
|
<Paper
|
||||||
|
style={modalStyle}
|
||||||
|
className={classes.paper}
|
||||||
|
headerText={headerText}
|
||||||
|
topRight={topRightPos}
|
||||||
|
topLeft={topLeftPos}
|
||||||
|
zoom={false}
|
||||||
|
headerContent={headerContent}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Paper>
|
||||||
|
</StyledMuiModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Modal;
|
173
src/components/NavItem/NavItem.jsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import { Box, Link, Accordion, AccordionSummary } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
import Chip from "../Chip/Chip";
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import ArrowUpIcon from "../../assets/icons/arrow-up.svg?react";
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
|
||||||
|
import { NavLink, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
const PREFIX = "NavItem";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
title: `${PREFIX}-title`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Root = styled("div", { shouldForwardProp: prop => prop !== "match" })(({ theme, match }) => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "12px",
|
||||||
|
"& .link-container": {
|
||||||
|
paddingRight: "12px",
|
||||||
|
},
|
||||||
|
"& a.active": {
|
||||||
|
"& .link-container": {
|
||||||
|
backgroundColor: theme.colors.gray[600],
|
||||||
|
},
|
||||||
|
textDecoration: "none",
|
||||||
|
},
|
||||||
|
"& .MuiAccordion-root": {
|
||||||
|
background: "transparent",
|
||||||
|
"& .MuiAccordionDetails-root a.active .activePill": {
|
||||||
|
marginLeft: "-35px",
|
||||||
|
marginRight: "35px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-expandIconWrapper": {
|
||||||
|
padding: "0px 18px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-contentGutters": {
|
||||||
|
"& > a": {
|
||||||
|
width: "100%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"& .MuiAccordion-root &:last-child": {
|
||||||
|
paddingBottom: "0px",
|
||||||
|
},
|
||||||
|
"& .MuiAccordionSummary-root": {
|
||||||
|
padding: "0px",
|
||||||
|
margin: "-12px 0 -12px",
|
||||||
|
|
||||||
|
"& :has(a.active .link-container)": {
|
||||||
|
backgroundColor: theme.colors.gray[600],
|
||||||
|
marginRight: "-67px",
|
||||||
|
},
|
||||||
|
"& a.active .link-container": {
|
||||||
|
margin: "0px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"& .MuiAccordionDetails-root": {
|
||||||
|
"& .link-container .title": {
|
||||||
|
fontSize: "13px",
|
||||||
|
lineHeight: 1,
|
||||||
|
},
|
||||||
|
"& a.active .link-container": {
|
||||||
|
backgroundColor: theme.colors.gray[600],
|
||||||
|
},
|
||||||
|
paddingLeft: "20px",
|
||||||
|
display: "block",
|
||||||
|
"& .nav-item-container": {
|
||||||
|
paddingTop: "3px",
|
||||||
|
paddingBottom: "3px",
|
||||||
|
paddingRight: "0px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"& svg": {
|
||||||
|
marginRight: "12px",
|
||||||
|
},
|
||||||
|
"& svg.accordion-arrow": {
|
||||||
|
marginRight: "0px",
|
||||||
|
},
|
||||||
|
"& .external-site-link": {
|
||||||
|
"& .external-site-link-icon": {
|
||||||
|
opacity: "0",
|
||||||
|
},
|
||||||
|
"&:hover .external-site-link-icon": {
|
||||||
|
marginLeft: "5px",
|
||||||
|
opacity: "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.title}`]: {
|
||||||
|
lineHeight: "33px",
|
||||||
|
paddingLeft: "12px",
|
||||||
|
paddingTop: "3px",
|
||||||
|
paddingBottom: "3px",
|
||||||
|
fontSize: "15px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const NavItem = ({
|
||||||
|
chip,
|
||||||
|
className = "",
|
||||||
|
customIcon,
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
to,
|
||||||
|
children,
|
||||||
|
defaultExpanded = false,
|
||||||
|
chipColor,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const currentLocation = useLocation();
|
||||||
|
const match = currentLocation.pathname === to || currentLocation.pathname === `/${to}`;
|
||||||
|
|
||||||
|
const linkProps = props.href
|
||||||
|
? {
|
||||||
|
href: props.href,
|
||||||
|
target: "_blank",
|
||||||
|
className: `external-site-link ${className}`,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
component: NavLink,
|
||||||
|
to: to,
|
||||||
|
className: `button-dapp-menu ${className}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const LinkItem = () => (
|
||||||
|
<Link {...linkProps} {...props} underline="hover">
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
alignContent="center"
|
||||||
|
justifyContent="space-around"
|
||||||
|
className="link-container"
|
||||||
|
>
|
||||||
|
<Box display="flex" width={"100%"} alignItems="center" className={`${classes.title} title`}>
|
||||||
|
{customIcon ? customIcon : icon && <GhostStyledIcon viewBox="0 0 25 25" component={icon} style={{ fontSize: "21px" }} />}
|
||||||
|
{label}
|
||||||
|
{props.href && <GhostStyledIcon
|
||||||
|
style={{ marginTop: "7px" }}
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
className="external-site-link-icon"
|
||||||
|
component={ArrowUpIcon}
|
||||||
|
/>}
|
||||||
|
</Box>
|
||||||
|
{chip && <Chip size="small" label={chip} template={chipColor} />}
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Root className={`${classes.root} nav-item-container`} match={match}>
|
||||||
|
{children ? (
|
||||||
|
<Accordion defaultExpanded={defaultExpanded} arrowonlycollapse="true">
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon style={{ width: "18px", height: "18px" }} />}
|
||||||
|
children={<LinkItem />}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Accordion>
|
||||||
|
) : (
|
||||||
|
<LinkItem />
|
||||||
|
)}
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavItem;
|
147
src/components/Notification/Notification.jsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { Alert, Box, Collapse, IconButton, Typography } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
|
const PREFIX = "Notification";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
icon: `${PREFIX}-icon`,
|
||||||
|
text: `${PREFIX}-text`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledCollapse = styled(Collapse, { shouldForwardProp: prop => prop !== "square" })(
|
||||||
|
({ theme, square }) => ({
|
||||||
|
[`& .${classes.root}`]: {
|
||||||
|
borderRadius: square ? "0px" : "9px",
|
||||||
|
paddingTop: "9px",
|
||||||
|
paddingBottom: "9px",
|
||||||
|
marginBottom: "10px",
|
||||||
|
wordBreak: "break-word",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: theme.colors.gray[700],
|
||||||
|
"& a": {
|
||||||
|
fontWeight: "500",
|
||||||
|
color: theme.colors.gray[700],
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
"& .MuiAlert-message": {
|
||||||
|
padding: "0px",
|
||||||
|
flexGrow: "1",
|
||||||
|
},
|
||||||
|
"& .MuiAlert-action": {
|
||||||
|
paddingLeft: "0px",
|
||||||
|
"& .MuiIconButton-sizeSmall": {
|
||||||
|
padding: "0px",
|
||||||
|
"& .MuiSvgIcon-fontSizeSmall": {
|
||||||
|
fontSize: "1.14rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"&.MuiAlert-filledSuccess": {
|
||||||
|
color: "#253449",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.icon}`]: {
|
||||||
|
fontSize: "16.5px",
|
||||||
|
marginRight: "9px",
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.text}`]: {
|
||||||
|
fontSize: "15px",
|
||||||
|
lineHeight: "24px",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const Notification = ({
|
||||||
|
template = "info",
|
||||||
|
dismissible = false,
|
||||||
|
onDismiss,
|
||||||
|
show,
|
||||||
|
square = false,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
let icon;
|
||||||
|
let severity = props.severity;
|
||||||
|
let variant = props.variant;
|
||||||
|
switch (template) {
|
||||||
|
case "default":
|
||||||
|
severity = "info";
|
||||||
|
variant = "filled";
|
||||||
|
break;
|
||||||
|
case "success":
|
||||||
|
icon = "check-circle";
|
||||||
|
severity = "success";
|
||||||
|
variant = "filled";
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
icon = "alert-circle";
|
||||||
|
severity = "error";
|
||||||
|
variant = "filled";
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
icon = "info";
|
||||||
|
severity = "info";
|
||||||
|
variant = "filled";
|
||||||
|
break;
|
||||||
|
case "warning":
|
||||||
|
icon = "info";
|
||||||
|
severity = "warning";
|
||||||
|
variant = "filled";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action =
|
||||||
|
props.action || dismissible === false ? (
|
||||||
|
props.action
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
if (onDismiss) {
|
||||||
|
onDismiss();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GhostStyledIcon component={CloseIcon} />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
if (show === true) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, [show]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledCollapse in={open} square={square}>
|
||||||
|
<Alert
|
||||||
|
variant={variant}
|
||||||
|
icon={false}
|
||||||
|
severity={severity}
|
||||||
|
className={classes.root}
|
||||||
|
action={action}
|
||||||
|
{...props}
|
||||||
|
elevation={8}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="center">
|
||||||
|
{icon && <GhostStyledIcon className={classes.icon} component={icon} />}
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Typography className={classes.text}>{props.children}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Alert>
|
||||||
|
</StyledCollapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification;
|
22
src/components/Notification/index.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Notification from "./Notification";
|
||||||
|
|
||||||
|
export const DefaultNotification = (props) => {
|
||||||
|
return <Notification template="default" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SuccessNotification = (props) => {
|
||||||
|
return <Notification template="success" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ErrorNotification = (props) => {
|
||||||
|
return <Notification template="error" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InfoNotification = (props) => {
|
||||||
|
return <Notification template="info" {...props} />;
|
||||||
|
};
|
||||||
|
export const WarningNotification = (props) => {
|
||||||
|
return <Notification template="warning" {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultNotification;
|
26
src/components/PageTitle/PageTitle.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
const PageTitle = ({ name, subtitle, noMargin }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const mobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
width="100%"
|
||||||
|
marginLeft={noMargin ? "0px" : mobile ? "9px" : "33px"}
|
||||||
|
marginTop={mobile ? "50px" : "0px"}
|
||||||
|
paddingBottom="22.5px"
|
||||||
|
>
|
||||||
|
<Box paddingTop="3px" display="flex" flexDirection="row" justifyContent="flex-start" alignContent="center">
|
||||||
|
<Typography variant="h1">{name}</Typography>
|
||||||
|
</Box>
|
||||||
|
{subtitle && (
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="flex-start">
|
||||||
|
<Typography color={theme.colors.gray[40]}>{subtitle}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageTitle;
|
98
src/components/Paper/Paper.jsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { Box, Grid, Paper as MuiPaper, Typography, Zoom } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
|
import Chip from "../Chip/Chip";
|
||||||
|
import InfoTooltip from "../Tooltip/InfoTooltip";
|
||||||
|
|
||||||
|
const PREFIX = "Paper";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledPaper = styled(MuiPaper, {
|
||||||
|
shouldForwardProp: prop => prop !== "fullWidth" && prop !== "enableBackground",
|
||||||
|
})(({ theme, fullWidth, enableBackground }) => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
zIndex: 5,
|
||||||
|
padding: "20px 30px 20px 30px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
maxWidth: fullWidth ? "100%" : "900px",
|
||||||
|
width: fullWidth ? "100%" : "97%",
|
||||||
|
marginBottom: "1.8rem",
|
||||||
|
overflow: "hidden",
|
||||||
|
background: enableBackground ? theme.colors.paper.card : "",
|
||||||
|
backdropFilter: enableBackground ? "" : "none",
|
||||||
|
"--webkitBackdropFilter": enableBackground ? "" : "none",
|
||||||
|
"& .card-header": {
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "33px",
|
||||||
|
marginBottom: "10px",
|
||||||
|
position: "relative",
|
||||||
|
"& h5": {
|
||||||
|
fontWeight: "600",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Paper = ({
|
||||||
|
headerText,
|
||||||
|
headerContent,
|
||||||
|
className = "",
|
||||||
|
tooltip,
|
||||||
|
fullWidth = false,
|
||||||
|
topLeft,
|
||||||
|
topRight,
|
||||||
|
zoom = true,
|
||||||
|
subHeader,
|
||||||
|
enableBackground = false,
|
||||||
|
headerChip,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Zoom in={true} appear={zoom}>
|
||||||
|
<StyledPaper
|
||||||
|
className={`${classes.root} ${className}`}
|
||||||
|
{...props}
|
||||||
|
elevation={0}
|
||||||
|
enableBackground={enableBackground}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
>
|
||||||
|
<Grid container direction="column" spacing={2}>
|
||||||
|
{(topLeft || headerText || topRight || headerContent) && (
|
||||||
|
<Grid item className="card-header">
|
||||||
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
{topLeft && <div className="top-left">{topLeft}</div>}
|
||||||
|
{headerText && !headerContent && (
|
||||||
|
<Box display="flex" flexDirection="row" alignItems="center">
|
||||||
|
<Typography fontSize="24px" className="header-text" fontWeight={700} lineHeight="33px">
|
||||||
|
{headerText}
|
||||||
|
</Typography>
|
||||||
|
{tooltip && (
|
||||||
|
<Box display="inline" alignSelf="center" style={{ fontSize: "9px" }}>
|
||||||
|
<InfoTooltip message={tooltip} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{headerChip && (
|
||||||
|
<Box ml="8px">
|
||||||
|
<Chip label={headerChip} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{headerContent}
|
||||||
|
<div className="top-right">{topRight}</div>
|
||||||
|
</Box>
|
||||||
|
{subHeader && <Box display="flex">{subHeader}</Box>}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item>{props.children}</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledPaper>
|
||||||
|
</Zoom>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Paper;
|
9
src/components/SafariFooter/SafariFooter.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const SafariFooter = () => {
|
||||||
|
const isSafariOniPhone = navigator.userAgent.match(/(iPhone).*(Safari)/);
|
||||||
|
|
||||||
|
if (!isSafariOniPhone) return <></>;
|
||||||
|
|
||||||
|
return <div id="safari-iphone-footer" style={{ paddingTop: "80px" }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SafariFooter;
|
229
src/components/Sidebar/NavContent.jsx
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import "./Sidebar.scss";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Grid,
|
||||||
|
Link,
|
||||||
|
Paper,
|
||||||
|
SvgIcon,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
|
import XIcon from '@mui/icons-material/X';
|
||||||
|
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||||
|
import HowToVoteIcon from '@mui/icons-material/HowToVote';
|
||||||
|
import HubIcon from '@mui/icons-material/Hub';
|
||||||
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
|
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';
|
||||||
|
|
||||||
|
import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react";
|
||||||
|
import NavItem from "../NavItem/NavItem";
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import DiscordIcon from "../Icon/DiscordIcon";
|
||||||
|
import BondIcon from "../Icon/BondIcon";
|
||||||
|
import StakeIcon from "../Icon/StakeIcon";
|
||||||
|
import WrapIcon from "../Icon/WrapIcon";
|
||||||
|
|
||||||
|
import { isNetworkAvailable } from "../../constants";
|
||||||
|
import { AVAILABLE_DEXES } from "../../constants/dexes";
|
||||||
|
import { ECOSYSTEM } from "../../constants/ecosystem";
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { sortBondsByDiscount, formatCurrency } from "../../helpers";
|
||||||
|
import BondDiscount from "../../containers/Bond/components/BondDiscount";
|
||||||
|
|
||||||
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
|
import ShowerIcon from '@mui/icons-material/Shower';
|
||||||
|
|
||||||
|
import { useFtsoPrice, useGhstPrice } from "../../hooks/prices";
|
||||||
|
import { useLiveBonds } from "../../hooks/bonds/index";
|
||||||
|
|
||||||
|
const PREFIX = "NavContent";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
gray: `${PREFIX}-gray`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
[`& .${classes.gray}`]: {
|
||||||
|
color: theme.colors.gray[90],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const NavContent = ({ chainId, addressChainId }) => {
|
||||||
|
const { liveBonds: ghostBonds } = useLiveBonds(chainId);
|
||||||
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
const ghstPrice = useGhstPrice(chainId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper className="dapp-sidebar">
|
||||||
|
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
|
||||||
|
<div className="dapp-menu-top">
|
||||||
|
<Box className="branding-header">
|
||||||
|
<Link href="https://app.dao.ghostchain.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
<SvgIcon
|
||||||
|
color="primary"
|
||||||
|
viewBox="0 0 400 372"
|
||||||
|
component={GhostIcon}
|
||||||
|
style={{ width: "150px", height: "150px" }}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Box display="flex" flexDirection="column" mt="10px">
|
||||||
|
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
|
||||||
|
FTSO Price: {formatCurrency(ftsoPrice, 2)}
|
||||||
|
</Box>
|
||||||
|
<Box fontSize="12px" fontWeight="500" lineHeight="15px">
|
||||||
|
GHST Price: {formatCurrency(ghstPrice, 2)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box className="menu-divider">
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<div className="dapp-menu-links">
|
||||||
|
<div className="dapp-nav" id="navbarNav">
|
||||||
|
{isNetworkAvailable(chainId, addressChainId) &&
|
||||||
|
<>
|
||||||
|
<NavItem icon={DashboardIcon} label={`Dashboard`} to="/dashboard" />
|
||||||
|
<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={StakeIcon} label={`Stake`} to="/stake" />
|
||||||
|
<NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
|
||||||
|
<NavItem
|
||||||
|
icon={CurrencyExchangeIcon}
|
||||||
|
label={`Dex`}
|
||||||
|
to={'/dex/uniswap'}
|
||||||
|
children={
|
||||||
|
<AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
{AVAILABLE_DEXES[chainId].map((dex, index) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
component={NavLink}
|
||||||
|
to={`/dex/${dex.name.toLowerCase()}`}
|
||||||
|
key={index}
|
||||||
|
style={{ textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="end">
|
||||||
|
<GhostStyledIcon color="inherit" viewBox={dex.viewBox} component={dex.icon} />
|
||||||
|
{dex.name} v2
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AccordionDetails>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box className="menu-divider">
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
<NavItem icon={PublicIcon} label={`Bridge`} href="https://bridge.ghostchain.io/" />
|
||||||
|
<NavItem
|
||||||
|
to=''
|
||||||
|
icon={PublicIcon}
|
||||||
|
label={`Ecosystem`}
|
||||||
|
children={
|
||||||
|
<AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
{ECOSYSTEM.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={item.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key={index}
|
||||||
|
style={{ textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
<Box display="flex" justifyContent="end">
|
||||||
|
<Typography style={{ display: "flex", alignItems: "center" }} >
|
||||||
|
{item.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AccordionDetails>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box className="menu-divider">
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<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} />
|
||||||
|
</Link>
|
||||||
|
<Link href="https://x.com/realGhostChain" target="_blank" rel="noopener noreferrer">
|
||||||
|
<GhostStyledIcon viewBox="0 0 24 24" component={XIcon} className={classes.gray} />
|
||||||
|
</Link>
|
||||||
|
<Link href="https://discord.gg/CvYP7vrqN3" target="_blank" rel="noopener noreferrer">
|
||||||
|
<GhostStyledIcon viewBox="0 0 24 24" component={DiscordIcon} className={classes.gray} />
|
||||||
|
</Link>
|
||||||
|
<Link href="https://t.me/realGhostChain" target="_blank" rel="noopener noreferrer">
|
||||||
|
<GhostStyledIcon viewBox="0 0 24 24" component={TelegramIcon} className={classes.gray} />
|
||||||
|
</Link>
|
||||||
|
</StyledBox>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavContent;
|
61
src/components/Sidebar/NavDrawer.jsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { SwipeableDrawer } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import NavContent from "./NavContent";
|
||||||
|
|
||||||
|
const PREFIX = "NavDrawer";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
drawer: `${PREFIX}-drawer`,
|
||||||
|
drawerPaper: `${PREFIX}-drawerPaper`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledSwipeableDrawer = styled(SwipeableDrawer)(({ theme }) => ({
|
||||||
|
[`& .${classes.drawer}`]: {
|
||||||
|
[theme.breakpoints.up("md")]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.drawerPaper}`]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
borderRight: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const drawerWidth = 280;
|
||||||
|
|
||||||
|
const NavDrawer = ({ chainId, addressChainId, mobileOpen, handleDrawerToggle }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mobileOpen && handleDrawerToggle) {
|
||||||
|
handleDrawerToggle();
|
||||||
|
}
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledSwipeableDrawer
|
||||||
|
variant="temporary"
|
||||||
|
anchor="left"
|
||||||
|
open={mobileOpen}
|
||||||
|
onOpen={handleDrawerToggle}
|
||||||
|
onClose={handleDrawerToggle}
|
||||||
|
classes={{
|
||||||
|
paper: classes.drawerPaper,
|
||||||
|
}}
|
||||||
|
ModalProps={{
|
||||||
|
keepMounted: true, // Better open performance on mobile.
|
||||||
|
}}
|
||||||
|
disableBackdropTransition={!isIOS}
|
||||||
|
disableDiscovery={isIOS}
|
||||||
|
>
|
||||||
|
<NavContent addressChainId={addressChainId} chainId={chainId} />
|
||||||
|
</StyledSwipeableDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavDrawer;
|
15
src/components/Sidebar/Sidebar.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import "./Sidebar.scss";
|
||||||
|
|
||||||
|
import { Drawer } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
import NavContent from "./NavContent";
|
||||||
|
|
||||||
|
const Sidebar = ({ chainId, addressChainId }) => (
|
||||||
|
<div className="sidebar" id="sidebarContent">
|
||||||
|
<Drawer variant="permanent" anchor="left">
|
||||||
|
<NavContent addressChainId={addressChainId} chainId={chainId} />
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Sidebar;
|
177
src/components/Sidebar/Sidebar.scss
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
.sidebar {
|
||||||
|
min-width: 264px;
|
||||||
|
z-index: 2;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MuiDrawer-paper .MuiPaper-root {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dapp-sidebar {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-divider {
|
||||||
|
margin-top: -4.5px;
|
||||||
|
margin-bottom: 7.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dapp-sidebar-inner {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branding-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 20px 0 30px 0;
|
||||||
|
.branding-header-icon {
|
||||||
|
width: 50px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-link {
|
||||||
|
margin: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.avatar {
|
||||||
|
margin-right: 8px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dapp-menu-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
min-height: max-content;
|
||||||
|
.dapp-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dapp-menu-bottom {
|
||||||
|
height: 40vh;
|
||||||
|
min-height: 30vh;
|
||||||
|
}
|
||||||
|
.dapp-menu-data.discounts {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dapp-menu-links {
|
||||||
|
min-height: 35vh;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bond-discounts {
|
||||||
|
text-align: left;
|
||||||
|
color: #999999;
|
||||||
|
padding-left: 26px;
|
||||||
|
margin-top: 8px;
|
||||||
|
a {
|
||||||
|
margin-top: 0.7rem;
|
||||||
|
margin-left: 33px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discounts-accordion {
|
||||||
|
backdrop-filter: none;
|
||||||
|
background-color: inherit;
|
||||||
|
margin: 12px 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MuiAccordionSummary-root {
|
||||||
|
padding: 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MuiAccordionSummary-content {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.MuiTypography-body2 {
|
||||||
|
color: #999999;
|
||||||
|
margin-left: 33px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.MuiAccordionDetails-root {
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.bond {
|
||||||
|
display: block;
|
||||||
|
padding: unset;
|
||||||
|
margin-bottom: 0 0 1px 0;
|
||||||
|
text-decoration: none !important;
|
||||||
|
.bond-pair-roi {
|
||||||
|
float: right;
|
||||||
|
margin-left: 33px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.give-sub-menus {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 33px;
|
||||||
|
> p {
|
||||||
|
margin-left: 33px;
|
||||||
|
}
|
||||||
|
.give-option {
|
||||||
|
display: block;
|
||||||
|
padding: unset;
|
||||||
|
margin-bottom: 0 0 1px 0;
|
||||||
|
margin-left: 33px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-row {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
padding: 1.3rem;
|
||||||
|
a {
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .show .dapp-sidebar {
|
||||||
|
.dapp-menu-links {
|
||||||
|
flex-flow: row !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
.dapp-nav {
|
||||||
|
.dapp-menu-data.discounts {
|
||||||
|
margin-top: 2.5em;
|
||||||
|
.bond-discounts {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
152
src/components/Swap/SwapCard.jsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { Box, Button, InputBase, Typography, useTheme } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { ReactElement, useRef } from "react";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import Token from "../Token/Token";
|
||||||
|
|
||||||
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
|
|
||||||
|
const StyledInputBase = styled(InputBase, { shouldForwardProp: prop => prop !== "inputWidth" })(
|
||||||
|
({ inputWidth, inputFontSize }) => ({
|
||||||
|
"& .MuiInputBase-input": {
|
||||||
|
padding: 0,
|
||||||
|
height: "24px",
|
||||||
|
},
|
||||||
|
"& input": {
|
||||||
|
width: `${inputWidth}` || "136px",
|
||||||
|
fontSize: `${inputFontSize}` ||"18px",
|
||||||
|
"&[type=number]": {
|
||||||
|
MozAppearance: "textfield",
|
||||||
|
},
|
||||||
|
"&::-webkit-outer-spin-button": {
|
||||||
|
WebkitAppearance: "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
"&::-webkit-inner-spin-button": {
|
||||||
|
WebkitAppearance: "none",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const SwapCard = ({
|
||||||
|
id,
|
||||||
|
endString,
|
||||||
|
endStringOnClick,
|
||||||
|
token,
|
||||||
|
placeholder = "0",
|
||||||
|
inputType = "number",
|
||||||
|
info,
|
||||||
|
usdValue,
|
||||||
|
tokenOnClick,
|
||||||
|
tokenName,
|
||||||
|
inputWidth,
|
||||||
|
inputFontSize,
|
||||||
|
maxWidth = "416px",
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const onClickProps = tokenOnClick ? { onClick: tokenOnClick } : "";
|
||||||
|
const ref = useRef(null);
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
maxWidth={maxWidth}
|
||||||
|
sx={{ backgroundColor: theme.colors.gray[750] }}
|
||||||
|
borderRadius="12px"
|
||||||
|
padding="15px"
|
||||||
|
onClick={() => {
|
||||||
|
ref.current && ref.current.focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(token || tokenName) && (
|
||||||
|
<Box display="flex" flexDirection="row">
|
||||||
|
<Box
|
||||||
|
display="inline-flex"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.colors.gray[600],
|
||||||
|
"&:hover": { backgroundColor: tokenOnClick ? theme.colors.gray[500] : theme.colors.gray[600] },
|
||||||
|
cursor: tokenOnClick ? "pointer" : "default",
|
||||||
|
}}
|
||||||
|
borderRadius="6px"
|
||||||
|
paddingX="9px"
|
||||||
|
paddingY="6.5px"
|
||||||
|
alignItems="center"
|
||||||
|
{...onClickProps}
|
||||||
|
>
|
||||||
|
{typeof token === "string" ? <Token name={token} sx={{ fontSize: "21px" }} /> : token}
|
||||||
|
|
||||||
|
{(typeof token === "string" || tokenName) && (
|
||||||
|
<Typography fontSize="15px" lineHeight="24px" marginLeft="9px">
|
||||||
|
{tokenName ? tokenName : token}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tokenOnClick && <GhostStyledIcon component={KeyboardArrowDownIcon} sx={{ fontSize: "7px", marginLeft: "10px" }} />}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box display="flex" flexDirection="row" marginTop="12px" justifyContent="space-between" alignItems="center">
|
||||||
|
<Box display="flex" flexDirection="row" alignItems="center">
|
||||||
|
<StyledInputBase
|
||||||
|
id={id}
|
||||||
|
sx={{
|
||||||
|
color: theme.colors.gray[40],
|
||||||
|
fontWeight: 500,
|
||||||
|
padding: 0,
|
||||||
|
height: "24px",
|
||||||
|
maxWidth: inputWidth || "136px",
|
||||||
|
}}
|
||||||
|
placeholder={placeholder}
|
||||||
|
type={inputType}
|
||||||
|
inputWidth={inputWidth}
|
||||||
|
{...props}
|
||||||
|
inputRef={ref}
|
||||||
|
/>
|
||||||
|
{usdValue && (
|
||||||
|
<Box sx={{ color: theme.colors.gray[500] }} fontSize="12px" lineHeight="15px">
|
||||||
|
≈{usdValue}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
sx={{ color: theme.colors.gray[90] }}
|
||||||
|
alignItems="center"
|
||||||
|
flexWrap="wrap"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
>
|
||||||
|
{info && (
|
||||||
|
<Typography fontSize="12px" lineHeight="24px">
|
||||||
|
{info}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{endString && (
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
marginLeft: "6px",
|
||||||
|
minWidth: 0,
|
||||||
|
padding: 0,
|
||||||
|
fontWeight: 450,
|
||||||
|
fontSize: "12px",
|
||||||
|
lineHeight: "12px",
|
||||||
|
}}
|
||||||
|
onClick={endStringOnClick}
|
||||||
|
>
|
||||||
|
{""}
|
||||||
|
{endString}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SwapCard;
|
65
src/components/Swap/SwapCollection.jsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Box, styled, useTheme } from "@mui/material";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
|
import LoopIcon from '@mui/icons-material/Loop';
|
||||||
|
|
||||||
|
const StyledArrow = styled(Box)(
|
||||||
|
({ theme, onClick }) =>
|
||||||
|
onClick && {
|
||||||
|
"&": {
|
||||||
|
transitionProperty: "all",
|
||||||
|
transitionTimingFunction: " cubic-bezier(.4,0,.2,1)",
|
||||||
|
transitionDuration: ".15s",
|
||||||
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
" & .rotate": {
|
||||||
|
WebkitTransform: "rotateZ(-360deg)",
|
||||||
|
MozTransition: "rotateZ(-360deg)",
|
||||||
|
transform: "rotateZ(-360deg)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .rotate": {
|
||||||
|
WebkitTransition: "0.6s ease-out",
|
||||||
|
MozTransition: "0.6s ease-out",
|
||||||
|
transition: " 0.6s ease-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexDirection="column" maxWidth="476px">
|
||||||
|
{UpperSwapCard}
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="center">
|
||||||
|
<StyledArrow
|
||||||
|
width="21px"
|
||||||
|
height="21px"
|
||||||
|
borderRadius="6px"
|
||||||
|
sx={{ backgroundColor: theme.colors.gray[600] }}
|
||||||
|
textAlign="center"
|
||||||
|
marginTop={"-7px"}
|
||||||
|
zIndex="10"
|
||||||
|
onClick={arrowOnClick}
|
||||||
|
>
|
||||||
|
<Box pt="1px">
|
||||||
|
<GhostStyledIcon
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="rotate"
|
||||||
|
component={LoopIcon}
|
||||||
|
sx={{
|
||||||
|
fontSize: "15px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</StyledArrow>
|
||||||
|
</Box>
|
||||||
|
<Box marginTop="-7px">{LowerSwapCard}</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SwapCollection;
|
35
src/components/Tabs/Tabs.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Tab as MuiTab, Tabs as MuiTabs } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import React from "react";
|
||||||
|
const PREFIX = "Tabs";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMuiTabs = styled(MuiTabs)(() => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
"&.MuiTabs-vertical": {
|
||||||
|
height: "auto",
|
||||||
|
minHeight: "auto",
|
||||||
|
},
|
||||||
|
" & .MuiTabs-scrollable": {
|
||||||
|
overflowY: "hidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const Tabs = ({ className = "", ...props }) => {
|
||||||
|
return (
|
||||||
|
<StyledMuiTabs textColor="primary" indicatorColor="primary" {...props} className={`${classes.root} ${className}`} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const a11yProps = (index) => ({
|
||||||
|
id: `simple-tab-${index}`,
|
||||||
|
"aria-controls": `simple-tabpanel-${index}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Tab = ({ value, label, ...props }) => (
|
||||||
|
<MuiTab key={value} value={value} label={label} {...a11yProps(value)} {...props} />
|
||||||
|
);
|
64
src/components/Token/Token.jsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { SvgIcon } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
import FtsoIcon from "../../assets/tokens/eGHST.svg?react";
|
||||||
|
import StnkIcon from "../../assets/tokens/sGHST.svg?react";
|
||||||
|
import GhstIcon from "../../assets/tokens/GHST.svg?react";
|
||||||
|
import DaiIcon from "../../assets/tokens/DAI.svg?react";
|
||||||
|
import WethIcon from "../../assets/tokens/wETH.svg?react";
|
||||||
|
import UnknownIcon from "../../assets/tokens/Unknown.svg?react";
|
||||||
|
|
||||||
|
const PREFIX = "Token";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
root: `${PREFIX}-root`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledSvgIcon = styled(SvgIcon)(() => ({
|
||||||
|
[`&.${classes.root}`]: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexDirection: "row",
|
||||||
|
margin: "12px 0px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Token = ({ name, viewBox = "0 0 260 260", fontSize = "large", ...props }) => {
|
||||||
|
const parseKnownToken = (name) => {
|
||||||
|
let icon;
|
||||||
|
switch (name?.toUpperCase()) {
|
||||||
|
case "FTSO":
|
||||||
|
icon = FtsoIcon;
|
||||||
|
break;
|
||||||
|
case "STNK":
|
||||||
|
icon = StnkIcon;
|
||||||
|
break;
|
||||||
|
case "GHST":
|
||||||
|
icon = GhstIcon;
|
||||||
|
break;
|
||||||
|
case "GDAI":
|
||||||
|
icon = DaiIcon;
|
||||||
|
break;
|
||||||
|
case "ETH":
|
||||||
|
icon = WethIcon;
|
||||||
|
break;
|
||||||
|
case "WETH":
|
||||||
|
icon = WethIcon;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon = UnknownIcon;
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledSvgIcon
|
||||||
|
viewBox={viewBox}
|
||||||
|
fontSize={fontSize}
|
||||||
|
component={parseKnownToken(name)}
|
||||||
|
{...props}
|
||||||
|
></StyledSvgIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Token;
|
125
src/components/TokenAllowanceGuard/TokenAllowanceGuard.jsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { Box, Grid, Typography, Skeleton } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import React, { ReactNode, useState } from "react";
|
||||||
|
import { useChainId } from "wagmi";
|
||||||
|
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { useAllowance, approveTokens } from "../../hooks/tokens";
|
||||||
|
import { PrimaryButton } from "../Button";
|
||||||
|
|
||||||
|
const PREFIX = "TokenAllowanceGuard";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
inputRow: `${PREFIX}-inputRow`,
|
||||||
|
gridItem: `${PREFIX}-gridItem`,
|
||||||
|
input: `${PREFIX}-input`,
|
||||||
|
button: `${PREFIX}-button`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledAllowanceGuard = styled("div")(({ theme }) => ({
|
||||||
|
[`& .${classes.inputRow}`]: {
|
||||||
|
justifyContent: "space-around",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "auto",
|
||||||
|
marginTop: "4px",
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.gridItem}`]: {
|
||||||
|
width: "100%",
|
||||||
|
paddingRight: "5px",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.input}`]: {
|
||||||
|
[theme.breakpoints.down("md")]: {
|
||||||
|
marginBottom: "10px",
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
marginBottom: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.button}`]: {
|
||||||
|
alignSelf: "center",
|
||||||
|
width: "100%",
|
||||||
|
minWidth: "163px",
|
||||||
|
maxWidth: "542px",
|
||||||
|
height: "43px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const TokenAllowanceGuard = ({
|
||||||
|
message,
|
||||||
|
isVertical = false,
|
||||||
|
approvalText = "Approve",
|
||||||
|
approvalPendingText = "Approving...",
|
||||||
|
width = "150px",
|
||||||
|
height = "auto",
|
||||||
|
spendAmount,
|
||||||
|
tokenName,
|
||||||
|
owner,
|
||||||
|
spender,
|
||||||
|
decimals,
|
||||||
|
connect,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const chainId = useChainId();
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
allowance: allowance,
|
||||||
|
refetch: allowanceRefetch
|
||||||
|
} = useAllowance(chainId, tokenName, owner, spender, decimals);
|
||||||
|
|
||||||
|
const approve = async () => {
|
||||||
|
setIsPending(true);
|
||||||
|
const bigValue = 100000000000000000000000000000000000000000000000000n;
|
||||||
|
await approveTokens(chainId, tokenName, owner, spender, bigValue);
|
||||||
|
await allowanceRefetch();
|
||||||
|
setIsPending(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowance) {
|
||||||
|
return (
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="center" height={isVertical ? "84px" : "40px"}>
|
||||||
|
<Skeleton height={height} width={width} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowance && spendAmount && allowance.lt(spendAmount))
|
||||||
|
return (
|
||||||
|
<Grid container alignItems="center">
|
||||||
|
<Grid item xs={12} sm={isVertical ? 12 : 8}>
|
||||||
|
<Box display="flex" textAlign="center" alignItems="center" justifyContent="center">
|
||||||
|
<Typography mr="10px" variant="body2" color="textSecondary">
|
||||||
|
<em>{owner === ""
|
||||||
|
? message ? message : "There is no connected wallet to check if allowance is sufficient."
|
||||||
|
: "Your current allowance is less than your requested spend amount."
|
||||||
|
}</em>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} sm={isVertical ? 12 : 4}>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="center" mt={[2, isVertical && message ? 2 : 0]}>
|
||||||
|
<PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={isPending}
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => owner === "" ? connect() : approve()}
|
||||||
|
>
|
||||||
|
{owner === "" ?
|
||||||
|
"Connect"
|
||||||
|
:
|
||||||
|
isPending ? `${approvalPendingText}` : `${approvalText}`
|
||||||
|
}
|
||||||
|
</PrimaryButton>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <StyledAllowanceGuard>{children}</StyledAllowanceGuard>;
|
||||||
|
};
|
41
src/components/TokenStack/TokenStack.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Avatar, Box } from "@mui/material";
|
||||||
|
import Token from "../Token/Token";
|
||||||
|
|
||||||
|
const TokenStack = ({ tokens, style, images, network, ...props }) => {
|
||||||
|
const imageStyles = {
|
||||||
|
height: "27px",
|
||||||
|
width: "27px",
|
||||||
|
...style,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexDirection="row" marginTop={network ? "3px" : "0px"} marginLeft={network ? "3px" : "0px"}>
|
||||||
|
{network && (
|
||||||
|
<Token
|
||||||
|
{...props}
|
||||||
|
name={network}
|
||||||
|
style={{ zIndex: 3, position: "fixed", marginLeft: "-3px", marginTop: "-3px", fontSize: "16px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{images?.map((image, i) => (
|
||||||
|
<Avatar
|
||||||
|
src={image}
|
||||||
|
style={{
|
||||||
|
...(i !== 0 ? { marginLeft: -7, zIndex: 1, ...imageStyles } : { zIndex: 1, ...imageStyles }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{tokens?.map((token, i) => (
|
||||||
|
<Token
|
||||||
|
{...props}
|
||||||
|
key={i}
|
||||||
|
name={token}
|
||||||
|
style={{
|
||||||
|
...(i !== 0 ? { marginLeft: -12, zIndex: 1, ...style } : { zIndex: 2, ...style }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TokenStack;
|
13
src/components/Tooltip/InfoTooltip.jsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import GhostStyledIcon from "../Icon/GhostIcon";
|
||||||
|
import Tooltip from "./Tooltip";
|
||||||
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
|
|
||||||
|
const InfoTooltip = ({ message }) => {
|
||||||
|
return (
|
||||||
|
<Tooltip message={message}>
|
||||||
|
<GhostStyledIcon viewBox="0 0 25 25" component={InfoIcon} style={{ margin: "0 5px", fontSize: "1em" }} className="info-icon" />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoTooltip;
|
56
src/components/Tooltip/Tooltip.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Box, Popper, Typography } from "@mui/material";
|
||||||
|
import { ReactElement, useState } from "react";
|
||||||
|
import Paper from "../Paper/Paper";
|
||||||
|
|
||||||
|
const Tooltip = ({ message, children }) => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
|
||||||
|
const handlePopoverOpen = (event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePopoverClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
const id = open ? undefined : "info-tooltip";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignSelf: "center",
|
||||||
|
}}
|
||||||
|
onMouseEnter={handlePopoverOpen}
|
||||||
|
onMouseLeave={handlePopoverClose}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<Popper
|
||||||
|
id={id}
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
placement="bottom"
|
||||||
|
className="tooltip"
|
||||||
|
nonce={undefined}
|
||||||
|
onResize={undefined}
|
||||||
|
onResizeCapture={undefined}
|
||||||
|
slotProps={undefined}
|
||||||
|
slots={undefined}
|
||||||
|
>
|
||||||
|
<Paper className="info-tooltip">
|
||||||
|
{typeof message === "string" ? (
|
||||||
|
<Typography variant="body1" className="info-tooltip-text">
|
||||||
|
{message}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<>{message}</>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
</Popper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tooltip;
|
46
src/components/TopBar/TopBar.jsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Box, Button, SvgIcon, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import MenuIcon from "../../assets/icons/hamburger.svg?react";
|
||||||
|
import Wallet from "./Wallet"
|
||||||
|
|
||||||
|
const PREFIX = "TopBar";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
appBar: `${PREFIX}-appBar`,
|
||||||
|
menuButton: `${PREFIX}-menuButton`,
|
||||||
|
pageTitle: `${PREFIX}-pageTitle`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function TopBar({ chainId, address, connect, handleDrawerToggle }) {
|
||||||
|
const themeColor = useTheme();
|
||||||
|
const desktop = useMediaQuery(themeColor.breakpoints.up(1048));
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
paddingTop="21px"
|
||||||
|
marginRight={desktop ? "33px" : "0px"}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Wallet address={address} connect={connect} chainId={chainId} />
|
||||||
|
</Box>
|
||||||
|
{!desktop && (
|
||||||
|
<Button
|
||||||
|
id="hamburger"
|
||||||
|
aria-label="open drawer"
|
||||||
|
size="large"
|
||||||
|
variant="text"
|
||||||
|
color="secondary"
|
||||||
|
onClick={handleDrawerToggle}
|
||||||
|
className={classes.menuButton}
|
||||||
|
>
|
||||||
|
<SvgIcon component={MenuIcon} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TopBar;
|
167
src/components/TopBar/Wallet/InitialWalletView.tsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
SvgIcon,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import LoopIcon from '@mui/icons-material/Loop';
|
||||||
|
import { ReactElement, useState } from "react";
|
||||||
|
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||||
|
import { Tokens, useWallet } from "./Token";
|
||||||
|
import { PrimaryButton, SecondaryButton } from "../../Button";
|
||||||
|
|
||||||
|
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
||||||
|
import { formatCurrency, shorten } from "../../../helpers";
|
||||||
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
import { DAI_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||||
|
|
||||||
|
import { useAccount, useDisconnect } from "wagmi";
|
||||||
|
|
||||||
|
const DisconnectButton = ({ onClose }) => {
|
||||||
|
const { disconnect } = useDisconnect();
|
||||||
|
return (
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={() => {
|
||||||
|
disconnect();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</PrimaryButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CloseButton = styled(IconButton)(theme => ({
|
||||||
|
root: {
|
||||||
|
...theme.overrides?.MuiButton?.containedSecondary,
|
||||||
|
width: "30px",
|
||||||
|
height: "30px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const WalletTotalValue = ({ address, tokens }) => {
|
||||||
|
const ghstPrice = tokens.ghst.price;
|
||||||
|
const [currency, setCurrency] = useState("USD");
|
||||||
|
const [topHovered, setTopHovered] = useState(false);
|
||||||
|
|
||||||
|
const walletTotalValueUSD = Object.values(tokens).reduce(
|
||||||
|
(totalValue, token) => totalValue.add(token.balance.mul(token.price)),
|
||||||
|
new DecimalBigNumber(0n, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const walletValue = {
|
||||||
|
USD: walletTotalValueUSD ? walletTotalValueUSD : new DecimalBigNumber(0n, 0),
|
||||||
|
GHST: walletTotalValueUSD && ghstPrice.gt(new DecimalBigNumber(0, 0)) ?
|
||||||
|
walletTotalValueUSD.div(ghstPrice)
|
||||||
|
:
|
||||||
|
new DecimalBigNumber(0n, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => setCurrency(currency === "USD" ? "GHST" : "USD")}
|
||||||
|
onMouseEnter={() => setTopHovered(true)}
|
||||||
|
onMouseLeave={() => setTopHovered(false)}
|
||||||
|
>
|
||||||
|
<Box width="120px" display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||||
|
<Box>
|
||||||
|
<Typography style={{ lineHeight: 1.1, fontWeight: 600, fontSize: "0.975rem" }} color="textSecondary">
|
||||||
|
MY WALLET
|
||||||
|
</Typography>
|
||||||
|
<Typography style={{ fontSize: ".85rem", lineHeight: 1.1 }} variant="h3">
|
||||||
|
{shorten(address)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box pt="1px">
|
||||||
|
<GhostStyledIcon
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
component={LoopIcon}
|
||||||
|
style={{
|
||||||
|
transition: "transform .3s ease-in-out",
|
||||||
|
transitionTimingFunction: " cubic-bezier(.4,0,.2,1)",
|
||||||
|
transform: topHovered ? "rotateZ(-180deg)" : "rotateZ(0deg)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Typography style={{ fontWeight: 700 }} variant="h3">
|
||||||
|
{formatCurrency(walletValue[currency], 2, currency === "USD" ? "USD" : "👻" )}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function InitialWalletView({ address, chainId, onClose }) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const tokens = useWallet(chainId, address);
|
||||||
|
|
||||||
|
const onBtnClick = (dexName, from, to) => {
|
||||||
|
navigate({
|
||||||
|
pathname: "/dex/" + dexName,
|
||||||
|
search: createSearchParams({
|
||||||
|
pool: true,
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
}).toString()
|
||||||
|
})
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper>
|
||||||
|
<Box sx={{ padding: theme.spacing(0, 3), display: "flex", flexDirection: "column", minHeight: "100vh" }}>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", padding: theme.spacing(3, 0, 1) }}>
|
||||||
|
<WalletTotalValue address={address} tokens={tokens} />
|
||||||
|
<CloseButton size="small" onClick={onClose} aria-label="close wallet">
|
||||||
|
<GhostStyledIcon component={CloseIcon} />
|
||||||
|
</CloseButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ margin: theme.spacing(2, -3) }}>
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column" }} style={{ gap: theme.spacing(1) }}>
|
||||||
|
<Tokens address={address} tokens={tokens} onClose={onClose} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ margin: theme.spacing(2, -3, 3) }}>
|
||||||
|
<Divider />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
style={{ gap: theme.spacing(1.5) }}
|
||||||
|
>
|
||||||
|
<SecondaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => onBtnClick("uniswap", DAI_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
|
||||||
|
>
|
||||||
|
<Typography>FTSO-gDAI on Uniswap</Typography>
|
||||||
|
</SecondaryButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
|
||||||
|
<DisconnectButton onClose={onClose} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InitialWalletView;
|
255
src/components/TopBar/Wallet/Token.tsx
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Skeleton,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import { ChangeEvent, useState, useEffect } from "react";
|
||||||
|
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { formatCurrency, formatNumber } from "../../../helpers";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||||
|
import TokenStack from "../../TokenStack/TokenStack";
|
||||||
|
import { PrimaryButton, SecondaryButton } from "../../Button";
|
||||||
|
|
||||||
|
import { useBalance } from "../../../hooks/tokens";
|
||||||
|
import { useDaiPrice, useFtsoPrice, useStnkPrice, useGhstPrice } from "../../../hooks/prices";
|
||||||
|
import { useAccount } from "wagmi";
|
||||||
|
|
||||||
|
const addTokenToWallet = async (token, userAddress) => {
|
||||||
|
if (!window.ethereum) return;
|
||||||
|
try {
|
||||||
|
await window.ethereum.request({
|
||||||
|
method: "wallet_watchAsset",
|
||||||
|
params: {
|
||||||
|
type: "ERC20",
|
||||||
|
options: {
|
||||||
|
address: token.address,
|
||||||
|
symbol: token.symbol,
|
||||||
|
decimals: token.balance._decimals,
|
||||||
|
image: token.externalUrl, // external host
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const BalanceValue = ({
|
||||||
|
balance,
|
||||||
|
balanceValueUSD,
|
||||||
|
isLoading = false,
|
||||||
|
}) => (
|
||||||
|
<Box sx={{ textAlign: "right", display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="body2" style={{ fontWeight: 600 }}>
|
||||||
|
{formatNumber(balance, 5)}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{formatCurrency(balanceValueUSD, 2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Token = (props) => {
|
||||||
|
const {
|
||||||
|
symbol,
|
||||||
|
icons,
|
||||||
|
address,
|
||||||
|
price = 0,
|
||||||
|
balance,
|
||||||
|
onAddTokenToWallet,
|
||||||
|
expanded,
|
||||||
|
onChangeExpanded,
|
||||||
|
daiAddress,
|
||||||
|
onClose,
|
||||||
|
isPool
|
||||||
|
} = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const useLink = (symbol, fromAddress, toAddress, isPool) => {
|
||||||
|
if (symbol === "GDAI") {
|
||||||
|
navigate({ pathname: "/faucet" })
|
||||||
|
} else {
|
||||||
|
navigate({
|
||||||
|
pathname: "/dex/uniswap",
|
||||||
|
search: isPool ?
|
||||||
|
createSearchParams({
|
||||||
|
pool: "true",
|
||||||
|
from: `${fromAddress}`,
|
||||||
|
to: `${toAddress}`,
|
||||||
|
}).toString()
|
||||||
|
:
|
||||||
|
createSearchParams({
|
||||||
|
from: `${fromAddress}`,
|
||||||
|
to: `${toAddress}`,
|
||||||
|
}).toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion expanded={expanded} onChange={onChangeExpanded}>
|
||||||
|
<AccordionSummary expandIcon={<GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", width: "100%", marginRight: "10px" }}>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||||
|
<TokenStack
|
||||||
|
style={{
|
||||||
|
width: "28px",
|
||||||
|
height: "28px",
|
||||||
|
}}
|
||||||
|
tokens={props.icons}
|
||||||
|
/>
|
||||||
|
<Typography>{symbol}</Typography>
|
||||||
|
</Box>
|
||||||
|
<BalanceValue
|
||||||
|
balance={balance}
|
||||||
|
balanceValueUSD={balance.mul(price)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}>
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", flexDirection: "column", flex: 1, mx: "32px", justifyContent: "center" }}
|
||||||
|
style={{ gap: theme.spacing(1) }}
|
||||||
|
>
|
||||||
|
<Box display="flex" flexDirection="column" className="ghst-pairs" style={{ width: "100%" }}>
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={onAddTokenToWallet}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<Typography>Add to Wallet</Typography>
|
||||||
|
</PrimaryButton>
|
||||||
|
<SecondaryButton
|
||||||
|
onClick={() => useLink(symbol, daiAddress, address, isPool)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<Typography>Get on {symbol === "GDAI" ? "Faucet" : "Uniswap"}</Typography>
|
||||||
|
</SecondaryButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sumObjValues = (obj: Record<string, string> = {}) =>
|
||||||
|
Object.values(obj).reduce((sum, b = "0.0") => sum + (parseFloat(b) || 0), 0);
|
||||||
|
|
||||||
|
export const useWallet = (chainId, userAddress) => {
|
||||||
|
const {
|
||||||
|
balance: daiBalance,
|
||||||
|
refetch: daiRefetch,
|
||||||
|
contractAddress: daiAddress,
|
||||||
|
} = useBalance(chainId, "GDAI", userAddress);
|
||||||
|
const {
|
||||||
|
balance: ftsoBalance,
|
||||||
|
refetch: ftsoRefetch,
|
||||||
|
contractAddress: ftsoAddress,
|
||||||
|
} = useBalance(chainId, "FTSO", userAddress);
|
||||||
|
const {
|
||||||
|
balance: stnkBalance,
|
||||||
|
refetch: stnkRefetch,
|
||||||
|
contractAddress: stnkAddress,
|
||||||
|
} = useBalance(chainId, "STNK", userAddress);
|
||||||
|
const {
|
||||||
|
balance: ghstBalance,
|
||||||
|
refetch: ghstRefetch,
|
||||||
|
contractAddress: ghstAddress,
|
||||||
|
} = useBalance(chainId, "GHST", userAddress);
|
||||||
|
const {
|
||||||
|
balance: lpDaiFtsoBalance,
|
||||||
|
refetch: lpDaiFtsoRefetch,
|
||||||
|
contractAddress: lpDaiFtsoBalanceAddress,
|
||||||
|
} = useBalance(chainId, "GDAI_FTSO", userAddress);
|
||||||
|
|
||||||
|
const daiPrice = useDaiPrice(chainId);
|
||||||
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
const stnkPrice = useStnkPrice(chainId);
|
||||||
|
const ghstPrice = useGhstPrice(chainId);
|
||||||
|
// TODO: add lp price
|
||||||
|
|
||||||
|
const tokens = {
|
||||||
|
dai: {
|
||||||
|
symbol: "GDAI",
|
||||||
|
address: daiAddress,
|
||||||
|
balance: daiBalance,
|
||||||
|
price: daiPrice,
|
||||||
|
icons: ["GDAI"],
|
||||||
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg",
|
||||||
|
},
|
||||||
|
ftso: {
|
||||||
|
symbol: "FTSO",
|
||||||
|
address: ftsoAddress,
|
||||||
|
balance: ftsoBalance,
|
||||||
|
price: ftsoPrice,
|
||||||
|
icons: ["FTSO"],
|
||||||
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg",
|
||||||
|
},
|
||||||
|
stnk: {
|
||||||
|
symbol: "STNK",
|
||||||
|
address: stnkAddress,
|
||||||
|
balance: stnkBalance,
|
||||||
|
price: stnkPrice,
|
||||||
|
icons: ["STNK"],
|
||||||
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/sGHST.svg",
|
||||||
|
},
|
||||||
|
ghst: {
|
||||||
|
symbol: "GHST",
|
||||||
|
address: ghstAddress,
|
||||||
|
balance: ghstBalance,
|
||||||
|
price: ghstPrice,
|
||||||
|
icons: ["GHST"],
|
||||||
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/GHST.svg",
|
||||||
|
},
|
||||||
|
daiFtso: {
|
||||||
|
isPool: true,
|
||||||
|
symbol: "UNI-V2",
|
||||||
|
address: lpDaiFtsoBalanceAddress,
|
||||||
|
balance: lpDaiFtsoBalance,
|
||||||
|
price: ftsoPrice,
|
||||||
|
icons: ["GDAI", "FTSO"],
|
||||||
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.entries(tokens).reduce((wallet, [key, token]) => {
|
||||||
|
return {
|
||||||
|
...wallet,
|
||||||
|
[key]: {
|
||||||
|
...token,
|
||||||
|
totalBalance: "0",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Tokens = ({ address, tokens, onClose }) => {
|
||||||
|
const [expanded, setExpanded] = useState(null);
|
||||||
|
const alwaysShowTokens = [tokens.dai, tokens.ftso, tokens.stnk, tokens.ghst, tokens.daiFtso];
|
||||||
|
|
||||||
|
const tokenProps = (token) => ({
|
||||||
|
...token,
|
||||||
|
expanded: expanded === token.symbol,
|
||||||
|
daiAddress: tokens.dai.address,
|
||||||
|
onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null),
|
||||||
|
onAddTokenToWallet: () => addTokenToWallet(token, address),
|
||||||
|
onClose: () => onClose(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{alwaysShowTokens.map(token => (
|
||||||
|
<Token key={token.symbol} {...tokenProps(token)} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
19
src/components/TopBar/Wallet/WalletAddressEns.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Link } from "@mui/material";
|
||||||
|
import { useAccount } from "wagmi";
|
||||||
|
import { shorten } from "../../../helpers";
|
||||||
|
|
||||||
|
export default function WalletAddressEns() {
|
||||||
|
const { address } = useAccount();
|
||||||
|
if (!address) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="wallet-link">
|
||||||
|
{/*ens?.avatar && <img className="avatar" src={ens.avatar} alt={address} />*/}
|
||||||
|
|
||||||
|
<Link href={`https://etherscan.io/address/${address}`} target="_blank">
|
||||||
|
{/*ens?.name || shorten(address)*/}
|
||||||
|
{shorten(address)}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
85
src/components/TopBar/Wallet/index.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { Button, SvgIcon, SwipeableDrawer, Typography } from "@mui/material";
|
||||||
|
import { styled, useTheme } from '@mui/material/styles';
|
||||||
|
import { useState } from "react";
|
||||||
|
import WalletIcon from "../../../assets/icons/wallet.svg?react";
|
||||||
|
import { useAccount, useConnect, injected } from "wagmi";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import InitialWalletView from "./InitialWalletView";
|
||||||
|
|
||||||
|
const WalletButton = ({ openWallet, connect }) => {
|
||||||
|
const { isConnected, chain } = useAccount();
|
||||||
|
const theme = useTheme();
|
||||||
|
const onClick = isConnected ? openWallet : connect;
|
||||||
|
const label = isConnected ? `Wallet on ${chain?.name ? chain.name : "Unknown"}` : `Connect Wallet`;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
id="fatso-menu-button"
|
||||||
|
variant="text"
|
||||||
|
size="large"
|
||||||
|
color="secondary"
|
||||||
|
onClick={() => onClick()}
|
||||||
|
>
|
||||||
|
<SvgIcon component={WalletIcon} style={{ marginRight: theme.spacing(1) }} />
|
||||||
|
<Typography>{label}</Typography>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PREFIX = "WalletDrawer";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
drawer: `${PREFIX}-drawer`,
|
||||||
|
drawerPaper: `${PREFIX}-drawerPaper`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledSwipeableDrawer = styled(SwipeableDrawer)(({ theme }) => ({
|
||||||
|
[`& .${classes.drawer}`]: {
|
||||||
|
[theme.breakpoints.up("md")]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`& .${classes.drawerPaper}`]: {
|
||||||
|
width: drawerWidth,
|
||||||
|
borderRight: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const drawerWidth = 360;
|
||||||
|
|
||||||
|
export function Wallet({ address, chainId, connect }) {
|
||||||
|
const [isWalletOpen, setWalletOpen] = useState(false);
|
||||||
|
const closeWallet = () => setWalletOpen(false);
|
||||||
|
const openWallet = () => setWalletOpen(true);
|
||||||
|
|
||||||
|
// only enable backdrop transition on ios devices,
|
||||||
|
// because we can assume IOS is hosted on hight-end devices and will not drop frames
|
||||||
|
// also disable discovery on IOS, because of it's 'swipe to go back' feat
|
||||||
|
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WalletButton connect={connect} openWallet={openWallet} />
|
||||||
|
<StyledSwipeableDrawer
|
||||||
|
disableBackdropTransition={!isIOS}
|
||||||
|
disableDiscovery={isIOS}
|
||||||
|
anchor="right"
|
||||||
|
classes={{
|
||||||
|
paper: classes.drawerPaper,
|
||||||
|
}}
|
||||||
|
ModalProps={{
|
||||||
|
keepMounted: true, // Better open performance on mobile.
|
||||||
|
}}
|
||||||
|
open={isWalletOpen}
|
||||||
|
onOpen={openWallet}
|
||||||
|
onClose={closeWallet}
|
||||||
|
>
|
||||||
|
<InitialWalletView address={address} chainId={chainId} onClose={closeWallet} />
|
||||||
|
</StyledSwipeableDrawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wallet;
|
18
src/config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { http, fallback, createConfig } from 'wagmi'
|
||||||
|
import { sepolia } from 'wagmi/chains'
|
||||||
|
|
||||||
|
export const config = createConfig({
|
||||||
|
chains: [sepolia],
|
||||||
|
transports: {
|
||||||
|
[sepolia.id]: fallback([
|
||||||
|
http('https://ethereum-sepolia-rpc.publicnode.com'),
|
||||||
|
http('https://rpc-sepolia.rockx.com/'),
|
||||||
|
http('https://1rpc.io/sepolia'),
|
||||||
|
http('https://eth-sepolia.public.blastapi.io'),
|
||||||
|
http('https://0xrpc.io/sep'),
|
||||||
|
http('https://api.zan.top/eth-sepolia'),
|
||||||
|
http('https://eth-sepolia.api.onfinality.io/public'),
|
||||||
|
http('https://sepolia.drpc.org')
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
})
|
16
src/constants.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export enum NetworkId {
|
||||||
|
TESTNET_SEPOLIA = 11155111,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isNetworkAvailable = (chainId, addressChainId) => {
|
||||||
|
chainId = addressChainId ? addressChainId : chainId;
|
||||||
|
let exists = false;
|
||||||
|
switch (chainId) {
|
||||||
|
case 11155111:
|
||||||
|
exists = true
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return exists;
|
||||||
|
}
|
57
src/constants/addresses.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { NetworkId } from "../constants";
|
||||||
|
|
||||||
|
export const STAKING_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xb22Ad3b4a23EaEA8c06CD151D7C0e3758d0FB580",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BOND_DEPOSITORY_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x8773AC3258b31D3ACfc99Ffd13768ccB170fcF9f",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DAO_TREASURY_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x2AAd1EA51044e69756880f580C13a92D910af238",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FTSO_DAI_LP_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x64B19626bd074cf7B1019798846c363bbA8A0d53",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FTSO_STNK_LP_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x29965676fc00C3eA9717B2A02739d294399a382e",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DAI_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xc7Afd3bC4c74f6E07880447b1759d5d639F2525F",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WETH_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GHST_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x4643076087234d9B81974beF1eC9c25F3A0202B9",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const STNK_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x84060da636f5a83f2668ad238f09f8c667a1ec8b",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FTSO_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0x0eF2E888710E9f1d5E734f9ce30FAD40c832D5F3",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DISTRIBUTOR_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xE433D078a555163dC6B53968E72418B6a1618f04",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GHOST_GOVERNANCE_ADDRESSES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xD40E6442Ee01c234CD8AaF335122CfbB2aec8548",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UNISWAP_V2_ROUTER = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xee567fe1712faf6149d80da1e6934e354124cfe3",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UNISWAP_V2_FACTORY = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: "0xF62c03E08ada871A0bEb309762E260a7a6a880E6",
|
||||||
|
};
|
14
src/constants/dexes.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { NetworkId } from "../constants";
|
||||||
|
|
||||||
|
import UniswapIcon from "../assets/icons/uniswap.svg?react";
|
||||||
|
// import SushiswapIcon from "../assets/icons/sushiswap.svg?react";
|
||||||
|
|
||||||
|
export const AVAILABLE_DEXES = {
|
||||||
|
[NetworkId.TESTNET_SEPOLIA]: [
|
||||||
|
{
|
||||||
|
name: "Uniswap",
|
||||||
|
icon: UniswapIcon,
|
||||||
|
viewBox: "0 0 195 230",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
28
src/constants/ecosystem.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { NetworkId } from "../constants";
|
||||||
|
|
||||||
|
export const ECOSYSTEM = [
|
||||||
|
{
|
||||||
|
name: "GHOST chain",
|
||||||
|
link: "https://chain.ghostchain.io/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ghostNFT",
|
||||||
|
link: "https://app.nft.ghostchain.io/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ghostAirdrop",
|
||||||
|
link: "https://airdrop.ghostchain.io/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ghostAirdrop bot",
|
||||||
|
link: "https://t.me/ghostDAO_airdrop_bot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ghostFaucet",
|
||||||
|
link: "https://faucet.ghostchain.io/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JML NFT Collection",
|
||||||
|
link: "https://jml.ghostchain.io/"
|
||||||
|
},
|
||||||
|
];
|
178
src/containers/Bond/BondModal.jsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { ArrowBack } from "@mui/icons-material";
|
||||||
|
import { Box, Link, Skeleton, Typography } from "@mui/material";
|
||||||
|
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";
|
||||||
|
import Metric from "../../components/Metric/Metric";
|
||||||
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
|
||||||
|
import BondPrice from "./components/BondPrice";
|
||||||
|
import BondDiscount from "./components/BondDiscount";
|
||||||
|
import BondInputArea from "./components/BondInputArea";
|
||||||
|
import BondSettingsModal from "./components/BondSettingsModal";
|
||||||
|
|
||||||
|
import NotFound from "../NotFound/NotFound";
|
||||||
|
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { NetworkId } from "../../constants";
|
||||||
|
import { formatNumber } from "../../helpers";
|
||||||
|
|
||||||
|
import { useLiveBonds } from "../../hooks/bonds";
|
||||||
|
import { useFtsoPrice } from "../../hooks/prices";
|
||||||
|
|
||||||
|
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 chainId={chainId} bond={bond} address={address} connect={connect} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BondModal = ({ bond, chainId, address, connect }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
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 [recipientAddress, setRecipientAddress] = useState(address);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ReactGA.send({ hitType: "pageview", page: pathname });
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const setSlippageInner = (value) => {
|
||||||
|
const maybeValue = parseFloat(value);
|
||||||
|
if (!maybeValue || parseFloat(value) <= 100) {
|
||||||
|
setSlippage(value);
|
||||||
|
localStorage.setItem("bond-slippage", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFormatDecimalsInner = (value) => {
|
||||||
|
if (Number(value) <= 17) {
|
||||||
|
setFormatDecimals(value);
|
||||||
|
localStorage.setItem("bond-decimals", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate("/bonds");
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [navigate, isSettingsOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<PageTitle
|
||||||
|
name={
|
||||||
|
<Box display="flex" flexDirection="row" alignItems="center">
|
||||||
|
<Link component={RouterLink} to="/bonds">
|
||||||
|
<Box display="flex" flexDirection="row">
|
||||||
|
<ArrowBack />
|
||||||
|
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
|
||||||
|
Back
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
|
||||||
|
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
|
||||||
|
<Typography variant="h4" fontWeight={500}>
|
||||||
|
{bond.quoteToken.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
|
<BondSettingsModal
|
||||||
|
open={isSettingsOpen}
|
||||||
|
handleClose={() => setSettingsOpen(false)}
|
||||||
|
slippage={slippage}
|
||||||
|
recipientAddress={recipientAddress}
|
||||||
|
formatDecimals={formatDecimals}
|
||||||
|
onRecipientAddressChange={event => setRecipientAddress(event.currentTarget.value)}
|
||||||
|
onSlippageChange={event => setSlippageInner(event.currentTarget.value)}
|
||||||
|
onDecimalsChange={event => setFormatDecimalsInner(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="space-between" width={["100%", "70%"]} mt="24px">
|
||||||
|
<Metric
|
||||||
|
label={`Bond Price`}
|
||||||
|
metric={
|
||||||
|
bond.isSoldOut ? (
|
||||||
|
"--"
|
||||||
|
) : (
|
||||||
|
<BondPrice
|
||||||
|
price={bond.price.inBaseToken}
|
||||||
|
symbol={bond.quoteToken.name}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Metric
|
||||||
|
label="Market Price"
|
||||||
|
metric={
|
||||||
|
<TokenPrice
|
||||||
|
chainId={chainId}
|
||||||
|
token={bond.baseToken}
|
||||||
|
baseSymbol={bond.baseToken.name}
|
||||||
|
quoteSymbol={bond.quoteToken.name}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Metric
|
||||||
|
label="Discount"
|
||||||
|
metric={<BondDiscount discount={bond.discount} textOnly />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box width="100%" mt="24px">
|
||||||
|
<BondInputArea
|
||||||
|
chainId={chainId}
|
||||||
|
bond={bond}
|
||||||
|
connect={connect}
|
||||||
|
address={address}
|
||||||
|
slippage={slippage}
|
||||||
|
recipientAddress={recipientAddress}
|
||||||
|
handleSettingsOpen={() => setSettingsOpen(true)}
|
||||||
|
formatDecimals={formatDecimals}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenPrice = ({
|
||||||
|
token,
|
||||||
|
chainId,
|
||||||
|
quoteSymbol,
|
||||||
|
baseSymbol,
|
||||||
|
}) => {
|
||||||
|
const priceToken = useFtsoPrice(chainId);
|
||||||
|
const sameToken = quoteSymbol === baseSymbol;
|
||||||
|
|
||||||
|
const price = sameToken
|
||||||
|
? formatNumber(1, 2)
|
||||||
|
: `${formatNumber(priceToken, 2)}`;
|
||||||
|
|
||||||
|
return price ? (
|
||||||
|
<>
|
||||||
|
{price} {quoteSymbol}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Skeleton width={60} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondModalContainer;
|
86
src/containers/Bond/Bonds.jsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { Box, Tab, Tabs, Container, useMediaQuery } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
import Metric from "../../components/Metric/Metric";
|
||||||
|
import MetricCollection from "../../components/Metric/MetricCollection";
|
||||||
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
|
|
||||||
|
import { formatCurrency } from "../../helpers";
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
|
import { BondList } from "./components/BondList";
|
||||||
|
import { ClaimBonds } from "./components/ClaimBonds";
|
||||||
|
|
||||||
|
import { useLiveBonds } from "../../hooks/bonds";
|
||||||
|
import { useTotalReserves } from "../../hooks/treasury";
|
||||||
|
import { useFtsoPrice } from "../../hooks/prices";
|
||||||
|
|
||||||
|
const Bonds = ({ chainId, address, connect }) => {
|
||||||
|
const [isZoomed] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [secondsTo, setSecondsTo] = useState(0);
|
||||||
|
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ReactGA.send({ hitType: "pageview", page: "/bonds" });
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { liveBonds } = useLiveBonds(chainId);
|
||||||
|
const totalReserves = useTotalReserves(chainId);
|
||||||
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const date = Math.round(Date.now() / 1000);
|
||||||
|
setSecondsTo(date);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<PageTitle name={"Protocol Bonding"} subtitle="Buy FTSO from the protocol at a discount" />
|
||||||
|
<Container
|
||||||
|
style={{
|
||||||
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
minHeight: "calc(100vh - 128px)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
||||||
|
<Paper headerText="Active bonds" fullWidth enableBackground>
|
||||||
|
<MetricCollection>
|
||||||
|
<Metric
|
||||||
|
label={`Treasury Balance`}
|
||||||
|
metric={formatCurrency(totalReserves, 2)}
|
||||||
|
isLoading={false}
|
||||||
|
/>
|
||||||
|
<Metric
|
||||||
|
label={`FTSO price`}
|
||||||
|
metric={formatCurrency(ftsoPrice, 2)}
|
||||||
|
isLoading={false}
|
||||||
|
/>
|
||||||
|
</MetricCollection>
|
||||||
|
|
||||||
|
<Box mt="24px">
|
||||||
|
<Box mt="24px">
|
||||||
|
<BondList chainId={chainId} bonds={liveBonds} secondsTo={secondsTo} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<ClaimBonds chainId={chainId} secondsTo={secondsTo} address={address} />
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Bonds;
|
115
src/containers/Bond/components/BondConfirmModal.jsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { styled, useTheme } from "@mui/material/styles";
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../../../components/Icon/GhostIcon";
|
||||||
|
import Metric from "../../../components/Metric/Metric";
|
||||||
|
import Modal from "../../../components/Modal/Modal";
|
||||||
|
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||||
|
import DataRow from "../../../components/DataRow/DataRow";
|
||||||
|
import { PrimaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
|
import BondDiscount from "./BondDiscount";
|
||||||
|
import BondVesting from "./BondVesting";
|
||||||
|
import BondSlippage from "./BondSlippage";
|
||||||
|
|
||||||
|
import { purchaseBond } from "../../../hooks/bonds";
|
||||||
|
|
||||||
|
const StyledBox = styled(Box, {
|
||||||
|
shouldForwardProp: prop => prop !== "template",
|
||||||
|
})(({ theme }) => {
|
||||||
|
return {
|
||||||
|
root: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const BondConfirmModal = ({
|
||||||
|
chainId,
|
||||||
|
bond,
|
||||||
|
slippage,
|
||||||
|
recipientAddress,
|
||||||
|
spendAmountValue,
|
||||||
|
spendAmount,
|
||||||
|
receiveAmount,
|
||||||
|
handleSettingsOpen,
|
||||||
|
isOpen,
|
||||||
|
handleConfirmClose
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
setIsPending(true);
|
||||||
|
|
||||||
|
const shares = 100000;
|
||||||
|
const one = BigInt(shares * 100);
|
||||||
|
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
||||||
|
const bigIntSlippage = one + BigInt(Math.round(floatSlippage * shares));
|
||||||
|
|
||||||
|
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
|
||||||
|
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
|
||||||
|
|
||||||
|
await purchaseBond(
|
||||||
|
chainId,
|
||||||
|
bond.id,
|
||||||
|
spendAmountValue._value.toBigInt(),
|
||||||
|
maxPrice,
|
||||||
|
recipientAddress,
|
||||||
|
referral
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsPending(false);
|
||||||
|
handleConfirmClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
maxWidth="476px"
|
||||||
|
minHeight="200px"
|
||||||
|
open={isOpen}
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" flexDirection="row">
|
||||||
|
<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
|
||||||
|
<Typography variant="h4" sx={{ marginLeft: "6px" }}>
|
||||||
|
{bond.quoteToken.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
onClose={!isPending && handleConfirmClose}
|
||||||
|
topLeft={<GhostStyledIcon viewBox="0 0 23 23" component={SettingsIcon} style={{ cursor: "pointer" }} onClick={handleSettingsOpen} />}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Metric
|
||||||
|
label="Assets to Bond"
|
||||||
|
metric={spendAmount}
|
||||||
|
/>
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="center">
|
||||||
|
<Typography>{bond.quoteToken.name}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Metric label="Assets to Receive" metric={receiveAmount} />
|
||||||
|
<Box display="flex" flexDirection="row" justifyContent="center">
|
||||||
|
<Typography>{bond.baseToken.name}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box mt="21px" mb="21px" borderTop={`1px solid ${theme.colors.gray[500]}`}></Box>
|
||||||
|
<DataRow title="ROI" balance={<BondDiscount discount={bond.discount} textOnly />} />
|
||||||
|
<DataRow title="Bond Slippage" balance={<BondSlippage slippage={slippage} textOnly />} />
|
||||||
|
<DataRow title="Vesting Term" balance={<BondVesting vesting={bond.vesting} />} />
|
||||||
|
<PrimaryButton fullWidth onClick={onSubmit} disabled={isPending} loading={isPending}>
|
||||||
|
{isPending ? "Bonding..." : "Confirm Bond Purchase"}
|
||||||
|
</PrimaryButton>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondConfirmModal;
|
26
src/containers/Bond/components/BondDiscount.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Box, useTheme } from "@mui/material";
|
||||||
|
import Chip from "../../../components/Chip/Chip";
|
||||||
|
import { formatNumber } from "../../../helpers";
|
||||||
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
|
const BondDiscount = ({ discount, textOnly }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const discountString = `${formatNumber(Number(discount.mul(new DecimalBigNumber("100").toString())), 2)}%`;
|
||||||
|
|
||||||
|
return textOnly ? (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
color: new DecimalBigNumber("0").gt(discount) ? theme.colors.feedback.error : theme.colors.feedback.pnlGain,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{discountString}
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Chip
|
||||||
|
label={`${formatNumber(Number(discount.mul(new DecimalBigNumber("100")).toString()), 2)}%`}
|
||||||
|
template={new DecimalBigNumber("0").gt(discount) ? "error" : "success"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondDiscount;
|
8
src/containers/Bond/components/BondDuration.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
||||||
|
|
||||||
|
const BondDuration = ({ duration, secondsTo, msg }) => {
|
||||||
|
return <>{duration - secondsTo > 0 ? prettifySecondsInDays(duration - secondsTo) : msg ? msg : "Closing"}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondDuration;
|
23
src/containers/Bond/components/BondInfoText.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Link, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
const BondInfoText = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
fontSize="0.875em"
|
||||||
|
lineHeight="15px"
|
||||||
|
>
|
||||||
|
Important: Bonding is the act of selling “naked” assets such as gDAI (reserve bonds) or liquidity tokens such as gDAI-FTSO SLP (liquidity bonds) for FTSO at a discount.
|
||||||
|
<Link
|
||||||
|
color={theme.colors.primary[300]}
|
||||||
|
href="https://ghostchain.io/ghostdao_litepaper"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>Learn more here.</Link>
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondInfoText;
|
231
src/containers/Bond/components/BondInputArea.jsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||||
|
import { Box, Checkbox, FormControlLabel } from "@mui/material";
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||||
|
import { shorten, formatNumber, formatCurrency } from "../../../helpers";
|
||||||
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
|
import BondDiscount from "./BondDiscount";
|
||||||
|
import BondVesting from "./BondVesting";
|
||||||
|
import BondConfirmModal from "./BondConfirmModal";
|
||||||
|
|
||||||
|
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||||
|
import { PrimaryButton } from "../../../components/Button";
|
||||||
|
import SwapCard from "../../../components/Swap/SwapCard";
|
||||||
|
import SwapCollection from "../../../components/Swap/SwapCollection";
|
||||||
|
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||||
|
import DataRow from "../../../components/DataRow/DataRow";
|
||||||
|
import Paper from "../../../components/Paper/Paper";
|
||||||
|
|
||||||
|
import { useCurrentIndex } from "../../../hooks/staking";
|
||||||
|
import { useBalance } from "../../../hooks/tokens";
|
||||||
|
|
||||||
|
const BondInputArea = ({
|
||||||
|
bond,
|
||||||
|
chainId,
|
||||||
|
slippage,
|
||||||
|
recipientAddress,
|
||||||
|
formatDecimals,
|
||||||
|
handleSettingsOpen,
|
||||||
|
address,
|
||||||
|
connect
|
||||||
|
}) => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const { currentIndex } = useCurrentIndex(chainId);
|
||||||
|
const { balance } = useBalance(chainId, bond.quoteToken.quoteTokenAddress, address);
|
||||||
|
|
||||||
|
const [amount, setAmount] = useState("");
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
|
|
||||||
|
const parsedAmount = useMemo(() => {
|
||||||
|
return new DecimalBigNumber(amount, bond.quoteToken.decimals);
|
||||||
|
}, [bond, amount])
|
||||||
|
|
||||||
|
const amountInBaseToken = useMemo(() => {
|
||||||
|
if (bond.price.inBaseToken._value !== 0n) return parsedAmount.div(bond.price.inBaseToken);
|
||||||
|
return new DecimalBigNumber(0n, 0);
|
||||||
|
}, [parsedAmount]);
|
||||||
|
|
||||||
|
const showDisclaimer = useMemo(() => {
|
||||||
|
return new DecimalBigNumber("0").gt(bond.discount);
|
||||||
|
}, [bond]);
|
||||||
|
|
||||||
|
const handleConfirmClose = () => {
|
||||||
|
setAmount("");
|
||||||
|
setConfirmOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMax = () => {
|
||||||
|
if (!balance) return;
|
||||||
|
|
||||||
|
if (bond.capacity.inQuoteToken.lt(bond.maxPayout.inQuoteToken)) {
|
||||||
|
return setAmount(
|
||||||
|
bond.capacity.inQuoteToken.lt(balance)
|
||||||
|
? bond.capacity.inQuoteToken.toString() // Capacity is the smallest
|
||||||
|
: balance.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAmount(
|
||||||
|
bond.maxPayout.inQuoteToken.lt(balance)
|
||||||
|
? bond.maxPayout.inQuoteToken.toString() // Payout is the smallest
|
||||||
|
: balance.toString(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseTokenString = (bond.maxPayout.inBaseToken.lt(bond.capacity.inBaseToken)
|
||||||
|
? bond.maxPayout.inBaseToken
|
||||||
|
: bond.capacity.inBaseToken
|
||||||
|
);
|
||||||
|
|
||||||
|
const quoteTokenString = (bond.maxPayout.inQuoteToken.lt(bond.capacity.inQuoteToken)
|
||||||
|
? bond.maxPayout.inQuoteToken
|
||||||
|
: bond.capacity.inQuoteToken
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
|
||||||
|
<Box display="flex" flexDirection="row" width="100%" justifyContent="center" mt="10px">
|
||||||
|
<Box display="flex" flexDirection="column" width="100%" maxWidth="476px">
|
||||||
|
<Box mb="21px">
|
||||||
|
<SwapCollection
|
||||||
|
UpperSwapCard={
|
||||||
|
<SwapCard
|
||||||
|
maxWidth="476px"
|
||||||
|
inputWidth="280px"
|
||||||
|
id="from"
|
||||||
|
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
|
||||||
|
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="280px"
|
||||||
|
id="to"
|
||||||
|
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
|
||||||
|
tokenName={bond.baseToken.name}
|
||||||
|
value={amountInBaseToken.toString({ decimals: 9 })}
|
||||||
|
inputProps={{ "data-testid": "toInput" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TokenAllowanceGuard
|
||||||
|
spendAmount={parsedAmount}
|
||||||
|
tokenName={bond.quoteToken.name}
|
||||||
|
owner={address}
|
||||||
|
spender={BOND_DEPOSITORY_ADDRESSES[chainId]}
|
||||||
|
decimals={parsedAmount._decimals}
|
||||||
|
approvalText={`Approve ${bond.quoteToken.name} to Bond`}
|
||||||
|
approvalPendingText={"Approving..."}
|
||||||
|
connect={connect}
|
||||||
|
width="100%"
|
||||||
|
height="60px"
|
||||||
|
isVertical
|
||||||
|
message={
|
||||||
|
<>
|
||||||
|
First time bonding <b>{bond.quoteToken.name}</b>? <br /> Please approve ghostDAO to use your{" "}
|
||||||
|
<b>{bond.quoteToken.name}</b> for bonding.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showDisclaimer && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={checked}
|
||||||
|
onChange={event => setChecked(event.target.checked)}
|
||||||
|
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||||
|
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="I understand that I'm buying a negative discount bond"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={bond.isSoldOut || (showDisclaimer && !checked)}
|
||||||
|
onClick={() => setConfirmOpen(true)}
|
||||||
|
>
|
||||||
|
Bond
|
||||||
|
</PrimaryButton>
|
||||||
|
</TokenAllowanceGuard>
|
||||||
|
|
||||||
|
<Paper style={{ marginBottom: "7px", marginTop: "21px" }} fullWidth enableBackground>
|
||||||
|
<Box mt="24px">
|
||||||
|
<DataRow
|
||||||
|
title={"You Will Get"}
|
||||||
|
balance={
|
||||||
|
<span>
|
||||||
|
{formatCurrency(amountInBaseToken, formatDecimals, "FTSO")}
|
||||||
|
{" "}
|
||||||
|
{!!currentIndex && (
|
||||||
|
<span>
|
||||||
|
(≈{formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, "GHST")})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
tooltip={`The total amount of payout asset you will receive from this bond purchase`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DataRow
|
||||||
|
title="Max You Can Buy"
|
||||||
|
tooltip={`The maximum quantity of payout token offered via bonds at this moment in time`}
|
||||||
|
balance={
|
||||||
|
<span>
|
||||||
|
{bond.baseToken === bond.quoteToken
|
||||||
|
? `${formatCurrency(baseTokenString, formatDecimals, "FTSO")}`
|
||||||
|
: `${formatCurrency(baseTokenString, formatDecimals, "FTSO")} (≈${formatCurrency(quoteTokenString, formatDecimals, "GHST")})`}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DataRow
|
||||||
|
title="Discount"
|
||||||
|
balance={<BondDiscount discount={bond.discount} textOnly />}
|
||||||
|
tooltip="The bond discount is the percentage difference between FTSO market value and the bond's price"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DataRow
|
||||||
|
title={`Vesting Term`}
|
||||||
|
balance={<BondVesting vesting={bond.vesting} />}
|
||||||
|
tooltip={"The duration of the Bond whereby the bond can be claimed in its entirety"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{recipientAddress !== address && (
|
||||||
|
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<BondConfirmModal
|
||||||
|
chainId={chainId}
|
||||||
|
bond={bond}
|
||||||
|
slippage={slippage}
|
||||||
|
recipientAddress={recipientAddress}
|
||||||
|
spendAmountValue={parsedAmount}
|
||||||
|
spendAmount={formatNumber(parsedAmount, formatDecimals)}
|
||||||
|
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
|
||||||
|
handleSettingsOpen={handleSettingsOpen}
|
||||||
|
isOpen={confirmOpen}
|
||||||
|
handleConfirmClose={() => handleConfirmClose()}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondInputArea;
|
265
src/containers/Bond/components/BondList.jsx
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Link,
|
||||||
|
SvgIcon,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery
|
||||||
|
} from "@mui/material";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import ArrowUp from "../../../assets/icons/arrow-up.svg?react";
|
||||||
|
|
||||||
|
import BondDiscount from "./BondDiscount";
|
||||||
|
import BondDuration from "./BondDuration";
|
||||||
|
import BondInfoText from "./BondInfoText";
|
||||||
|
import BondPrice from "./BondPrice";
|
||||||
|
|
||||||
|
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||||
|
import { sortBondsByDiscount, formatNumber } from "../../../helpers";
|
||||||
|
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||||
|
import { TertiaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
|
export const BondList = ({ bonds, secondsTo, chainId }) => {
|
||||||
|
const isSmallScreen = useScreenSize("md");
|
||||||
|
|
||||||
|
if (bonds.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box display="flex" justifyContent="center">
|
||||||
|
<Typography variant="h4">No active bonds</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box my="24px" textAlign="center">
|
||||||
|
<BondInfoText />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{sortBondsByDiscount(bonds).map(bond => (
|
||||||
|
<BondCard key={bond.id} secondsTo={secondsTo} bond={bond} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BondTable>
|
||||||
|
{sortBondsByDiscount(bonds).map(bond => (
|
||||||
|
<BondRow key={bond.id} bond={bond} secondsTo={secondsTo} />
|
||||||
|
))}
|
||||||
|
</BondTable>
|
||||||
|
|
||||||
|
<Box mt="24px" textAlign="center" width="70%" mx="auto">
|
||||||
|
<BondInfoText />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BondCard = ({ bond, secondsTo }) => {
|
||||||
|
const quoteTokenName = bond.quoteToken.name;
|
||||||
|
const baseTokenName = bond.baseToken.name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={bond.id} id={bond.id + `--bond`} mt="32px">
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<TokenStack tokens={bond.quoteToken.icons} />
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="column" ml="8px">
|
||||||
|
<Typography>{bond.quoteToken.name}</Typography>
|
||||||
|
|
||||||
|
<Link href={bond.quoteToken.purchaseUrl} target="_blank" rel="noopener noreferrer">
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Typography>Get Asset</Typography>
|
||||||
|
|
||||||
|
<Box ml="4px">
|
||||||
|
<SvgIcon component={ArrowUp} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="space-between" mt="16px">
|
||||||
|
<Typography>Price</Typography>
|
||||||
|
|
||||||
|
<Typography component={'span'}>
|
||||||
|
{bond.isSoldOut ? (
|
||||||
|
"--"
|
||||||
|
) : (
|
||||||
|
<BondPrice
|
||||||
|
price={bond.price.inBaseToken}
|
||||||
|
symbol={quoteTokenName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="space-between" mt="8px">
|
||||||
|
<Typography>Discount</Typography>
|
||||||
|
<Typography component={'span'}>{
|
||||||
|
bond.isSoldOut
|
||||||
|
? "--"
|
||||||
|
: <BondDiscount discount={bond.discount} />
|
||||||
|
}</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="space-between" mt="8px">
|
||||||
|
<Typography>Max Payout</Typography>
|
||||||
|
|
||||||
|
<Typography>
|
||||||
|
{`${payoutTokenCapacity(bond)}${
|
||||||
|
bond.baseToken.name !== bond.quoteToken.name ? ` (${quoteTokenCapacity(bond)})` : ``
|
||||||
|
}`}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="space-between" mt="8px">
|
||||||
|
<Typography>Duration</Typography>
|
||||||
|
|
||||||
|
<Typography component={'span'}>
|
||||||
|
<BondDuration secondsTo={secondsTo} duration={bond.duration} />
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt="16px">
|
||||||
|
<Link
|
||||||
|
component={NavLink}
|
||||||
|
to={`/bonds/${bond.id}`}
|
||||||
|
>
|
||||||
|
<TertiaryButton fullWidth>
|
||||||
|
Bond
|
||||||
|
</TertiaryButton>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BondTable = ({ children }) => (
|
||||||
|
<TableContainer>
|
||||||
|
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell style={{ padding: "8px 0", width: "162px" }}>Bond</TableCell>
|
||||||
|
<TableCell style={{ padding: "8px 0", width: "146px" }}>Price</TableCell>
|
||||||
|
<TableCell style={{ padding: "8px 0", width: "92px" }}>Discount</TableCell>
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>Max Payout</TableCell>
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>Duration</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
|
<TableBody>{children}</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const quoteTokenCapacity = (bond) => {
|
||||||
|
const quoteTokenCapacity = bond.maxPayout.inQuoteToken.lt(bond.capacity.inQuoteToken)
|
||||||
|
? bond.maxPayout.inQuoteToken
|
||||||
|
: bond.capacity.inQuoteToken;
|
||||||
|
return `${formatNumber(quoteTokenCapacity, 4)} ${bond.quoteToken.name}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const payoutTokenCapacity = (bond) => {
|
||||||
|
const payoutTokenCapacity = bond.maxPayout.inBaseToken.lt(bond.capacity.inBaseToken)
|
||||||
|
? bond.maxPayout.inBaseToken
|
||||||
|
: bond.capacity.inBaseToken;
|
||||||
|
return `${formatNumber(payoutTokenCapacity, 4)} FTSO`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BondRow = ({ bond, secondsTo }) => {
|
||||||
|
const quoteTokenName = bond.quoteToken.name;
|
||||||
|
const baseTokenName = bond.baseToken.name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow id={bond.id + `--bond`} data-testid={bond.id + `--bond`}>
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<TokenIcons token={bond.quoteToken} />
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<Typography>
|
||||||
|
{bond.isSoldOut ? (
|
||||||
|
"--"
|
||||||
|
) : (
|
||||||
|
<BondPrice
|
||||||
|
price={bond.price.inBaseToken}
|
||||||
|
symbol={quoteTokenName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<Typography component="div">
|
||||||
|
{bond.isSoldOut ? "--" : <BondDiscount discount={bond.discount} />}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<Box display="flex" flexDirection={"column"}>
|
||||||
|
<Typography style={{ lineHeight: "20px" }}>{payoutTokenCapacity(bond)}</Typography>
|
||||||
|
{bond.baseToken.name !== bond.quoteToken.name && (
|
||||||
|
<Typography color="textSecondary" style={{ fontSize: "12px", fontWeight: 400, lineHeight: "18px" }}>
|
||||||
|
{quoteTokenCapacity(bond)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<Typography>{
|
||||||
|
bond.isSoldOut
|
||||||
|
? "--"
|
||||||
|
: <BondDuration secondsTo={secondsTo} duration={bond.duration} />
|
||||||
|
}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<Link
|
||||||
|
component={NavLink}
|
||||||
|
to={`/bonds/${bond.id}`}
|
||||||
|
>
|
||||||
|
<TertiaryButton fullWidth disabled={bond.isSoldOut}>
|
||||||
|
{bond.isSoldOut ? "Sold Out" : `Bond`}
|
||||||
|
</TertiaryButton>
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenIcons = ({ token, chainId, explorer }) => (
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<TokenStack tokens={token.icons} />
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="column" ml="16px">
|
||||||
|
<Typography style={{ fontSize: "12px", fontWeight: 600, lineHeight: "18px" }}>{token.name}</Typography>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
color="primary"
|
||||||
|
target="_blank"
|
||||||
|
href={explorer ? `https://etherscan.io/token/${token.addresses[chainId]}` : token.purchaseUrl}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Typography style={{ fontSize: "12px", lineHeight: "18px" }}>
|
||||||
|
{explorer ? `Explorer` : `Get Asset`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box ml="4px">
|
||||||
|
<SvgIcon component={ArrowUp} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
9
src/containers/Bond/components/BondPrice.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const BondPrice = ({ price, symbol }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{price.toString({ decimals: 2, format: true, trim: false })} {symbol}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondPrice;
|
78
src/containers/Bond/components/BondSettingsModal.jsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Box, FormControl, InputAdornment, InputLabel, OutlinedInput, Typography } from "@mui/material";
|
||||||
|
import Modal from "../../../components/Modal/Modal";
|
||||||
|
|
||||||
|
const BondSettingsModal = props => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
id="hades"
|
||||||
|
maxWidth="468px"
|
||||||
|
minHeight="350px"
|
||||||
|
open={props.open}
|
||||||
|
headerText="Settings"
|
||||||
|
onClose={props.handleClose}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<InputLabel htmlFor="slippage">Slippage</InputLabel>
|
||||||
|
<Box mt="8px">
|
||||||
|
<FormControl variant="outlined" color="primary" fullWidth>
|
||||||
|
<OutlinedInput
|
||||||
|
inputProps={{ "data-testid": "slippage" }}
|
||||||
|
type="number"
|
||||||
|
id="slippage"
|
||||||
|
value={props.slippage}
|
||||||
|
onChange={props.onSlippageChange}
|
||||||
|
endAdornment={<InputAdornment position="end">%</InputAdornment>}
|
||||||
|
/>
|
||||||
|
</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="decimals">Decimal representation</InputLabel>
|
||||||
|
<Box mt="8px">
|
||||||
|
<FormControl variant="outlined" color="primary" fullWidth>
|
||||||
|
<OutlinedInput
|
||||||
|
inputProps={{ "data-testid": "decimals" }}
|
||||||
|
type="number"
|
||||||
|
id="decimals"
|
||||||
|
value={props.formatDecimals}
|
||||||
|
onChange={props.onDecimalsChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
<Box mt="8px">
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Number of decimals to be shown in token balances
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt="32px">
|
||||||
|
<InputLabel htmlFor="recipient">Recipient Address</InputLabel>
|
||||||
|
<Box mt="8px">
|
||||||
|
<FormControl variant="outlined" color="primary" fullWidth>
|
||||||
|
<OutlinedInput
|
||||||
|
inputProps={{ "data-testid": "recipient" }}
|
||||||
|
type="text"
|
||||||
|
id="recipient"
|
||||||
|
value={props.recipientAddress}
|
||||||
|
onChange={props.onRecipientAddressChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
<Box mt="8px">
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Choose recipient address. By default, this is your currently connected address
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondSettingsModal;
|
26
src/containers/Bond/components/BondSlippage.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Box, useTheme } from "@mui/material";
|
||||||
|
import Chip from "../../../components/Chip/Chip";
|
||||||
|
import { formatNumber } from "../../../helpers";
|
||||||
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
|
const BondSlippage = ({ slippage, textOnly }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSlippageBig = parseFloat(slippage) > 15;
|
||||||
|
|
||||||
|
return textOnly ? (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
color: isSlippageBig ? theme.colors.feedback.error : theme.colors.feedback.pnlGain,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{slippage}%
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Chip
|
||||||
|
label={`${slippage}%`}
|
||||||
|
template={isSlippageBig ? "error" : "success"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondSlippage;
|
7
src/containers/Bond/components/BondVesting.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
||||||
|
|
||||||
|
const BondVesting = ({ vesting }) => {
|
||||||
|
return <>{vesting > 0 ? prettifySecondsInDays(vesting) : "Instant"}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BondVesting;
|
236
src/containers/Bond/components/ClaimBonds.jsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import BondVesting from "./BondVesting";
|
||||||
|
import BondDuration from "./BondDuration";
|
||||||
|
import WarmupConfirmModal from "./WarmupConfirmModal";
|
||||||
|
|
||||||
|
import Paper from "../../../components/Paper/Paper";
|
||||||
|
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||||
|
import { Tab, Tabs } from "../../../components/Tabs/Tabs";
|
||||||
|
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||||
|
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||||
|
|
||||||
|
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||||
|
|
||||||
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
import { formatCurrency } from "../../../helpers";
|
||||||
|
|
||||||
|
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
|
||||||
|
import { useNotes, redeem } from "../../../hooks/bonds";
|
||||||
|
|
||||||
|
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, setIsPayoutGhst] = useState(localStorage.getItem("bond-isGhstPayout")
|
||||||
|
? localStorage.getItem("bond-isGhstPayout") === "true"
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
|
||||||
|
const { epoch } = useEpoch(chainId);
|
||||||
|
const { warmupExists } = useWarmupLength(chainId);
|
||||||
|
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
|
||||||
|
|
||||||
|
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
|
||||||
|
const { notes, refetch: notesRefetch } = useNotes(chainId, address);
|
||||||
|
|
||||||
|
if (!notes || notes.length === 0) return null;
|
||||||
|
|
||||||
|
const totalClaimableBalance = new DecimalBigNumber(
|
||||||
|
notes.reduce((res, note) => {
|
||||||
|
if (secondsTo < note.matured) return res + 0n;
|
||||||
|
return res + note.payout._value
|
||||||
|
}, 0n),
|
||||||
|
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(
|
||||||
|
chainId,
|
||||||
|
address,
|
||||||
|
isPayoutGhst,
|
||||||
|
indexes
|
||||||
|
);
|
||||||
|
setIsPending(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WarmupConfirmModal
|
||||||
|
isOpen={isWarmup}
|
||||||
|
handleConfirmClose={() => setIsWapmup(false)}
|
||||||
|
address={address}
|
||||||
|
warmupLength={warmupInfo.expiry - epoch.number}
|
||||||
|
setPreClaimConfirmed={() => setPreClaimConfirmed(true)}
|
||||||
|
/>
|
||||||
|
<Paper headerText="Your Bonds" fullWidth enableBackground>
|
||||||
|
<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="STNK" style={{ fontSize: "1rem" }} />
|
||||||
|
<Tab aria-label="payout-ghst-button" label="GHST" 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" }}>
|
||||||
|
Claimable Balance
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box mt="4px" mb="8px">
|
||||||
|
<Typography variant="h4" align="center">
|
||||||
|
{isPayoutGhst
|
||||||
|
? formatCurrency(totalClaimableBalance, 5, "GHST")
|
||||||
|
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, "STNK")
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
disabled={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>
|
||||||
|
</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>Payout</Typography>
|
||||||
|
<Typography>
|
||||||
|
{isPayoutGhst
|
||||||
|
? formatCurrency(note.payout, 5, "GHST")
|
||||||
|
: formatCurrency(currentIndex.mul(note.payout), 5, "STNK")
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt="16px">
|
||||||
|
<TertiaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={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>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</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>
|
||||||
|
{isPayoutGhst
|
||||||
|
? formatCurrency(note.payout, 5, "GHST")
|
||||||
|
: formatCurrency(currentIndex.mul(note.payout), 5, "STNK")
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<TertiaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={secondsTo < note.matured}
|
||||||
|
onClick={() => onSubmit([note.id])}
|
||||||
|
>
|
||||||
|
Claim
|
||||||
|
</TertiaryButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
67
src/containers/Bond/components/WarmupConfirmModal.jsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { styled, useTheme } from "@mui/material/styles";
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
|
||||||
|
import Modal from "../../../components/Modal/Modal";
|
||||||
|
import { PrimaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
import { formatNumber } from "../../../helpers";
|
||||||
|
|
||||||
|
import { purchaseBond } from "../../../hooks/bonds";
|
||||||
|
import { claim } from "../../../hooks/staking";
|
||||||
|
|
||||||
|
const StyledBox = styled(Box, {
|
||||||
|
shouldForwardProp: prop => prop !== "template",
|
||||||
|
})(({ theme }) => {
|
||||||
|
return {
|
||||||
|
root: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const WarmupConfirmModal = ({
|
||||||
|
chainId,
|
||||||
|
address,
|
||||||
|
warmupLength,
|
||||||
|
isOpen,
|
||||||
|
handleConfirmClose,
|
||||||
|
setPreClaimConfirmed
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
setPreClaimConfirmed();
|
||||||
|
handleConfirmClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
maxWidth="476px"
|
||||||
|
minHeight={warmupLength >= 0 ? "150px" : "200px"}
|
||||||
|
open={isOpen}
|
||||||
|
headerText={
|
||||||
|
warmupLength >= 0
|
||||||
|
? "Bond Warm-Up is Pending..."
|
||||||
|
: "Unlock Bond"
|
||||||
|
}
|
||||||
|
onClose={handleConfirmClose}
|
||||||
|
>
|
||||||
|
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Typography>{
|
||||||
|
warmupLength >= 0
|
||||||
|
? `This bond is currently in a warm-up period and cannot be claimed yet. It'll be available for claim in ${warmupLength} epochs.`
|
||||||
|
: "This bond is warmed-up and can be unlocked. Please unlock it on behalf of the collective."
|
||||||
|
}</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{warmupLength < 0 && <PrimaryButton fullWidth onClick={onSubmit}>
|
||||||
|
Confirm
|
||||||
|
</PrimaryButton>}
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WarmupConfirmModal;
|
355
src/containers/Dex/Dex.jsx
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
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;
|
305
src/containers/Dex/PoolContainer.jsx
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
import SwapCard from "../../components/Swap/SwapCard";
|
||||||
|
import SwapCollection from "../../components/Swap/SwapCollection";
|
||||||
|
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||||
|
import { SecondaryButton } from "../../components/Button";
|
||||||
|
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { formatNumber, formatCurrency } from "../../helpers";
|
||||||
|
|
||||||
|
import { useBalance, useTotalSupply } from "../../hooks/tokens";
|
||||||
|
import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2";
|
||||||
|
|
||||||
|
const PoolContainer = ({
|
||||||
|
tokenNameTop,
|
||||||
|
tokenNameBottom,
|
||||||
|
onCardsSwap,
|
||||||
|
address,
|
||||||
|
chainId,
|
||||||
|
dexAddresses,
|
||||||
|
connect,
|
||||||
|
slippage,
|
||||||
|
secondsToWait,
|
||||||
|
setTopTokenListOpen,
|
||||||
|
setBottomTokenListOpen,
|
||||||
|
formatDecimals
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
||||||
|
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
const [amountTop, setAmountTop] = useState("");
|
||||||
|
const [amountBottom, setAmountBottom] = useState("");
|
||||||
|
|
||||||
|
const {
|
||||||
|
balance: balanceTop,
|
||||||
|
refetch: balanceRefetchTop,
|
||||||
|
contractAddress: addressTop,
|
||||||
|
} = useBalance(chainId, tokenNameTop, address);
|
||||||
|
const {
|
||||||
|
balance: balanceBottom,
|
||||||
|
refetch: balanceRefetchBottom,
|
||||||
|
contractAddress: addressBottom,
|
||||||
|
} = useBalance(chainId, tokenNameBottom, address);
|
||||||
|
|
||||||
|
const {
|
||||||
|
pairAddress,
|
||||||
|
refetch: pairAddressRefetch,
|
||||||
|
} = useUniswapV2Pair(chainId, dexAddresses.factory, addressTop, addressBottom);
|
||||||
|
const {
|
||||||
|
balance: lpBalance,
|
||||||
|
refetch: lpRefetch,
|
||||||
|
} = useBalance(chainId, pairAddress, address);
|
||||||
|
const {
|
||||||
|
totalSupply: lpTotalSupply,
|
||||||
|
refetch: lpTotalSupplyRefetch
|
||||||
|
} = useTotalSupply(chainId, pairAddress);
|
||||||
|
const {
|
||||||
|
reserves: pairReserves,
|
||||||
|
refetch: pairReservesRefetch,
|
||||||
|
} = useUniswapV2PairReserves(
|
||||||
|
chainId,
|
||||||
|
pairAddress,
|
||||||
|
balanceTop._decimals,
|
||||||
|
balanceBottom._decimals,
|
||||||
|
tokenNameTop,
|
||||||
|
tokenNameBottom,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSwap = () => {
|
||||||
|
const oldAmountTop = amountTop;
|
||||||
|
const oldAmountBottom = amountBottom;
|
||||||
|
setAmountBottom(oldAmountTop);
|
||||||
|
setAmountTop(oldAmountBottom);
|
||||||
|
onCardsSwap();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 zero = new DecimalBigNumber(0n, 0);
|
||||||
|
const value0 = new DecimalBigNumber(amountTop, balanceTop._decimals)
|
||||||
|
const value1 = new DecimalBigNumber(amountBottom, balanceBottom._decimals)
|
||||||
|
const amountToAddA = new DecimalBigNumber(value0._value.toBigInt(), balanceTop._decimals);
|
||||||
|
const amountToAddB = new DecimalBigNumber(value1._value.toBigInt(), balanceBottom._decimals);
|
||||||
|
|
||||||
|
if (
|
||||||
|
pairReserves.reserve0.gt(zero) &&
|
||||||
|
pairReserves.reserve1.gt(zero) &&
|
||||||
|
lpTotalSupply.gt(new DecimalBigNumber(0n, 0))
|
||||||
|
) {
|
||||||
|
const lpTokensFromA = (amountToAddA.mul(lpTotalSupply).div(pairReserves.reserve0));
|
||||||
|
const lpTokensFromB = (amountToAddB.mul(lpTotalSupply).div(pairReserves.reserve1));
|
||||||
|
const lpTokensToMint = lpTokensFromA.gt(lpTokensFromB) ? lpTokensFromB : lpTokensFromA;
|
||||||
|
return lpTokensToMint;
|
||||||
|
} else {
|
||||||
|
const tokens = bigIntSqrt(BigInt(amountToAddA.mul(amountToAddB)._value));
|
||||||
|
const lpTokensToMint = new DecimalBigNumber(tokens, 18);
|
||||||
|
return lpTokensToMint;
|
||||||
|
}
|
||||||
|
}, [pairReserves, amountTop, amountBottom, balanceTop, balanceBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pairReserves && pairReserves.reserve1.gt(new DecimalBigNumber(0n, 0))) {
|
||||||
|
const value = new DecimalBigNumber(amountTop, balanceTop._decimals)
|
||||||
|
const amountToAdd = new DecimalBigNumber(value._value.toBigInt(), balanceTop._decimals);
|
||||||
|
const amount = amountToAdd.mul(pairReserves.reserve0).div(pairReserves.reserve1);
|
||||||
|
setAmountBottom(amount.toString());
|
||||||
|
}
|
||||||
|
}, [amountTop, pairReserves])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAmountTop("");
|
||||||
|
setAmountBottom("");
|
||||||
|
}, [tokenNameTop, tokenNameBottom])
|
||||||
|
|
||||||
|
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);
|
||||||
|
const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));
|
||||||
|
|
||||||
|
if (floatSlippage < 3) toast("Slippage is too low, transaction highly likely will fail.");
|
||||||
|
|
||||||
|
const amountADesired = BigInt(Math.round(parseFloat(amountTop) * Math.pow(10, balanceTop._decimals)));
|
||||||
|
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
|
||||||
|
const amountAMin = amountADesired * bigIntSlippage / one;
|
||||||
|
const amountBMin = amountBDesired * bigIntSlippage / one;
|
||||||
|
|
||||||
|
await addLiquidity(
|
||||||
|
chainId,
|
||||||
|
tokenNameTop,
|
||||||
|
tokenNameBottom,
|
||||||
|
amountADesired,
|
||||||
|
amountBDesired,
|
||||||
|
amountAMin,
|
||||||
|
amountBMin,
|
||||||
|
destination,
|
||||||
|
deadline,
|
||||||
|
);
|
||||||
|
|
||||||
|
await balanceRefetchTop();
|
||||||
|
await balanceRefetchBottom();
|
||||||
|
await pairAddressRefetch();
|
||||||
|
await lpRefetch();
|
||||||
|
await lpTotalSupplyRefetch();
|
||||||
|
await pairReservesRefetch();
|
||||||
|
|
||||||
|
setAmountTop("");
|
||||||
|
setAmountBottom("");
|
||||||
|
|
||||||
|
setIsPending(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyPool = useMemo(() => {
|
||||||
|
return pairAddress === "" || pairAddress === "0x0000000000000000000000000000000000000000"
|
||||||
|
}, [pairAddress])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
|
||||||
|
<SwapCollection
|
||||||
|
UpperSwapCard={
|
||||||
|
<SwapCard
|
||||||
|
id="from"
|
||||||
|
token={<TokenStack tokens={[tokenNameTop]} sx={{ fontSize: "21px" }} />}
|
||||||
|
tokenName={tokenNameTop}
|
||||||
|
info={(!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceTop, formatDecimals, tokenNameTop)}
|
||||||
|
endString="Max"
|
||||||
|
endStringOnClick={setMaxTop}
|
||||||
|
value={amountTop}
|
||||||
|
onChange={event => setAmountTop(event.currentTarget.value)}
|
||||||
|
tokenOnClick={() => setTopTokenListOpen(true)}
|
||||||
|
inputProps={{ "data-testid": "fromInput" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
LowerSwapCard={
|
||||||
|
<SwapCard
|
||||||
|
id="to"
|
||||||
|
token={<TokenStack tokens={[tokenNameBottom]} sx={{ fontSize: "21px" }} />}
|
||||||
|
tokenName={tokenNameBottom}
|
||||||
|
value={amountBottom }
|
||||||
|
onChange={event => emptyPool ? setAmountBottom(event.currentTarget.value) : {}}
|
||||||
|
endString={emptyPool ? "Max" : undefined}
|
||||||
|
endStringOnClick={emptyPool ? setMaxBottom : {}}
|
||||||
|
inputProps={{ "data-testid": "toInput" }}
|
||||||
|
tokenOnClick={() => setBottomTokenListOpen(true)}
|
||||||
|
info={(!isSmallScreen ? "Balance: " : "") + formatCurrency(balanceBottom, formatDecimals, tokenNameBottom)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
arrowOnClick={onSwap}
|
||||||
|
/>
|
||||||
|
{!isSmallScreen && <Box
|
||||||
|
m="10px 0"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
gap="0px"
|
||||||
|
maxWidth="356px"
|
||||||
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
||||||
|
>
|
||||||
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
|
<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">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">Extra Balance:</Typography>
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">~{formatNumber(estimatedAmountOut, formatDecimals)} LP</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>}
|
||||||
|
<TokenAllowanceGuard
|
||||||
|
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
||||||
|
tokenName={tokenNameTop}
|
||||||
|
owner={address}
|
||||||
|
spender={dexAddresses.router}
|
||||||
|
decimals={balanceTop._decimals}
|
||||||
|
approvalText={"Approve " + tokenNameTop}
|
||||||
|
approvalPendingText={"Approving..."}
|
||||||
|
connect={connect}
|
||||||
|
width="100%"
|
||||||
|
height="60px"
|
||||||
|
>
|
||||||
|
<TokenAllowanceGuard
|
||||||
|
spendAmount={new DecimalBigNumber(amountBottom, balanceBottom._decimals)}
|
||||||
|
tokenName={tokenNameBottom}
|
||||||
|
owner={address}
|
||||||
|
spender={dexAddresses.router}
|
||||||
|
decimals={balanceBottom._decimals}
|
||||||
|
approvalText={"Approve " + tokenNameBottom}
|
||||||
|
approvalPendingText={"Approving..."}
|
||||||
|
connect={connect}
|
||||||
|
width="100%"
|
||||||
|
height="60px"
|
||||||
|
>
|
||||||
|
<SecondaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={
|
||||||
|
address !== "" && (
|
||||||
|
(new DecimalBigNumber(amountTop, balanceTop._decimals)).eq(new DecimalBigNumber("0", 18)) ||
|
||||||
|
(new DecimalBigNumber(amountBottom, balanceBottom._decimals)).eq(new DecimalBigNumber("0", 18)) ||
|
||||||
|
balanceTop?.lt(new DecimalBigNumber(amountTop, balanceTop._decimals)) ||
|
||||||
|
balanceBottom?.lt(new DecimalBigNumber(amountBottom, balanceBottom._decimals)) ||
|
||||||
|
isPending
|
||||||
|
)
|
||||||
|
}
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => address === "" ? connect() : addLiquidityInner()}
|
||||||
|
>
|
||||||
|
{address === "" ?
|
||||||
|
"Connect"
|
||||||
|
:
|
||||||
|
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
||||||
|
"Create Pool"
|
||||||
|
:
|
||||||
|
"Add Liquidity"
|
||||||
|
}
|
||||||
|
</SecondaryButton>
|
||||||
|
</TokenAllowanceGuard>
|
||||||
|
</TokenAllowanceGuard>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PoolContainer;
|
243
src/containers/Dex/SwapContainer.jsx
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
import SwapCard from "../../components/Swap/SwapCard";
|
||||||
|
import SwapCollection from "../../components/Swap/SwapCollection";
|
||||||
|
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||||
|
import { SecondaryButton } from "../../components/Button";
|
||||||
|
|
||||||
|
import { formatCurrency } from "../../helpers";
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
||||||
|
|
||||||
|
import { getTokenAddress } from "../../hooks/helpers";
|
||||||
|
import { useBalance } from "../../hooks/tokens";
|
||||||
|
import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens } from "../../hooks/uniswapv2";
|
||||||
|
|
||||||
|
const SwapContainer = ({
|
||||||
|
tokenNameTop,
|
||||||
|
tokenNameBottom,
|
||||||
|
address,
|
||||||
|
chainId,
|
||||||
|
dexAddresses,
|
||||||
|
connect,
|
||||||
|
onCardsSwap,
|
||||||
|
setTopTokenListOpen,
|
||||||
|
setBottomTokenListOpen,
|
||||||
|
slippage,
|
||||||
|
secondsToWait,
|
||||||
|
setIsSwap,
|
||||||
|
formatDecimals
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
||||||
|
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
const [amountBottom, setAmountBottom] = useState("");
|
||||||
|
const [amountTop, setAmountTop] = useState("");
|
||||||
|
|
||||||
|
const [currentPrice, setCurrentPrice] = useState(new DecimalBigNumber(0n, 0));
|
||||||
|
const [nextPrice, setNextPrice] = useState(new DecimalBigNumber(0n, 0));
|
||||||
|
|
||||||
|
const {
|
||||||
|
balance: balanceTop,
|
||||||
|
refetch: balanceRefetchTop,
|
||||||
|
contractAddress: addressTop,
|
||||||
|
} = useBalance(chainId, tokenNameTop, address);
|
||||||
|
const {
|
||||||
|
balance: balanceBottom,
|
||||||
|
refetch: balanceRefetchBottom,
|
||||||
|
contractAddress: addressBottom,
|
||||||
|
} = useBalance(chainId, tokenNameBottom, address);
|
||||||
|
|
||||||
|
const {
|
||||||
|
pairAddress,
|
||||||
|
} = useUniswapV2Pair(chainId, dexAddresses.factory, addressTop, addressBottom);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserves: pairReserves,
|
||||||
|
tokens: tokenAddresses,
|
||||||
|
refetch: pairReservesRefetch,
|
||||||
|
} = useUniswapV2PairReserves(
|
||||||
|
chainId,
|
||||||
|
pairAddress,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSwap = () => {
|
||||||
|
setAmountTop("");
|
||||||
|
setAmountBottom("");
|
||||||
|
onCardsSwap();
|
||||||
|
}
|
||||||
|
const setMax = () => setAmountTop(balanceTop.toString());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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 (amountIn.eq(zero)) {
|
||||||
|
setCurrentPrice("");
|
||||||
|
} else {
|
||||||
|
setCurrentPrice(amountIn.div(amountOut).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountIn.eq(zero) || amountInWithFee.eq(zero)) {
|
||||||
|
setAmountBottom("");
|
||||||
|
setNextPrice("");
|
||||||
|
} else {
|
||||||
|
const nominator = amountOut.mul(amountIn);
|
||||||
|
const denominator = amountIn.add(amountInWithFee);
|
||||||
|
const newAmountOut = nominator.div(denominator);
|
||||||
|
|
||||||
|
setAmountBottom(amountOut.sub(newAmountOut).toString());
|
||||||
|
setNextPrice(denominator.div(newAmountOut).toString())
|
||||||
|
}
|
||||||
|
}, [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);
|
||||||
|
const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));
|
||||||
|
|
||||||
|
if (floatSlippage < 3) toast("Slippage is too low, transaction highly likely will fail.");
|
||||||
|
|
||||||
|
const amountADesired = BigInt(Math.round(parseFloat(amountTop) * Math.pow(10, balanceTop._decimals)));
|
||||||
|
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
|
||||||
|
const amountBMin = amountBDesired * bigIntSlippage / one;
|
||||||
|
|
||||||
|
await swapExactTokensForTokens(
|
||||||
|
chainId,
|
||||||
|
amountADesired,
|
||||||
|
amountBMin,
|
||||||
|
[tokenNameTop, tokenNameBottom],
|
||||||
|
destination,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
|
||||||
|
await balanceRefetchTop();
|
||||||
|
await balanceRefetchBottom();
|
||||||
|
await pairReservesRefetch();
|
||||||
|
|
||||||
|
setAmountTop("");
|
||||||
|
setIsPending(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
|
||||||
|
<SwapCollection
|
||||||
|
UpperSwapCard={
|
||||||
|
<SwapCard
|
||||||
|
maxWidth="356px"
|
||||||
|
id="from"
|
||||||
|
token={<TokenStack tokens={[tokenNameTop]} sx={{ fontSize: "21px" }} />}
|
||||||
|
tokenName={tokenNameTop}
|
||||||
|
info={
|
||||||
|
(!isSmallScreen ? "Balance: " : "") +
|
||||||
|
formatCurrency(balanceTop ? balanceTop : "0", formatDecimals, tokenNameTop)
|
||||||
|
}
|
||||||
|
endString="Max"
|
||||||
|
endStringOnClick={setMax}
|
||||||
|
value={amountTop}
|
||||||
|
onChange={event => setAmountTop(event.currentTarget.value)}
|
||||||
|
tokenOnClick={() => setTopTokenListOpen(true)}
|
||||||
|
inputProps={{ "data-testid": "fromInput" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
LowerSwapCard={
|
||||||
|
<SwapCard
|
||||||
|
maxWidth="356px"
|
||||||
|
id="to"
|
||||||
|
token={<TokenStack tokens={[tokenNameBottom]} sx={{ fontSize: "21px" }} />}
|
||||||
|
tokenName={tokenNameBottom}
|
||||||
|
value={amountBottom}
|
||||||
|
inputProps={{ "data-testid": "toInput" }}
|
||||||
|
tokenOnClick={() => setBottomTokenListOpen(true)}
|
||||||
|
info={
|
||||||
|
(!isSmallScreen ? "Balance: " : "") +
|
||||||
|
formatCurrency(balanceBottom ? balanceBottom : "0", formatDecimals, tokenNameBottom)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
arrowOnClick={onSwap}
|
||||||
|
/>
|
||||||
|
{!isSmallScreen && <Box
|
||||||
|
m="10px 0"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
gap="0px"
|
||||||
|
maxWidth="356px"
|
||||||
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
||||||
|
>
|
||||||
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">Current price:</Typography>
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">Next price:</Typography>
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">Transaction deadline:</Typography>
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>}
|
||||||
|
<TokenAllowanceGuard
|
||||||
|
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
||||||
|
tokenName={tokenNameTop}
|
||||||
|
owner={address}
|
||||||
|
spender={dexAddresses.router}
|
||||||
|
decimals={balanceTop._decimals}
|
||||||
|
approvalText={"Approve " + tokenNameTop}
|
||||||
|
approvalPendingText={"Approving..."}
|
||||||
|
connect={connect}
|
||||||
|
width="100%"
|
||||||
|
height="60px"
|
||||||
|
>
|
||||||
|
<SecondaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={
|
||||||
|
address !== "" && (
|
||||||
|
(new DecimalBigNumber(amountTop, balanceTop._decimals)).eq(new DecimalBigNumber("0", 18)) ||
|
||||||
|
balanceTop?.lt(new DecimalBigNumber(amountTop, balanceTop._decimals)) ||
|
||||||
|
isPending
|
||||||
|
)
|
||||||
|
}
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => address === "" ?
|
||||||
|
connect()
|
||||||
|
:
|
||||||
|
pairAddress === "0x0000000000000000000000000000000000000000" ? setIsSwap(false) : swapTokens()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{address === "" ?
|
||||||
|
"Connect"
|
||||||
|
:
|
||||||
|
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
||||||
|
"Create Pool"
|
||||||
|
:
|
||||||
|
"Swap"
|
||||||
|
}
|
||||||
|
</SecondaryButton>
|
||||||
|
</TokenAllowanceGuard>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SwapContainer;
|
146
src/containers/Dex/TokenModal.jsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import {
|
||||||
|
Divider,
|
||||||
|
Typography,
|
||||||
|
TableContainer,
|
||||||
|
Table,
|
||||||
|
TableHead,
|
||||||
|
TableBody,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
useMediaQuery,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { isAddress, getAddress } from "@ethersproject/address";
|
||||||
|
|
||||||
|
import Modal from "../../components/Modal/Modal";
|
||||||
|
import SwapCard from "../../components/Swap/SwapCard";
|
||||||
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { formatNumber } from "../../helpers/";
|
||||||
|
import { useBalance, useTokenSymbol } from "../../hooks/tokens";
|
||||||
|
import { DAI_ADDRESSES, FTSO_ADDRESSES, STNK_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
|
||||||
|
|
||||||
|
const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }) => {
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 599px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 425px)");
|
||||||
|
|
||||||
|
const [address, setAddress] = useState("");
|
||||||
|
const [userInput, setUserInput] = useState("");
|
||||||
|
const [isAddressCorrect, setIsAddressCorrect] = useState(false);
|
||||||
|
|
||||||
|
const setUsedAddress = (token) => {
|
||||||
|
setTokenAddress(token.address);
|
||||||
|
setListOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { symbol: searchSymbol } = useTokenSymbol(chainId, address);
|
||||||
|
const { balance: searchBalance } = useBalance(chainId, address, account);
|
||||||
|
|
||||||
|
const { balance: daiBalance } = useBalance(chainId, "GDAI", account);
|
||||||
|
const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account);
|
||||||
|
const { balance: stnkBalance } = useBalance(chainId, "STNK", account);
|
||||||
|
const { balance: ghstBalance } = useBalance(chainId, "GHST", account);
|
||||||
|
|
||||||
|
const searchToken = useMemo(() => {
|
||||||
|
return [{
|
||||||
|
name: searchSymbol,
|
||||||
|
icons: ["X"],
|
||||||
|
balance: searchBalance,
|
||||||
|
address: address
|
||||||
|
}]
|
||||||
|
}, [searchSymbol, searchBalance, address]);
|
||||||
|
|
||||||
|
const knownTokens = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: "gDAI",
|
||||||
|
icons: ["GDAI"],
|
||||||
|
balance: daiBalance,
|
||||||
|
address: DAI_ADDRESSES[chainId]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FTSO",
|
||||||
|
icons: ["FTSO"],
|
||||||
|
balance: ftsoBalance,
|
||||||
|
address: FTSO_ADDRESSES[chainId]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "STNK",
|
||||||
|
icons: ["STNK"],
|
||||||
|
balance: stnkBalance,
|
||||||
|
address: STNK_ADDRESSES[chainId]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GHST",
|
||||||
|
icons: ["GHST"],
|
||||||
|
balance: ghstBalance,
|
||||||
|
address: GHST_ADDRESSES[chainId]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [daiBalance, ghstBalance, stnkBalance, ghstBalance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAddress(userInput)) {
|
||||||
|
setAddress(getAddress(userInput));
|
||||||
|
setIsAddressCorrect(true);
|
||||||
|
} else {
|
||||||
|
setAddress("");
|
||||||
|
setIsAddressCorrect(false);
|
||||||
|
}
|
||||||
|
}, [userInput])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
maxWidth="476px"
|
||||||
|
minHeight="200px"
|
||||||
|
open={listOpen}
|
||||||
|
headerText={"Select a token"}
|
||||||
|
onClose={() => setListOpen(false)}
|
||||||
|
>
|
||||||
|
<SwapCard
|
||||||
|
placeholder="0x"
|
||||||
|
inputType="string"
|
||||||
|
inputFontSize="12px"
|
||||||
|
inputWidth={isVerySmallScreen ? "210px" : isSmallScreen ? "340px" : "386px"}
|
||||||
|
value={userInput}
|
||||||
|
onChange={event => setUserInput(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Divider style={{ margin: "20px 0" }} />
|
||||||
|
<Typography variant="h7">Token Name</Typography>
|
||||||
|
<TableContainer sx={{ maxHeight: "260px" }}>
|
||||||
|
<Table aria-label="Available tokens" style={{ tableLayout: "fixed" }}>
|
||||||
|
<TableHead sx={{ display: "" }}>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell style={{ padding: "8px 0", width: "50px" }}></TableCell>
|
||||||
|
<TableCell style={{ padding: "8px 0", width: "100px" }}></TableCell>
|
||||||
|
<TableCell style={{ padding: "8px 0", width: "auto" }}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{(isAddressCorrect ? searchToken : knownTokens).map((token, index) => {
|
||||||
|
return (
|
||||||
|
<TableRow onClick={() => setUsedAddress(token)} hover key={index} id={index + `--token`} data-testid={index + `--token`}>
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<TokenStack style={{ width: "32px", height: "32px" }} tokens={token.icons} />
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ padding: "8px 0", height: "32px" }}>
|
||||||
|
<Typography>{token.name}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell style={{ display: "flex", justifyContent: "end" }}>
|
||||||
|
<Typography>{formatNumber(token.balance, 5)}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenModal;
|
203
src/containers/Faucet/Faucet.jsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
||||||
|
import { useConfig, useBalance } from "wagmi";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
import SwapCard from "../../components/Swap/SwapCard";
|
||||||
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
import { PrimaryButton } from "../../components/Button";
|
||||||
|
|
||||||
|
import { DAI_ADDRESSES } from "../../constants/addresses";
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { formatCurrency, formatNumber } from "../../helpers";
|
||||||
|
|
||||||
|
import { useBalance as useTokenBalance, useConversionRate, mintDai } from "../../hooks/tokens";
|
||||||
|
|
||||||
|
const Faucet = ({ chainId, address, config, connect }) => {
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
|
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
|
const daiConversionRate = useConversionRate(chainId, "GDAI");
|
||||||
|
const { balance: daiBalance, refetch: daiBalanceRefetch } = useTokenBalance(chainId, "GDAI", address);
|
||||||
|
const { data: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
|
||||||
|
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
const [balance, setBalance] = useState(new DecimalBigNumber(0, 0));
|
||||||
|
const [amount, setAmount] = useState("");
|
||||||
|
const [faucetToken, setFaucetToken] = useState({
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
});
|
||||||
|
const [scanInfo, setScanInfo] = useState({
|
||||||
|
name: "",
|
||||||
|
url: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ReactGA.send({ hitType: "pageview", page: "/faucet" });
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const value = nativeBalance ? nativeBalance.value : 0n;
|
||||||
|
const decimals = nativeBalance ? nativeBalance.decimals : 18;
|
||||||
|
setBalance(new DecimalBigNumber(value, decimals));
|
||||||
|
}, [nativeBalance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let scanName = "";
|
||||||
|
let scanUrl = "";
|
||||||
|
|
||||||
|
const tokenName = "gDAI";
|
||||||
|
const tokenAddress = DAI_ADDRESSES[chainId];
|
||||||
|
|
||||||
|
const client = config?.getClient();
|
||||||
|
scanName = client?.chain?.blockExplorers?.default?.name;
|
||||||
|
scanUrl = client?.chain?.blockExplorers?.default?.url;
|
||||||
|
|
||||||
|
setFaucetToken({
|
||||||
|
name: tokenName,
|
||||||
|
address: tokenAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
setScanInfo({
|
||||||
|
name: scanName,
|
||||||
|
url: scanUrl,
|
||||||
|
})
|
||||||
|
}, [chainId]);
|
||||||
|
|
||||||
|
const preparedAmount = useMemo(() => {
|
||||||
|
if (address === "") new DecimalBigNumber("0", 0);
|
||||||
|
return new DecimalBigNumber(amount, 18);
|
||||||
|
}, [amount, balance])
|
||||||
|
|
||||||
|
const estimatedAmount = useMemo(() => {
|
||||||
|
const rate = new DecimalBigNumber(daiConversionRate.toString(), 0);
|
||||||
|
const value = new DecimalBigNumber(amount, 18);
|
||||||
|
return value.mul(rate);
|
||||||
|
}, [amount, daiConversionRate])
|
||||||
|
|
||||||
|
const mintOrConnect = async () => {
|
||||||
|
if (address === "") {
|
||||||
|
connect();
|
||||||
|
} else {
|
||||||
|
setIsPending(true);
|
||||||
|
await mintDai(chainId, address, preparedAmount._value.toString());
|
||||||
|
|
||||||
|
await balanceRefetch();
|
||||||
|
await daiBalanceRefetch();
|
||||||
|
setAmount("");
|
||||||
|
setIsPending(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box height="calc(100vh - 43px)">
|
||||||
|
<Helmet>
|
||||||
|
<title>ghostFaucet | gDAI Faucet</title>
|
||||||
|
<meta name="description" content="ghostFaucet is a No KYC gDAI Faucet powered by GHOST. Get gDAI sent directly to your wallet." />
|
||||||
|
<meta name="keywords" content="ghostFaucet, web3 faucet, gDAI, ethereum, sepolia, polygon, bnb, bsc, AVAX" />
|
||||||
|
|
||||||
|
<meta property="og:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-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="ghostFaucet is a No KYC gDAI Faucet powered by GHOST. Get gDAI sent directly to your wallet." />
|
||||||
|
|
||||||
|
<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="ghostFaucet is a No KYC gDAI Faucet powered by GHOST. Get gDAI sent directly to your wallet." />
|
||||||
|
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
|
<PageTitle name={"gDAI Faucet"} subtitle="Swap Sepolia ETH for gDAI." />
|
||||||
|
<Container
|
||||||
|
style={{
|
||||||
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "calc(100vh - 153px)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box width="100%" maxWidth="476px" display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
||||||
|
<Paper
|
||||||
|
headerContent={
|
||||||
|
<Box alignItems="center" justifyContent="space-between" display="flex" width="100%">
|
||||||
|
<Typography variant="h4">Get {faucetToken.name}</Typography>
|
||||||
|
{!isSemiSmallScreen && <PrimaryButton
|
||||||
|
variant="text"
|
||||||
|
href={`${scanInfo.url}/token/${faucetToken.address}`}
|
||||||
|
>
|
||||||
|
Check on {scanInfo.name}
|
||||||
|
</PrimaryButton>}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
enableBackground
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<SwapCard
|
||||||
|
id={`faucet-sepolia-eth`}
|
||||||
|
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
||||||
|
tokenName={"ETH"}
|
||||||
|
token={<TokenStack tokens={["ETH"]} sx={{ fontSize: "21px" }} />}
|
||||||
|
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(balance.toString(), 4, "ETH")}`}
|
||||||
|
value={amount}
|
||||||
|
onChange={event => setAmount(event.currentTarget.value)}
|
||||||
|
inputProps={{ "data-testid": "fromInput" }}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
mb="20px"
|
||||||
|
mt="20px"
|
||||||
|
flexDirection="column"
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||||
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">ETH multiplier:</Typography>}
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(daiConversionRate, 2)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||||
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmount, 5, faucetToken.name)}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
|
||||||
|
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {faucetToken.name} balance:</Typography>}
|
||||||
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(daiBalance, 5, faucetToken.name)}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={
|
||||||
|
address !== "" && (
|
||||||
|
preparedAmount?.eq(new DecimalBigNumber(0, 18)) ||
|
||||||
|
balance?.lt(preparedAmount) ||
|
||||||
|
isPending
|
||||||
|
)
|
||||||
|
}
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => mintOrConnect()}
|
||||||
|
>
|
||||||
|
{address === "" ?
|
||||||
|
"Connect"
|
||||||
|
:
|
||||||
|
"Mint"
|
||||||
|
}
|
||||||
|
</PrimaryButton>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Faucet;
|
26
src/containers/NotFound/NotFound.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Box, Link, Typography } from "@mui/material";
|
||||||
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
|
import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
height="calc(100vh - 45px)"
|
||||||
|
width="100%"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Link href="https://ghostchain.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
<GhostStyledIcon
|
||||||
|
color="primary"
|
||||||
|
viewBox="0 0 385 372"
|
||||||
|
component={GhostIcon}
|
||||||
|
style={{ minWidth: "230px", minHeight: "230px", width: "230px" }}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Typography variant="h1">Page not found</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
90
src/containers/Stake/Stake.jsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import "./Stake.scss";
|
||||||
|
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
import { memo, useState, useEffect } from "react";
|
||||||
|
import { useChainId } from "wagmi";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import { StakeArea } from "./components/StakeArea";
|
||||||
|
|
||||||
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
|
import Modal from "../../components/Modal/Modal";
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
|
||||||
|
const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
|
||||||
|
const [action, setAction] = useState("STAKE");
|
||||||
|
const [upperToken, setUpperToken] = useState("FTSO");
|
||||||
|
const [bottomToken, setBottomToken] = useState("GHST");
|
||||||
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const setSettingsModalOpenInner = (value) => {
|
||||||
|
setSettingsModalOpen(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (true) {
|
||||||
|
case (upperToken === "FTSO" && bottomToken === "STNK"):
|
||||||
|
setAction("STAKE")
|
||||||
|
break;
|
||||||
|
case (upperToken === "FTSO" && bottomToken === "GHST"):
|
||||||
|
setAction("STAKE")
|
||||||
|
break;
|
||||||
|
case (upperToken === "STNK" && bottomToken === "FTSO"):
|
||||||
|
setAction("UNSTAKE")
|
||||||
|
break;
|
||||||
|
case (upperToken === "GHST" && bottomToken === "FTSO"):
|
||||||
|
setAction("UNSTAKE")
|
||||||
|
break;
|
||||||
|
case (upperToken === "STNK" && bottomToken === "GHST"):
|
||||||
|
setAction("WRAP")
|
||||||
|
break;
|
||||||
|
case (upperToken === "GHST" && bottomToken === "STNK"):
|
||||||
|
setAction("UNWRAP")
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setAction("STAKE")
|
||||||
|
}
|
||||||
|
}, [upperToken, bottomToken])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||||
|
<Typography variant="h4">{action}</Typography>
|
||||||
|
<TokenStack tokens={[upperToken, bottomToken]} />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
topLeft={
|
||||||
|
<GhostStyledIcon
|
||||||
|
viewBox="0 0 23 23"
|
||||||
|
component={SettingsIcon}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => setSettingsModalOpenInner(true)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
open={isOpened}
|
||||||
|
onClose={closeModal}
|
||||||
|
maxWidth="460px"
|
||||||
|
minHeight="200px"
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
||||||
|
<StakeArea
|
||||||
|
settingsModalOpen={settingsModalOpen}
|
||||||
|
closeSettingModal={() => setSettingsModalOpenInner(false)}
|
||||||
|
upperToken={upperToken}
|
||||||
|
setUpperToken={setUpperToken}
|
||||||
|
bottomToken={bottomToken}
|
||||||
|
setBottomToken={setBottomToken}
|
||||||
|
action={action}
|
||||||
|
chainId={chainId}
|
||||||
|
address={address}
|
||||||
|
connect={connect}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Stake);
|
0
src/containers/Stake/Stake.scss
Normal file
108
src/containers/Stake/StakeContainer.jsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { Dispatch, SetStateAction, useState, useEffect } from "react";
|
||||||
|
import { Box, Container, Grid, Divider, Typography, Link, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
|
import Countdown from "../../components/Countdown/Countdown";
|
||||||
|
import { PrimaryButton } from "../../components/Button";
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
|
import Stake from "./Stake";
|
||||||
|
import FarmPools from "./components/FarmPools";
|
||||||
|
import StakeInfoText from "./components/StakeInfoText";
|
||||||
|
import { ClaimsArea } from "./components/ClaimsArea";
|
||||||
|
import { Apy, CurrentIndex, TotalDeposit } from "./components/Metric";
|
||||||
|
|
||||||
|
import { useEpoch } from "../../hooks/staking";
|
||||||
|
|
||||||
|
export const StakeContainer = ({ chainId, address, connect }) => {
|
||||||
|
const [isModalOpened, handleModal] = useState(false);
|
||||||
|
|
||||||
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
|
const { epoch, refetch: refetchEpoch } = useEpoch(chainId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ReactGA.send({ hitType: "pageview", page: "/stake" });
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (isModalOpened) {
|
||||||
|
return (
|
||||||
|
<Stake
|
||||||
|
chainId={chainId}
|
||||||
|
address={address}
|
||||||
|
isOpened={isModalOpened}
|
||||||
|
connect={connect}
|
||||||
|
closeModal={() => handleModal(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box >
|
||||||
|
<PageTitle name="Protocol Staking" subtitle="Stake your FTSO to earn rebase yields" />
|
||||||
|
<Container
|
||||||
|
style={{
|
||||||
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
minHeight: "calc(100vh - 128px)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ mt: "15px" }}>
|
||||||
|
<Paper
|
||||||
|
fullWidth
|
||||||
|
enableBackground
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||||
|
<Typography variant="h6">
|
||||||
|
Rebase
|
||||||
|
<Countdown otherwise="Ready" targetDate={new Date(epoch.end * 1000)} />
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||||
|
<Apy distribute={epoch.distribute} chainId={chainId} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||||
|
<TotalDeposit chainId={chainId} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||||
|
<CurrentIndex chainId={chainId} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Divider sx={{ marginTop: "30px" }} />
|
||||||
|
<ClaimsArea chainId={chainId} address={address} epoch={epoch} />
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box mt="15px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
||||||
|
<PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => handleModal(true)}
|
||||||
|
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
||||||
|
>
|
||||||
|
Start staking
|
||||||
|
</PrimaryButton>
|
||||||
|
<Box textAlign="center" mt="15px">
|
||||||
|
<StakeInfoText />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<FarmPools chainId={chainId} />
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StakeContainer;
|