Compare commits
No commits in common. "main" and "dao-governance" have entirely different histories.
main
...
dao-govern
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ghost-dao-interface",
|
"name": "ghost-dao-interface",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.49",
|
"version": "0.5.20",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
99
src/App.jsx
@ -5,27 +5,18 @@ import CssBaseline from "@mui/material/CssBaseline";
|
|||||||
import { styled, ThemeProvider } from "@mui/material/styles";
|
import { styled, ThemeProvider } from "@mui/material/styles";
|
||||||
import { lazy, Suspense, useCallback, useEffect, useState } from "react";
|
import { lazy, Suspense, useCallback, useEffect, useState } from "react";
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
import toast, { Toaster } from "react-hot-toast";
|
||||||
import { Outlet, Navigate, Route, Routes, useLocation, useParams } from "react-router-dom";
|
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||||
import {
|
import { useAccount, useConnect, useChainId, useConfig, usePublicClient, injected } from "wagmi";
|
||||||
useAccount,
|
|
||||||
useConnect,
|
|
||||||
useChainId,
|
|
||||||
useConfig,
|
|
||||||
usePublicClient,
|
|
||||||
useSwitchChain,
|
|
||||||
injected
|
|
||||||
} from "wagmi";
|
|
||||||
import { watchChainId } from '@wagmi/core'
|
|
||||||
|
|
||||||
import Messages from "./components/Messages/Messages";
|
import Messages from "./components/Messages/Messages";
|
||||||
import NavDrawer from "./components/Sidebar/NavDrawer";
|
import NavDrawer from "./components/Sidebar/NavDrawer";
|
||||||
import Sidebar from "./components/Sidebar/Sidebar";
|
import Sidebar from "./components/Sidebar/Sidebar";
|
||||||
import TopBar from "./components/TopBar/TopBar";
|
import TopBar from "./components/TopBar/TopBar";
|
||||||
import BreakoutModal from "./containers/Breakout/BreakoutModal";
|
|
||||||
|
|
||||||
import { shouldTriggerSafetyCheck } from "./helpers";
|
import { shouldTriggerSafetyCheck } from "./helpers";
|
||||||
import { isNetworkAvailable } from "./constants";
|
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "./constants";
|
||||||
import useTheme from "./hooks/useTheme";
|
import useTheme from "./hooks/useTheme";
|
||||||
|
import { useUnstableProvider } from "./hooks/ghost";
|
||||||
import { dark as darkTheme } from "./themes/dark.js";
|
import { dark as darkTheme } from "./themes/dark.js";
|
||||||
import { girth as gTheme } from "./themes/girth.js";
|
import { girth as gTheme } from "./themes/girth.js";
|
||||||
import { light as lightTheme } from "./themes/light.js";
|
import { light as lightTheme } from "./themes/light.js";
|
||||||
@ -35,6 +26,8 @@ const Bonds = lazy(() => import("./containers/Bond/Bonds"));
|
|||||||
const BondModalContainer = lazy(() => import("./containers/Bond/BondModal"));
|
const BondModalContainer = lazy(() => import("./containers/Bond/BondModal"));
|
||||||
const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
|
const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
|
||||||
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
|
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
|
||||||
|
const Faucet = lazy(() => import("./containers/Faucet/Faucet"));
|
||||||
|
const Wrapper = lazy(() => import("./containers/WethWrapper/WethWrapper"));
|
||||||
const Dex = lazy(() => import("./containers/Dex/Dex"));
|
const Dex = lazy(() => import("./containers/Dex/Dex"));
|
||||||
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
|
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
|
||||||
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
|
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
|
||||||
@ -107,15 +100,16 @@ function App() {
|
|||||||
const [theme, toggleTheme] = useTheme();
|
const [theme, toggleTheme] = useTheme();
|
||||||
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const { chains } = useSwitchChain();
|
|
||||||
const { connect, error: errorMessage } = useConnect();
|
const { connect, error: errorMessage } = useConnect();
|
||||||
const tryConnectInjected = () => connect({ connector: injected() });
|
const tryConnectInjected = () => connect({ connector: injected() });
|
||||||
|
|
||||||
|
const bondIndexes = [];
|
||||||
|
|
||||||
const [wrongNetworkToastId, setWrongNetworkToastId] = useState(null);
|
const [wrongNetworkToastId, setWrongNetworkToastId] = useState(null);
|
||||||
const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
|
const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|
||||||
const { address = "", chainId: addressChainId, status, isConnected, isReconnecting } = useAccount();
|
const { address = "", chainId: addressChainId, isConnected, isReconnecting } = useAccount();
|
||||||
|
|
||||||
const provider = usePublicClient();
|
const provider = usePublicClient();
|
||||||
const chainId = useChainId();
|
const chainId = useChainId();
|
||||||
@ -123,6 +117,24 @@ function App() {
|
|||||||
const isSmallerScreen = useMediaQuery("(max-width: 1047px)");
|
const isSmallerScreen = useMediaQuery("(max-width: 1047px)");
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 600px)");
|
const isSmallScreen = useMediaQuery("(max-width: 600px)");
|
||||||
|
|
||||||
|
const {
|
||||||
|
providerDetail,
|
||||||
|
providerDetails,
|
||||||
|
connectProviderDetail
|
||||||
|
} = useUnstableProvider()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// TODO: make sure we are using correct extension
|
||||||
|
const maybeProvider = providerDetails?.find(obj => obj.info.rdns === "io.ghostchain.GhostWalletExtension")
|
||||||
|
if (maybeProvider && !providerDetail) {
|
||||||
|
try {
|
||||||
|
connectProviderDetail(maybeProvider)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [providerDetail, providerDetails, connectProviderDetail])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldTriggerSafetyCheck()) {
|
if (shouldTriggerSafetyCheck()) {
|
||||||
toast.success("Safety Check: Always verify you're on app.dao.ghostchain.io!", { duration: 5000 });
|
toast.success("Safety Check: Always verify you're on app.dao.ghostchain.io!", { duration: 5000 });
|
||||||
@ -131,17 +143,17 @@ function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isConnected && chainId !== addressChainId) {
|
if (isConnected && chainId !== addressChainId) {
|
||||||
if (wrongNetworkToastId === null) {
|
const toastId = toast.loading("You are connected to wrong network. Use one of the deployed networks please.", {
|
||||||
const toastId = toast.loading("You are connected to wrong network. Use one of the deployed networks please.", {
|
position: 'bottom-right'
|
||||||
position: 'bottom-right'
|
});
|
||||||
});
|
setWrongNetworkToastId(toastId);
|
||||||
setWrongNetworkToastId(toastId);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
toast.dismiss(wrongNetworkToastId);
|
if (wrongNetworkToastId) {
|
||||||
setWrongNetworkToastId(null);
|
toast.dismiss(wrongNetworkToastId);
|
||||||
|
setWrongNetworkToastId(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [chainId, addressChainId, isConnected, wrongNetworkToastId])
|
}, [chainId, addressChainId, isConnected])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
@ -158,6 +170,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme;
|
const themeMode = theme === "light" ? lightTheme : theme === "dark" ? darkTheme : gTheme;
|
||||||
|
|
||||||
const chainExists = isNetworkAvailable(chainId, addressChainId);
|
const chainExists = isNetworkAvailable(chainId, addressChainId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -174,7 +187,6 @@ function App() {
|
|||||||
wrongNetworkToastId={wrongNetworkToastId}
|
wrongNetworkToastId={wrongNetworkToastId}
|
||||||
setWrongNetworkToastId={setWrongNetworkToastId}
|
setWrongNetworkToastId={setWrongNetworkToastId}
|
||||||
address={address}
|
address={address}
|
||||||
status={status}
|
|
||||||
chainId={addressChainId ? addressChainId : chainId}
|
chainId={addressChainId ? addressChainId : chainId}
|
||||||
chainExists={chainExists}
|
chainExists={chainExists}
|
||||||
handleDrawerToggle={handleDrawerToggle}
|
handleDrawerToggle={handleDrawerToggle}
|
||||||
@ -190,21 +202,24 @@ function App() {
|
|||||||
|
|
||||||
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
|
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
|
||||||
<Suspense fallback={<div></div>}>
|
<Suspense fallback={<div></div>}>
|
||||||
<BreakoutModal chainId={chainId} address={address} />
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to={chainExists ? `/${chains.at(0).name.toLowerCase()}/dashboard` : "/empty"} />} />
|
<Route path="/" element={<Navigate to={chainExists ? "/dashboard" : "/empty"} />} />
|
||||||
{chainExists &&
|
{chainExists &&
|
||||||
<Route path="/:network" element={<AvailableNetworkGuard allowedNetworks={chains.map(chain => chain.name.toLowerCase())} /> }>
|
<>
|
||||||
<Route path="dashboard" element={<TreasuryDashboard chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/dashboard" element={<TreasuryDashboard chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
<Route path="bonds" element={<Bonds connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/bonds" element={<Bonds connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
<Route path="bonds/:id" element={<BondModalContainer config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/bonds/:id" element={<BondModalContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
<Route path="stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId}/>} />
|
||||||
<Route path="bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
{isNetworkLegacy(chainId)
|
||||||
<Route path="dex/:name" element={<Dex config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
? <Route path="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
<Route path="governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
: <Route path="/wrapper" element={<Wrapper config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
<Route path="governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
}
|
||||||
<Route path="governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
</Route>
|
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
|
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||||
|
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||||
|
{isGovernanceAvailable(chainId, addressChainId) && <Route path="/governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
<Route path="/empty" element={<NotFound
|
<Route path="/empty" element={<NotFound
|
||||||
wrongNetworkToastId={wrongNetworkToastId}
|
wrongNetworkToastId={wrongNetworkToastId}
|
||||||
@ -227,12 +242,4 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AvailableNetworkGuard = ({ allowedNetworks }) => {
|
|
||||||
const { network } = useParams();
|
|
||||||
if (!allowedNetworks.includes(network)) {
|
|
||||||
return <Navigate to="/empty" replace />;
|
|
||||||
}
|
|
||||||
return <Outlet />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
{"abi":[{"inputs":[{"internalType":"address","name":"_staking","type":"address"},{"internalType":"uint256","name":"_ghostedSupply","type":"uint256"},{"internalType":"uint256","name":"_existential","type":"uint256"},{"internalType":"uint48","name":"_deployedAt","type":"uint48"},{"internalType":"uint104","name":"_amountIn","type":"uint104"},{"internalType":"uint104","name":"_amountOut","type":"uint104"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyExecuted","type":"error"},{"inputs":[],"name":"NonExistentAmount","type":"error"},{"inputs":[],"name":"NotStaking","type":"error"},{"inputs":[],"name":"WrongSignature","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"receiver","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Ghosted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Materialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"aggregatedPublicKey","type":"uint256"}],"name":"Rotated","type":"event"},{"inputs":[],"name":"DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"existentialDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"receiver","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ghost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ghostedSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rx","type":"uint256"},{"internalType":"uint256","name":"s","type":"uint256"}],"name":"materialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"metadata","outputs":[{"components":[{"internalType":"uint48","name":"deployedAt","type":"uint48"},{"internalType":"uint104","name":"amountIn","type":"uint104"},{"internalType":"uint104","name":"amountOut","type":"uint104"}],"internalType":"struct IGatekeeperMetadata.GatekeeperMetadata","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"aggregatedPublicKey","type":"uint256"},{"internalType":"uint256","name":"rx","type":"uint256"},{"internalType":"uint256","name":"s","type":"uint256"}],"name":"rotate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"staking","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg id="uuid-61b3c585-44d6-4fd8-a0e7-6a5e76478eb1" data-name="uuid-70ba15fb-c44e-4945-a02a-8d07f94a9abb" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 513 513">
|
<svg id="uuid-61b3c585-44d6-4fd8-a0e7-6a5e76478eb1" data-name="uuid-70ba15fb-c44e-4945-a02a-8d07f94a9abb" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 505 505">
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
.uuid-6437a6f0-42e0-4c9c-b0ef-b632253e989a {
|
.uuid-6437a6f0-42e0-4c9c-b0ef-b632253e989a {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 254 254">
|
<svg id="uuid-39ebc51d-db02-45d2-ad4b-e7ae31bb915b" data-name="Ethereum" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
.uuid-e080659c-91a4-4965-9905-603394020b2c {
|
.uuid-e080659c-91a4-4965-9905-603394020b2c {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg id="uuid-cf283373-2eb4-44d0-88d2-93533a7f4b1d" data-name="eGHST" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 254 254">
|
<svg id="uuid-cf283373-2eb4-44d0-88d2-93533a7f4b1d" data-name="eGHST" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
.uuid-26b0e53c-73c9-4369-9725-1cee728298be {
|
.uuid-26b0e53c-73c9-4369-9725-1cee728298be {
|
||||||
@ -21,4 +21,4 @@
|
|||||||
<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="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"/>
|
<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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 258.751 258.751">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 254.751 254.751">
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
.uuid-67f42f03-098b-49dc-a7ed-dc13bd713387 {
|
.uuid-67f42f03-098b-49dc-a7ed-dc13bd713387 {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg id="uuid-0fc887e6-719b-4e51-bef8-e5c269508f6b" data-name="sGHST" xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 254 254">
|
<svg id="uuid-0fc887e6-719b-4e51-bef8-e5c269508f6b" data-name="sGHST" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
.uuid-a205ebe2-7c8f-434c-9bde-29ac218716be {
|
.uuid-a205ebe2-7c8f-434c-9bde-29ac218716be {
|
||||||
@ -84,4 +84,4 @@
|
|||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@ -9,12 +9,12 @@ const classes = {
|
|||||||
|
|
||||||
const StyledMuiChip = styled(MuiChip, {
|
const StyledMuiChip = styled(MuiChip, {
|
||||||
shouldForwardProp: prop => prop !== "template" && prop !== "strong",
|
shouldForwardProp: prop => prop !== "template" && prop !== "strong",
|
||||||
})(({ theme, template, background, strong }) => {
|
})(({ theme, template, strong }) => {
|
||||||
return {
|
return {
|
||||||
[`&.${classes.chip}`]: {
|
[`&.${classes.chip}`]: {
|
||||||
height: "21px",
|
height: "21px",
|
||||||
borderRadius: "16px",
|
borderRadius: "16px",
|
||||||
backgroundColor: background ? background : template
|
backgroundColor: template
|
||||||
? template === "purple"
|
? template === "purple"
|
||||||
? theme.colors.special["olyZaps"]
|
? theme.colors.special["olyZaps"]
|
||||||
: template === "gray"
|
: template === "gray"
|
||||||
@ -42,8 +42,8 @@ const StyledMuiChip = styled(MuiChip, {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const Chip = ({ background, template, strong = false, ...props }) => {
|
const Chip = ({ template, strong = false, ...props }) => {
|
||||||
return <StyledMuiChip className={classes.chip} background={background} template={template} strong={strong} {...props} />;
|
return <StyledMuiChip className={classes.chip} template={template} strong={strong} {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Chip;
|
export default Chip;
|
||||||
|
|||||||
@ -118,19 +118,19 @@ const NavItem = ({
|
|||||||
const match = currentLocation.pathname === to || currentLocation.pathname === `/${to}`;
|
const match = currentLocation.pathname === to || currentLocation.pathname === `/${to}`;
|
||||||
|
|
||||||
const linkProps = props.href
|
const linkProps = props.href
|
||||||
? {
|
? {
|
||||||
href: props.href,
|
href: props.href,
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
className: `external-site-link ${className}`,
|
className: `external-site-link ${className}`,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
component: NavLink,
|
component: NavLink,
|
||||||
to: to,
|
to: to,
|
||||||
className: `button-dapp-menu ${className}`,
|
className: `button-dapp-menu ${className}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const LinkItem = () => (
|
const LinkItem = () => (
|
||||||
<Link {...linkProps} {...props} underline="hover" onClick={(e) => e.stopPropagation()}>
|
<Link {...linkProps} {...props} underline="hover">
|
||||||
<Box
|
<Box
|
||||||
sx={{ fontFamily: "Ubuntu" }}
|
sx={{ fontFamily: "Ubuntu" }}
|
||||||
display="flex"
|
display="flex"
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const LinearProgressBar = (props) => {
|
|||||||
{props.target && <Box
|
{props.target && <Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: `${Math.min(Math.max(props.target, 0), 100)}%`,
|
left: `${props.target}%`,
|
||||||
top: props.targetTop || 0,
|
top: props.targetTop || 0,
|
||||||
bottom: props.targetBottom || 0,
|
bottom: props.targetBottom || 0,
|
||||||
width: props.targetWidth || "2px",
|
width: props.targetWidth || "2px",
|
||||||
|
|||||||
@ -34,7 +34,6 @@ const Select = ({
|
|||||||
inputWidth,
|
inputWidth,
|
||||||
renderValue = null,
|
renderValue = null,
|
||||||
width = "100%",
|
width = "100%",
|
||||||
maxWidth = "100%"
|
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
@ -42,7 +41,6 @@ const Select = ({
|
|||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
width={width}
|
width={width}
|
||||||
maxWidth={maxWidth}
|
|
||||||
sx={{ backgroundColor: theme.colors.gray[750] }}
|
sx={{ backgroundColor: theme.colors.gray[750] }}
|
||||||
borderRadius="12px"
|
borderRadius="12px"
|
||||||
padding="15px"
|
padding="15px"
|
||||||
@ -61,15 +59,6 @@ const Select = ({
|
|||||||
IconComponent={KeyboardArrowDownIcon}
|
IconComponent={KeyboardArrowDownIcon}
|
||||||
renderValue={renderValue}
|
renderValue={renderValue}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
MenuProps={{
|
|
||||||
PaperProps: {
|
|
||||||
sx: {
|
|
||||||
maxHeight: 160,
|
|
||||||
overflowY: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
disableScrollLock: true,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{options.map((opt) => (
|
{options.map((opt) => (
|
||||||
<MenuItem key={opt.value} value={opt.value}>
|
<MenuItem key={opt.value} value={opt.value}>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useEffect } from "react";
|
import React from "react";
|
||||||
|
|
||||||
import "./Sidebar.scss";
|
import "./Sidebar.scss";
|
||||||
|
|
||||||
@ -16,7 +16,6 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { useSwitchChain } from "wagmi";
|
|
||||||
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
@ -27,7 +26,7 @@ import HubIcon from '@mui/icons-material/Hub';
|
|||||||
import PublicIcon from '@mui/icons-material/Public';
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||||
import GavelIcon from '@mui/icons-material/Gavel';
|
import GavelIcon from '@mui/icons-material/Gavel';
|
||||||
import CasinoIcon from '@mui/icons-material/Casino';
|
import ForumIcon from '@mui/icons-material/Forum';
|
||||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
||||||
import BookIcon from '@mui/icons-material/Book';
|
import BookIcon from '@mui/icons-material/Book';
|
||||||
import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange';
|
import CurrencyExchangeIcon from '@mui/icons-material/CurrencyExchange';
|
||||||
@ -40,20 +39,19 @@ import BondIcon from "../Icon/BondIcon";
|
|||||||
import StakeIcon from "../Icon/StakeIcon";
|
import StakeIcon from "../Icon/StakeIcon";
|
||||||
import WrapIcon from "../Icon/WrapIcon";
|
import WrapIcon from "../Icon/WrapIcon";
|
||||||
|
|
||||||
import { isNetworkAvailable } from "../../constants";
|
import { isNetworkAvailable, isNetworkLegacy, isGovernanceAvailable } from "../../constants";
|
||||||
import { AVAILABLE_DEXES } from "../../constants/dexes";
|
import { AVAILABLE_DEXES } from "../../constants/dexes";
|
||||||
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
|
|
||||||
import { ECOSYSTEM } from "../../constants/ecosystem";
|
import { ECOSYSTEM } from "../../constants/ecosystem";
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { sortBondsByDiscount, formatCurrency } from "../../helpers";
|
import { sortBondsByDiscount, formatCurrency } from "../../helpers";
|
||||||
import BondDiscount from "../../containers/Bond/components/BondDiscount";
|
import BondDiscount from "../../containers/Bond/components/BondDiscount";
|
||||||
import Chip from "../Chip/Chip";
|
|
||||||
|
|
||||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
import ShowerIcon from '@mui/icons-material/Shower';
|
import ShowerIcon from '@mui/icons-material/Shower';
|
||||||
import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup';
|
import WifiProtectedSetupIcon from '@mui/icons-material/WifiProtectedSetup';
|
||||||
|
|
||||||
import { useTokenSymbol } from "../../hooks/tokens";
|
import { useTokenSymbol } from "../../hooks/tokens";
|
||||||
|
import { useFtsoPrice, useGhstPrice, useGhostedSupplyPrice } from "../../hooks/prices";
|
||||||
import { useLiveBonds } from "../../hooks/bonds/index";
|
import { useLiveBonds } from "../../hooks/bonds/index";
|
||||||
import pckg from "../../../package.json"
|
import pckg from "../../../package.json"
|
||||||
|
|
||||||
@ -70,22 +68,14 @@ const StyledBox = styled(Box)(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const NavContent = ({ chainId, addressChainId }) => {
|
const NavContent = ({ chainId, addressChainId }) => {
|
||||||
const { chains } = useSwitchChain();
|
|
||||||
const chainName = useMemo(() => {
|
|
||||||
return chains.find(chain => chain.id === chainId).name.toLowerCase();
|
|
||||||
}, [chains, chainId, addressChainId])
|
|
||||||
|
|
||||||
const { liveBonds: ghostBonds } = useLiveBonds(chainId);
|
const { liveBonds: ghostBonds } = useLiveBonds(chainId);
|
||||||
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
const ghstPrice = useGhstPrice(chainId);
|
||||||
|
const ghostedSupplyPrice = useGhostedSupplyPrice(chainId);
|
||||||
|
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
const sortedGhostBonds = useMemo(() => sortBondsByDiscount(ghostBonds), [ghostBonds]);
|
|
||||||
const bridgeNumbers = useMemo(() => {
|
|
||||||
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
|
|
||||||
const number = 1 + connectedNetworks * 3;
|
|
||||||
return `(${number}, ${number})`;
|
|
||||||
}, [chainId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="dapp-sidebar">
|
<Paper className="dapp-sidebar">
|
||||||
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
|
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
|
||||||
@ -102,6 +92,17 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
Version {pckg.version}
|
Version {pckg.version}
|
||||||
</Box>
|
</Box>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Box display="flex" flexDirection="column" mt="10px">
|
||||||
|
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
|
||||||
|
{ftsoSymbol} Price: {formatCurrency(ftsoPrice, 2)}
|
||||||
|
</Box>
|
||||||
|
<Box fontSize="12px" fontWeight="500" lineHeight="15px">
|
||||||
|
{ghstSymbol} Price: {formatCurrency(ghstPrice, 2)}
|
||||||
|
</Box>
|
||||||
|
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
|
||||||
|
GHOSTed Supply: {formatCurrency(ghostedSupplyPrice, 2)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="menu-divider">
|
<Box className="menu-divider">
|
||||||
@ -112,18 +113,58 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
<div className="dapp-nav" id="navbarNav">
|
<div className="dapp-nav" id="navbarNav">
|
||||||
{isNetworkAvailable(chainId, addressChainId) &&
|
{isNetworkAvailable(chainId, addressChainId) &&
|
||||||
<>
|
<>
|
||||||
<NavItem icon={DashboardIcon} label={`Dashboard`} to={`/${chainName}/dashboard`} />
|
<NavItem icon={DashboardIcon} label={`Dashboard`} to="/dashboard" />
|
||||||
|
{isNetworkLegacy(chainId)
|
||||||
|
? <NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
|
||||||
|
: <NavItem icon={WifiProtectedSetupIcon} label={`Wrapper`} to="/wrapper" />
|
||||||
|
}
|
||||||
|
<NavItem
|
||||||
|
defaultExpanded
|
||||||
|
icon={BondIcon}
|
||||||
|
label={`Bond`}
|
||||||
|
to="/bonds"
|
||||||
|
children={
|
||||||
|
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
component={NavLink}
|
||||||
|
to={`/bonds/${bond.id}`}
|
||||||
|
key={index}
|
||||||
|
style={{ textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
<Box mb="10px" display="flex" justifyContent="end">
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
width: "180px",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px"
|
||||||
|
}}
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
>
|
||||||
|
{bond.displayName}
|
||||||
|
<BondDiscount textOnly discount={bond.discount} />
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AccordionDetails>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<NavItem
|
<NavItem
|
||||||
icon={CurrencyExchangeIcon}
|
icon={CurrencyExchangeIcon}
|
||||||
label={`(3, 3) Swap`}
|
label={`Dex`}
|
||||||
to={`/${chainName}/dex/uniswap`}
|
to={'/dex/uniswap'}
|
||||||
children={
|
children={
|
||||||
AVAILABLE_DEXES[chainId].length > 1 && <AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
<AccordionDetails style={{ margin: "0 0 -10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
{AVAILABLE_DEXES[chainId].map((dex, index) => {
|
{AVAILABLE_DEXES[chainId].map((dex, index) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
component={NavLink}
|
component={NavLink}
|
||||||
to={`/${chainName}/dex/${dex.name.toLowerCase()}`}
|
to={`/dex/${dex.name.toLowerCase()}`}
|
||||||
key={index}
|
key={index}
|
||||||
style={{ textDecoration: "none" }}
|
style={{ textDecoration: "none" }}
|
||||||
>
|
>
|
||||||
@ -137,54 +178,11 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NavItem icon={StakeIcon} label={`(3, 3) Stake`} to={`/${chainName}/stake`} />
|
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
|
||||||
<NavItem
|
<NavItem icon={ForkRightIcon} label={`Bridge`} to="/bridge" />
|
||||||
defaultExpanded
|
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to="/governance" />}
|
||||||
icon={BondIcon}
|
|
||||||
label={`(1, 1) Bond`}
|
|
||||||
to={`/${chainName}/bonds`}
|
|
||||||
children={
|
|
||||||
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
|
||||||
{sortedGhostBonds.length > 0 && <Box width="180px" mb="10px" ml="auto">
|
|
||||||
<Typography component="span" variant="body2">Bond Discounts</Typography>
|
|
||||||
</Box>}
|
|
||||||
{sortedGhostBonds.map((bond, index) => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
component={NavLink}
|
|
||||||
to={`/${chainName}/bonds/${bond.id}`}
|
|
||||||
key={index}
|
|
||||||
style={{ textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
<Box mb="10px" display="flex" justifyContent="end">
|
|
||||||
<Typography
|
|
||||||
style={{
|
|
||||||
width: "180px",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
gap: "10px"
|
|
||||||
}}
|
|
||||||
component="span"
|
|
||||||
variant="body2"
|
|
||||||
>
|
|
||||||
{bond.displayName}
|
|
||||||
{bond.isSoldOut
|
|
||||||
? <Chip label="Sold Out" template="darkGray" />
|
|
||||||
: <BondDiscount discount={bond.discount} />
|
|
||||||
}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</AccordionDetails>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<NavItem icon={ForkRightIcon} label={`${bridgeNumbers} Stake\u00B2`} to={`/${chainName}/bridge`} />
|
|
||||||
<NavItem icon={GavelIcon} label={`Governance`} to={`/${chainName}/governance`} />
|
|
||||||
<Box className="menu-divider">
|
<Box className="menu-divider">
|
||||||
<Divider />
|
<Divider />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -193,9 +191,10 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<NavItem href="https://ghostchain.io/game-theory-3-3" icon={CasinoIcon} label={`${bridgeNumbers} Game Theory`} />
|
|
||||||
<NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} />
|
<NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} />
|
||||||
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Documentation`} />
|
<NavItem href="http://ghostchain.io/builders" icon={ForumIcon} label={`Forum`} />
|
||||||
|
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Docs`} />
|
||||||
|
<NavItem href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts/issues" icon={ErrorOutlineIcon} label={`Git Issues`} />
|
||||||
<StyledBox display="flex" justifyContent="space-around" paddingY="24px">
|
<StyledBox display="flex" justifyContent="space-around" paddingY="24px">
|
||||||
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">
|
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">
|
||||||
<GhostStyledIcon viewBox="0 0 24 24" component={GitHubIcon} className={classes.gray} />
|
<GhostStyledIcon viewBox="0 0 24 24" component={GitHubIcon} className={classes.gray} />
|
||||||
|
|||||||
@ -86,8 +86,8 @@ const Token = ({ chainTokenName, name, viewBox = "0 0 260 260", fontSize = "larg
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
marginLeft: "70%",
|
marginLeft: "70%",
|
||||||
marginTop: "45%",
|
marginTop: "45%",
|
||||||
width: "45%",
|
width: "65%",
|
||||||
height: "45%",
|
height: "65%",
|
||||||
border: "1px solid #fff",
|
border: "1px solid #fff",
|
||||||
borderRadius: "100%"
|
borderRadius: "100%"
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -58,7 +58,6 @@ export const TokenAllowanceGuard = ({
|
|||||||
height = "auto",
|
height = "auto",
|
||||||
spendAmount,
|
spendAmount,
|
||||||
tokenName,
|
tokenName,
|
||||||
isNative,
|
|
||||||
owner,
|
owner,
|
||||||
spender,
|
spender,
|
||||||
decimals,
|
decimals,
|
||||||
@ -89,7 +88,7 @@ export const TokenAllowanceGuard = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNative && allowance && spendAmount && allowance.lt(spendAmount))
|
if (allowance && spendAmount && allowance.lt(spendAmount))
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems="center">
|
<Grid container alignItems="center">
|
||||||
<Grid item xs={12} sm={isVertical ? 12 : 8}>
|
<Grid item xs={12} sm={isVertical ? 12 : 8}>
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
import { Box, Typography, SvgIcon, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { parseKnownToken } from "../../components/Token/Token";
|
|
||||||
import { useUnstableProvider } from "../../hooks/ghost";
|
|
||||||
import { PrimaryButton } from "../Button"
|
|
||||||
|
|
||||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
|
||||||
|
|
||||||
function GhostChainSelect({ small }) {
|
|
||||||
const { providerDetail, isConnected } = useUnstableProvider();
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
if (providerDetail) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
height="39px"
|
|
||||||
width={small ? "100px" : "155px"}
|
|
||||||
borderRadius="4px"
|
|
||||||
padding="0 14px"
|
|
||||||
border="1px solid #50759e"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
flexDirection="row"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
>
|
|
||||||
<Box display="flex" flexDirection="row">
|
|
||||||
<SvgIcon component={parseKnownToken("CSPR")} inheritViewBox />
|
|
||||||
{!small && <Typography sx={{ paddingLeft: "6px"}}>CASPER</Typography>}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width="21px"
|
|
||||||
height="21px"
|
|
||||||
backgroundColor={isConnected ? theme.colors.feedback.success : theme.colors.feedback.error}
|
|
||||||
borderRadius="50%"
|
|
||||||
></Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box height="39px" width={small ? "100px" : "155px"}>
|
|
||||||
<PrimaryButton
|
|
||||||
sx={{ margin: "0 !important", padding: "0 !important" }}
|
|
||||||
onClick={() => window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
Get GHOST {small ? "" : "Connect"}
|
|
||||||
</PrimaryButton>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GhostChainSelect;
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import { Box, Typography, SvgIcon, useTheme } from "@mui/material";
|
import { Box, Typography, SvgIcon, useTheme } from "@mui/material";
|
||||||
|
|
||||||
@ -12,62 +12,28 @@ import { isNetworkAvailable } from "../../constants";
|
|||||||
import { parseKnownToken } from "../../components/Token/Token";
|
import { parseKnownToken } from "../../components/Token/Token";
|
||||||
|
|
||||||
import { useSwitchChain } from 'wagmi';
|
import { useSwitchChain } from 'wagmi';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small, status }) {
|
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
|
||||||
const hasAttemptedSwitch = useRef(false);
|
|
||||||
|
|
||||||
const { chains, switchChain } = useSwitchChain();
|
const { chains, switchChain } = useSwitchChain();
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
const [networkId, setNetworkId] = useState(chainId);
|
const [networkId, setNetworkId] = useState(chainId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'reconnecting' || status === 'connecting') return;
|
if (chainId !== networkId) setNetworkId(chainId);
|
||||||
|
}, [chainId])
|
||||||
|
|
||||||
const parts = pathname.split('/');
|
const handleChange = (event) => {
|
||||||
if (!hasAttemptedSwitch.current && parts.length > 2) {
|
const chainName = chains.find((chain) => chain.id === event.target.value).name;
|
||||||
let targetChain = chains.at(0);
|
|
||||||
const chainName = parts[1].toLowerCase();
|
|
||||||
const chain = chains.find(chain => chain.name.toLowerCase() === chainName);
|
|
||||||
if (chain && targetChain.name !== chain.name) {
|
|
||||||
targetChain = chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasAttemptedSwitch.current = true;
|
|
||||||
changeNetworkId(targetChain.name, targetChain.id);
|
|
||||||
}
|
|
||||||
}, [status, chains, pathname]);
|
|
||||||
|
|
||||||
const changeNetworkId = (chainName, newNetworkId) => {
|
|
||||||
toast.dismiss(wrongNetworkToastId);
|
toast.dismiss(wrongNetworkToastId);
|
||||||
const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, {
|
const toastId = toast.loading(`Trying to connect to ${chainName} network... Wait please`, {
|
||||||
position: 'bottom-right'
|
position: 'bottom-right'
|
||||||
});
|
});
|
||||||
setWrongNetworkToastId(toastId);
|
setWrongNetworkToastId(toastId);
|
||||||
|
|
||||||
setNetworkId(newNetworkId);
|
setNetworkId(event.target.value);
|
||||||
switchChain({
|
switchChain({ chainId: event.target.value });
|
||||||
chainId: newNetworkId
|
|
||||||
}, {
|
|
||||||
onSuccess: (data) => console.log("Network switched to", data.name),
|
|
||||||
onError: (error) => console.log("Error during network switching", error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (event) => {
|
|
||||||
const chainName = chains.find((chain) => chain.id === event.target.value).name;
|
|
||||||
changeNetworkId(chainName, event.target.value);
|
|
||||||
|
|
||||||
const parts = pathname.split('/');
|
|
||||||
if (parts.length > 2) {
|
|
||||||
parts[1] = chainName.toLowerCase();
|
|
||||||
const newPath = parts.join("/");
|
|
||||||
navigate(newPath, { replace: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return(
|
return(
|
||||||
@ -76,7 +42,6 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
|
|||||||
labelId="network-select-helper-label"
|
labelId="network-select-helper-label"
|
||||||
id="network-select-helper"
|
id="network-select-helper"
|
||||||
value={networkId}
|
value={networkId}
|
||||||
disabled={!hasAttemptedSwitch.current}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
sx={{
|
sx={{
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
@ -92,7 +57,7 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
|
|||||||
return (
|
return (
|
||||||
<MenuItem key={chain.name} value={chain.id}>
|
<MenuItem key={chain.name} value={chain.id}>
|
||||||
<Box gap="10px" display="flex" flexDirection="row" alignItems="center">
|
<Box gap="10px" display="flex" flexDirection="row" alignItems="center">
|
||||||
<SvgIcon sx={{ width: 22 }} component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox />
|
<SvgIcon component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox />
|
||||||
{!small && <Typography>{chain.name}</Typography>}
|
{!small && <Typography>{chain.name}</Typography>}
|
||||||
</Box>
|
</Box>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import MenuIcon from "../../assets/icons/hamburger.svg?react";
|
|||||||
import Wallet from "./Wallet"
|
import Wallet from "./Wallet"
|
||||||
import SelectNetwork from "./SelectNetwork";
|
import SelectNetwork from "./SelectNetwork";
|
||||||
|
|
||||||
|
|
||||||
const PREFIX = "TopBar";
|
const PREFIX = "TopBar";
|
||||||
|
|
||||||
const classes = {
|
const classes = {
|
||||||
@ -17,7 +18,6 @@ function TopBar({
|
|||||||
address,
|
address,
|
||||||
wrongNetworkToastId,
|
wrongNetworkToastId,
|
||||||
connect,
|
connect,
|
||||||
status,
|
|
||||||
handleDrawerToggle,
|
handleDrawerToggle,
|
||||||
setWrongNetworkToastId
|
setWrongNetworkToastId
|
||||||
}) {
|
}) {
|
||||||
@ -33,19 +33,12 @@ function TopBar({
|
|||||||
marginRight={desktop ? "33px" : "0px"}
|
marginRight={desktop ? "33px" : "0px"}
|
||||||
>
|
>
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box
|
<Box display="flex" justifyContent="space-between" alignItems="center" width={small ? "calc(100vw - 78px)" : "320px"}>
|
||||||
display="flex"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
width={small ? "calc(100vw - 78px)" : "320px"}
|
|
||||||
height="40px"
|
|
||||||
>
|
|
||||||
<SelectNetwork
|
<SelectNetwork
|
||||||
wrongNetworkToastId={wrongNetworkToastId}
|
wrongNetworkToastId={wrongNetworkToastId}
|
||||||
setWrongNetworkToastId={setWrongNetworkToastId}
|
setWrongNetworkToastId={setWrongNetworkToastId}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
small={small}
|
small={small}
|
||||||
status={status}
|
|
||||||
/>
|
/>
|
||||||
<Wallet address={address} connect={connect} chainId={chainId} />
|
<Wallet address={address} connect={connect} chainId={chainId} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -13,9 +12,8 @@ import {
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import LoopIcon from '@mui/icons-material/Loop';
|
import LoopIcon from '@mui/icons-material/Loop';
|
||||||
import ReactGA from "react-ga4";
|
|
||||||
import { ReactElement, useState, useEffect } from "react";
|
import { ReactElement, useState, useEffect } from "react";
|
||||||
import { useNavigate, useLocation, createSearchParams } from "react-router-dom";
|
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import GhostStyledIcon from "../../Icon/GhostIcon";
|
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||||
import { Tokens, useWallet } from "./Token";
|
import { Tokens, useWallet } from "./Token";
|
||||||
@ -26,7 +24,7 @@ import { formatCurrency, shorten } from "../../../helpers";
|
|||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||||
|
|
||||||
import { useAccount, useDisconnect, useSwitchChain } from "wagmi";
|
import { useAccount, useDisconnect } from "wagmi";
|
||||||
|
|
||||||
const DisconnectButton = ({ onClose }) => {
|
const DisconnectButton = ({ onClose }) => {
|
||||||
const { disconnect } = useDisconnect();
|
const { disconnect } = useDisconnect();
|
||||||
@ -110,17 +108,11 @@ const WalletTotalValue = ({ address, tokens }) => {
|
|||||||
function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
|
||||||
const tokens = useWallet(chainId, address);
|
const tokens = useWallet(chainId, address);
|
||||||
const { chains } = useSwitchChain();
|
|
||||||
|
|
||||||
const chainName = useMemo(() => {
|
|
||||||
return chains.find(chain => chain.id === chainId).name.toLowerCase();
|
|
||||||
}, [chains, chainId])
|
|
||||||
|
|
||||||
const onBtnClick = (dexName, from, to) => {
|
const onBtnClick = (dexName, from, to) => {
|
||||||
navigate({
|
navigate({
|
||||||
pathname: `${chains.find(chain => chain.id === chainId).name.toLowerCase()}/dex/` + dexName,
|
pathname: "/dex/" + dexName,
|
||||||
search: createSearchParams({
|
search: createSearchParams({
|
||||||
pool: true,
|
pool: true,
|
||||||
from: from,
|
from: from,
|
||||||
@ -130,20 +122,12 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
|||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isWalletOpen) {
|
|
||||||
console.log(`/${chainName.toLowerCase()}/sidebar`)
|
|
||||||
ReactGA.send({ hitType: "pageview", page: `/${chainName.toLowerCase()}/sidebar` });
|
|
||||||
}
|
|
||||||
}, [isWalletOpen, chainName])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWalletOpen) {
|
if (isWalletOpen) {
|
||||||
Object.values(tokens ?? {}).forEach(token => token.refetch());
|
Object.values(tokens ?? {}).forEach(token => token.refetch());
|
||||||
}
|
}
|
||||||
}, [isWalletOpen, tokens])
|
}, [isWalletOpen, tokens])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper>
|
<Paper>
|
||||||
<Box sx={{ padding: theme.spacing(0, 3), display: "flex", flexDirection: "column", minHeight: "100vh" }}>
|
<Box sx={{ padding: theme.spacing(0, 3), display: "flex", flexDirection: "column", minHeight: "100vh" }}>
|
||||||
@ -166,6 +150,18 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
|||||||
<Divider />
|
<Divider />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
style={{ gap: theme.spacing(1.5) }}
|
||||||
|
>
|
||||||
|
<SecondaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => onBtnClick("uniswap", RESERVE_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
|
||||||
|
>
|
||||||
|
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.reserve?.symbol} on Uniswap`}</Typography>
|
||||||
|
</SecondaryButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
|
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
|
||||||
<DisconnectButton onClose={onClose} />
|
<DisconnectButton onClose={onClose} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import { ChangeEvent, useState, useMemo, useEffect } from "react";
|
import { ChangeEvent, useState, useEffect } from "react";
|
||||||
import { useNavigate, createSearchParams } from "react-router-dom";
|
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { formatCurrency, formatNumber } from "../../../helpers";
|
import { formatCurrency, formatNumber } from "../../../helpers";
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"
|
||||||
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
||||||
import { EMPTY_ADDRESS } from "../../../constants/addresses";
|
import { isNetworkLegacy } from "../../../constants";
|
||||||
|
|
||||||
import GhostStyledIcon from "../../Icon/GhostIcon";
|
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||||
import TokenStack from "../../TokenStack/TokenStack";
|
import TokenStack from "../../TokenStack/TokenStack";
|
||||||
@ -26,12 +26,11 @@ import {
|
|||||||
useNativePrice,
|
useNativePrice,
|
||||||
useReservePrice,
|
useReservePrice,
|
||||||
useFtsoPrice,
|
useFtsoPrice,
|
||||||
|
useStnkPrice,
|
||||||
useGhstPrice
|
useGhstPrice
|
||||||
} from "../../../hooks/prices";
|
} from "../../../hooks/prices";
|
||||||
import { useLpValuation } from "../../../hooks/treasury";
|
import { useLpValuation } from "../../../hooks/treasury";
|
||||||
import { useUniswapV2PairReserves } from "../../../hooks/uniswapv2";
|
import { useAccount, useBalance as useNativeBalance, useConfig } from "wagmi";
|
||||||
import { getTokenIcons } from "../../../hooks/helpers";
|
|
||||||
import { useAccount, useBalance as useNativeBalance, useConfig, useSwitchChain, useChainId } from "wagmi";
|
|
||||||
|
|
||||||
const addTokenToWallet = async (token, userAddress) => {
|
const addTokenToWallet = async (token, userAddress) => {
|
||||||
if (!window.ethereum) return;
|
if (!window.ethereum) return;
|
||||||
@ -72,7 +71,6 @@ export const Token = (props) => {
|
|||||||
const {
|
const {
|
||||||
isNative,
|
isNative,
|
||||||
symbol,
|
symbol,
|
||||||
faucetPath,
|
|
||||||
icons,
|
icons,
|
||||||
address,
|
address,
|
||||||
price = 0,
|
price = 0,
|
||||||
@ -86,20 +84,13 @@ export const Token = (props) => {
|
|||||||
} = props;
|
} = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const chainId = useChainId();
|
|
||||||
|
|
||||||
const { chains } = useSwitchChain();
|
|
||||||
|
|
||||||
const chainName = useMemo(() => {
|
|
||||||
return chains.find(chain => chain.id === chainId).name.toLowerCase();
|
|
||||||
}, [chains, chainId])
|
|
||||||
|
|
||||||
const useLink = (symbol, fromAddress, toAddress, isPool) => {
|
const useLink = (symbol, fromAddress, toAddress, isPool) => {
|
||||||
if (faucetPath) {
|
if (symbol.toUpperCase() === "RESERVE") {
|
||||||
navigate({ pathname: faucetPath })
|
navigate({ pathname: "/faucet" })
|
||||||
} else {
|
} else {
|
||||||
navigate({
|
navigate({
|
||||||
pathname: `${chainName}/dex/uniswap`,
|
pathname: "/dex/uniswap",
|
||||||
search: isPool ?
|
search: isPool ?
|
||||||
createSearchParams({
|
createSearchParams({
|
||||||
pool: "true",
|
pool: "true",
|
||||||
@ -155,7 +146,7 @@ export const Token = (props) => {
|
|||||||
onClick={() => useLink(symbol, reserveAddress, address, isPool)}
|
onClick={() => useLink(symbol, reserveAddress, address, isPool)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<Typography>Get {faucetPath ? "for Free" : "on DEX"}</Typography>
|
<Typography>Get on {symbol?.toUpperCase() === "RESERVE" ? "Faucet" : "Uniswap"}</Typography>
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -183,6 +174,11 @@ export const useWallet = (chainId, userAddress) => {
|
|||||||
refetch: ftsoRefetch,
|
refetch: ftsoRefetch,
|
||||||
contractAddress: ftsoAddress,
|
contractAddress: ftsoAddress,
|
||||||
} = useBalance(chainId, "FTSO", userAddress);
|
} = useBalance(chainId, "FTSO", userAddress);
|
||||||
|
const {
|
||||||
|
balance: stnkBalance,
|
||||||
|
refetch: stnkRefetch,
|
||||||
|
contractAddress: stnkAddress,
|
||||||
|
} = useBalance(chainId, "STNK", userAddress);
|
||||||
const {
|
const {
|
||||||
balance: ghstBalance,
|
balance: ghstBalance,
|
||||||
refetch: ghstRefetch,
|
refetch: ghstRefetch,
|
||||||
@ -193,57 +189,40 @@ export const useWallet = (chainId, userAddress) => {
|
|||||||
refetch: lpReserveFtsoRefetch,
|
refetch: lpReserveFtsoRefetch,
|
||||||
contractAddress: lpReserveFtsoBalanceAddress,
|
contractAddress: lpReserveFtsoBalanceAddress,
|
||||||
} = useBalance(chainId, "RESERVE_FTSO", userAddress);
|
} = useBalance(chainId, "RESERVE_FTSO", userAddress);
|
||||||
const {
|
|
||||||
tokens: lpReserveFtsoTokens,
|
|
||||||
} = useUniswapV2PairReserves(chainId, "RESERVE_FTSO");
|
|
||||||
|
|
||||||
const nativePrice = useNativePrice(chainId);
|
const nativePrice = useNativePrice(chainId);
|
||||||
const reservePrice = useReservePrice(chainId);
|
const reservePrice = useReservePrice(chainId);
|
||||||
const ftsoPrice = useFtsoPrice(chainId);
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
const stnkPrice = useStnkPrice(chainId);
|
||||||
const ghstPrice = useGhstPrice(chainId);
|
const ghstPrice = useGhstPrice(chainId);
|
||||||
const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n);
|
const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n);
|
||||||
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
const nativeSymbol = useMemo(() => {
|
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||||
return config?.getClient()?.chain?.nativeCurrency?.symbol;
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO");
|
const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO");
|
||||||
|
|
||||||
const lpReserveFtsoTokenNames = useMemo(() => {
|
|
||||||
const token0 = getTokenIcons(chainId, lpReserveFtsoTokens?.token0 ?? []);
|
|
||||||
const token1 = getTokenIcons(chainId, lpReserveFtsoTokens?.token1 ?? []);
|
|
||||||
|
|
||||||
let tokenAddresses = [lpReserveFtsoTokens?.token1, lpReserveFtsoTokens?.token0];
|
|
||||||
let tokenNames = [...token1, ...token0];
|
|
||||||
|
|
||||||
if (token0?.at(0) === reserveSymbol) {
|
|
||||||
tokenAddresses = [lpReserveFtsoTokens?.token0, lpReserveFtsoTokens?.token1];
|
|
||||||
let tokenNames = [...token0, ...token1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { tokenAddresses, tokenNames }
|
|
||||||
}, [chainId, reserveSymbol, lpReserveFtsoTokens]);
|
|
||||||
|
|
||||||
const tokens = {
|
const tokens = {
|
||||||
native: {
|
native: {
|
||||||
symbol: nativeSymbol,
|
symbol: nativeSymbol,
|
||||||
icons: [nativeSymbol],
|
icons: [nativeSymbol],
|
||||||
balance: nativeBalance,
|
balance: nativeBalance,
|
||||||
price: nativePrice,
|
price: nativePrice,
|
||||||
refetch: nativeBalanceRefetch,
|
refetch: nativeBalanceRefetch
|
||||||
},
|
},
|
||||||
reserve: {
|
reserve: {
|
||||||
symbol: reserveSymbol,
|
symbol: reserveSymbol,
|
||||||
address: reserveAddress,
|
address: reserveAddress,
|
||||||
balance: reserveBalance,
|
balance: reserveBalance,
|
||||||
price: reservePrice,
|
price: reservePrice,
|
||||||
icons: [tokenNameConverter(chainId, reserveSymbol)],
|
icons: isNetworkLegacy(chainId) ? ["GDAI"] : [tokenNameConverter(chainId, reserveSymbol)],
|
||||||
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg",
|
externalUrl: isNetworkLegacy(chainId)
|
||||||
|
? "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg"
|
||||||
|
: "https://ghostchain.io/wp-content/uploads/2025/11/6A-Classic-ETC-Token.svg",
|
||||||
refetch: reserveRefetch,
|
refetch: reserveRefetch,
|
||||||
},
|
},
|
||||||
ftso: {
|
ftso: {
|
||||||
@ -255,6 +234,15 @@ export const useWallet = (chainId, userAddress) => {
|
|||||||
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg",
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg",
|
||||||
refetch: ftsoRefetch,
|
refetch: ftsoRefetch,
|
||||||
},
|
},
|
||||||
|
stnk: {
|
||||||
|
symbol: stnkSymbol,
|
||||||
|
address: stnkAddress,
|
||||||
|
balance: stnkBalance,
|
||||||
|
price: stnkPrice,
|
||||||
|
icons: ["STNK"],
|
||||||
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/sGHST.svg",
|
||||||
|
refetch: stnkRefetch,
|
||||||
|
},
|
||||||
ghst: {
|
ghst: {
|
||||||
symbol: ghstSymbol,
|
symbol: ghstSymbol,
|
||||||
address: ghstAddress,
|
address: ghstAddress,
|
||||||
@ -267,10 +255,10 @@ export const useWallet = (chainId, userAddress) => {
|
|||||||
reserveFtso: {
|
reserveFtso: {
|
||||||
isPool: true,
|
isPool: true,
|
||||||
symbol: lpReserveFtsoSymbol,
|
symbol: lpReserveFtsoSymbol,
|
||||||
address: lpReserveFtsoTokenNames?.tokenAddresses.at(1) ?? "",
|
address: lpReserveFtsoBalanceAddress,
|
||||||
balance: lpReserveFtsoBalance,
|
balance: lpReserveFtsoBalance,
|
||||||
price: lpReserveFtsoPrice,
|
price: lpReserveFtsoPrice,
|
||||||
icons: lpReserveFtsoTokenNames?.tokenNames,
|
icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
|
||||||
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg",
|
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/uni-v2.svg",
|
||||||
refetch: lpReserveFtsoRefetch,
|
refetch: lpReserveFtsoRefetch,
|
||||||
}
|
}
|
||||||
@ -289,12 +277,12 @@ export const useWallet = (chainId, userAddress) => {
|
|||||||
|
|
||||||
export const Tokens = ({ address, tokens, onClose }) => {
|
export const Tokens = ({ address, tokens, onClose }) => {
|
||||||
const [expanded, setExpanded] = useState(null);
|
const [expanded, setExpanded] = useState(null);
|
||||||
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.ghst, tokens.reserveFtso];
|
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.stnk, tokens.ghst, tokens.reserveFtso];
|
||||||
|
|
||||||
const tokenProps = (token) => ({
|
const tokenProps = (token) => ({
|
||||||
...token,
|
...token,
|
||||||
expanded: expanded === token.symbol,
|
expanded: expanded === token.symbol,
|
||||||
reserveAddress: EMPTY_ADDRESS,
|
reserveAddress: tokens.reserve.address,
|
||||||
onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null),
|
onChangeExpanded: (e, isExpanded) => setExpanded(isExpanded ? token.symbol : null),
|
||||||
onAddTokenToWallet: () => addTokenToWallet(token, address),
|
onAddTokenToWallet: () => addTokenToWallet(token, address),
|
||||||
onClose: () => onClose(),
|
onClose: () => onClose(),
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const WalletButton = ({ openWallet, connect }) => {
|
|||||||
const { isConnected, chain } = useAccount();
|
const { isConnected, chain } = useAccount();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const onClick = isConnected ? openWallet : connect;
|
const onClick = isConnected ? openWallet : connect;
|
||||||
const label = `${isConnected ? "Open" : "Connect"} Wallet`;
|
const label = isConnected ? "Open Wallet" : `Connect Wallet`;
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
id="fatso-menu-button"
|
id="fatso-menu-button"
|
||||||
@ -76,12 +76,7 @@ export function Wallet({ address, chainId, connect }) {
|
|||||||
onOpen={openWallet}
|
onOpen={openWallet}
|
||||||
onClose={closeWallet}
|
onClose={closeWallet}
|
||||||
>
|
>
|
||||||
<InitialWalletView
|
<InitialWalletView isWalletOpen={isWalletOpen} address={address} chainId={chainId} onClose={closeWallet} />
|
||||||
isWalletOpen={isWalletOpen}
|
|
||||||
address={address}
|
|
||||||
chainId={chainId}
|
|
||||||
onClose={closeWallet}
|
|
||||||
/>
|
|
||||||
</StyledSwipeableDrawer>
|
</StyledSwipeableDrawer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,6 +4,19 @@ export enum NetworkId {
|
|||||||
TESTNET_MORDOR = 63,
|
TESTNET_MORDOR = 63,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isGovernanceAvailable = (chainId, addressChainId) => {
|
||||||
|
chainId = addressChainId ? addressChainId : chainId;
|
||||||
|
let exists = false;
|
||||||
|
switch (chainId) {
|
||||||
|
case 11155111:
|
||||||
|
exists = true
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
export const isNetworkAvailable = (chainId, addressChainId) => {
|
export const isNetworkAvailable = (chainId, addressChainId) => {
|
||||||
chainId = addressChainId ? addressChainId : chainId;
|
chainId = addressChainId ? addressChainId : chainId;
|
||||||
let exists = false;
|
let exists = false;
|
||||||
@ -24,30 +37,15 @@ export const isNetworkAvailable = (chainId, addressChainId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const isNetworkLegacy = (chainId) => {
|
export const isNetworkLegacy = (chainId) => {
|
||||||
let isLegacy = false;
|
let exists = false;
|
||||||
switch (chainId) {
|
switch (chainId) {
|
||||||
case 11155111:
|
case 560048:
|
||||||
isLegacy = true
|
exists = true
|
||||||
break;
|
|
||||||
case 63:
|
|
||||||
isLegacy = true
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return isLegacy;
|
return exists;
|
||||||
}
|
|
||||||
|
|
||||||
export const isNetworkLegacyType = (chainId) => {
|
|
||||||
let isLegacyType = false;
|
|
||||||
switch (chainId) {
|
|
||||||
case 63:
|
|
||||||
isLegacyType = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return isLegacyType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const networkAvgBlockSpeed = (chainId) => {
|
export const networkAvgBlockSpeed = (chainId) => {
|
||||||
|
|||||||
@ -1,40 +1,38 @@
|
|||||||
import { NetworkId } from "../constants";
|
import { NetworkId } from "../constants";
|
||||||
|
|
||||||
export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
||||||
|
|
||||||
export const STAKING_ADDRESSES = {
|
export const STAKING_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
[NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
[NetworkId.TESTNET_MORDOR]: "0xC25C9C56a89ebd6ef291b415d00ACfa7913c55e7",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BOND_DEPOSITORY_ADDRESSES = {
|
export const BOND_DEPOSITORY_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
[NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
[NetworkId.TESTNET_MORDOR]: "0x7C85cDEddBAd0f50453d373F7332BEa11ECa7BAf",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DAO_TREASURY_ADDRESSES = {
|
export const DAO_TREASURY_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
[NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
[NetworkId.TESTNET_MORDOR]: "0x5883C8e2259556B534036c7fDF4555E09dE9f243",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FTSO_DAI_LP_ADDRESSES = {
|
export const FTSO_DAI_LP_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
|
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D",
|
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
|
[NetworkId.TESTNET_MORDOR]: "0xE6546D12665dB5B22Cb92FB9e0221aE51A57aeaa",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FTSO_STNK_LP_ADDRESSES = {
|
export const FTSO_STNK_LP_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
|
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
|
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x0000000000000000000000000000000000000000",
|
[NetworkId.TESTNET_MORDOR]: "0x0000000000000000000000000000000000000000",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RESERVE_ADDRESSES = {
|
export const RESERVE_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
[NetworkId.TESTNET_SEPOLIA]: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xE69a5c6dd88cA798b93c3C92fc50c51Fd5305eB4",
|
[NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
|
[NetworkId.TESTNET_MORDOR]: "0x6af91B3763b5d020E0985f85555EB50e5852d7AC",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,44 +44,41 @@ export const WETH_ADDRESSES = {
|
|||||||
|
|
||||||
export const GHST_ADDRESSES = {
|
export const GHST_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
[NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
[NetworkId.TESTNET_MORDOR]: "0x14b5787F8a1E62786F50A7998A9b14aa24298423",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const STNK_ADDRESSES = {
|
export const STNK_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
[NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
[NetworkId.TESTNET_MORDOR]: "0x137bA9403885D8ECEa95AaFBb8734F5a16121bAC",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FTSO_ADDRESSES = {
|
export const FTSO_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
[NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
[NetworkId.TESTNET_MORDOR]: "0xeA170CC0faceC531a6a9e93a28C4330Ac50343a1",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DISTRIBUTOR_ADDRESSES = {
|
export const DISTRIBUTOR_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
[NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
[NetworkId.TESTNET_MORDOR]: "0xaf5e76706520db7fb01096E322940206bf3fce57",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GHOST_GOVERNANCE_ADDRESSES = {
|
export const GHOST_GOVERNANCE_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
|
[NetworkId.TESTNET_SEPOLIA]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
|
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BONDING_CALCULATOR_ADDRESSES = {
|
export const BONDING_CALCULATOR_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
[NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
[NetworkId.TESTNET_MORDOR]: "0x0c4C7C49a173E2a3f9Eed93125F3F146D8e17bCb",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GATEKEEPER_ADDRESSES = {
|
export const GATEKEEPER_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B",
|
[NetworkId.TESTNET_SEPOLIA]: "0xc85129A097773B7F8970a7364c928C05f265E6A1",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
|
[NetworkId.TESTNET_MORDOR]: "0xA59cB4ff90bE2206121aE61eEB68d0AeC7BA095f",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UNISWAP_V2_ROUTER = {
|
export const UNISWAP_V2_ROUTER = {
|
||||||
@ -118,10 +113,6 @@ export const CEX_TICKERS = {
|
|||||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
|
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
|
||||||
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
|
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
|
||||||
],
|
],
|
||||||
[NetworkId.TESTNET_HOODI]: [
|
|
||||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
|
|
||||||
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
|
|
||||||
],
|
|
||||||
[NetworkId.TESTNET_MORDOR]: [
|
[NetworkId.TESTNET_MORDOR]: [
|
||||||
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",
|
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",
|
||||||
"https://api.coinbase.com/v2/prices/ETC-USDT/spot",
|
"https://api.coinbase.com/v2/prices/ETC-USDT/spot",
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { NetworkId } from "../constants";
|
import { NetworkId } from "../constants";
|
||||||
|
|
||||||
export const GHOST_CONNECT = "https://connect.ghostchain.io/";
|
|
||||||
|
|
||||||
export const ECOSYSTEM = [
|
export const ECOSYSTEM = [
|
||||||
{
|
{
|
||||||
name: "GHOST chain",
|
name: "GHOST chain",
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
import { NetworkId } from "../constants";
|
|
||||||
|
|
||||||
export const amountInHistory = {
|
|
||||||
[NetworkId.TESTNET_SEPOLIA]: 0n,
|
|
||||||
[NetworkId.TESTNET_HOODI]: 0n,
|
|
||||||
[NetworkId.TESTNET_MORDOR]: 0n,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const amountOutHistory = {
|
|
||||||
[NetworkId.TESTNET_SEPOLIA]: 0n,
|
|
||||||
[NetworkId.TESTNET_HOODI]: 0n,
|
|
||||||
[NetworkId.TESTNET_MORDOR]: 0n,
|
|
||||||
}
|
|
||||||
|
|
||||||
// denominator is 1e5
|
|
||||||
export const feeIn = {
|
|
||||||
[NetworkId.TESTNET_SEPOLIA]: 6900n,
|
|
||||||
[NetworkId.TESTNET_HOODI]: 6900n,
|
|
||||||
[NetworkId.TESTNET_MORDOR]: 6900n,
|
|
||||||
}
|
|
||||||
|
|
||||||
// denominator is 1e5
|
|
||||||
export const feeOut = {
|
|
||||||
[NetworkId.TESTNET_SEPOLIA]: 0n,
|
|
||||||
[NetworkId.TESTNET_HOODI]: 0n,
|
|
||||||
[NetworkId.TESTNET_MORDOR]: 0n,
|
|
||||||
}
|
|
||||||
|
|
||||||
// denominator is 1e5
|
|
||||||
export const stakeRatio = 5000n;
|
|
||||||
@ -1,9 +1,8 @@
|
|||||||
import { ArrowBack } from "@mui/icons-material";
|
import { ArrowBack } from "@mui/icons-material";
|
||||||
import { Box, Link, Skeleton, Typography } from "@mui/material";
|
import { Box, Link, Skeleton, Typography } from "@mui/material";
|
||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom";
|
import { Link as RouterLink, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import { useAccount, useSwitchChain } from "wagmi";
|
import { useAccount, useChainId } from "wagmi";
|
||||||
import { isAddress } from "viem";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
@ -18,92 +17,64 @@ import BondSettingsModal from "./components/BondSettingsModal";
|
|||||||
import NotFound from "../NotFound/NotFound";
|
import NotFound from "../NotFound/NotFound";
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { isNetworkLegacy } from "../../constants";
|
import { NetworkId } from "../../constants";
|
||||||
import { formatCurrency } from "../../helpers";
|
import { formatCurrency } from "../../helpers";
|
||||||
|
|
||||||
import { useLocalStorage } from "../../hooks/localstorage";
|
|
||||||
import { useLiveBonds } from "../../hooks/bonds";
|
import { useLiveBonds } from "../../hooks/bonds";
|
||||||
import { useFtsoPrice } from "../../hooks/prices";
|
import { useFtsoPrice } from "../../hooks/prices";
|
||||||
|
|
||||||
const BondModalContainer = ({ chainId, address, config, connect }) => {
|
const BondModalContainer = ({ chainId, address, connect }) => {
|
||||||
const { id, network } = useParams();
|
const { id } = useParams();
|
||||||
const { liveBonds } = useLiveBonds(chainId, network);
|
const { liveBonds } = useLiveBonds(chainId);
|
||||||
const bond = liveBonds.find(bond => bond.id === Number(id));
|
const bond = liveBonds.find(bond => bond.id === Number(id));
|
||||||
|
|
||||||
if (!bond) return <NotFound />;
|
if (!bond) return <NotFound />;
|
||||||
|
|
||||||
return <BondModal config={config} chainId={chainId} bond={bond} address={address} connect={connect} />;
|
return <BondModal chainId={chainId} bond={bond} address={address} connect={connect} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BondModal = ({ bond, chainId, address, config, connect }) => {
|
export const BondModal = ({ bond, chainId, address, connect }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { network } = useParams();
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const [slippage, setSlippage] = useState(localStorage.getItem("bond-slippage") || "10");
|
||||||
|
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("bond-decimals") || "5");
|
||||||
const [isSettingsOpen, setSettingsOpen] = useState(false);
|
const [isSettingsOpen, setSettingsOpen] = useState(false);
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
const [recipientAddress, setRecipientAddress] = useState(address);
|
||||||
|
|
||||||
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "bond-slippage", "10"));
|
|
||||||
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "bond-decimals", "5"));
|
|
||||||
const [recipientAddress, setRecipientAddressInner] = useState(() => getStorageValue(chainId, address, "bond-recipient", address));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: pathname });
|
ReactGA.send({ hitType: "pageview", page: pathname });
|
||||||
}, [pathname])
|
}, [])
|
||||||
|
|
||||||
const setRecipientAddress = useCallback((value) => {
|
const setSlippageInner = (value) => {
|
||||||
setRecipientAddressInner(value);
|
|
||||||
if (isAddress(value)) {
|
|
||||||
setStorageValue(chainId, address, "bond-recipient", value);
|
|
||||||
}
|
|
||||||
}, [chainId, address]);
|
|
||||||
|
|
||||||
const setSlippageInner = useCallback((value) => {
|
|
||||||
const maybeValue = parseFloat(value);
|
const maybeValue = parseFloat(value);
|
||||||
if (!maybeValue || parseFloat(value) <= 100) {
|
if (!maybeValue || parseFloat(value) <= 100) {
|
||||||
setSlippage(value);
|
setSlippage(value);
|
||||||
setStorageValue(chainId, address, "bond-slippage", value);
|
localStorage.setItem("bond-slippage", value);
|
||||||
}
|
}
|
||||||
}, [chainId, address]);
|
}
|
||||||
|
|
||||||
const setFormatDecimalsInner = useCallback((value) => {
|
const setFormatDecimalsInner = (value) => {
|
||||||
if (Number(value) <= 17) {
|
if (Number(value) <= 17) {
|
||||||
setFormatDecimals(value);
|
setFormatDecimals(value);
|
||||||
setStorageValue(chainId, address, "bond-decimals", value);
|
localStorage.setItem("bond-decimals", value);
|
||||||
}
|
}
|
||||||
}, [chainId, address]);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate(`/${network}/bonds`);
|
if (event.key === "Escape") isSettingsOpen ? setSettingsOpen(false) : navigate("/bonds");
|
||||||
};
|
};
|
||||||
window.addEventListener("keydown", handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [network, navigate, isSettingsOpen]);
|
}, [navigate, isSettingsOpen]);
|
||||||
|
|
||||||
const chainSymbol = useMemo(() => {
|
|
||||||
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
|
||||||
if (chainSymbol) return chainSymbol;
|
|
||||||
return "WTF";
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
const preparedQuoteToken = useMemo(() => {
|
|
||||||
if (isNetworkLegacy(chainId)) {
|
|
||||||
return {
|
|
||||||
address: bond.quoteToken.quoteTokenAddress,
|
|
||||||
icons: bond.quoteToken.icons,
|
|
||||||
name: bond.quoteToken.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { address: undefined, icons: [chainSymbol], name: chainSymbol };
|
|
||||||
}, [bond, chainSymbol, chainId])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<PageTitle
|
<PageTitle
|
||||||
name={
|
name={
|
||||||
<Box display="flex" flexDirection="row" alignItems="center">
|
<Box display="flex" flexDirection="row" alignItems="center">
|
||||||
<Link component={RouterLink} to={`/${network}/bonds`}>
|
<Link component={RouterLink} to="/bonds">
|
||||||
<Box display="flex" flexDirection="row">
|
<Box display="flex" flexDirection="row">
|
||||||
<ArrowBack />
|
<ArrowBack />
|
||||||
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
|
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
|
||||||
@ -112,10 +83,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<TokenStack tokens={preparedQuoteToken.icons} sx={{ fontSize: "27px" }} />
|
<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
|
||||||
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
|
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
|
||||||
<Typography variant="h4" fontWeight={500}>
|
<Typography variant="h4" fontWeight={500}>
|
||||||
{preparedQuoteToken.name}
|
{bond.quoteToken.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -161,12 +132,10 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
|
|||||||
<BondInputArea
|
<BondInputArea
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
bond={bond}
|
bond={bond}
|
||||||
config={config}
|
|
||||||
connect={connect}
|
connect={connect}
|
||||||
address={address}
|
address={address}
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
recipientAddress={recipientAddress}
|
recipientAddress={recipientAddress}
|
||||||
preparedQuoteToken={preparedQuoteToken}
|
|
||||||
handleSettingsOpen={() => setSettingsOpen(true)}
|
handleSettingsOpen={() => setSettingsOpen(true)}
|
||||||
formatDecimals={formatDecimals}
|
formatDecimals={formatDecimals}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Box, Tab, Tabs, Typography, Container, useMediaQuery } from "@mui/material";
|
import { Box, Tab, Tabs, Typography, Container, useMediaQuery } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams, useLocation } from "react-router-dom";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import Paper from "../../components/Paper/Paper";
|
import Paper from "../../components/Paper/Paper";
|
||||||
@ -20,9 +19,6 @@ import { useFtsoPrice } from "../../hooks/prices";
|
|||||||
import { useTokenSymbol } from "../../hooks/tokens";
|
import { useTokenSymbol } from "../../hooks/tokens";
|
||||||
|
|
||||||
const Bonds = ({ chainId, address, connect }) => {
|
const Bonds = ({ chainId, address, connect }) => {
|
||||||
const location = useLocation();
|
|
||||||
const { network } = useParams();
|
|
||||||
|
|
||||||
const [isZoomed] = useState(false);
|
const [isZoomed] = useState(false);
|
||||||
const [secondsTo, setSecondsTo] = useState(0);
|
const [secondsTo, setSecondsTo] = useState(0);
|
||||||
|
|
||||||
@ -30,10 +26,10 @@ const Bonds = ({ chainId, address, connect }) => {
|
|||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/bonds" });
|
||||||
}, [location]);
|
}, []);
|
||||||
|
|
||||||
const { liveBonds } = useLiveBonds(chainId, network);
|
const { liveBonds } = useLiveBonds(chainId);
|
||||||
const totalReserves = useTotalReserves(chainId);
|
const totalReserves = useTotalReserves(chainId);
|
||||||
const ftsoPrice = useFtsoPrice(chainId);
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { Box, Typography, Checkbox, FormControlLabel } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
|
||||||
import { styled, useTheme } from "@mui/material/styles";
|
import { styled, useTheme } from "@mui/material/styles";
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||||
@ -11,14 +10,13 @@ import Metric from "../../../components/Metric/Metric";
|
|||||||
import Modal from "../../../components/Modal/Modal";
|
import Modal from "../../../components/Modal/Modal";
|
||||||
import TokenStack from "../../../components/TokenStack/TokenStack";
|
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||||
import DataRow from "../../../components/DataRow/DataRow";
|
import DataRow from "../../../components/DataRow/DataRow";
|
||||||
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
|
import { PrimaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
import BondDiscount from "./BondDiscount";
|
import BondDiscount from "./BondDiscount";
|
||||||
import BondVesting from "./BondVesting";
|
import BondVesting from "./BondVesting";
|
||||||
import BondSlippage from "./BondSlippage";
|
import BondSlippage from "./BondSlippage";
|
||||||
|
|
||||||
import { purchaseBond } from "../../../hooks/bonds";
|
import { purchaseBond } from "../../../hooks/bonds";
|
||||||
import { useWarmupLength } from "../../../hooks/staking";
|
|
||||||
|
|
||||||
const StyledBox = styled(Box, {
|
const StyledBox = styled(Box, {
|
||||||
shouldForwardProp: prop => prop !== "template",
|
shouldForwardProp: prop => prop !== "template",
|
||||||
@ -39,63 +37,11 @@ const BondConfirmModal = ({
|
|||||||
sender,
|
sender,
|
||||||
handleSettingsOpen,
|
handleSettingsOpen,
|
||||||
isOpen,
|
isOpen,
|
||||||
isNative,
|
|
||||||
bondQuoteTokenName,
|
|
||||||
bondQuoteTokenIcons,
|
|
||||||
handleConfirmClose
|
handleConfirmClose
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { warmupLength, warmupExists: needsWarmup } = useWarmupLength(chainId);
|
|
||||||
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup]);
|
|
||||||
|
|
||||||
const AcknowledgeWarmupCheckbox = () => {
|
|
||||||
if (!needsWarmup) return <></>;
|
|
||||||
return (
|
|
||||||
<Box sx={{ marginBottom: "1rem" }}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
data-testid="acknowledge-bond-warm-up"
|
|
||||||
checked={acknowledgedWarmup}
|
|
||||||
onChange={event => setAcknowledgedWarmup(event.target.checked)}
|
|
||||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
|
||||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Typography variant="body2">
|
|
||||||
{`I understand the ${bondQuoteTokenName} I’m bonding will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed, and the warm-up extends with each bond purchase`}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const NeedsWarmupDetails = () => {
|
|
||||||
if (!needsWarmup) return <></>;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<AcknowledgeWarmupCheckbox />
|
|
||||||
<SecondaryButton
|
|
||||||
fullWidth
|
|
||||||
href="https://ghostchain.io/ghostdao_litepaper"
|
|
||||||
>
|
|
||||||
Why is there a warm-up?
|
|
||||||
</SecondaryButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmCloseMaster = () => {
|
|
||||||
setAcknowledgedWarmup(false);
|
|
||||||
handleConfirmClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
|
|
||||||
@ -107,19 +53,18 @@ const BondConfirmModal = ({
|
|||||||
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
|
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
|
||||||
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
|
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
|
||||||
|
|
||||||
await purchaseBond({
|
await purchaseBond(
|
||||||
chainId,
|
chainId,
|
||||||
bondId: bond.id,
|
bond.id,
|
||||||
amount: spendAmountValue._value.toBigInt(),
|
spendAmountValue._value.toBigInt(),
|
||||||
maxPrice,
|
maxPrice,
|
||||||
user: recipientAddress,
|
recipientAddress,
|
||||||
sender,
|
sender,
|
||||||
referral,
|
referral
|
||||||
isNative
|
);
|
||||||
});
|
|
||||||
|
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
handleConfirmCloseMaster();
|
handleConfirmClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -129,13 +74,13 @@ const BondConfirmModal = ({
|
|||||||
open={isOpen}
|
open={isOpen}
|
||||||
headerContent={
|
headerContent={
|
||||||
<Box display="flex" flexDirection="row">
|
<Box display="flex" flexDirection="row">
|
||||||
<TokenStack tokens={bondQuoteTokenIcons} sx={{ fontSize: "27px" }} />
|
<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "27px" }} />
|
||||||
<Typography variant="h4" sx={{ marginLeft: "6px" }}>
|
<Typography variant="h4" sx={{ marginLeft: "6px" }}>
|
||||||
{bondQuoteTokenName}
|
{bond.quoteToken.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
onClose={!isPending && handleConfirmCloseMaster}
|
onClose={!isPending && handleConfirmClose}
|
||||||
topLeft={<GhostStyledIcon viewBox="0 0 23 23" component={SettingsIcon} style={{ cursor: "pointer" }} onClick={handleSettingsOpen} />}
|
topLeft={<GhostStyledIcon viewBox="0 0 23 23" component={SettingsIcon} style={{ cursor: "pointer" }} onClick={handleSettingsOpen} />}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -146,7 +91,7 @@ const BondConfirmModal = ({
|
|||||||
metric={spendAmount}
|
metric={spendAmount}
|
||||||
/>
|
/>
|
||||||
<Box display="flex" flexDirection="row" justifyContent="center">
|
<Box display="flex" flexDirection="row" justifyContent="center">
|
||||||
<Typography>{bondQuoteTokenName}</Typography>
|
<Typography>{bond.quoteToken.name}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />
|
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />
|
||||||
@ -161,13 +106,9 @@ const BondConfirmModal = ({
|
|||||||
<DataRow title="ROI" balance={<BondDiscount discount={bond.discount} textOnly />} />
|
<DataRow title="ROI" balance={<BondDiscount discount={bond.discount} textOnly />} />
|
||||||
<DataRow title="Bond Slippage" balance={<BondSlippage slippage={slippage} textOnly />} />
|
<DataRow title="Bond Slippage" balance={<BondSlippage slippage={slippage} textOnly />} />
|
||||||
<DataRow title="Vesting Term" balance={<BondVesting vesting={bond.vesting} />} />
|
<DataRow title="Vesting Term" balance={<BondVesting vesting={bond.vesting} />} />
|
||||||
{!acknowledgedWarmup && <Box>
|
<PrimaryButton fullWidth onClick={onSubmit} disabled={isPending} loading={isPending}>
|
||||||
<Box mt="21px" mb="21px" borderTop={`1px solid ${theme.colors.gray[500]}`}></Box>
|
|
||||||
<NeedsWarmupDetails />
|
|
||||||
</Box>}
|
|
||||||
{acknowledgedWarmup && <PrimaryButton fullWidth onClick={onSubmit} disabled={isPending} loading={isPending}>
|
|
||||||
{isPending ? "Bonding..." : "Confirm Bond Purchase"}
|
{isPending ? "Bonding..." : "Confirm Bond Purchase"}
|
||||||
</PrimaryButton>}
|
</PrimaryButton>
|
||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||||
import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material";
|
import { Box, Checkbox, FormControlLabel } from "@mui/material";
|
||||||
import { useState, useMemo, useCallback } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||||
@ -30,15 +30,12 @@ const BondInputArea = ({
|
|||||||
formatDecimals,
|
formatDecimals,
|
||||||
handleSettingsOpen,
|
handleSettingsOpen,
|
||||||
address,
|
address,
|
||||||
preparedQuoteToken,
|
|
||||||
connect
|
connect
|
||||||
}) => {
|
}) => {
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const { currentIndex } = useCurrentIndex(chainId);
|
const { currentIndex } = useCurrentIndex(chainId);
|
||||||
const { balance, refetch } = useBalance(chainId, preparedQuoteToken.address, address);
|
const { balance, refetch } = useBalance(chainId, bond.quoteToken.quoteTokenAddress, address);
|
||||||
|
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
@ -66,7 +63,7 @@ const BondInputArea = ({
|
|||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
const setMax = useCallback(() => {
|
const setMax = () => {
|
||||||
if (!balance) return;
|
if (!balance) return;
|
||||||
|
|
||||||
if (bond.capacity.inQuoteToken.lt(bond.maxPayout.inQuoteToken)) {
|
if (bond.capacity.inQuoteToken.lt(bond.maxPayout.inQuoteToken)) {
|
||||||
@ -82,17 +79,12 @@ const BondInputArea = ({
|
|||||||
? bond.maxPayout.inQuoteToken.toString() // Payout is the smallest
|
? bond.maxPayout.inQuoteToken.toString() // Payout is the smallest
|
||||||
: balance.toString(),
|
: balance.toString(),
|
||||||
);
|
);
|
||||||
}, [bond, balance]);
|
};
|
||||||
|
|
||||||
const baseTokenString = useMemo(() => (bond.maxPayout.inBaseToken.lt(bond.capacity.inBaseToken)
|
const baseTokenString = (bond.maxPayout.inBaseToken.lt(bond.capacity.inBaseToken)
|
||||||
? bond.maxPayout.inBaseToken
|
? bond.maxPayout.inBaseToken
|
||||||
: bond.capacity.inBaseToken
|
: bond.capacity.inBaseToken
|
||||||
), [bond]);
|
);
|
||||||
|
|
||||||
const incorrectInputAmount = useMemo(() => {
|
|
||||||
if (!balance) return false;
|
|
||||||
return balance.lt(parsedAmount) || baseTokenString.lt(amountInBaseToken);
|
|
||||||
}, [amountInBaseToken, baseTokenString, balance, parsedAmount]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
|
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
|
||||||
@ -103,21 +95,22 @@ const BondInputArea = ({
|
|||||||
UpperSwapCard={
|
UpperSwapCard={
|
||||||
<SwapCard
|
<SwapCard
|
||||||
maxWidth="476px"
|
maxWidth="476px"
|
||||||
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
inputWidth="280px"
|
||||||
id="from"
|
id="from"
|
||||||
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
|
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
|
||||||
tokenName={preparedQuoteToken.name}
|
tokenName={bond.quoteToken.name}
|
||||||
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
|
info={formatCurrency(balance, formatDecimals, bond.quoteToken.name)}
|
||||||
endString="Max"
|
endString="Max"
|
||||||
endStringOnClick={setMax}
|
endStringOnClick={setMax}
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={event => setAmount(event.currentTarget.value)}
|
onChange={event => setAmount(event.currentTarget.value)}
|
||||||
inputProps={{ "data-testid": "fromInput" }}
|
inputProps={{ "data-testid": "fromInput" }}
|
||||||
/>}
|
/>
|
||||||
|
}
|
||||||
LowerSwapCard={
|
LowerSwapCard={
|
||||||
<SwapCard
|
<SwapCard
|
||||||
maxWidth="476px"
|
maxWidth="476px"
|
||||||
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
inputWidth="280px"
|
||||||
id="to"
|
id="to"
|
||||||
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
|
token={<TokenStack tokens={bond.baseToken.icons} sx={{ fontSize: "21px" }} />}
|
||||||
tokenName={bond.baseToken.name}
|
tokenName={bond.baseToken.name}
|
||||||
@ -139,7 +132,6 @@ const BondInputArea = ({
|
|||||||
connect={connect}
|
connect={connect}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="60px"
|
height="60px"
|
||||||
isNative={preparedQuoteToken.address === undefined}
|
|
||||||
isVertical
|
isVertical
|
||||||
message={
|
message={
|
||||||
<>
|
<>
|
||||||
@ -163,7 +155,7 @@ const BondInputArea = ({
|
|||||||
)}
|
)}
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={incorrectInputAmount || bond.isSoldOut || (showDisclaimer && !checked)}
|
disabled={bond.isSoldOut || (showDisclaimer && !checked)}
|
||||||
onClick={() => setConfirmOpen(true)}
|
onClick={() => setConfirmOpen(true)}
|
||||||
>
|
>
|
||||||
Bond
|
Bond
|
||||||
@ -185,12 +177,12 @@ const BondInputArea = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
|
tooltip={`The total amount of payout asset you will receive from this bond purchase`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataRow
|
<DataRow
|
||||||
title="Max You Can Buy"
|
title="Max You Can Buy"
|
||||||
tooltip={`Maximum ${ftsoSymbol} that can be purchased in a single transaction. Prevents whales from buying entire bond supply at once.`}
|
tooltip={`The maximum quantity of payout token offered via bonds at this moment in time`}
|
||||||
balance={
|
balance={
|
||||||
<span>
|
<span>
|
||||||
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
|
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
|
||||||
@ -203,14 +195,14 @@ const BondInputArea = ({
|
|||||||
<DataRow
|
<DataRow
|
||||||
title="Discount"
|
title="Discount"
|
||||||
balance={<BondDiscount discount={bond.discount} textOnly />}
|
balance={<BondDiscount discount={bond.discount} textOnly />}
|
||||||
tooltip={`The discount (or premium) between ${ftsoSymbol} market price and bond price. Higher discount = better deal for bond buyers.`}
|
tooltip={`The bond discount is the percentage difference between ${ftsoSymbol} market value and the bond's price`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataRow
|
<DataRow
|
||||||
title={`Vesting Term`}
|
title={`Vesting Term`}
|
||||||
balance={<BondVesting vesting={bond.vesting} />}
|
balance={<BondVesting vesting={bond.vesting} />}
|
||||||
tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
|
tooltip={"The duration of the Bond whereby the bond can be claimed in its entirety"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{recipientAddress !== address && (
|
{recipientAddress !== address && (
|
||||||
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
|
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
|
||||||
@ -227,9 +219,6 @@ const BondInputArea = ({
|
|||||||
spendAmountValue={parsedAmount}
|
spendAmountValue={parsedAmount}
|
||||||
spendAmount={formatNumber(parsedAmount, formatDecimals)}
|
spendAmount={formatNumber(parsedAmount, formatDecimals)}
|
||||||
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
|
receiveAmount={formatNumber(amountInBaseToken, formatDecimals)}
|
||||||
bondQuoteTokenName={preparedQuoteToken.name}
|
|
||||||
bondQuoteTokenIcons={preparedQuoteToken.icons}
|
|
||||||
isNative={preparedQuoteToken.address === undefined}
|
|
||||||
handleSettingsOpen={handleSettingsOpen}
|
handleSettingsOpen={handleSettingsOpen}
|
||||||
isOpen={confirmOpen}
|
isOpen={confirmOpen}
|
||||||
sender={address}
|
sender={address}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useMediaQuery
|
useMediaQuery
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { NavLink, useParams } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import ArrowUp from "../../../assets/icons/arrow-up.svg?react";
|
import ArrowUp from "../../../assets/icons/arrow-up.svg?react";
|
||||||
|
|
||||||
import BondDiscount from "./BondDiscount";
|
import BondDiscount from "./BondDiscount";
|
||||||
@ -26,7 +26,6 @@ import { TertiaryButton } from "../../../components/Button";
|
|||||||
|
|
||||||
export const BondList = ({ bonds, secondsTo, chainId }) => {
|
export const BondList = ({ bonds, secondsTo, chainId }) => {
|
||||||
const isSmallScreen = useScreenSize("md");
|
const isSmallScreen = useScreenSize("md");
|
||||||
const { network } = useParams();
|
|
||||||
|
|
||||||
if (bonds.length === 0) {
|
if (bonds.length === 0) {
|
||||||
return (
|
return (
|
||||||
@ -44,7 +43,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{sortBondsByDiscount(bonds).map(bond => (
|
{sortBondsByDiscount(bonds).map(bond => (
|
||||||
<BondCard key={bond.id} secondsTo={secondsTo} bond={bond} chainName={network} />
|
<BondCard key={bond.id} secondsTo={secondsTo} bond={bond} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -54,7 +53,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
|
|||||||
<>
|
<>
|
||||||
<BondTable>
|
<BondTable>
|
||||||
{sortBondsByDiscount(bonds).map(bond => (
|
{sortBondsByDiscount(bonds).map(bond => (
|
||||||
<BondRow key={bond.id} bond={bond} secondsTo={secondsTo} chainName={network} />
|
<BondRow key={bond.id} bond={bond} secondsTo={secondsTo} />
|
||||||
))}
|
))}
|
||||||
</BondTable>
|
</BondTable>
|
||||||
|
|
||||||
@ -65,7 +64,7 @@ export const BondList = ({ bonds, secondsTo, chainId }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const BondCard = ({ bond, secondsTo, chainName }) => {
|
const BondCard = ({ bond, secondsTo }) => {
|
||||||
const quoteTokenName = bond.quoteToken.name;
|
const quoteTokenName = bond.quoteToken.name;
|
||||||
const baseTokenName = bond.baseToken.name;
|
const baseTokenName = bond.baseToken.name;
|
||||||
|
|
||||||
@ -131,11 +130,10 @@ const BondCard = ({ bond, secondsTo, chainName }) => {
|
|||||||
<Box mt="16px">
|
<Box mt="16px">
|
||||||
<Link
|
<Link
|
||||||
component={NavLink}
|
component={NavLink}
|
||||||
to={`/${chainName}/bonds/${bond.id}`}
|
to={`/bonds/${bond.id}`}
|
||||||
sx={{ pointerEvents: bond.isSoldOut ? 'none' : 'auto' }}
|
|
||||||
>
|
>
|
||||||
<TertiaryButton fullWidth disabled={bond.isSoldOut}>
|
<TertiaryButton fullWidth>
|
||||||
{bond.isSoldOut ? "Sold Out" : `Bond`}
|
Bond
|
||||||
</TertiaryButton>
|
</TertiaryButton>
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
@ -175,7 +173,7 @@ const payoutTokenCapacity = (bond) => {
|
|||||||
return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`;
|
return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BondRow = ({ bond, secondsTo, chainName }) => {
|
const BondRow = ({ bond, secondsTo }) => {
|
||||||
const quoteTokenName = bond.quoteToken.name;
|
const quoteTokenName = bond.quoteToken.name;
|
||||||
const baseTokenName = bond.baseToken.name;
|
const baseTokenName = bond.baseToken.name;
|
||||||
|
|
||||||
@ -223,8 +221,7 @@ const BondRow = ({ bond, secondsTo, chainName }) => {
|
|||||||
<TableCell style={{ padding: "8px 0" }}>
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
<Link
|
<Link
|
||||||
component={NavLink}
|
component={NavLink}
|
||||||
to={`/${chainName}/bonds/${bond.id}`}
|
to={`/bonds/${bond.id}`}
|
||||||
sx={{ pointerEvents: bond.isSoldOut ? 'none' : 'auto' }}
|
|
||||||
>
|
>
|
||||||
<TertiaryButton fullWidth disabled={bond.isSoldOut}>
|
<TertiaryButton fullWidth disabled={bond.isSoldOut}>
|
||||||
{bond.isSoldOut ? "Sold Out" : `Bond`}
|
{bond.isSoldOut ? "Sold Out" : `Bond`}
|
||||||
|
|||||||
@ -13,27 +13,24 @@ import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
|||||||
import { useScreenSize } from "../../../hooks/useScreenSize";
|
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||||
|
|
||||||
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||||
import { isNetworkLegacy } from "../../../constants";
|
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
import { formatCurrency } from "../../../helpers";
|
import { formatCurrency } from "../../../helpers";
|
||||||
|
|
||||||
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
|
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
|
||||||
import { useNotes, redeem, forceRedeem } from "../../../hooks/bonds";
|
import { useNotes, redeem } from "../../../hooks/bonds";
|
||||||
import { useTokenSymbol } from "../../../hooks/tokens";
|
import { useTokenSymbol } from "../../../hooks/tokens";
|
||||||
import { useGhstPrice } from "../../../hooks/prices";
|
|
||||||
import { useBreakoutModal } from "../../../hooks/breakoutModal";
|
|
||||||
|
|
||||||
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||||
const isSmallScreen = useScreenSize("md");
|
const isSmallScreen = useScreenSize("md");
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [pendingIndexes, setPendingIndexes] = useState([]);
|
|
||||||
const [isWarmup, setIsWapmup] = useState(false);
|
const [isWarmup, setIsWapmup] = useState(false);
|
||||||
const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false);
|
const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false);
|
||||||
const [isPayoutGhst, _] = useState(true);
|
const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("bond-isGhstPayout")
|
||||||
|
? localStorage.getItem("bond-isGhstPayout") === "true"
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
|
||||||
const ghstPrice = useGhstPrice(chainId);
|
|
||||||
const { breakoutFromBonding } = useBreakoutModal();
|
|
||||||
const { epoch } = useEpoch(chainId);
|
const { epoch } = useEpoch(chainId);
|
||||||
const { warmupExists } = useWarmupLength(chainId);
|
const { warmupExists } = useWarmupLength(chainId);
|
||||||
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
|
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
|
||||||
@ -42,6 +39,7 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
|||||||
const { notes, refetch: notesRefetch } = useNotes(chainId, address);
|
const { notes, refetch: notesRefetch } = useNotes(chainId, address);
|
||||||
|
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
if (!notes || notes.length === 0) return null;
|
if (!notes || notes.length === 0) return null;
|
||||||
@ -54,38 +52,26 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
|||||||
18
|
18
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setIsPayoutGhstInner = (value) => {
|
||||||
|
setIsPayoutGhst(value);
|
||||||
|
localStorage.setItem("bond-isGhstPayout", value);
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = async (indexes) => {
|
const onSubmit = async (indexes) => {
|
||||||
setIsPending(true);
|
const isFundsInWarmup = warmupInfo.deposit._value > 0n;
|
||||||
setPendingIndexes(indexes);
|
if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
|
||||||
|
setIsWapmup(true);
|
||||||
const defaultFunction = async () => {
|
|
||||||
await redeem({ chainId, user: address, isGhst: isPayoutGhst, indexes });
|
|
||||||
await notesRefetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isNetworkLegacy(chainId)) {
|
|
||||||
const isFundsInWarmup = warmupInfo.deposit._value > 0n;
|
|
||||||
if (warmupExists && isFundsInWarmup && !isPreClaimConfirmed) {
|
|
||||||
setIsWapmup(true);
|
|
||||||
} else {
|
|
||||||
await defaultFunction();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const warmupLeft = warmupInfo.expiry - epoch.number;
|
setIsPending(true);
|
||||||
const amountRaw = notes
|
await redeem(
|
||||||
.filter(note => indexes.includes(note.id))
|
chainId,
|
||||||
.reduce((sum, note) => sum + note.payout._value, 0n);
|
address,
|
||||||
const amount = new DecimalBigNumber(amountRaw, 18);
|
isPayoutGhst,
|
||||||
|
indexes
|
||||||
const toExecute = async (receiver) => {
|
);
|
||||||
const txHash = await forceRedeem({ chainId, user: address, receiver, indexes });
|
await notesRefetch();
|
||||||
return txHash;
|
setIsPending(false);
|
||||||
}
|
|
||||||
|
|
||||||
breakoutFromBonding({ defaultFunction, toExecute, amount, warmupLeft })
|
|
||||||
}
|
}
|
||||||
setPendingIndexes([]);
|
|
||||||
setIsPending(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -108,6 +94,24 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
|
<Typography variant="h4" align="center" color="textSecondary">
|
||||||
|
Payout Options
|
||||||
|
</Typography>
|
||||||
|
<Tabs
|
||||||
|
centered
|
||||||
|
textColor="primary"
|
||||||
|
indicatorColor="primary"
|
||||||
|
value={isPayoutGhst ? 1 : 0}
|
||||||
|
aria-label="Payout token tabs"
|
||||||
|
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
|
||||||
|
TabIndicatorProps={{ style: { display: "none" } }}
|
||||||
|
>
|
||||||
|
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
|
||||||
|
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center">
|
<Box display="flex" justifyContent="center">
|
||||||
<Box display="flex" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}>
|
<Box display="flex" flexDirection="column" alignItems="center" mt="24px" width={isSmallScreen ? "100%" : "50%"}>
|
||||||
<Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}>
|
<Typography variant="h5" align="center" color="textSecondary" style={{ fontSize: "1.2rem" }}>
|
||||||
@ -120,139 +124,128 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
|||||||
? formatCurrency(totalClaimableBalance, 5, ghstSymbol)
|
? formatCurrency(totalClaimableBalance, 5, ghstSymbol)
|
||||||
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
|
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="subtitle1" align="center">
|
</Box>
|
||||||
{formatCurrency(totalClaimableBalance * ghstPrice, 2)}
|
|
||||||
</Typography>
|
<PrimaryButton
|
||||||
|
disabled={isPending || totalClaimableBalance._value === 0n}
|
||||||
|
fullWidth
|
||||||
|
className=""
|
||||||
|
onClick={() => onSubmit(notes.filter((note) => secondsTo > note.matured).map(note => note.id))}
|
||||||
|
>
|
||||||
|
Claim All
|
||||||
|
</PrimaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
disabled={isPending || totalClaimableBalance._value === 0n}
|
|
||||||
fullWidth
|
|
||||||
className=""
|
|
||||||
onClick={() => onSubmit(notes.filter((note) => secondsTo > note.matured).map(note => note.id))}
|
|
||||||
>
|
|
||||||
Claim All
|
|
||||||
</PrimaryButton>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box mt="48px">
|
<Box mt="48px">
|
||||||
{isSmallScreen ? (
|
{isSmallScreen ? (
|
||||||
<>
|
<>
|
||||||
{notes.map((note, index) => (
|
{notes.map((note, index) => (
|
||||||
<Box key={index} mt="32px">
|
<Box key={index} mt="32px">
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<TokenStack tokens={note.quoteToken.icons} />
|
<TokenStack tokens={note.quoteToken.icons} />
|
||||||
<Box ml="8px">
|
<Box ml="8px">
|
||||||
<Typography>{note.quoteToken.name}</Typography>
|
<Typography>{note.quoteToken.name}</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between" mt="16px">
|
<Box display="flex" justifyContent="space-between" mt="16px">
|
||||||
<Typography>Duration</Typography>
|
<Typography>Duration</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
<BondVesting vesting={note.vesting} />
|
<BondVesting vesting={note.vesting} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between" mt="8px">
|
<Box display="flex" justifyContent="space-between" mt="8px">
|
||||||
<Typography>Remaining</Typography>
|
<Typography>Remaining</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
|
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between" mt="8px">
|
<Box display="flex" justifyContent="space-between" mt="8px">
|
||||||
<Typography>Payout</Typography>
|
<Typography>Payout</Typography>
|
||||||
<Box display="flex" flexDirection="column" alignItems="flex-end">
|
|
||||||
<Typography>
|
<Typography>
|
||||||
{isPayoutGhst
|
{isPayoutGhst
|
||||||
? formatCurrency(note.payout, 5, ghstSymbol)
|
? formatCurrency(note.payout, 5, ghstSymbol)
|
||||||
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
|
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
|
</Box>
|
||||||
|
|
||||||
|
<Box mt="16px">
|
||||||
|
<TertiaryButton
|
||||||
|
fullWidth
|
||||||
|
disabled={isPending || secondsTo < note.matured}
|
||||||
|
onClick={() => onSubmit([note.id])}
|
||||||
|
>
|
||||||
|
Claim
|
||||||
|
</TertiaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
))}
|
||||||
<Box mt="16px">
|
</>
|
||||||
<TertiaryButton
|
) : (
|
||||||
fullWidth
|
<TableContainer>
|
||||||
disabled={isPending || secondsTo < note.matured}
|
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||||
loading={isPending && pendingIndexes.includes(note.id)}
|
<TableHead>
|
||||||
onClick={() => onSubmit([note.id])}
|
<TableRow>
|
||||||
>
|
<TableCell style={{ width: "180px", padding: "8px 0" }}>Bond</TableCell>
|
||||||
{isPending && pendingIndexes.includes(note.id) ? "Claiming" : "Claim"}
|
<TableCell style={{ padding: "8px 0" }}>Duration</TableCell>
|
||||||
</TertiaryButton>
|
<TableCell style={{ padding: "8px 0" }}>Remaining</TableCell>
|
||||||
</Box>
|
<TableCell style={{ padding: "8px 0" }}>Payout</TableCell>
|
||||||
</Box>
|
</TableRow>
|
||||||
))}
|
</TableHead>
|
||||||
</>
|
<TableBody>
|
||||||
) : (
|
{notes.map((note, index) => (
|
||||||
<TableContainer>
|
<TableRow key={index}>
|
||||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
<TableHead>
|
<Box display="flex" alignItems="center">
|
||||||
<TableRow>
|
<TokenStack tokens={note.quoteToken.icons} />
|
||||||
<TableCell style={{ width: "180px", padding: "8px 0" }}>Bond</TableCell>
|
<Box display="flex" flexDirection="column" ml="16px">
|
||||||
<TableCell style={{ padding: "8px 0" }}>Duration</TableCell>
|
<Typography>{note.quoteToken.name}</Typography>
|
||||||
<TableCell style={{ padding: "8px 0" }}>Remaining</TableCell>
|
</Box>
|
||||||
<TableCell style={{ padding: "8px 0" }}>Payout</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{notes.map((note, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell style={{ padding: "8px 0" }}>
|
|
||||||
<Box display="flex" alignItems="center">
|
|
||||||
<TokenStack tokens={note.quoteToken.icons} />
|
|
||||||
<Box display="flex" flexDirection="column" ml="16px">
|
|
||||||
<Typography>{note.quoteToken.name}</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</TableCell>
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell style={{ padding: "8px 0" }}>
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<BondVesting vesting={note.vesting} />
|
<BondVesting vesting={note.vesting} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell style={{ padding: "8px 0" }}>
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
|
<BondDuration msg="Matured" secondsTo={secondsTo} duration={note.matured} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell style={{ padding: "8px 0" }}>
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
<Box display="flex" flexDirection="column" alignItems="flex-start">
|
|
||||||
<Typography>
|
<Typography>
|
||||||
{isPayoutGhst
|
{isPayoutGhst
|
||||||
? formatCurrency(note.payout, 5, ghstSymbol)
|
? formatCurrency(note.payout, 5, ghstSymbol)
|
||||||
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
|
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">{formatCurrency(note.payout * ghstPrice, 2)}</Typography>
|
</TableCell>
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell style={{ padding: "8px 0" }}>
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
<TertiaryButton
|
<TertiaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={isPending || secondsTo < note.matured}
|
disabled={isPending || secondsTo < note.matured}
|
||||||
loading={isPending && pendingIndexes.includes(note.id)}
|
onClick={() => onSubmit([note.id])}
|
||||||
onClick={() => onSubmit([note.id])}
|
>
|
||||||
>
|
Claim
|
||||||
{isPending && pendingIndexes.includes(note.id) ? "Claiming" : "Claim"}
|
</TertiaryButton>
|
||||||
</TertiaryButton>
|
</TableCell>
|
||||||
</TableCell>
|
</TableRow>
|
||||||
</TableRow>
|
))}
|
||||||
))}
|
</TableBody>
|
||||||
</TableBody>
|
</Table>
|
||||||
</Table>
|
</TableContainer>
|
||||||
</TableContainer>
|
)}
|
||||||
)}
|
</Box>
|
||||||
</Box>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const WarmupConfirmModal = ({
|
|||||||
minHeight="150px"
|
minHeight="150px"
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
headerText={
|
headerText={
|
||||||
warmupLength <= 0
|
warmupLength < 0
|
||||||
? "Bond Notification"
|
? "Bond Notification"
|
||||||
: "Bond in Warm-up"
|
: "Bond in Warm-up"
|
||||||
}
|
}
|
||||||
@ -52,24 +52,24 @@ const WarmupConfirmModal = ({
|
|||||||
>
|
>
|
||||||
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
|
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
|
||||||
<Box display="flex" flexDirection="column">
|
<Box display="flex" flexDirection="column">
|
||||||
{warmupLength <= 0
|
{warmupLength < 0
|
||||||
? <FormControlLabel
|
? <FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
data-testid="acknowledge-warm-up"
|
data-testid="acknowledge-warmup"
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onChange={event => setIsChecked(event.target.checked)}
|
onChange={event => setIsChecked(event.target.checked)}
|
||||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={`I acknowledge that I am releasing warm-up funds for the bonding contract on behalf of the collective.`}
|
label={`I acknowledge that I am releasing warmup funds for the bonding contract on behalf of the collective.`}
|
||||||
/>
|
/>
|
||||||
: `Bonding address is in a warm-up period and cannot be claimed now. It'll be available for claim in ${warmupLength} epochs.`
|
: `Bonding address is in a warm-up period and cannot be claimed now. It'll be available for claim in ${warmupLength} epochs.`
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{warmupLength <= 0 && <PrimaryButton fullWidth disabled={!isChecked} onClick={onSubmit}>
|
{warmupLength < 0 && <PrimaryButton fullWidth disabled={!isChecked} onClick={onSubmit}>
|
||||||
Confirm
|
Confirm
|
||||||
</PrimaryButton>}
|
</PrimaryButton>}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,502 +0,0 @@
|
|||||||
import { useMemo, useState, useCallback, useEffect } from "react";
|
|
||||||
import { Box, Typography, Link, Checkbox, FormControlLabel, useTheme } from "@mui/material";
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { getBlockNumber } from "@wagmi/core";
|
|
||||||
import { useConfig } from "wagmi";
|
|
||||||
import { ss58Decode } from "@polkadot-labs/hdkd-helpers";
|
|
||||||
import { toHex } from "@polkadot-api/utils";
|
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
||||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
|
||||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
|
||||||
|
|
||||||
import Metric from "../../components/Metric/Metric";
|
|
||||||
import Modal from "../../components/Modal/Modal";
|
|
||||||
import SwapCard from "../../components/Swap/SwapCard";
|
|
||||||
import Token from "../../components/Token/Token";
|
|
||||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
|
||||||
import { PrimaryButton, SecondaryButton } from "../../components/Button";
|
|
||||||
import { GATEKEEPER_ADDRESSES, EMPTY_ADDRESS } from "../../constants/addresses";
|
|
||||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
|
||||||
|
|
||||||
import { useLocalStorage } from "../../hooks/localstorage";
|
|
||||||
import { useBreakoutModal } from "../../hooks/breakoutModal";
|
|
||||||
import { useTokenSymbol } from "../../hooks/tokens";
|
|
||||||
import { useEpoch, useGatekeeperApy, useGatekeeperAddress } from "../../hooks/staking";
|
|
||||||
import { useEvmNetwork, useCurrentIndex, useUnstableProvider } from "../../hooks/ghost";
|
|
||||||
import { formatNumber, shorten } from "../../helpers";
|
|
||||||
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
|
||||||
|
|
||||||
const BreakoutModal = ({ chainId, address }) => {
|
|
||||||
const [step, setStep] = useState(0);
|
|
||||||
const [receiver, setReceiver] = useState("");
|
|
||||||
const [convertedReceiver, setConvertedReceiver] = useState(undefined);
|
|
||||||
|
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
|
||||||
|
|
||||||
const evmNetwork = useEvmNetwork({ evmChainId: chainId });
|
|
||||||
|
|
||||||
const {
|
|
||||||
isOpened,
|
|
||||||
closeModal: closeModalInner,
|
|
||||||
isStakingOpened,
|
|
||||||
isClaimBondOpened,
|
|
||||||
warmupPeriod,
|
|
||||||
setActiveTxIndex,
|
|
||||||
defaultFunction,
|
|
||||||
executableFunction,
|
|
||||||
estimatedAmount
|
|
||||||
} = useBreakoutModal();
|
|
||||||
|
|
||||||
const incomingFee = useMemo(() => {
|
|
||||||
return new DecimalBigNumber(
|
|
||||||
evmNetwork ? evmNetwork.incoming_fee : 100000000,
|
|
||||||
7
|
|
||||||
);
|
|
||||||
}, [evmNetwork]);
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
setActiveTxIndex(-1);
|
|
||||||
closeModalPure();
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeModalPure = () => {
|
|
||||||
setStep(0);
|
|
||||||
setReceiver("");
|
|
||||||
closeModalInner();
|
|
||||||
}
|
|
||||||
|
|
||||||
const header = useMemo(() => {
|
|
||||||
if (isStakingOpened && warmupPeriod <= 0) return "Stake Warmed-up"
|
|
||||||
if (isClaimBondOpened && warmupPeriod <= 0) return "Bond Warmed-up"
|
|
||||||
if (isStakingOpened && warmupPeriod > 0) return "Stake in Warm-up"
|
|
||||||
if (isClaimBondOpened && warmupPeriod > 0) return "Bond in Warm-up"
|
|
||||||
}, [isStakingOpened, isClaimBondOpened, warmupPeriod]);
|
|
||||||
|
|
||||||
const bridgeNumbers = useMemo(() => {
|
|
||||||
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
|
|
||||||
const number = 1 + connectedNetworks * 3;
|
|
||||||
return `(${number}, ${number})`;
|
|
||||||
}, [chainId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
const [publicKey, prefix] = ss58Decode(receiver);
|
|
||||||
if (prefix !== 1995 && prefix !== 1996) {
|
|
||||||
throw new Error("bad prefix");
|
|
||||||
}
|
|
||||||
setConvertedReceiver(toHex(publicKey));
|
|
||||||
} catch {
|
|
||||||
setConvertedReceiver(undefined);
|
|
||||||
}
|
|
||||||
}, [receiver]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
headerContent={
|
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
|
||||||
<Typography variant="h4">{step === 0 ? header : step === 1 ? `Start ${bridgeNumbers} ${"Stake\u00B2"}` : "Bridge Confirmation"}</Typography>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
open={isOpened}
|
|
||||||
onClose={closeModal}
|
|
||||||
maxWidth="380px"
|
|
||||||
minHeight="200px"
|
|
||||||
>
|
|
||||||
<Box height="420px" display="flex" flexDirection="column" justifyContent="space-between">
|
|
||||||
{step === 0
|
|
||||||
? <WelcomeView
|
|
||||||
isStakingOpened={isStakingOpened}
|
|
||||||
chainId={chainId}
|
|
||||||
warmupPeriod={warmupPeriod}
|
|
||||||
ghstSymbol={ghstSymbol}
|
|
||||||
ftsoSymbol={ftsoSymbol}
|
|
||||||
bridgeNumbers={bridgeNumbers}
|
|
||||||
defaultFunction={defaultFunction}
|
|
||||||
goNext={() => setStep(1)}
|
|
||||||
closeModal={closeModal}
|
|
||||||
/>
|
|
||||||
: step === 1
|
|
||||||
? <BridgeView
|
|
||||||
receiver={receiver}
|
|
||||||
setReceiver={setReceiver}
|
|
||||||
chainId={chainId}
|
|
||||||
bridgeNumbers={bridgeNumbers}
|
|
||||||
ghstSymbol={ghstSymbol}
|
|
||||||
estimatedAmount={estimatedAmount}
|
|
||||||
incomingFee={incomingFee}
|
|
||||||
goNext={() => setStep(2)}
|
|
||||||
convertedReceiver={convertedReceiver}
|
|
||||||
setConvertedReceiver={setConvertedReceiver}
|
|
||||||
|
|
||||||
/>
|
|
||||||
: <ConfirmStep
|
|
||||||
chainId={chainId}
|
|
||||||
address={address}
|
|
||||||
receiver={receiver}
|
|
||||||
executableFunction={executableFunction}
|
|
||||||
isStakingOpened={isStakingOpened}
|
|
||||||
bridgeNumbers={bridgeNumbers}
|
|
||||||
incomingFee={incomingFee}
|
|
||||||
estimatedAmount={estimatedAmount}
|
|
||||||
ghstSymbol={ghstSymbol}
|
|
||||||
setActiveTxIndex={setActiveTxIndex}
|
|
||||||
closeModal={closeModalPure}
|
|
||||||
evmNetwork={evmNetwork}
|
|
||||||
convertedReceiver={convertedReceiver}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const BridgeView = ({
|
|
||||||
chainId,
|
|
||||||
receiver,
|
|
||||||
setReceiver,
|
|
||||||
convertedReceiver,
|
|
||||||
setConvertedReceiver,
|
|
||||||
bridgeNumbers,
|
|
||||||
ghstSymbol,
|
|
||||||
estimatedAmount,
|
|
||||||
goNext,
|
|
||||||
incomingFee
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const config = useConfig();
|
|
||||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
|
||||||
|
|
||||||
const chainExplorerUrl = useMemo(() => {
|
|
||||||
const client = config?.getClient();
|
|
||||||
return client?.chain?.blockExplorers?.default?.url;
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography>Bridge to start earning {bridgeNumbers} {"Stake\u00B2"} on your {ghstSymbol} balance:</Typography>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center">
|
|
||||||
<Typography variant="h5">{formatNumber(estimatedAmount, 5)} {ghstSymbol}</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Typography>
|
|
||||||
Generate a unique address for per tx with <Link underline="hover" href={GHOST_CONNECT} color={theme.colors.primary[300]}>GHOST Connect</Link> for privacy.
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<SwapCard
|
|
||||||
id={`bridge-token-receiver`}
|
|
||||||
inputWidth={"100%"}
|
|
||||||
value={convertedReceiver ? shorten(receiver, 15, -10) : receiver}
|
|
||||||
onChange={event => setReceiver(convertedReceiver ? "" : event.currentTarget.value)}
|
|
||||||
inputProps={{ "data-testid": "fromInput" }}
|
|
||||||
placeholder="GHOST address (sf prefixed)"
|
|
||||||
endString={convertedReceiver
|
|
||||||
? <GhostStyledIcon color="success" viewBox="0 0 25 25" component={CheckCircleIcon} />
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
type="text"
|
|
||||||
maxWidth="100%"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center" flexDirection="column" alignItems="center">
|
|
||||||
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between">
|
|
||||||
<Typography variant="body2">Gatekeeper</Typography>
|
|
||||||
<Link
|
|
||||||
fontSize="12px"
|
|
||||||
lineHeight="15px"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href={`${chainExplorerUrl}/token/${gatekeeperAddress}`}
|
|
||||||
>
|
|
||||||
<Typography variant="body2">
|
|
||||||
{shorten(gatekeeperAddress, 10, -8)}
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between">
|
|
||||||
<Typography variant="body2">Bridge Fee</Typography>
|
|
||||||
<Typography variant="body2">{formatNumber(incomingFee, 4)}%</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between">
|
|
||||||
<Typography variant="body2">Est. Time</Typography>
|
|
||||||
<Typography variant="body2">20 mins</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={goNext}
|
|
||||||
disabled={convertedReceiver === undefined}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
Proceed
|
|
||||||
</PrimaryButton>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const WelcomeView = ({
|
|
||||||
bridgeNumbers,
|
|
||||||
goNext,
|
|
||||||
isStakingOpened,
|
|
||||||
chainId,
|
|
||||||
warmupPeriod,
|
|
||||||
ghstSymbol,
|
|
||||||
ftsoSymbol,
|
|
||||||
defaultFunction,
|
|
||||||
closeModal
|
|
||||||
}) => {
|
|
||||||
const [refreshNeeded, setRefreshNeeded] = useState(false);
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
|
||||||
|
|
||||||
const { epoch } = useEpoch(chainId);
|
|
||||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
|
||||||
const { gatekeepedApy, apyInner } = useGatekeeperApy(chainId);
|
|
||||||
|
|
||||||
const { isExtensionMissing } = useUnstableProvider();
|
|
||||||
|
|
||||||
const refreshPage = () => window.location.reload();
|
|
||||||
const getConnect = () => {
|
|
||||||
setRefreshNeeded(true)
|
|
||||||
window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer');
|
|
||||||
}
|
|
||||||
|
|
||||||
const callDefaultFunction = useCallback(async () => {
|
|
||||||
setIsPending(true);
|
|
||||||
await defaultFunction()();
|
|
||||||
setIsPending(false);
|
|
||||||
closeModal();
|
|
||||||
}, [defaultFunction]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography>{warmupPeriod <= 0
|
|
||||||
? `You have succesfully warmed-up your ${isStakingOpened ? " " : "bonded "}${ftsoSymbol} ${isStakingOpened ? "(3, 3)" : "(1, 1)"} Staked at:`
|
|
||||||
: `${isStakingOpened ? "Stake" : "Bond"} is in warm-up${isStakingOpened ? "" : ", which extends with each purchase"}. Your ${ftsoSymbol} ${isStakingOpened ? "(3, 3)" : "(1, 1)"} is Staked at:`
|
|
||||||
}</Typography>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center">
|
|
||||||
<Typography variant="h5">{formatNumber(apyInner, 2)}% APY</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<SecondaryButton
|
|
||||||
onClick={() => callDefaultFunction()}
|
|
||||||
disabled={isPending || warmupPeriod > 0}
|
|
||||||
loading={isPending}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
{warmupPeriod > 0
|
|
||||||
? `Warm-up ends in ${prettifySecondsInDays(epoch.length * warmupPeriod)}`
|
|
||||||
: `${isPending ? "Claiming..." : "Claim"} ${isStakingOpened ? "(3, 3) Stake" : "(1, 1) Bond"}`
|
|
||||||
}
|
|
||||||
</SecondaryButton>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center" flexDirection="column" alignItems="center">
|
|
||||||
<hr style={{ width: "100%" }} />
|
|
||||||
<Typography variant="h5">OR</Typography>
|
|
||||||
<hr style={{ width: "100%" }} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box display="flex" flexDirection="column" justifyContent="space-between" gap="10px">
|
|
||||||
<Typography fontWeight="bold">Skip the Warm-up Now!</Typography>
|
|
||||||
<Typography>{`Bridge your ${ghstSymbol} to GHOST Chain and start ${bridgeNumbers} ${"Stake\u00B2"} at:`}</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center" flexDirection="column" alignItems="center">
|
|
||||||
<Typography variant="h5">{formatNumber(gatekeepedApy, 2)}% APY</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
disabled={isPending || gatekeeperAddress === EMPTY_ADDRESS}
|
|
||||||
onClick={isExtensionMissing
|
|
||||||
? (refreshNeeded ? refreshPage : getConnect)
|
|
||||||
: goNext
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
{isExtensionMissing
|
|
||||||
? (refreshNeeded ? "Refresh Page" : "Get GHOST Connect")
|
|
||||||
: `Start ${bridgeNumbers} ${"Stake\u00B2"}`}
|
|
||||||
</PrimaryButton>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConfirmStep = ({
|
|
||||||
chainId,
|
|
||||||
address,
|
|
||||||
receiver,
|
|
||||||
convertedReceiver,
|
|
||||||
executableFunction,
|
|
||||||
ghstSymbol,
|
|
||||||
bridgeNumbers,
|
|
||||||
estimatedAmount,
|
|
||||||
bridgingRisk,
|
|
||||||
incomingFee,
|
|
||||||
setActiveTxIndex,
|
|
||||||
closeModal,
|
|
||||||
evmNetwork
|
|
||||||
}) => {
|
|
||||||
const config = useConfig();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const currentSession = useCurrentIndex();
|
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
|
||||||
|
|
||||||
const [blockNumber, setBlockNumber] = useState(0n);
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
|
||||||
const [acknowledgeBridgingRisk, setAcknowledgeBridgingRisk] = useState(false);
|
|
||||||
const [acknowledgeWalletCustody, setAcknowledgeWalletCustody] = useState(false);
|
|
||||||
|
|
||||||
getBlockNumber(config).then(block => setBlockNumber(block));
|
|
||||||
|
|
||||||
const nativeSymbol = useMemo(() => config?.getClient()?.chain?.nativeCurrency?.symbol, [config]);
|
|
||||||
const networkName= useMemo(() => config?.getClient()?.chain?.name.toLowerCase(), [config]);
|
|
||||||
|
|
||||||
const receivedEstimation = useMemo(() => {
|
|
||||||
const decimals = incomingFee._decimals + 2;
|
|
||||||
const afterFee = new DecimalBigNumber(
|
|
||||||
BigInt(Math.pow(10, decimals) - incomingFee._value),
|
|
||||||
decimals
|
|
||||||
);
|
|
||||||
return estimatedAmount.mul(afterFee);
|
|
||||||
}, [incomingFee, estimatedAmount]);
|
|
||||||
|
|
||||||
const execute = useCallback(async () => {
|
|
||||||
setIsPending(true);
|
|
||||||
try {
|
|
||||||
const txHash = await executableFunction()(convertedReceiver);
|
|
||||||
if (txHash) {
|
|
||||||
const expectedSessionIndex = (currentSession ?? 0) + (evmNetwork
|
|
||||||
? Number((evmNetwork.avg_block_speed * evmNetwork.finality_delay) / (1000n * 14400n))
|
|
||||||
: 0);
|
|
||||||
|
|
||||||
const transaction = {
|
|
||||||
receiverAddress: receiver,
|
|
||||||
amount: estimatedAmount._value.toString(),
|
|
||||||
sessionIndex: expectedSessionIndex,
|
|
||||||
transactionHash: txHash,
|
|
||||||
blockNumber: Number(blockNumber),
|
|
||||||
chainId: chainId,
|
|
||||||
bridgeStability: 69, // TODO: avoid stability
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedTransactions = getStorageValue(chainId, address, "bridge-txs", []);
|
|
||||||
const newStoredTransactions = [transaction, ...storedTransactions];
|
|
||||||
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
|
|
||||||
|
|
||||||
setActiveTxIndex(0);
|
|
||||||
navigate(`${networkName}/bridge`);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsPending(false);
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
executableFunction,
|
|
||||||
convertedReceiver,
|
|
||||||
receiver,
|
|
||||||
networkName,
|
|
||||||
chainId,
|
|
||||||
address,
|
|
||||||
blockNumber,
|
|
||||||
evmNetwork,
|
|
||||||
currentSession,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
|
||||||
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="center" gap="10px">
|
|
||||||
<Metric label="To Bridge" metric={formatNumber(estimatedAmount, 5)} />
|
|
||||||
<Box width="100%" display="flex" flexDirection="column" justifyContent="center" alignItems="center">
|
|
||||||
<Token chainTokenName={nativeSymbol} name={"GHST"} sx={{ fontSize: "55px" }} />
|
|
||||||
<Typography>{ghstSymbol}</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<GhostStyledIcon sx={{ transform: "rotate(-90deg)" }} component={ArrowDropDownIcon} />
|
|
||||||
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="center" gap="10px">
|
|
||||||
<Metric label="To Receive" metric={formatNumber(receivedEstimation, 5)} />
|
|
||||||
<Box width="100%" display="flex" flexDirection="column" justifyContent="center" alignItems="center">
|
|
||||||
<Token name={"GHST"} sx={{ fontSize: "55px" }} />
|
|
||||||
<Typography>{ghstSymbol}</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Typography>{`You are bridging to GHOST Chain now to claim ${bridgeNumbers} ${"Stake\u00B2"} rewards.`}</Typography>
|
|
||||||
|
|
||||||
<hr style={{ width: "100%" }} />
|
|
||||||
|
|
||||||
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="left">
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
data-testid="acknowledge-breakout-warm-up"
|
|
||||||
checked={acknowledgeBridgingRisk}
|
|
||||||
onChange={event => setAcknowledgeBridgingRisk(event.target.checked)}
|
|
||||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
|
||||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Typography variant="body2">
|
|
||||||
{`I acknowledge decentralized bridging risk.`}
|
|
||||||
<Link
|
|
||||||
sx={{
|
|
||||||
margin: "0px",
|
|
||||||
font: "inherit",
|
|
||||||
letterSpacing: "inherit",
|
|
||||||
textDecoration: "underline",
|
|
||||||
textUnderlineOffset: "0.23rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
textDecorationThickness: "1px",
|
|
||||||
"&:hover": {
|
|
||||||
textDecoration: "underline",
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href="https://ghostchain.io/bridge-disclaimer"
|
|
||||||
>
|
|
||||||
Learn more.
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
data-testid="acknowledge-breakout-warm-up"
|
|
||||||
checked={acknowledgeWalletCustody}
|
|
||||||
onChange={event => setAcknowledgeWalletCustody(event.target.checked)}
|
|
||||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
|
||||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Typography variant="body2">
|
|
||||||
I confirm that recipient address is a self-custodial wallet, not an exchange, third party service, or smart-contract.
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={{ '& .MuiFormControlLabel-label': { textAlign: "justify" } }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => execute()}
|
|
||||||
loading={isPending}
|
|
||||||
disabled={isPending || !acknowledgeWalletCustody || !acknowledgeBridgingRisk}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
{isPending ? "Confirming..." : "I Confirm"}
|
|
||||||
</PrimaryButton>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BreakoutModal;
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
import { useEffect, useState, useMemo, useCallback } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -11,10 +10,9 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { decodeAddress } from "@polkadot/util-crypto";
|
import { decodeAddress } from "@polkadot/util-crypto";
|
||||||
import { fromHex } from "@polkadot-api/utils";
|
|
||||||
import { getBlockNumber } from "@wagmi/core";
|
import { getBlockNumber } from "@wagmi/core";
|
||||||
import { useTransaction } from "wagmi";
|
import { useTransaction } from "wagmi";
|
||||||
import { keccak256, decodeFunctionData } from "viem";
|
import { keccak256 } from "viem";
|
||||||
import { u32, u64, u128 } from "scale-ts";
|
import { u32, u64, u128 } from "scale-ts";
|
||||||
|
|
||||||
import PendingActionsIcon from '@mui/icons-material/PendingActions';
|
import PendingActionsIcon from '@mui/icons-material/PendingActions';
|
||||||
@ -24,7 +22,6 @@ import PageTitle from "../../components/PageTitle/PageTitle";
|
|||||||
import Paper from "../../components/Paper/Paper";
|
import Paper from "../../components/Paper/Paper";
|
||||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
|
|
||||||
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
|
|
||||||
import { networkAvgBlockSpeed } from "../../constants";
|
import { networkAvgBlockSpeed } from "../../constants";
|
||||||
import { timeConverter } from "../../helpers";
|
import { timeConverter } from "../../helpers";
|
||||||
|
|
||||||
@ -45,20 +42,16 @@ import {
|
|||||||
useCurrentSlot,
|
useCurrentSlot,
|
||||||
useGenesisSlot,
|
useGenesisSlot,
|
||||||
useErasTotalStake,
|
useErasTotalStake,
|
||||||
useLatestBlockNumber,
|
|
||||||
useEraIndex,
|
|
||||||
} from "../../hooks/ghost";
|
} from "../../hooks/ghost";
|
||||||
import { useLocalStorage } from "../../hooks/localstorage";
|
|
||||||
import { useBreakoutModal } from "../../hooks/breakoutModal";
|
|
||||||
|
|
||||||
import { ValidatorTable } from "./ValidatorTable";
|
import { ValidatorTable } from "./ValidatorTable";
|
||||||
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
|
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
|
||||||
import { BridgeHeader } from "./BridgeHeader";
|
import { BridgeHeader } from "./BridgeHeader";
|
||||||
import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard";
|
import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard";
|
||||||
|
|
||||||
const Bridge = ({ chainId, address, config, connect }) => {
|
const STORAGE_PREFIX = "storedTransactions"
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
|
const Bridge = ({ chainId, address, config, connect }) => {
|
||||||
const isBigScreen = useMediaQuery("(max-width: 980px)")
|
const isBigScreen = useMediaQuery("(max-width: 980px)")
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 540px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 540px)");
|
||||||
@ -66,20 +59,19 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
|
|
||||||
const [bridgeModalOpen, setBridgeModalOpen] = useState(false);
|
const [bridgeModalOpen, setBridgeModalOpen] = useState(false);
|
||||||
const [isConfirmed, setIsConfirmed] = useState(false);
|
const [isConfirmed, setIsConfirmed] = useState(false);
|
||||||
|
const [activeTxIndex, setActiveTxIndex] = useState(-1);
|
||||||
const [blockNumber, setBlockNumber] = useState(0n);
|
const [blockNumber, setBlockNumber] = useState(0n);
|
||||||
const [bridgeAction, setBridgeAction] = useState(true);
|
const [bridgeAction, setBridgeAction] = useState(true);
|
||||||
const [currentTime, setCurrentTime] = useState(Date.now());
|
const [currentTime, setCurrentTime] = useState(Date.now());
|
||||||
|
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
|
||||||
const { activeTxIndex, setActiveTxIndex } = useBreakoutModal();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
|
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [storedTransactions, setStoredTransactions] = useState(() =>
|
const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX);
|
||||||
getStorageValue(chainId, address, "bridge-txs", [])
|
const [storedTransactions, setStoredTransactions] = useState(
|
||||||
|
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
|
||||||
);
|
);
|
||||||
|
|
||||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
||||||
@ -105,33 +97,29 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hashedArguments = useMemo(() => {
|
const hashedArguments = useMemo(() => {
|
||||||
if (!watchTransaction) return undefined;
|
if (!watchTransaction) return undefined
|
||||||
|
|
||||||
const networkIdEncoded = u64.enc(BigInt(chainId));
|
const networkIdEncoded = u64.enc(BigInt(chainId));
|
||||||
const amountEncoded = u128.enc(BigInt(watchTransaction.amount));
|
const amountEncoded = u128.enc(BigInt(watchTransaction.amount));
|
||||||
const addressEncoded = decodeAddress(watchTransaction.receiverAddress, false, 1996);
|
const addressEncoded = decodeAddress(watchTransaction.receiverAddress, false, 1996);
|
||||||
const transactionHashEncoded = fromHex(watchTransaction.transactionHash);
|
|
||||||
const blockNumber = u64.enc(watchTransactionInfo?.blockNumber ?? 0n);
|
const blockNumber = u64.enc(watchTransactionInfo?.blockNumber ?? 0n);
|
||||||
|
|
||||||
const clapArgsStr = new Uint8Array([
|
const clapArgsStr = new Uint8Array([
|
||||||
...addressEncoded,
|
...addressEncoded,
|
||||||
...amountEncoded,
|
...amountEncoded,
|
||||||
...blockNumber,
|
...blockNumber,
|
||||||
...transactionHashEncoded,
|
|
||||||
...networkIdEncoded
|
...networkIdEncoded
|
||||||
]);
|
]);
|
||||||
return keccak256(clapArgsStr)
|
return keccak256(clapArgsStr)
|
||||||
}, [watchTransaction, watchTransactionInfo])
|
}, [watchTransaction, watchTransactionInfo])
|
||||||
|
|
||||||
const latestBlockNumber = useLatestBlockNumber();
|
|
||||||
const eraIndex = useEraIndex();
|
|
||||||
const currentSlot = useCurrentSlot();
|
const currentSlot = useCurrentSlot();
|
||||||
const genesisSlot = useGenesisSlot();
|
const genesisSlot = useGenesisSlot();
|
||||||
const currentSession = useCurrentIndex();
|
const currentSession = useCurrentIndex();
|
||||||
const applauseThreshold = useApplauseThreshold();
|
const applauseThreshold = useApplauseThreshold();
|
||||||
const evmNetwork = useEvmNetwork({ evmChainId: chainId });
|
const evmNetwork = useEvmNetwork({ evmChainId: chainId });
|
||||||
const totalStakedAmount = useErasTotalStake({
|
const totalStakedAmount = useErasTotalStake({
|
||||||
epochIndex: eraIndex?.index ?? 0,
|
eraIndex: Math.floor((watchTransaction?.sessionIndex ?? currentSession) / 6)
|
||||||
});
|
});
|
||||||
const authorities = useAuthorities({
|
const authorities = useAuthorities({
|
||||||
currentSession: watchTransaction?.sessionIndex ?? currentSession
|
currentSession: watchTransaction?.sessionIndex ?? currentSession
|
||||||
@ -161,8 +149,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/bridge" });
|
||||||
}, [location]);
|
}, []);
|
||||||
|
|
||||||
const chainExplorerUrl = useMemo(() => {
|
const chainExplorerUrl = useMemo(() => {
|
||||||
const client = config?.getClient();
|
const client = config?.getClient();
|
||||||
@ -187,8 +175,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
return sum + countOnesInBigInt(bigIntValue);
|
return sum + countOnesInBigInt(bigIntValue);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const storedBlockNumber = watchTransactionInfo ? Number(watchTransactionInfo.blockNumber) : 0;
|
const finalization = Math.max(0, (finalityDelay + watchTransaction.blockNumber) - Number(blockNumber));
|
||||||
const finalization = Math.max(0, (finalityDelay + storedBlockNumber) - Number(blockNumber));
|
|
||||||
const applaused = transactionApplaused?.finalized ?? false;
|
const applaused = transactionApplaused?.finalized ?? false;
|
||||||
const clappedAmount = transactionApplaused?.clapped_amount ?? 0n;
|
const clappedAmount = transactionApplaused?.clapped_amount ?? 0n;
|
||||||
const clappedPercentage = clappedAmount * 100n / (totalStakedAmount ?? 1n);
|
const clappedPercentage = clappedAmount * 100n / (totalStakedAmount ?? 1n);
|
||||||
@ -204,10 +191,9 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
clapsPercentage,
|
clapsPercentage,
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
watchTransaction,
|
|
||||||
watchTransactionInfo,
|
|
||||||
transactionApplaused,
|
transactionApplaused,
|
||||||
finalityDelay,
|
finalityDelay,
|
||||||
|
watchTransaction,
|
||||||
blockNumber,
|
blockNumber,
|
||||||
totalStakedAmount,
|
totalStakedAmount,
|
||||||
authorities
|
authorities
|
||||||
@ -219,20 +205,16 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
|
|
||||||
const latestCommits = useMemo(() => {
|
const latestCommits = useMemo(() => {
|
||||||
return validators?.map((validator, index) => {
|
return validators?.map((validator, index) => {
|
||||||
const lastUpdatedNumber = Number(blockCommitments?.at(index)?.last_updated ?? 0);
|
|
||||||
const timestampDelta = (latestBlockNumber - lastUpdatedNumber) * 6000; // ideal 6 seconds for block
|
|
||||||
const lastUpdatedTimestamp = Math.floor(currentTime - timestampDelta);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
validator: validator,
|
validator: validator,
|
||||||
lastActive: timestampDelta,
|
lastActive: currentTime - Number(blockCommitments?.at(index)?.last_updated ?? 0),
|
||||||
lastUpdated: lastUpdatedTimestamp,
|
lastUpdated: blockCommitments?.at(index)?.last_updated,
|
||||||
lastStoredBlock: blockCommitments?.at(index)?.last_stored_block,
|
lastStoredBlock: blockCommitments?.at(index)?.last_stored_block,
|
||||||
storedBlockTime: (blockCommitments?.at(index)?.last_stored_block ?? 0n) * networkAvgBlockSpeed(chainId),
|
storedBlockTime: (blockCommitments?.at(index)?.last_stored_block ?? 0n) * networkAvgBlockSpeed(chainId),
|
||||||
disabled: disabledValidators?.includes(index),
|
disabled: disabledValidators?.includes(index),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [blockCommitments, disabledValidators, validators, latestBlockNumber, chainId]);
|
}, [blockCommitments, disabledValidators, validators, chainId]);
|
||||||
|
|
||||||
const latestUpdate = useMemo(() => {
|
const latestUpdate = useMemo(() => {
|
||||||
const validCommits = latestCommits?.filter(commit =>
|
const validCommits = latestCommits?.filter(commit =>
|
||||||
@ -288,7 +270,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
const removeStoredRecord = useCallback(() => {
|
const removeStoredRecord = useCallback(() => {
|
||||||
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
|
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
|
||||||
setStoredTransactions(newStoredTransactions);
|
setStoredTransactions(newStoredTransactions);
|
||||||
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
|
localStorage.setItem(storagePrefix, JSON.stringify(newStoredTransactions));
|
||||||
setActiveTxIndex(-1);
|
setActiveTxIndex(-1);
|
||||||
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
|
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
|
||||||
|
|
||||||
@ -314,8 +296,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
|||||||
|
|
||||||
const newStoredTransactions = [transaction, ...storedTransactions];
|
const newStoredTransactions = [transaction, ...storedTransactions];
|
||||||
setStoredTransactions(newStoredTransactions);
|
setStoredTransactions(newStoredTransactions);
|
||||||
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
|
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
|
||||||
setActiveTxIndex(0);
|
setActiveTxIndex(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -246,7 +246,7 @@ export const BridgeCardAction = ({
|
|||||||
loading={isPending}
|
loading={isPending}
|
||||||
onClick={() => ghostOrConnect()}
|
onClick={() => ghostOrConnect()}
|
||||||
>
|
>
|
||||||
{address === "" ? "Connect" : isPending ? "Bridging..." : "Bridge" }
|
{address === "" ? "Connect" : "Bridge" }
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
@ -307,7 +307,7 @@ export const BridgeCardHistory = ({
|
|||||||
<Box display="flex" flexDirection="column" justifyContent="center">
|
<Box display="flex" flexDirection="column" justifyContent="center">
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
{formatCurrency(
|
{formatCurrency(
|
||||||
new DecimalBigNumber(BigInt(obj.amount ?? "0"), 18).toString(),
|
new DecimalBigNumber(BigInt(obj.amount), 18).toString(),
|
||||||
isSemiSmallScreen ? 3 : 8,
|
isSemiSmallScreen ? 3 : 8,
|
||||||
ghstSymbol
|
ghstSymbol
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import { Box, Typography, Link, FormControlLabel, Checkbox, useTheme } from "@mui/material";
|
import { Box, Typography, Link, FormControlLabel, Checkbox, useTheme } from "@mui/material";
|
||||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||||
@ -19,12 +19,10 @@ import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
|||||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
import Modal from "../../components/Modal/Modal";
|
import Modal from "../../components/Modal/Modal";
|
||||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||||
import { PrimaryButton, TertiaryButton, SecondaryButton } from "../../components/Button";
|
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
||||||
|
|
||||||
import { formatCurrency } from "../../helpers";
|
import { formatCurrency } from "../../helpers";
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
|
|
||||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
|
||||||
|
|
||||||
export const BridgeModal = ({
|
export const BridgeModal = ({
|
||||||
providerDetail,
|
providerDetail,
|
||||||
@ -52,12 +50,6 @@ export const BridgeModal = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const bridgeNumbers = () => {
|
|
||||||
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
|
|
||||||
const number = 1 + connectedNetworks * 3;
|
|
||||||
return `(${number}, ${number})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
data-testid="transaction-details-modal"
|
data-testid="transaction-details-modal"
|
||||||
@ -97,36 +89,15 @@ export const BridgeModal = ({
|
|||||||
minHeight={"100px"}
|
minHeight={"100px"}
|
||||||
>
|
>
|
||||||
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
|
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
|
||||||
{!providerDetail && <Box
|
{!providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||||
width="90%"
|
<TertiaryButton
|
||||||
display="flex"
|
|
||||||
flexDirection="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
backgroundColor="#1f4771"
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '130px',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SecondaryButton
|
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ marginTop: "0 !important", marginBottom: "0 !important" }}
|
onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}
|
||||||
onClick={() => window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}
|
|
||||||
>
|
>
|
||||||
Get GHOST Connect
|
Get GHOST Connect
|
||||||
</SecondaryButton>
|
</TertiaryButton>
|
||||||
</Box>}
|
</Box>}
|
||||||
<Box
|
{providerDetail && <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||||
display="flex"
|
|
||||||
flexDirection="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
sx={{ filter: providerDetail ? '' : 'blur(5px)' }}
|
|
||||||
>
|
|
||||||
{currentRecord?.finalization > 0 && (
|
{currentRecord?.finalization > 0 && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -314,7 +285,7 @@ export const BridgeModal = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>}
|
||||||
|
|
||||||
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
|
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
|
||||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
@ -369,7 +340,7 @@ export const BridgeModal = ({
|
|||||||
<Typography variant="body2">Bridged Amount:</Typography>
|
<Typography variant="body2">Bridged Amount:</Typography>
|
||||||
<Typography variant="body2">{formatCurrency(
|
<Typography variant="body2">{formatCurrency(
|
||||||
new DecimalBigNumber(
|
new DecimalBigNumber(
|
||||||
BigInt(currentRecord && currentRecord.amount ? currentRecord.amount : "0"),
|
BigInt(currentRecord ? currentRecord.amount : "0"),
|
||||||
18
|
18
|
||||||
).toString(), 9, ghstSymbol)
|
).toString(), 9, ghstSymbol)
|
||||||
}</Typography>
|
}</Typography>
|
||||||
@ -386,21 +357,13 @@ export const BridgeModal = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box display="flex" flexDirection="column" gap="5px">
|
<Box display="flex" flexDirection="column" gap="5px">
|
||||||
{currentRecord && currentRecord.finalization < 1 && currentRecord.applaused && <PrimaryButton
|
<PrimaryButton
|
||||||
fullWidth
|
|
||||||
loading={false}
|
|
||||||
onClick={() => removeStoredRecord()}
|
|
||||||
>
|
|
||||||
{`Get ${bridgeNumbers()} Stake\u00B2`}
|
|
||||||
</PrimaryButton>}
|
|
||||||
|
|
||||||
<TertiaryButton
|
|
||||||
fullWidth
|
fullWidth
|
||||||
loading={false}
|
loading={false}
|
||||||
onClick={() => removeStoredRecord()}
|
onClick={() => removeStoredRecord()}
|
||||||
>
|
>
|
||||||
Erase Record
|
Erase Record
|
||||||
</TertiaryButton>
|
</PrimaryButton>
|
||||||
|
|
||||||
<Typography variant="body2" sx={{ fontStyle: "italic" }}>
|
<Typography variant="body2" sx={{ fontStyle: "italic" }}>
|
||||||
This will permanently remove the bridge transaction record from the session storage, but it will not cancel the bridge transaction.
|
This will permanently remove the bridge transaction record from the session storage, but it will not cancel the bridge transaction.
|
||||||
@ -436,12 +399,6 @@ export const BridgeConfirmModal = ({
|
|||||||
>
|
>
|
||||||
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
|
<Box gap="20px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
|
||||||
<Box width="100%" display="flex" flexDirection="column" alignItems="start">
|
<Box width="100%" display="flex" flexDirection="column" alignItems="start">
|
||||||
<Box>
|
|
||||||
<Typography variant="subtitle1">
|
|
||||||
You are bridging to GHOST Chain. We will guide you towards (10, 10) Stake<sup>2</sup> rewards right after that.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<hr style={{ margin: "10px 0", width: "100%" }} />
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -477,7 +434,7 @@ export const BridgeConfirmModal = ({
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<hr style={{ margin: "10px 0", width: "100%" }} />
|
<hr style={{ margin: "10px 0", width: "100%" }} />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@ -25,8 +25,6 @@ import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
|||||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
import { PrimaryButton } from "../../components/Button";
|
import { PrimaryButton } from "../../components/Button";
|
||||||
|
|
||||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
|
||||||
|
|
||||||
export const ValidatorTable = ({
|
export const ValidatorTable = ({
|
||||||
currentTime,
|
currentTime,
|
||||||
currentBlock,
|
currentBlock,
|
||||||
@ -110,7 +108,7 @@ export const ValidatorTable = ({
|
|||||||
<Typography sx={{ textAlign: "center" }} variant="h6">GHOST Connect is not detected on your browser!</Typography>
|
<Typography sx={{ textAlign: "center" }} variant="h6">GHOST Connect is not detected on your browser!</Typography>
|
||||||
<Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Connect browser extension for real-time visibility into validator status and related transaction risks.</Typography>
|
<Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Connect browser extension for real-time visibility into validator status and related transaction risks.</Typography>
|
||||||
<Typography sx={{ textAlign: "center" }} variant="body2"><b>Important:</b> The GHOST Connect is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators.</Typography>
|
<Typography sx={{ textAlign: "center" }} variant="body2"><b>Important:</b> The GHOST Connect is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators.</Typography>
|
||||||
<PrimaryButton onClick={() => window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}>
|
<PrimaryButton onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}>
|
||||||
Get GHOST Connect
|
Get GHOST Connect
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -10,10 +10,9 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import { useEffect, useMemo, useState, useCallback } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams, useLocation, useSearchParams } from "react-router-dom";
|
import { useParams, useLocation, useSearchParams } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { isAddress } from "viem";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
@ -29,23 +28,18 @@ import {
|
|||||||
UNISWAP_V2_FACTORY,
|
UNISWAP_V2_FACTORY,
|
||||||
RESERVE_ADDRESSES,
|
RESERVE_ADDRESSES,
|
||||||
FTSO_ADDRESSES,
|
FTSO_ADDRESSES,
|
||||||
EMPTY_ADDRESS,
|
|
||||||
WETH_ADDRESSES,
|
|
||||||
} from "../../constants/addresses";
|
} from "../../constants/addresses";
|
||||||
import { useLocalStorage } from "../../hooks/localstorage";
|
|
||||||
import { useTokenSymbol } from "../../hooks/tokens";
|
import { useTokenSymbol } from "../../hooks/tokens";
|
||||||
import { getTokenAddress } from "../../hooks/helpers";
|
|
||||||
|
|
||||||
import PoolContainer from "./PoolContainer";
|
import PoolContainer from "./PoolContainer";
|
||||||
import SwapContainer from "./SwapContainer";
|
import SwapContainer from "./SwapContainer";
|
||||||
import TokenModal from "./TokenModal";
|
import TokenModal from "./TokenModal";
|
||||||
|
|
||||||
const Dex = ({ chainId, address, connect, config }) => {
|
const Dex = ({ chainId, address, connect }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const pathname = useParams();
|
const pathname = useParams();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
@ -58,50 +52,15 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
const [topTokenListOpen, setTopTokenListOpen] = useState(false);
|
const [topTokenListOpen, setTopTokenListOpen] = useState(false);
|
||||||
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
|
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
|
||||||
|
|
||||||
const [secondsToWait, setSecondsToWait] = useState(() => getStorageValue(chainId, address, "dex-deadline", "60"));
|
const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
|
||||||
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "dex-slippage", "5"));
|
const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
|
||||||
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "dex-decimals", "5"));
|
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
|
||||||
const [actualDestinationAddress, setActualDestinationAddress] = useState(() => getStorageValue(chainId, address, "dex-destination", address));
|
|
||||||
const [destinationAddress, setDestinationAddress] = useState(actualDestinationAddress);
|
|
||||||
|
|
||||||
const [tokenAddressTop, setTokenAddressTop] = useState(EMPTY_ADDRESS);
|
const [tokenAddressTop, setTokenAddressTop] = useState(RESERVE_ADDRESSES[chainId]);
|
||||||
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
|
const [tokenAddressBottom, setTokenAddressBottom] = useState(FTSO_ADDRESSES[chainId]);
|
||||||
|
|
||||||
const { symbol: tokenNameTopInner } = useTokenSymbol(chainId, tokenAddressTop);
|
const { symbol: tokenNameTop } = useTokenSymbol(chainId, tokenAddressTop);
|
||||||
const { symbol: tokenNameBottomInner } = useTokenSymbol(chainId, tokenAddressBottom);
|
const { symbol: tokenNameBottom } = useTokenSymbol(chainId, tokenAddressBottom);
|
||||||
|
|
||||||
const chainSymbol = useMemo(() => {
|
|
||||||
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
|
||||||
if (chainSymbol) return chainSymbol;
|
|
||||||
return "WTF";
|
|
||||||
}, [config, chainId])
|
|
||||||
|
|
||||||
const tokenNameTop = useMemo(() => {
|
|
||||||
if (chainSymbol && tokenAddressTop === EMPTY_ADDRESS) {
|
|
||||||
return chainSymbol;
|
|
||||||
}
|
|
||||||
return tokenNameTopInner;
|
|
||||||
}, [tokenAddressTop, tokenNameTopInner, chainSymbol]);
|
|
||||||
|
|
||||||
const tokenNameBottom = useMemo(() => {
|
|
||||||
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
|
||||||
if (chainSymbol && tokenAddressBottom === EMPTY_ADDRESS) {
|
|
||||||
return chainSymbol;
|
|
||||||
}
|
|
||||||
return tokenNameBottomInner;
|
|
||||||
}, [tokenAddressBottom, tokenNameBottomInner, config]);
|
|
||||||
|
|
||||||
const isWrapping = useMemo(() => {
|
|
||||||
const isNative = tokenAddressTop === EMPTY_ADDRESS;
|
|
||||||
const isWrappedNative = tokenAddressBottom === WETH_ADDRESSES[chainId];
|
|
||||||
return isNative && isWrappedNative;
|
|
||||||
}, [chainId, tokenAddressTop, tokenAddressBottom]);
|
|
||||||
|
|
||||||
const isUnwrapping = useMemo(() => {
|
|
||||||
const isWrappedNative = tokenAddressTop === WETH_ADDRESSES[chainId];
|
|
||||||
const isNative = tokenAddressBottom === EMPTY_ADDRESS;
|
|
||||||
return isNative && isWrappedNative;
|
|
||||||
}, [chainId, tokenAddressTop, tokenAddressBottom]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentQueryParameters.has("pool")) {
|
if (currentQueryParameters.has("pool")) {
|
||||||
@ -116,8 +75,8 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
setTokenAddressTop(currentQueryParameters.get("from"));
|
setTokenAddressTop(currentQueryParameters.get("from"));
|
||||||
newQueryParameters.set("from", currentQueryParameters.get("from"));
|
newQueryParameters.set("from", currentQueryParameters.get("from"));
|
||||||
} else {
|
} else {
|
||||||
setTokenAddressTop(EMPTY_ADDRESS);
|
setTokenAddressTop(RESERVE_ADDRESSES[chainId]);
|
||||||
newQueryParameters.set("from", EMPTY_ADDRESS);
|
newQueryParameters.set("from", RESERVE_ADDRESSES[chainId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentQueryParameters.has("to")) {
|
if (currentQueryParameters.has("to")) {
|
||||||
@ -133,7 +92,7 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname + location.search });
|
ReactGA.send({ hitType: "pageview", page: location.pathname + location.search });
|
||||||
}, [location]);
|
}, [location])
|
||||||
|
|
||||||
const dexAddresses = {
|
const dexAddresses = {
|
||||||
router: UNISWAP_V2_ROUTER[chainId],
|
router: UNISWAP_V2_ROUTER[chainId],
|
||||||
@ -154,7 +113,7 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const changeSwapTab = (swap) => {
|
const changeSwapTab = (swap) => {
|
||||||
if (swap || (isWrapping || isUnwrapping)) newQueryParameters.delete("pool");
|
if (swap) newQueryParameters.delete("pool");
|
||||||
else newQueryParameters.set("pool", true);
|
else newQueryParameters.set("pool", true);
|
||||||
newQueryParameters.set("from", currentQueryParameters.get("from"));
|
newQueryParameters.set("from", currentQueryParameters.get("from"));
|
||||||
newQueryParameters.set("to", currentQueryParameters.get("to"));
|
newQueryParameters.set("to", currentQueryParameters.get("to"));
|
||||||
@ -183,38 +142,24 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
setSearchParams(newQueryParameters);
|
setSearchParams(newQueryParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
const setSlippageInner = useCallback((value) => {
|
const setSlippageInner = (value) => {
|
||||||
const maybeValue = parseFloat(value);
|
const maybeValue = parseFloat(value);
|
||||||
if (!maybeValue || parseFloat(value) <= 100) {
|
if (!maybeValue || parseFloat(value) <= 100) {
|
||||||
setSlippage(value);
|
setSlippage(value);
|
||||||
setStorageValue(chainId, address, "dex-slippage", value);
|
localStorage.setItem("dex-slippage", value);
|
||||||
}
|
}
|
||||||
}, [chainId, address]);
|
}
|
||||||
|
|
||||||
const setSecondsToWaitInner = useCallback((value) => {
|
const setSecondsToWaitInner = (value) => {
|
||||||
|
localStorage.setItem("dex-deadline", value);
|
||||||
setSecondsToWait(value);
|
setSecondsToWait(value);
|
||||||
setStorageValue(chainId, address, "dex-deadline", value);
|
}
|
||||||
}, [chainId, address]);
|
|
||||||
|
|
||||||
const setFormatDecimalsInner = useCallback((value) => {
|
const setFormatDecimalsInner = (value) => {
|
||||||
if (Number(value) <= 17) {
|
if (Number(value) <= 17) {
|
||||||
|
localStorage.setItem("dex-decimals", value);
|
||||||
setFormatDecimals(value);
|
setFormatDecimals(value);
|
||||||
setStorageValue(chainId, address, "dex-decimals", value);
|
|
||||||
}
|
}
|
||||||
}, [chainId, address]);
|
|
||||||
|
|
||||||
const setDestinationAddressInner = useCallback((value) => {
|
|
||||||
const cleanedValue = value.trim();
|
|
||||||
setDestinationAddress(value);
|
|
||||||
if (isAddress(cleanedValue)) {
|
|
||||||
setActualDestinationAddress(value);
|
|
||||||
setStorageValue(chainId, address, "dex-destination", value);
|
|
||||||
}
|
|
||||||
}, [chainId, address]);
|
|
||||||
|
|
||||||
const handleCloseSetting = () => {
|
|
||||||
setDestinationAddress(undefined);
|
|
||||||
handleSettingsOpen(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -254,11 +199,11 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Modal
|
<Modal
|
||||||
maxWidth="450px"
|
maxWidth="376px"
|
||||||
minHeight="200px"
|
minHeight="200px"
|
||||||
open={settingsOpen}
|
open={settingsOpen}
|
||||||
headerText={"Settings"}
|
headerText={"Settings"}
|
||||||
onClose={() => handleCloseSetting()}
|
onClose={() => handleSettingsOpen(false)}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<InputLabel htmlFor="slippage">Slippage</InputLabel>
|
<InputLabel htmlFor="slippage">Slippage</InputLabel>
|
||||||
@ -322,35 +267,9 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box mt="32px">
|
|
||||||
<InputLabel htmlFor="recipient">
|
|
||||||
{`${actualDestinationAddress ? "Custom" : "Default"} destination address`}
|
|
||||||
</InputLabel>
|
|
||||||
<Box mt="8px">
|
|
||||||
<FormControl variant="outlined" color="primary" fullWidth>
|
|
||||||
<OutlinedInput
|
|
||||||
inputProps={{ "data-testid": "decimals-to-wait" }}
|
|
||||||
type="text"
|
|
||||||
id="destination-to-wait"
|
|
||||||
value={destinationAddress
|
|
||||||
? destinationAddress
|
|
||||||
: actualDestinationAddress ?? address
|
|
||||||
}
|
|
||||||
onChange={event => setDestinationAddressInner(event.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
<Box mt="8px">
|
|
||||||
<Typography variant="body2" color="textSecondary">
|
|
||||||
Recipient address of swapped assets and liquidity tokens
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<TokenModal
|
<TokenModal
|
||||||
chainSymbol={chainSymbol}
|
|
||||||
account={address}
|
account={address}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
listOpen={topTokenListOpen}
|
listOpen={topTokenListOpen}
|
||||||
@ -358,7 +277,6 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
setTokenAddress={setInnerTokenAddressTop}
|
setTokenAddress={setInnerTokenAddressTop}
|
||||||
/>
|
/>
|
||||||
<TokenModal
|
<TokenModal
|
||||||
chainSymbol={chainSymbol}
|
|
||||||
account={address}
|
account={address}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
listOpen={bottomTokenListOpen}
|
listOpen={bottomTokenListOpen}
|
||||||
@ -404,14 +322,11 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
dexAddresses={dexAddresses}
|
dexAddresses={dexAddresses}
|
||||||
connect={connect}
|
connect={connect}
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
destination={actualDestinationAddress ? actualDestinationAddress : address}
|
|
||||||
secondsToWait={secondsToWait}
|
secondsToWait={secondsToWait}
|
||||||
setTopTokenListOpen={setTopTokenListOpen}
|
setTopTokenListOpen={setTopTokenListOpen}
|
||||||
setBottomTokenListOpen={setBottomTokenListOpen}
|
setBottomTokenListOpen={setBottomTokenListOpen}
|
||||||
setIsSwap={setIsSwap}
|
setIsSwap={setIsSwap}
|
||||||
formatDecimals={formatDecimals}
|
formatDecimals={formatDecimals}
|
||||||
isWrapping={isWrapping}
|
|
||||||
isUnwrapping={isUnwrapping}
|
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<PoolContainer
|
<PoolContainer
|
||||||
@ -423,7 +338,6 @@ const Dex = ({ chainId, address, connect, config }) => {
|
|||||||
dexAddresses={dexAddresses}
|
dexAddresses={dexAddresses}
|
||||||
connect={connect}
|
connect={connect}
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
destination={actualDestinationAddress ? actualDestinationAddress : address}
|
|
||||||
secondsToWait={secondsToWait}
|
secondsToWait={secondsToWait}
|
||||||
setTopTokenListOpen={setTopTokenListOpen}
|
setTopTokenListOpen={setTopTokenListOpen}
|
||||||
setBottomTokenListOpen={setBottomTokenListOpen}
|
setBottomTokenListOpen={setBottomTokenListOpen}
|
||||||
|
|||||||
@ -9,15 +9,10 @@ import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenA
|
|||||||
import { SecondaryButton } from "../../components/Button";
|
import { SecondaryButton } from "../../components/Button";
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { formatNumber, formatCurrency, bigIntSqrt } from "../../helpers";
|
import { formatNumber, formatCurrency } from "../../helpers";
|
||||||
|
|
||||||
import { useBalance, useTotalSupply } from "../../hooks/tokens";
|
import { useBalance, useTotalSupply } from "../../hooks/tokens";
|
||||||
import {
|
import { useUniswapV2Pair, useUniswapV2PairReserves, addLiquidity } from "../../hooks/uniswapv2";
|
||||||
useUniswapV2Pair,
|
|
||||||
useUniswapV2PairReserves,
|
|
||||||
addLiquidity,
|
|
||||||
addLiquidityETH,
|
|
||||||
} from "../../hooks/uniswapv2";
|
|
||||||
|
|
||||||
const PoolContainer = ({
|
const PoolContainer = ({
|
||||||
tokenNameTop,
|
tokenNameTop,
|
||||||
@ -28,11 +23,10 @@ const PoolContainer = ({
|
|||||||
dexAddresses,
|
dexAddresses,
|
||||||
connect,
|
connect,
|
||||||
slippage,
|
slippage,
|
||||||
destination,
|
|
||||||
secondsToWait,
|
secondsToWait,
|
||||||
setTopTokenListOpen,
|
setTopTokenListOpen,
|
||||||
setBottomTokenListOpen,
|
setBottomTokenListOpen,
|
||||||
formatDecimals,
|
formatDecimals
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
||||||
@ -45,13 +39,11 @@ const PoolContainer = ({
|
|||||||
balance: balanceTop,
|
balance: balanceTop,
|
||||||
refetch: balanceRefetchTop,
|
refetch: balanceRefetchTop,
|
||||||
contractAddress: addressTop,
|
contractAddress: addressTop,
|
||||||
isNative: topIsNative,
|
|
||||||
} = useBalance(chainId, tokenNameTop, address);
|
} = useBalance(chainId, tokenNameTop, address);
|
||||||
const {
|
const {
|
||||||
balance: balanceBottom,
|
balance: balanceBottom,
|
||||||
refetch: balanceRefetchBottom,
|
refetch: balanceRefetchBottom,
|
||||||
contractAddress: addressBottom,
|
contractAddress: addressBottom,
|
||||||
isNative: bottomIsNative,
|
|
||||||
} = useBalance(chainId, tokenNameBottom, address);
|
} = useBalance(chainId, tokenNameBottom, address);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -83,6 +75,34 @@ const PoolContainer = ({
|
|||||||
const setMaxTop = () => setAmountTop(balanceTop.toString());
|
const setMaxTop = () => setAmountTop(balanceTop.toString());
|
||||||
const setMaxBottom = () => setAmountBottom(balanceBottom.toString());
|
const setMaxBottom = () => setAmountBottom(balanceBottom.toString());
|
||||||
|
|
||||||
|
const bigIntSqrt = (n) => {
|
||||||
|
if (n < 0n) {
|
||||||
|
throw new Error("Cannot compute the square root of a negative number.");
|
||||||
|
}
|
||||||
|
if (n < 2n) {
|
||||||
|
return n; // The square root of 0 or 1 is the number itself
|
||||||
|
}
|
||||||
|
|
||||||
|
let low = 0n;
|
||||||
|
let high = n;
|
||||||
|
let mid;
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
mid = (low + high) / 2n;
|
||||||
|
const midSquared = mid * mid;
|
||||||
|
|
||||||
|
if (midSquared === n) {
|
||||||
|
return mid; // Found the exact square root
|
||||||
|
} else if (midSquared < n) {
|
||||||
|
low = mid + 1n; // Move to the right half
|
||||||
|
} else {
|
||||||
|
high = mid - 1n; // Move to the left half
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return high; // The integer part of the square root
|
||||||
|
}
|
||||||
|
|
||||||
const estimatedAmountOut = useMemo(() => {
|
const estimatedAmountOut = useMemo(() => {
|
||||||
const pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
|
const pairReserves0 = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
|
||||||
? pairReserves.reserve0
|
? pairReserves.reserve0
|
||||||
@ -131,40 +151,14 @@ const PoolContainer = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves]);
|
}, [addressBottom, balanceTop, balanceBottom, amountTop, amountBottom, tokenAddresses, pairReserves])
|
||||||
|
|
||||||
const poolShares = useMemo(() => {
|
|
||||||
const hundred = new DecimalBigNumber(1n, 2);
|
|
||||||
if (lpTotalSupply?._value == 0n) {
|
|
||||||
return { currentShares: lpTotalSupply, nextShares: lpTotalSupply };
|
|
||||||
}
|
|
||||||
const currentShares = lpBalance.div(lpTotalSupply).div(hundred);
|
|
||||||
const nextShares = (lpBalance.add(estimatedAmountOut)).div(lpTotalSupply.add(estimatedAmountOut)).div(hundred);
|
|
||||||
return { currentShares, nextShares };
|
|
||||||
}, [lpBalance, lpTotalSupply, estimatedAmountOut]);
|
|
||||||
|
|
||||||
const poolPrices = useMemo(() => {
|
|
||||||
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase()
|
|
||||||
? pairReserves.reserve0
|
|
||||||
: pairReserves.reserve1;
|
|
||||||
|
|
||||||
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase()
|
|
||||||
? pairReserves.reserve1
|
|
||||||
: pairReserves.reserve0;
|
|
||||||
|
|
||||||
let priceIn = "0";
|
|
||||||
let priceOut = "0";
|
|
||||||
|
|
||||||
if (amountIn?._value > 0n) priceIn = (amountOut.div(amountIn)).toString();
|
|
||||||
if (amountOut?._value > 0n) priceOut = (amountIn.div(amountOut)).toString();
|
|
||||||
|
|
||||||
return { priceIn , priceOut }
|
|
||||||
}, [addressTop, addressBottom, balanceTop, tokenAddresses, pairReserves]);
|
|
||||||
|
|
||||||
const addLiquidityInner = async () => {
|
const addLiquidityInner = async () => {
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
|
|
||||||
const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
|
const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
|
||||||
|
const destination = address;
|
||||||
|
|
||||||
const shares = 100000;
|
const shares = 100000;
|
||||||
const one = BigInt(shares * 100);
|
const one = BigInt(shares * 100);
|
||||||
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
||||||
@ -177,7 +171,7 @@ const PoolContainer = ({
|
|||||||
const amountAMin = amountADesired * bigIntSlippage / one;
|
const amountAMin = amountADesired * bigIntSlippage / one;
|
||||||
const amountBMin = amountBDesired * bigIntSlippage / one;
|
const amountBMin = amountBDesired * bigIntSlippage / one;
|
||||||
|
|
||||||
const params = {
|
await addLiquidity(
|
||||||
chainId,
|
chainId,
|
||||||
tokenNameTop,
|
tokenNameTop,
|
||||||
tokenNameBottom,
|
tokenNameBottom,
|
||||||
@ -185,16 +179,9 @@ const PoolContainer = ({
|
|||||||
amountBDesired,
|
amountBDesired,
|
||||||
amountAMin,
|
amountAMin,
|
||||||
amountBMin,
|
amountBMin,
|
||||||
address,
|
|
||||||
destination,
|
destination,
|
||||||
deadline,
|
deadline,
|
||||||
}
|
);
|
||||||
|
|
||||||
if (topIsNative || bottomIsNative) {
|
|
||||||
await addLiquidityETH(params)
|
|
||||||
} else {
|
|
||||||
await addLiquidity(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
await balanceRefetchTop();
|
await balanceRefetchTop();
|
||||||
await balanceRefetchBottom();
|
await balanceRefetchBottom();
|
||||||
@ -246,7 +233,7 @@ const PoolContainer = ({
|
|||||||
}
|
}
|
||||||
arrowOnClick={onSwap}
|
arrowOnClick={onSwap}
|
||||||
/>
|
/>
|
||||||
<Box
|
{!isSmallScreen && <Box
|
||||||
m="10px 0"
|
m="10px 0"
|
||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
@ -257,29 +244,24 @@ const PoolContainer = ({
|
|||||||
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
||||||
>
|
>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameTop}`}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">Current Balance:</Typography>
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceIn, formatDecimals, tokenNameBottom)}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(lpBalance, formatDecimals)} LP</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom}`}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">Total Supply:</Typography>
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(poolPrices.priceOut, formatDecimals, tokenNameTop)}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">{formatNumber(lpTotalSupply, formatDecimals)} LP</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
<Typography fontSize="12px" lineHeight="15px">Current Pool Share:</Typography>
|
<Typography fontSize="12px" lineHeight="15px">Extra Balance:</Typography>
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.currentShares, formatDecimals)}%</Typography>
|
<Typography fontSize="12px" lineHeight="15px">~{formatNumber(estimatedAmountOut, formatDecimals)} LP</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
</Box>}
|
||||||
<Typography fontSize="12px" lineHeight="15px">Next Pool Share:</Typography>
|
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatNumber(poolShares.nextShares, formatDecimals)}%</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<TokenAllowanceGuard
|
<TokenAllowanceGuard
|
||||||
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
||||||
tokenName={tokenNameTop}
|
tokenName={tokenNameTop}
|
||||||
owner={address}
|
owner={address}
|
||||||
spender={dexAddresses.router}
|
spender={dexAddresses.router}
|
||||||
decimals={balanceTop._decimals}
|
decimals={balanceTop._decimals}
|
||||||
isNative={topIsNative}
|
|
||||||
approvalText={"Approve " + tokenNameTop}
|
approvalText={"Approve " + tokenNameTop}
|
||||||
approvalPendingText={"Approving..."}
|
approvalPendingText={"Approving..."}
|
||||||
connect={connect}
|
connect={connect}
|
||||||
@ -292,7 +274,6 @@ const PoolContainer = ({
|
|||||||
owner={address}
|
owner={address}
|
||||||
spender={dexAddresses.router}
|
spender={dexAddresses.router}
|
||||||
decimals={balanceBottom._decimals}
|
decimals={balanceBottom._decimals}
|
||||||
isNative={bottomIsNative}
|
|
||||||
approvalText={"Approve " + tokenNameBottom}
|
approvalText={"Approve " + tokenNameBottom}
|
||||||
approvalPendingText={"Approving..."}
|
approvalPendingText={"Approving..."}
|
||||||
connect={connect}
|
connect={connect}
|
||||||
@ -317,9 +298,9 @@ const PoolContainer = ({
|
|||||||
"Connect"
|
"Connect"
|
||||||
:
|
:
|
||||||
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
||||||
isPending ? "Creating Pool..." : "Create Pool"
|
"Create Pool"
|
||||||
:
|
:
|
||||||
isPending ? "Adding Liquidity..." : "Add Liquidity"
|
"Add Liquidity"
|
||||||
}
|
}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</TokenAllowanceGuard>
|
</TokenAllowanceGuard>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
|
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
@ -13,16 +13,8 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
|||||||
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
||||||
|
|
||||||
import { getTokenAddress } from "../../hooks/helpers";
|
import { getTokenAddress } from "../../hooks/helpers";
|
||||||
import { useBalance, depositNative, withdrawWeth } from "../../hooks/tokens";
|
import { useBalance } from "../../hooks/tokens";
|
||||||
import {
|
import { useUniswapV2Pair, useUniswapV2PairReserves, swapExactTokensForTokens } from "../../hooks/uniswapv2";
|
||||||
useUniswapV2Pair,
|
|
||||||
useUniswapV2PairReserves,
|
|
||||||
swapExactTokensForTokens,
|
|
||||||
swapExactETHForTokens,
|
|
||||||
swapExactTokensForETH,
|
|
||||||
} from "../../hooks/uniswapv2";
|
|
||||||
|
|
||||||
import { EMPTY_ADDRESS } from "../../constants/addresses";
|
|
||||||
|
|
||||||
const SwapContainer = ({
|
const SwapContainer = ({
|
||||||
tokenNameTop,
|
tokenNameTop,
|
||||||
@ -35,12 +27,9 @@ const SwapContainer = ({
|
|||||||
setTopTokenListOpen,
|
setTopTokenListOpen,
|
||||||
setBottomTokenListOpen,
|
setBottomTokenListOpen,
|
||||||
slippage,
|
slippage,
|
||||||
destination,
|
|
||||||
secondsToWait,
|
secondsToWait,
|
||||||
setIsSwap,
|
setIsSwap,
|
||||||
formatDecimals,
|
formatDecimals
|
||||||
isWrapping,
|
|
||||||
isUnwrapping,
|
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
||||||
@ -56,14 +45,12 @@ const SwapContainer = ({
|
|||||||
balance: balanceTop,
|
balance: balanceTop,
|
||||||
refetch: balanceRefetchTop,
|
refetch: balanceRefetchTop,
|
||||||
contractAddress: addressTop,
|
contractAddress: addressTop,
|
||||||
isNative: topIsNative,
|
|
||||||
} = useBalance(chainId, tokenNameTop, address);
|
} = useBalance(chainId, tokenNameTop, address);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
balance: balanceBottom,
|
balance: balanceBottom,
|
||||||
refetch: balanceRefetchBottom,
|
refetch: balanceRefetchBottom,
|
||||||
contractAddress: addressBottom,
|
contractAddress: addressBottom,
|
||||||
isNative: bottomIsNative,
|
|
||||||
} = useBalance(chainId, tokenNameBottom, address);
|
} = useBalance(chainId, tokenNameBottom, address);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -87,80 +74,42 @@ const SwapContainer = ({
|
|||||||
const setMax = () => setAmountTop(balanceTop.toString());
|
const setMax = () => setAmountTop(balanceTop.toString());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWrapping || isUnwrapping) {
|
|
||||||
setAmountBottom(amountTop.toString());
|
|
||||||
setNextPrice("1");
|
|
||||||
setCurrentPrice("1");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const zero = new DecimalBigNumber(0n, 0);
|
const zero = new DecimalBigNumber(0n, 0);
|
||||||
const raw = new DecimalBigNumber(amountTop, balanceTop._decimals);
|
const raw = new DecimalBigNumber(amountTop, balanceTop._decimals);
|
||||||
const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals);
|
const amountInRaw = new DecimalBigNumber(raw._value.toBigInt(), balanceTop._decimals);
|
||||||
const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3));
|
const amountInWithFee = amountInRaw.mul(new DecimalBigNumber(997n, 3));
|
||||||
|
|
||||||
|
const topAddress = getTokenAddress(chainId, tokenNameTop);
|
||||||
|
const bottomAddress = getTokenAddress(chainId, tokenNameBottom);
|
||||||
|
|
||||||
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1;
|
const amountIn = addressTop.toUpperCase() === tokenAddresses.token0.toUpperCase() ? pairReserves.reserve0 : pairReserves.reserve1;
|
||||||
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0;
|
const amountOut = addressBottom.toUpperCase() === tokenAddresses.token1.toUpperCase() ? pairReserves.reserve1 : pairReserves.reserve0;
|
||||||
|
|
||||||
if (amountOut.eq(zero)) {
|
if (amountIn.eq(zero)) {
|
||||||
setCurrentPrice("");
|
setCurrentPrice("");
|
||||||
} else {
|
} else {
|
||||||
setCurrentPrice(amountIn.div(amountOut).toString());
|
setCurrentPrice(amountIn.div(amountOut).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amountOut.eq(zero) || amountInWithFee.eq(zero)) {
|
if (amountIn.eq(zero) || amountInWithFee.eq(zero)) {
|
||||||
setAmountBottom("");
|
setAmountBottom("");
|
||||||
setNextPrice("");
|
setNextPrice("");
|
||||||
} else {
|
} else {
|
||||||
const nominator = amountOut.mul(amountInWithFee);
|
const nominator = amountOut.mul(amountIn);
|
||||||
const denominator = amountIn.add(amountInWithFee);
|
const denominator = amountIn.add(amountInWithFee);
|
||||||
const newAmountOut = nominator.div(denominator);
|
const newAmountOut = nominator.div(denominator);
|
||||||
|
|
||||||
const newReserveIn = amountIn.add(amountInWithFee);
|
setAmountBottom(amountOut.sub(newAmountOut).toString());
|
||||||
const newReserveOut = amountOut.sub(newAmountOut);
|
setNextPrice(denominator.div(newAmountOut).toString())
|
||||||
const nextPrice = newReserveIn.div(newReserveOut);
|
|
||||||
|
|
||||||
setAmountBottom(newAmountOut.toString());
|
|
||||||
setNextPrice(nextPrice.toString());
|
|
||||||
}
|
}
|
||||||
}, [pairReserves, addressBottom, amountTop, addressTop, isWrapping, isUnwrapping]);
|
}, [amountTop, addressTop]);
|
||||||
|
|
||||||
const minReceived = useMemo(() => {
|
|
||||||
const decimals = 7;
|
|
||||||
const shares = Math.pow(10, decimals);
|
|
||||||
const one = BigInt(shares * 100);
|
|
||||||
|
|
||||||
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
|
||||||
const bigIntSlippage = one - BigInt(Math.round(floatSlippage * shares));
|
|
||||||
const slippageDecimalBigNumber = new DecimalBigNumber(bigIntSlippage, 2);
|
|
||||||
|
|
||||||
const bigIntAmount = BigInt(Math.round(amountBottom * shares));
|
|
||||||
const amountDecimalBigNumber = new DecimalBigNumber(bigIntAmount, decimals);
|
|
||||||
|
|
||||||
const tmpResult = amountDecimalBigNumber.mul(slippageDecimalBigNumber);
|
|
||||||
const result = new DecimalBigNumber(tmpResult?._value, tmpResult?._decimals + decimals);
|
|
||||||
return result?.toString();
|
|
||||||
}, [amountBottom, amountBottom, slippage, balanceBottom]);
|
|
||||||
|
|
||||||
const buttonText = useMemo(() => {
|
|
||||||
let text = "Swap";
|
|
||||||
if (isWrapping) text = "Wrap";
|
|
||||||
else if (isUnwrapping) text = "Unwrap";
|
|
||||||
else if (pairAddress === EMPTY_ADDRESS) text = "Create Pool";
|
|
||||||
|
|
||||||
if (isPending) text = `${text}ping...`
|
|
||||||
if (
|
|
||||||
!(isWrapping || isUnwrapping) &&
|
|
||||||
(pairAddress === EMPTY_ADDRESS && isPending)
|
|
||||||
) text = "Creating Pool..."
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}, [isPending, isWrapping, isUnwrapping, pairAddress]);
|
|
||||||
|
|
||||||
const swapTokens = async () => {
|
const swapTokens = async () => {
|
||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
|
|
||||||
const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
|
const deadline = Math.floor(Date.now() / 1000) + secondsToWait;
|
||||||
|
const destination = address;
|
||||||
|
|
||||||
const shares = 100000;
|
const shares = 100000;
|
||||||
const one = BigInt(shares * 100);
|
const one = BigInt(shares * 100);
|
||||||
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
||||||
@ -172,30 +121,14 @@ const SwapContainer = ({
|
|||||||
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
|
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
|
||||||
const amountBMin = amountBDesired * bigIntSlippage / one;
|
const amountBMin = amountBDesired * bigIntSlippage / one;
|
||||||
|
|
||||||
if (isWrapping) {
|
await swapExactTokensForTokens(
|
||||||
await depositNative(chainId, address, amountADesired);
|
chainId,
|
||||||
} else if (isUnwrapping) {
|
amountADesired,
|
||||||
await withdrawWeth(chainId, address, amountADesired);
|
amountBMin,
|
||||||
} else {
|
[tokenNameTop, tokenNameBottom],
|
||||||
const params = {
|
destination,
|
||||||
chainId,
|
deadline
|
||||||
amountADesired,
|
);
|
||||||
amountBMin,
|
|
||||||
tokenNameTop,
|
|
||||||
tokenNameBottom,
|
|
||||||
destination,
|
|
||||||
address,
|
|
||||||
deadline
|
|
||||||
};
|
|
||||||
|
|
||||||
if (topIsNative) {
|
|
||||||
await swapExactETHForTokens(params)
|
|
||||||
} else if (bottomIsNative) {
|
|
||||||
await swapExactTokensForETH(params)
|
|
||||||
} else {
|
|
||||||
await swapExactTokensForTokens(params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await balanceRefetchTop();
|
await balanceRefetchTop();
|
||||||
await balanceRefetchBottom();
|
await balanceRefetchBottom();
|
||||||
@ -243,7 +176,7 @@ const SwapContainer = ({
|
|||||||
}
|
}
|
||||||
arrowOnClick={onSwap}
|
arrowOnClick={onSwap}
|
||||||
/>
|
/>
|
||||||
<Box
|
{!isSmallScreen && <Box
|
||||||
m="10px 0"
|
m="10px 0"
|
||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
@ -254,29 +187,24 @@ const SwapContainer = ({
|
|||||||
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
||||||
>
|
>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Current)`}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">Current price:</Typography>
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals, tokenNameTop)}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(currentPrice, formatDecimals, tokenNameTop)}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
<Typography fontSize="12px" lineHeight="15px">{`1 ${tokenNameBottom} (Next)`}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">Next price:</Typography>
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(nextPrice === "" ? currentPrice : nextPrice, formatDecimals, tokenNameTop)}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
<Typography fontSize="12px" lineHeight="15px">Min. Receive:</Typography>
|
<Typography fontSize="12px" lineHeight="15px">Transaction deadline:</Typography>
|
||||||
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(minReceived, formatDecimals, tokenNameBottom)}</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
|
||||||
<Typography fontSize="12px" lineHeight="15px">Tx. Deadline:</Typography>
|
|
||||||
<Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
|
<Typography fontSize="12px" lineHeight="15px">~{prettifySecondsInDays(secondsToWait)}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>}
|
||||||
<TokenAllowanceGuard
|
<TokenAllowanceGuard
|
||||||
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
spendAmount={new DecimalBigNumber(amountTop, balanceTop._decimals)}
|
||||||
tokenName={tokenNameTop}
|
tokenName={tokenNameTop}
|
||||||
owner={address}
|
owner={address}
|
||||||
spender={dexAddresses.router}
|
spender={dexAddresses.router}
|
||||||
decimals={balanceTop._decimals}
|
decimals={balanceTop._decimals}
|
||||||
isNative={topIsNative}
|
|
||||||
approvalText={"Approve " + tokenNameTop}
|
approvalText={"Approve " + tokenNameTop}
|
||||||
approvalPendingText={"Approving..."}
|
approvalPendingText={"Approving..."}
|
||||||
connect={connect}
|
connect={connect}
|
||||||
@ -296,10 +224,17 @@ const SwapContainer = ({
|
|||||||
onClick={() => address === "" ?
|
onClick={() => address === "" ?
|
||||||
connect()
|
connect()
|
||||||
:
|
:
|
||||||
(!isWrapping && !isUnwrapping) && pairAddress === EMPTY_ADDRESS ? setIsSwap(false) : swapTokens()
|
pairAddress === "0x0000000000000000000000000000000000000000" ? setIsSwap(false) : swapTokens()
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{address === "" ? "Connect" : buttonText }
|
{address === "" ?
|
||||||
|
"Connect"
|
||||||
|
:
|
||||||
|
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
||||||
|
"Create Pool"
|
||||||
|
:
|
||||||
|
"Swap"
|
||||||
|
}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</TokenAllowanceGuard>
|
</TokenAllowanceGuard>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -20,14 +20,10 @@ import TokenStack from "../../components/TokenStack/TokenStack";
|
|||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { formatNumber } from "../../helpers/";
|
import { formatNumber } from "../../helpers/";
|
||||||
import { useBalance, useTokenSymbol } from "../../hooks/tokens";
|
import { useBalance, useTokenSymbol } from "../../hooks/tokens";
|
||||||
import {
|
import { isNetworkLegacy } from "../../constants";
|
||||||
RESERVE_ADDRESSES,
|
import { RESERVE_ADDRESSES, FTSO_ADDRESSES, STNK_ADDRESSES, GHST_ADDRESSES } from "../../constants/addresses";
|
||||||
FTSO_ADDRESSES,
|
|
||||||
GHST_ADDRESSES,
|
|
||||||
EMPTY_ADDRESS
|
|
||||||
} from "../../constants/addresses";
|
|
||||||
|
|
||||||
const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setTokenAddress }) => {
|
const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }) => {
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 599px)");
|
const isSmallScreen = useMediaQuery("(max-width: 599px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 425px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 425px)");
|
||||||
|
|
||||||
@ -43,15 +39,19 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
|
|||||||
const { symbol: searchSymbol } = useTokenSymbol(chainId, address);
|
const { symbol: searchSymbol } = useTokenSymbol(chainId, address);
|
||||||
const { balance: searchBalance } = useBalance(chainId, address, account);
|
const { balance: searchBalance } = useBalance(chainId, address, account);
|
||||||
|
|
||||||
const { balance: nativeBalance } = useBalance(chainId, chainSymbol, account);
|
|
||||||
const { balance: reserveBalance } = useBalance(chainId, "RESERVE", account);
|
const { balance: reserveBalance } = useBalance(chainId, "RESERVE", account);
|
||||||
const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account);
|
const { balance: ftsoBalance } = useBalance(chainId, "FTSO", account);
|
||||||
|
const { balance: stnkBalance } = useBalance(chainId, "STNK", account);
|
||||||
const { balance: ghstBalance } = useBalance(chainId, "GHST", account);
|
const { balance: ghstBalance } = useBalance(chainId, "GHST", account);
|
||||||
|
|
||||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||||
|
|
||||||
const searchToken = useMemo(() => {
|
const searchToken = useMemo(() => {
|
||||||
return [{
|
return [{
|
||||||
name: searchSymbol,
|
name: searchSymbol,
|
||||||
@ -63,15 +63,9 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
|
|||||||
|
|
||||||
const knownTokens = useMemo(() => {
|
const knownTokens = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
name: chainSymbol,
|
|
||||||
icons: [chainSymbol],
|
|
||||||
balance: nativeBalance,
|
|
||||||
address: EMPTY_ADDRESS,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: reserveSymbol,
|
name: reserveSymbol,
|
||||||
icons: [chainSymbol],
|
icons: isNetworkLegacy(chainId) ? ["GDAI"] : [nativeSymbol],
|
||||||
balance: reserveBalance,
|
balance: reserveBalance,
|
||||||
address: RESERVE_ADDRESSES[chainId]
|
address: RESERVE_ADDRESSES[chainId]
|
||||||
},
|
},
|
||||||
@ -81,6 +75,12 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
|
|||||||
balance: ftsoBalance,
|
balance: ftsoBalance,
|
||||||
address: FTSO_ADDRESSES[chainId]
|
address: FTSO_ADDRESSES[chainId]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: stnkSymbol,
|
||||||
|
icons: ["STNK"],
|
||||||
|
balance: stnkBalance,
|
||||||
|
address: STNK_ADDRESSES[chainId]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: ghstSymbol,
|
name: ghstSymbol,
|
||||||
icons: ["GHST"],
|
icons: ["GHST"],
|
||||||
@ -88,7 +88,7 @@ const TokenModal = ({ chainId, account, chainSymbol, listOpen, setListOpen, setT
|
|||||||
address: GHST_ADDRESSES[chainId]
|
address: GHST_ADDRESSES[chainId]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, [reserveSymbol, ftsoSymbol, ghstSymbol, reserveBalance, ftsoBalance, ghstBalance]);
|
}, [reserveSymbol, ftsoSymbol, stnkSymbol, ghstSymbol, reserveBalance, ftsoBalance, stnkBalance, ghstBalance]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAddress(userInput)) {
|
if (isAddress(userInput)) {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { useState, useEffect, useMemo } from "react";
|
|||||||
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
||||||
import { useConfig, useBalance } from "wagmi";
|
import { useConfig, useBalance } from "wagmi";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
@ -27,8 +26,6 @@ import {
|
|||||||
} from "../../hooks/tokens";
|
} from "../../hooks/tokens";
|
||||||
|
|
||||||
const Faucet = ({ chainId, address, config, connect }) => {
|
const Faucet = ({ chainId, address, config, connect }) => {
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
@ -57,8 +54,8 @@ const Faucet = ({ chainId, address, config, connect }) => {
|
|||||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/faucet" });
|
||||||
}, [location])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const value = nativeBalance ? nativeBalance.value : 0n;
|
const value = nativeBalance ? nativeBalance.value : 0n;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
import { useNavigate, useParams, useLocation } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Box, Container, Grid, Divider, Typography, useMediaQuery } from "@mui/material";
|
import { Box, Container, Grid, Divider, Typography, useMediaQuery } from "@mui/material";
|
||||||
|
|
||||||
@ -15,19 +15,20 @@ import { ProposalsCount, MinQuorumPercentage, ProposalThreshold } from "./compon
|
|||||||
import { useTokenSymbol } from "../../hooks/tokens";
|
import { useTokenSymbol } from "../../hooks/tokens";
|
||||||
|
|
||||||
const Governance = ({ connect, config, address, chainId }) => {
|
const Governance = ({ connect, config, address, chainId }) => {
|
||||||
const location = useLocation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { network } = useParams();
|
|
||||||
|
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
|
const handleModal = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/governance" });
|
||||||
}, [location]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -69,7 +70,7 @@ const Governance = ({ connect, config, address, chainId }) => {
|
|||||||
<Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
<Box mt="45px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => navigate(`/${network}/governance/create`)}
|
onClick={() => navigate(`/governance/create`)}
|
||||||
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
||||||
>
|
>
|
||||||
Create Proposal
|
Create Proposal
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -29,7 +28,6 @@ import PageTitle from "../../components/PageTitle/PageTitle";
|
|||||||
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
import { PrimaryButton, TertiaryButton } from "../../components/Button";
|
||||||
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||||
|
|
||||||
import { useLocalStorage } from "../../hooks/localstorage";
|
|
||||||
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
|
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
|
||||||
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
|
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
@ -39,17 +37,16 @@ import { parseFunctionCalldata } from "./components/functions/index";
|
|||||||
import { MY_PROPOSALS_PREFIX } from "./helpers";
|
import { MY_PROPOSALS_PREFIX } from "./helpers";
|
||||||
|
|
||||||
const NewProposal = ({ config, address, connect, chainId }) => {
|
const NewProposal = ({ config, address, connect, chainId }) => {
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
|
||||||
|
|
||||||
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
|
const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
|
||||||
const [myProposals, setMyProposalsInner] = useState(() => myStoredProposals.map(id => BigInt(id)));
|
const [myProposals, setMyProposals] = useState(
|
||||||
|
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
|
||||||
|
);
|
||||||
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [isModalOpened, setIsModalOpened] = useState(false);
|
const [isModalOpened, setIsModalOpened] = useState(false);
|
||||||
@ -64,14 +61,14 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
proposalDescription
|
proposalDescription
|
||||||
} = useProposalHash(chainId, proposalFunctions);
|
} = useProposalHash(chainId, proposalFunctions);
|
||||||
|
|
||||||
const setMyProposals = useCallback((proposals) => {
|
useEffect(() => {
|
||||||
setMyProposalsInner(proposals);
|
const toStore = JSON.stringify(myProposals.map(id => id.toString()));
|
||||||
setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString()))
|
localStorage.setItem(MY_PROPOSALS_PREFIX, toStore);
|
||||||
}, [chainId, address]);
|
}, [myProposals]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/governance/create" });
|
||||||
}, [location]);
|
}, []);
|
||||||
|
|
||||||
const addCalldata = (calldata) => setProposalFunctions(prev => [...prev, calldata]);
|
const addCalldata = (calldata) => setProposalFunctions(prev => [...prev, calldata]);
|
||||||
const removeCalldata = (index) => setProposalFunctions(prev => prev.filter((_, i) => i !== index));
|
const removeCalldata = (index) => setProposalFunctions(prev => prev.filter((_, i) => i !== index));
|
||||||
@ -166,14 +163,6 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
isVertical
|
isVertical
|
||||||
>
|
>
|
||||||
<Box display="flex" flexDirection="column" alignItems="center">
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
<TertiaryButton
|
|
||||||
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
|
||||||
fullWidth
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={() => setIsModalOpened(true)}
|
|
||||||
>
|
|
||||||
Add New Function
|
|
||||||
</TertiaryButton>
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={
|
disabled={
|
||||||
proposalFunctions.length === 0 ||
|
proposalFunctions.length === 0 ||
|
||||||
@ -181,11 +170,18 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
isPending
|
isPending
|
||||||
}
|
}
|
||||||
fullWidth
|
fullWidth
|
||||||
loading={isPending}
|
|
||||||
onClick={() => submitProposal()}
|
onClick={() => submitProposal()}
|
||||||
>
|
>
|
||||||
{isPending ? "Submitting..." : "Submit Proposal"}
|
{isPending ? "Submitting..." : "Submit Proposal"}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
|
<TertiaryButton
|
||||||
|
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
||||||
|
fullWidth
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => setIsModalOpened(true)}
|
||||||
|
>
|
||||||
|
Add New
|
||||||
|
</TertiaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
</TokenAllowanceGuard>
|
</TokenAllowanceGuard>
|
||||||
</Box>
|
</Box>
|
||||||
@ -204,41 +200,21 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isSmallScreen
|
<TableContainer>
|
||||||
? <Box display="flex" flexDirection="column">
|
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||||
{proposalFunctions?.map((metadata, index) => {
|
<TableHead>
|
||||||
return parseFunctionCalldata({
|
<TableRow>
|
||||||
metadata,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
|
||||||
index,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
|
||||||
chainId,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
|
||||||
removeCalldata,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
|
||||||
nativeCoin: nativeCurrency,
|
</TableRow>
|
||||||
isTable: false,
|
</TableHead>
|
||||||
});
|
<TableBody>{proposalFunctions.map((metadata, index) => {
|
||||||
})}
|
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency, removeCalldata);
|
||||||
</Box>
|
})}</TableBody>
|
||||||
: <TableContainer>
|
</Table>
|
||||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
</TableContainer>
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>{proposalFunctions?.map((metadata, index) => {
|
|
||||||
return parseFunctionCalldata({
|
|
||||||
metadata,
|
|
||||||
index,
|
|
||||||
chainId,
|
|
||||||
removeCalldata,
|
|
||||||
nativeCoin: nativeCurrency,
|
|
||||||
});
|
|
||||||
})}</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
}
|
|
||||||
</Paper>}
|
</Paper>}
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
import { useParams, useLocation } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useBlock, useBlockNumber } from 'wagmi'
|
||||||
import { useBlock, useBlockNumber } from "wagmi";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@ -33,16 +32,13 @@ import { formatNumber, formatCurrency, shorten } from "../../helpers";
|
|||||||
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
||||||
import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber, } from "../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
import { convertStatusToColor, convertStatusToLabel } from "./helpers";
|
import { convertStatusToTemplate, convertStatusToLabel } from "./helpers";
|
||||||
import { parseFunctionCalldata } from "./components/functions";
|
import { parseFunctionCalldata } from "./components/functions";
|
||||||
|
|
||||||
import { networkAvgBlockSpeed } from "../../constants";
|
import { networkAvgBlockSpeed } from "../../constants";
|
||||||
|
|
||||||
import { useLocalStorage } from "../../hooks/localstorage";
|
|
||||||
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
|
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
|
||||||
import {
|
import {
|
||||||
getVoteValue,
|
|
||||||
getVoteTarget,
|
|
||||||
useProposalState,
|
useProposalState,
|
||||||
useProposalProposer,
|
useProposalProposer,
|
||||||
useProposalLocked,
|
useProposalLocked,
|
||||||
@ -77,13 +73,10 @@ import RepeatIcon from '@mui/icons-material/Repeat';
|
|||||||
const HUNDRED = new DecimalBigNumber(100n, 0);
|
const HUNDRED = new DecimalBigNumber(100n, 0);
|
||||||
|
|
||||||
const ProposalDetails = ({ chainId, address, connect, config }) => {
|
const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||||
const location = useLocation();
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const proposalId = BigInt(id);
|
const proposalId = BigInt(id);
|
||||||
|
|
||||||
const [isPendingVote, setIsPendingVote] = useState(-1);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [isPendingExecute, setIsPendingExecute] = useState(false);
|
|
||||||
const [isPendingRelease, setIsPendingRelease] = useState(false);
|
|
||||||
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
||||||
|
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
@ -91,8 +84,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
|
||||||
|
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
const { balance } = useBalance(chainId, "GHST", address);
|
const { balance } = useBalance(chainId, "GHST", address);
|
||||||
@ -110,10 +101,8 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
const { pastTotalSupply: totalSupply } = usePastTotalSupply(chainId, "GHST", proposalSnapshot);
|
const { pastTotalSupply: totalSupply } = usePastTotalSupply(chainId, "GHST", proposalSnapshot);
|
||||||
const { pastVotes } = usePastVotes(chainId, "GHST", proposalSnapshot, address);
|
const { pastVotes } = usePastVotes(chainId, "GHST", proposalSnapshot, address);
|
||||||
|
|
||||||
console.log(location.pathname)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: `${chainId}/governance/${id}` });
|
ReactGA.send({ hitType: "pageview", page: `/governance/${id}` });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const quorumPercentage = useMemo(() => {
|
const quorumPercentage = useMemo(() => {
|
||||||
@ -123,24 +112,48 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
|
|
||||||
const votePercentage = useMemo(() => {
|
const votePercentage = useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return 0;
|
if (totalSupply._value === 0n) return 0;
|
||||||
return totalVotes * HUNDRED / totalSupply;
|
const value = (totalVotes?._value ?? 0n) * 100n / (totalSupply?._value ?? 1n);
|
||||||
|
return new DecimalBigNumber(value, 0);
|
||||||
}, [totalVotes, totalSupply]);
|
}, [totalVotes, totalSupply]);
|
||||||
|
|
||||||
const forPercentage = useMemo(() => {
|
const forPercentage = useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
||||||
return forVotes * HUNDRED / totalSupply;
|
const value = (forVotes?._value ?? 0n) * 10000n / (totalSupply?._value ?? 1n);
|
||||||
|
return new DecimalBigNumber(value, 2);
|
||||||
}, [forVotes, totalSupply]);
|
}, [forVotes, totalSupply]);
|
||||||
|
|
||||||
const againstPercentage= useMemo(() => {
|
const againstPercentage= useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
||||||
return againstVotes * HUNDRED / totalSupply;
|
const value = (againstVotes?._value ?? 0n) * 10000n / (totalSupply?._value ?? 1n);
|
||||||
|
return new DecimalBigNumber(value, 2);
|
||||||
}, [againstVotes, totalSupply]);
|
}, [againstVotes, totalSupply]);
|
||||||
|
|
||||||
const voteWeightPercentage = useMemo(() => {
|
const voteWeightPercentage = useMemo(() => {
|
||||||
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
if (totalSupply._value === 0n) return new DecimalBigNumber(0n, 2);
|
||||||
return pastVotes * HUNDRED / totalSupply;
|
const value = (pastVotes?._value ?? 0n) * 10000n / (totalSupply?._value ?? 1n);
|
||||||
|
return new DecimalBigNumber(value, 2);
|
||||||
}, [pastVotes, totalSupply]);
|
}, [pastVotes, totalSupply]);
|
||||||
|
|
||||||
|
const voteValue = useMemo(() => {
|
||||||
|
if (totalVotes?._value == 0n) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Number(forVotes._value * 100n / totalVotes._value);
|
||||||
|
}, [forVotes, totalVotes]);
|
||||||
|
|
||||||
|
const voteTarget = useMemo(() => {
|
||||||
|
const first = (5n * againstVotes._value + forVotes._value);
|
||||||
|
const second = BigInt(Math.floor(Math.sqrt(Number(totalVotes._value))));
|
||||||
|
const bias = 3n * first + second;
|
||||||
|
const denominator = totalVotes._value + bias;
|
||||||
|
|
||||||
|
if (denominator === 0n) {
|
||||||
|
return 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(totalVotes?._value * 100n / denominator);
|
||||||
|
}, [againstVotes, forVotes, totalVotes]);
|
||||||
|
|
||||||
const nativeCurrency = useMemo(() => {
|
const nativeCurrency = useMemo(() => {
|
||||||
const client = config?.getClient();
|
const client = config?.getClient();
|
||||||
return client?.chain?.nativeCurrency?.symbol;
|
return client?.chain?.nativeCurrency?.symbol;
|
||||||
@ -155,45 +168,38 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
return url;
|
return url;
|
||||||
}, [proposalProposer, config]);
|
}, [proposalProposer, config]);
|
||||||
|
|
||||||
const isPending = useMemo(() => {
|
const handleVote = async (against) => {
|
||||||
return isPendingExecute || isPendingRelease || isPendingVote > -1;
|
setIsPending(true);
|
||||||
}, [isPendingExecute, isPendingRelease, isPendingVote]);
|
const support = against ? 0 : 1;
|
||||||
|
|
||||||
const handleVote = useCallback(async (support) => {
|
|
||||||
setIsPendingVote(support);
|
|
||||||
const result = await castVote(chainId, address, proposalId, support);
|
const result = await castVote(chainId, address, proposalId, support);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
|
const toStore = JSON.stringify(proposalId.toString());
|
||||||
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
|
localStorage.setItem(VOTED_PROPOSALS_PREFIX, toStore);
|
||||||
proposals.push(proposalId);
|
|
||||||
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
|
|
||||||
await queryClient.invalidateQueries();
|
|
||||||
}
|
}
|
||||||
setIsPendingVote(-1);
|
|
||||||
}, [chainId, address, proposalId]);
|
|
||||||
|
|
||||||
const handleExecute = async () => {
|
setIsPending(true);
|
||||||
setIsPendingExecute(true);
|
|
||||||
await executeProposal(chainId, address, proposalId);
|
|
||||||
await queryClient.invalidateQueries();
|
|
||||||
setIsPendingExecute(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRelease = async () => {
|
const handleExecute = async () => {
|
||||||
setIsPendingRelease(true);
|
setIsPending(true);
|
||||||
|
await executeProposal(chainId, address, proposalId);
|
||||||
|
setIsPending(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRelease = async (proposalId) => {
|
||||||
|
setIsPending(true);
|
||||||
await releaseLocked(chainId, address, proposalId);
|
await releaseLocked(chainId, address, proposalId);
|
||||||
await queryClient.invalidateQueries();
|
setIsPending(true);
|
||||||
setIsPendingRelease(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<PageTitle
|
<PageTitle
|
||||||
name={`GDP-${id.slice(-5)}`}
|
name={`GBP-${id.slice(-5)}`}
|
||||||
subtitle={
|
subtitle={
|
||||||
<Typography component="span">
|
<Typography component="span">
|
||||||
{`Cast $${ghstSymbol} to shape this proposal's outcome`}
|
Proposal details, need more in-depth description
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -208,24 +214,19 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ mt: "15px" }}>
|
<Box sx={{ mt: "15px" }}>
|
||||||
<Box
|
<Box display="flex" justifyContent="space-between" gap="20px">
|
||||||
gap="20px"
|
|
||||||
display="flex"
|
|
||||||
justifyContent={"space-between"}
|
|
||||||
flexDirection={isSmallScreen ? "column" : "row"}
|
|
||||||
>
|
|
||||||
<Paper
|
<Paper
|
||||||
fullWidth
|
fullWidth
|
||||||
enableBackground
|
enableBackground
|
||||||
headerContent={
|
headerContent={
|
||||||
<Box display="flex" alignItems="center" flexDirection="row" gap="10px">
|
<Box display="flex" alignItems="center" flexDirection="row" gap="10px">
|
||||||
{!isSmallScreen &&<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
Progress
|
Progress
|
||||||
</Typography>}
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
sx={{ marginTop: "4px", width: "88px" }}
|
sx={{ marginTop: "4px", width: "88px" }}
|
||||||
label={convertStatusToLabel(proposalState)}
|
label={convertStatusToLabel(proposalState)}
|
||||||
background={convertStatusToColor(proposalState)}
|
template={convertStatusToTemplate(proposalState)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
@ -249,8 +250,8 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
barColor={theme.colors.feedback.success}
|
barColor={theme.colors.feedback.success}
|
||||||
barBackground={theme.colors.feedback.error}
|
barBackground={theme.colors.feedback.error}
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={getVoteValue(forVotes?._value ?? 0n, totalVotes?._value ?? 0n)}
|
value={voteValue}
|
||||||
target={getVoteTarget(totalVotes?._value ?? 0n, totalSupply?._value ?? 0n)}
|
target={voteTarget}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -301,18 +302,16 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
||||||
loading={isPendingVote === 1}
|
|
||||||
onClick={() => handleVote(1)}
|
onClick={() => handleVote(1)}
|
||||||
>
|
>
|
||||||
{isPendingVote === 1 ? "Voting For..." : "For"}
|
For
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
||||||
loading={isPendingVote === 0}
|
|
||||||
onClick={() => handleVote(0)}
|
onClick={() => handleVote(0)}
|
||||||
>
|
>
|
||||||
{isPendingVote === 0 ? "Voting Against..." : "Against"}
|
Against
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</>
|
</>
|
||||||
: <SecondaryButton
|
: <SecondaryButton
|
||||||
@ -347,9 +346,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
isProposer={proposalProposer === address}
|
isProposer={proposalProposer === address}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
proposalId={id}
|
proposalId={id}
|
||||||
isPending={isPending}
|
|
||||||
isPendingExecute={isPendingExecute}
|
|
||||||
isPendingRelease={isPendingRelease}
|
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
@ -366,59 +362,28 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isSmallScreen
|
<TableContainer>
|
||||||
? <Box display="flex" flexDirection="column">
|
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||||
{proposalDetails?.map((metadata, index) => {
|
<TableHead>
|
||||||
return parseFunctionCalldata({
|
<TableRow>
|
||||||
metadata,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
|
||||||
index,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
|
||||||
chainId,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
|
||||||
nativeCoin: nativeCurrency,
|
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
|
||||||
isTable: false,
|
</TableRow>
|
||||||
});
|
</TableHead>
|
||||||
})}
|
<TableBody>{proposalDetails?.map((metadata, index) => {
|
||||||
</Box>
|
return parseFunctionCalldata(metadata, index, chainId, nativeCurrency);
|
||||||
: <TableContainer>
|
})}</TableBody>
|
||||||
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
</Table>
|
||||||
<TableHead>
|
</TableContainer>
|
||||||
<TableRow>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Function</TableCell>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Target</TableCell>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Calldata</TableCell>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Value</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>{proposalDetails?.map((metadata, index) => {
|
|
||||||
return parseFunctionCalldata({
|
|
||||||
metadata,
|
|
||||||
index,
|
|
||||||
chainId,
|
|
||||||
nativeCoin: nativeCurrency,
|
|
||||||
});
|
|
||||||
})}</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
}
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const VotingTimeline = ({
|
const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer }) => {
|
||||||
connect,
|
|
||||||
handleExecute,
|
|
||||||
handleRelease,
|
|
||||||
proposalLocked,
|
|
||||||
proposalId,
|
|
||||||
chainId,
|
|
||||||
state,
|
|
||||||
address,
|
|
||||||
isProposer,
|
|
||||||
isPending,
|
|
||||||
isPendingExecute,
|
|
||||||
isPendingRelease,
|
|
||||||
}) => {
|
|
||||||
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
||||||
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
||||||
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
||||||
@ -438,26 +403,19 @@ const VotingTimeline = ({
|
|||||||
<VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" />
|
<VotingTimelineItem chainId={chainId} occured={proposalDeadline} message="Voting ends" />
|
||||||
</Timeline>
|
</Timeline>
|
||||||
<Box width="100%" display="flex" gap="10px">
|
<Box width="100%" display="flex" gap="10px">
|
||||||
{(isProposer && (proposalLocked?._value ?? 0n) > 0n) && <SecondaryButton
|
{isProposer && <SecondaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
loading={isPendingRelease}
|
disabled={(proposalLocked?._value ?? 0n) === 0n || state < 2}
|
||||||
disabled={isPending || state < 2}
|
|
||||||
onClick={() => address === "" ? connect() : handleRelease()}
|
onClick={() => address === "" ? connect() : handleRelease()}
|
||||||
>
|
>
|
||||||
{address === "" ? "Connect" : isPendingRelease ? "Releasing..." : "Release"}
|
{address === "" ? "Connect" : "Release"}
|
||||||
</SecondaryButton>}
|
</SecondaryButton>}
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
loading={isPending}
|
disabled={state !== 4}
|
||||||
disabled={isPending || state !== 4}
|
|
||||||
onClick={() => address === "" ? connect() : handleExecute()}
|
onClick={() => address === "" ? connect() : handleExecute()}
|
||||||
>
|
>
|
||||||
{address === ""
|
{address === "" ? "Connect" : "Execute"}
|
||||||
? "Connect"
|
|
||||||
: state !== 4
|
|
||||||
? convertStatusToLabel(state)
|
|
||||||
: isPendingExecute ? "Executing..." : "Execute"
|
|
||||||
}
|
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const GovernanceInfoText = () => {
|
|||||||
ghostDAO’s adaptive governance system algorithmically sets minimum collateral based on activity.
|
ghostDAO’s adaptive governance system algorithmically sets minimum collateral based on activity.
|
||||||
<Link
|
<Link
|
||||||
color={theme.colors.primary[300]}
|
color={theme.colors.primary[300]}
|
||||||
href="http://forum.ghostchain.io/"
|
href="https://docs.dao.ghostchain.io/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>Learn more here.</Link>
|
>Learn more here.</Link>
|
||||||
|
|||||||
@ -103,11 +103,7 @@ const InitialStep = ({ selectedOption, handleChange, handleCalldata, handleProce
|
|||||||
return allPossibleFunctions.find(opt => opt.value === selected)?.label || selected;
|
return allPossibleFunctions.find(opt => opt.value === selected)?.label || selected;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography align="center" variant="body2">
|
<Typography align="center" variant="body2">{functionDescription}</Typography>
|
||||||
<b>{selectedOption}</b>
|
|
||||||
{" "}
|
|
||||||
{functionDescription}
|
|
||||||
</Typography>
|
|
||||||
{ready
|
{ready
|
||||||
? <TertiaryButton disabled={!selectedOption} onClick={() => handleProceed()} fullWidth>Proceed</TertiaryButton>
|
? <TertiaryButton disabled={!selectedOption} onClick={() => handleProceed()} fullWidth>Proceed</TertiaryButton>
|
||||||
: <PrimaryButton disabled={!selectedOption} onClick={() => handleCalldata()} fullWidth>Create Function</PrimaryButton>
|
: <PrimaryButton disabled={!selectedOption} onClick={() => handleCalldata()} fullWidth>Create Function</PrimaryButton>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@ -23,7 +23,6 @@ import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
|||||||
|
|
||||||
import { networkAvgBlockSpeed } from "../../../constants";
|
import { networkAvgBlockSpeed } from "../../../constants";
|
||||||
import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil";
|
import { prettifySecondsInDays, prettifySeconds } from "../../../helpers/timeUtil";
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
|
||||||
|
|
||||||
import Chip from "../../../components/Chip/Chip";
|
import Chip from "../../../components/Chip/Chip";
|
||||||
import Modal from "../../../components/Modal/Modal";
|
import Modal from "../../../components/Modal/Modal";
|
||||||
@ -33,7 +32,7 @@ import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
|||||||
|
|
||||||
import ProposalInfoText from "./ProposalInfoText";
|
import ProposalInfoText from "./ProposalInfoText";
|
||||||
import {
|
import {
|
||||||
convertStatusToColor,
|
convertStatusToTemplate,
|
||||||
convertStatusToLabel,
|
convertStatusToLabel,
|
||||||
MY_PROPOSALS_PREFIX,
|
MY_PROPOSALS_PREFIX,
|
||||||
VOTED_PROPOSALS_PREFIX
|
VOTED_PROPOSALS_PREFIX
|
||||||
@ -41,8 +40,9 @@ import {
|
|||||||
|
|
||||||
import { useScreenSize } from "../../../hooks/useScreenSize";
|
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||||
|
|
||||||
import { useProposals } from "../../../hooks/governance";
|
import {
|
||||||
import { useLocalStorage } from "../../../hooks/localstorage";
|
useProposals,
|
||||||
|
} from "../../../hooks/governance";
|
||||||
|
|
||||||
const MAX_PROPOSALS_TO_SHOW = 10;
|
const MAX_PROPOSALS_TO_SHOW = 10;
|
||||||
|
|
||||||
@ -51,16 +51,17 @@ const ProposalsList = ({ chainId, address, config }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { network } = useParams();
|
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
|
||||||
|
|
||||||
const [proposalsFilter, setProposalFilter] = useState("active");
|
const [proposalsFilter, setProposalFilter] = useState("active");
|
||||||
|
|
||||||
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
|
const myStoredProposals = localStorage.getItem(MY_PROPOSALS_PREFIX);
|
||||||
const [myProposals, setMyProposals] = useState(() => myStoredProposals.map(id => BigInt(id)));
|
const [myProposals, setMyProposals] = useState(
|
||||||
|
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
|
||||||
|
);
|
||||||
|
|
||||||
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
|
const storedVotedProposals = localStorage.getItem(VOTED_PROPOSALS_PREFIX);
|
||||||
const [votedProposals, setVotedProposals] = useState(() => storedVotedProposals.map(id => BigInt(id)));
|
const [votedProposals, setVotedProposals] = useState(
|
||||||
|
storedVotedProposals ? JSON.parse(storedVotedProposals).map(id => BigInt(id)) : []
|
||||||
|
);
|
||||||
|
|
||||||
const searchedIndexes = useMemo(() => {
|
const searchedIndexes = useMemo(() => {
|
||||||
switch (proposalsFilter) {
|
switch (proposalsFilter) {
|
||||||
@ -106,7 +107,7 @@ const ProposalsList = ({ chainId, address, config }) => {
|
|||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
blockNumber={blockNumber}
|
blockNumber={blockNumber}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
openProposal={() => navigate(`${network}/governance/${proposal.hashes.full}`)}
|
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@ -153,7 +154,7 @@ const ProposalsList = ({ chainId, address, config }) => {
|
|||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
blockNumber={blockNumber}
|
blockNumber={blockNumber}
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
openProposal={() => navigate(`/${network}/governance/${proposal.hashes.full}`)}
|
openProposal={() => navigate(`/governance/${proposal.hashes.full}`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ProposalTable>
|
</ProposalTable>
|
||||||
@ -174,7 +175,7 @@ const ProposalTable = ({ children }) => (
|
|||||||
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Status</TableCell>
|
<TableCell align="center" style={{ width: "130px", padding: "8px 0" }}>Status</TableCell>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
|
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
|
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
|
||||||
<TableCell align="center" style={{ width: "180px", padding: "8px 0" }}></TableCell>
|
<TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
@ -185,6 +186,34 @@ const ProposalTable = ({ children }) => (
|
|||||||
|
|
||||||
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const voteValue = useMemo(() => {
|
||||||
|
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
|
||||||
|
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
|
||||||
|
const totalVotes = againstVotes + forVotes;
|
||||||
|
if (totalVotes == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Number(forVotes * 100n / totalVotes);
|
||||||
|
}, [proposal]);
|
||||||
|
|
||||||
|
const voteTarget = useMemo(() => {
|
||||||
|
const againstVotes = proposal?.votes?.at(0)?._value ?? 0n;
|
||||||
|
const forVotes = proposal?.votes?.at(1)?._value ?? 0n;
|
||||||
|
const totalVotes = againstVotes + forVotes;
|
||||||
|
|
||||||
|
const first = (5n * againstVotes + forVotes);
|
||||||
|
const second = BigInt(Math.floor(Math.sqrt(Number(totalVotes))));
|
||||||
|
const bias = 3n * first + second;
|
||||||
|
const denominator = totalVotes + bias;
|
||||||
|
|
||||||
|
if (denominator === 0n) {
|
||||||
|
return 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(totalVotes * 100n / denominator);
|
||||||
|
}, [proposal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
|
<TableRow id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
@ -195,7 +224,7 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
<Chip
|
<Chip
|
||||||
sx={{ width: "100px" }}
|
sx={{ width: "100px" }}
|
||||||
label={convertStatusToLabel(proposal.state)}
|
label={convertStatusToLabel(proposal.state)}
|
||||||
background={convertStatusToColor(proposal.state)}
|
template={convertStatusToTemplate(proposal.state)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
@ -215,8 +244,8 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
barColor={theme.colors.feedback.success}
|
barColor={theme.colors.feedback.success}
|
||||||
barBackground={theme.colors.feedback.error}
|
barBackground={theme.colors.feedback.error}
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={proposal?.voteValue ?? 0}
|
value={voteValue}
|
||||||
target={proposal?.voteTarget ?? 50}
|
target={voteTarget}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@ -244,6 +273,7 @@ const ProposalRow = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery('(max-width: 450px)');
|
const isSmallScreen = useMediaQuery('(max-width: 450px)');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
|
<Box id={proposal.hashes.short + `--proposal`} data-testid={proposal.hashes.short + `--proposal`}>
|
||||||
<Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between">
|
<Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between">
|
||||||
@ -253,7 +283,7 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
<Chip
|
<Chip
|
||||||
sx={{ width: "88px" }}
|
sx={{ width: "88px" }}
|
||||||
label={convertStatusToLabel(proposal.state)}
|
label={convertStatusToLabel(proposal.state)}
|
||||||
background={convertStatusToColor(proposal.state)}
|
template={convertStatusToTemplate(proposal.state)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography>
|
<Typography>
|
||||||
@ -271,8 +301,8 @@ const ProposalCard = ({ proposal, blockNumber, openProposal, chainId }) => {
|
|||||||
barColor={theme.colors.feedback.success}
|
barColor={theme.colors.feedback.success}
|
||||||
barBackground={theme.colors.feedback.error}
|
barBackground={theme.colors.feedback.error}
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={proposal?.voteValue ?? 0}
|
value={69}
|
||||||
target={proposal?.voteTarget ?? 50}
|
target={Math.floor(Math.random() * 101)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginBottom="20px">
|
<Box marginBottom="20px">
|
||||||
@ -312,11 +342,10 @@ const ProposalFilterTrigger = ({ trigger, setTrigger }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const convertDeadline = (deadline, blockNumber, chainId) => {
|
const convertDeadline = (deadline, blockNumber, chainId) => {
|
||||||
const alreadyHappened = blockNumber > deadline;
|
const diff = blockNumber > deadline ? blockNumber - deadline : deadline - blockNumber;
|
||||||
const diff = alreadyHappened ? blockNumber - deadline : deadline - blockNumber;
|
|
||||||
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
|
const voteSeconds = Number(diff * networkAvgBlockSpeed(chainId));
|
||||||
|
|
||||||
const result = prettifySeconds(voteSeconds, "min");
|
const result = prettifySeconds(voteSeconds, "mins");
|
||||||
if (result === "now") {
|
if (result === "now") {
|
||||||
return new Date(Date.now()).toLocaleDateString('en-US', {
|
return new Date(Date.now()).toLocaleDateString('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@ -325,10 +354,7 @@ const convertDeadline = (deadline, blockNumber, chainId) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = alreadyHappened ? "" : "in ";
|
return `in ${result}`;
|
||||||
const postfix = alreadyHappened ? " ago" : "";
|
|
||||||
|
|
||||||
return `${prefix}${result}${postfix}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProposalsList;
|
export default ProposalsList;
|
||||||
|
|||||||
@ -20,12 +20,20 @@ export const prepareAuditReservesCalldata = (chainId) => {
|
|||||||
return { label, target, calldata, value };
|
return { label, target, calldata, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prepareAuditReservesDescription = "function audits and updates the protocol's total reserve value. It sums the value of all approved reserve and liquidity tokens, then stores and logs the new total.";
|
export const prepareAuditReservesDescription = "Audit Reserves function audits and updates the protocol's total reserve value. It sums the value of all approved reserve and liquidity tokens, then stores and logs the new total.";
|
||||||
|
|
||||||
export const AuditReservesSteps = () => {
|
export const AuditReservesSteps = () => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuditReservesParsed = (props) => {
|
export const AuditReservesParsed = (props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.isTable && <AuditReservesParsedCell {...props} />}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuditReservesParsedCell = (props) => {
|
||||||
return <ParsedCell {...props} />
|
return <ParsedCell {...props} />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export const prepareCreateBondCalldata = (chainId, markets, terms, quoteToken, i
|
|||||||
return { label, target, calldata, value };
|
return { label, target, calldata, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prepareCreateBondDescription = "function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol.";
|
export const prepareCreateBondDescription = "Create Bond function creates a new bond market by processing pricing, capacity, and term inputs. It initializes and stores the new bond market's complete configuration in the protocol.";
|
||||||
|
|
||||||
export const CreateBondParsed = (props) => {
|
export const CreateBondParsed = (props) => {
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
const [isOpened, setIsOpened] = useState(false);
|
||||||
@ -51,11 +51,15 @@ export const CreateBondParsed = (props) => {
|
|||||||
<CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} />
|
<CreateBondSteps args={props.args} ftsoSymbol={ftsoSymbol} nativeCurrency={props.nativeCoin} {...props} />
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
|
{props.isTable && <CreateBondParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props}/>}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CreateBondParsedCell = (props) => {
|
||||||
|
return <ParsedCell {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata, args }) => {
|
export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitialStep, addCalldata, args }) => {
|
||||||
const createMode = args === undefined;
|
const createMode = args === undefined;
|
||||||
|
|
||||||
@ -114,7 +118,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
|
|||||||
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
||||||
|
|
||||||
const possibleTokens = [
|
const possibleTokens = [
|
||||||
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
|
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${nativeCurrency} LP`, label: `${ftsoSymbol}-${nativeCurrency} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
|
||||||
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
|
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -125,7 +129,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
|
|||||||
createMode={createMode}
|
createMode={createMode}
|
||||||
possibleTokens={possibleTokens}
|
possibleTokens={possibleTokens}
|
||||||
setTokenAddress={createMode ? setTokenAddress : empty}
|
setTokenAddress={createMode ? setTokenAddress : empty}
|
||||||
nativeCurrency={reserveSymbol}
|
nativeCurrency={nativeCurrency}
|
||||||
ftsoSymbol={ftsoSymbol}
|
ftsoSymbol={ftsoSymbol}
|
||||||
capacityInQuote={capacityInQuote}
|
capacityInQuote={capacityInQuote}
|
||||||
setCapacityInQuote={createMode ? setCapacityInQuote : empty}
|
setCapacityInQuote={createMode ? setCapacityInQuote : empty}
|
||||||
@ -222,7 +226,7 @@ const IntervalsArguments = ({
|
|||||||
/>
|
/>
|
||||||
<ArgumentInput
|
<ArgumentInput
|
||||||
endString="seconds"
|
endString="seconds"
|
||||||
label="_intervals[1]"
|
label="_intervals[0]"
|
||||||
value={tuneInterval ?? ""}
|
value={tuneInterval ?? ""}
|
||||||
setValue={setTuneInterval}
|
setValue={setTuneInterval}
|
||||||
tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval."
|
tooltip="Time in seconds between bond price tuning events that evaluate actual vs. expected bond sales, adjusting how aggressively price decays in the next interval."
|
||||||
@ -292,7 +296,7 @@ const TokenAndBooleansArguments = ({
|
|||||||
rightText={"False"}
|
rightText={"False"}
|
||||||
setLeftValue={() => setCapacityInQuote(true)}
|
setLeftValue={() => setCapacityInQuote(true)}
|
||||||
setRightValue={() => setCapacityInQuote(false)}
|
setRightValue={() => setCapacityInQuote(false)}
|
||||||
tooltip={`Determines how the bond market capacity is measured. True = measured in _quoteToken, False = measured in ${ftsoSymbol}.`}
|
tooltip={`Determines how the bond market capacity is measured. True = measured in ${nativeCurrency}, False = measured in ${ftsoSymbol}.`}
|
||||||
/>
|
/>
|
||||||
<BooleanTrigger
|
<BooleanTrigger
|
||||||
value={fixedTerm}
|
value={fixedTerm}
|
||||||
@ -311,7 +315,6 @@ const TokenAndBooleansArguments = ({
|
|||||||
value={selectedOption ?? ""}
|
value={selectedOption ?? ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
options={possibleTokens}
|
options={possibleTokens}
|
||||||
maxWidth="calc(100vw - 70px)"
|
|
||||||
inputWidth="100%"
|
inputWidth="100%"
|
||||||
renderValue={(selected) => {
|
renderValue={(selected) => {
|
||||||
if (!selected || selected.length === 0) {
|
if (!selected || selected.length === 0) {
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { encodeFunctionData } from 'viem';
|
|
||||||
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import Modal from "../../../../components/Modal/Modal";
|
|
||||||
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
|
|
||||||
|
|
||||||
import { GHOST_GOVERNANCE_ADDRESSES } from "../../../../constants/addresses";
|
|
||||||
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
|
|
||||||
|
|
||||||
import { ArgumentInput, ParsedCell } from "./index";
|
|
||||||
|
|
||||||
export const prepareSetLateQuorumExtensionCalldata = (chainId, lateQuorumVoteExtension) => {
|
|
||||||
const value = 0n;
|
|
||||||
const label = "setLateQuorumVoteExtension";
|
|
||||||
const target = GHOST_GOVERNANCE_ADDRESSES[chainId];
|
|
||||||
const calldata = encodeFunctionData({
|
|
||||||
abi: GovernorAbi,
|
|
||||||
functionName: 'setLateQuorumVoteExtension',
|
|
||||||
args: [lateQuorumVoteExtension]
|
|
||||||
});
|
|
||||||
return { label, target, calldata, value };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const prepareSetLateQuorumExtensionDescription = "updates the current value of the vote extension parameter: the number of blocks that are required to pass from the time a proposal reaches quorum until its voting period ends.";
|
|
||||||
|
|
||||||
export const SetLateQuorumExtensionParsed = (props) => {
|
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
headerContent={
|
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
|
||||||
<Typography variant="h4">View setLateQuorumVoteExtension</Typography>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
open={isOpened}
|
|
||||||
onClose={() => setIsOpened(false)}
|
|
||||||
maxWidth="460px"
|
|
||||||
minHeight="80px"
|
|
||||||
>
|
|
||||||
<Box minHeight="100px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
|
||||||
<SetLateQuorumExtensionSteps {...props} />
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SetLateQuorumExtensionSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
|
|
||||||
const createMode = args === undefined;
|
|
||||||
|
|
||||||
const [lateQuorumVoteExtension, setlateQuorumVoteExtension] = useState(args?.at(0));
|
|
||||||
|
|
||||||
const handleProceed = () => {
|
|
||||||
addCalldata(prepareSetLateQuorumExtensionCalldata(chainId, lateQuorumVoteExtension));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent={"space-between"}>
|
|
||||||
<ArgumentInput
|
|
||||||
disabled={!createMode}
|
|
||||||
endString="blocks"
|
|
||||||
label="lateQuorumVoteExtension"
|
|
||||||
value={lateQuorumVoteExtension ?? ""}
|
|
||||||
setValue={createMode ? setlateQuorumVoteExtension : () => {}}
|
|
||||||
tooltip="Vote extension defines extension in blocks if a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at least a specified time has passed"
|
|
||||||
/>
|
|
||||||
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
|
||||||
<Box display="flex" gap="10px">
|
|
||||||
<TertiaryButton onClick={toInitialStep} fullWidth>Back</TertiaryButton>
|
|
||||||
<TertiaryButton disabled fullWidth>Next</TertiaryButton>
|
|
||||||
</Box>
|
|
||||||
<PrimaryButton
|
|
||||||
disabled={!lateQuorumVoteExtension}
|
|
||||||
onClick={() => handleProceed()}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
Create Function
|
|
||||||
</PrimaryButton>
|
|
||||||
</Box>}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { encodeFunctionData } from 'viem';
|
|
||||||
import { Box, Typography, TableRow, TableCell, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import Modal from "../../../../components/Modal/Modal";
|
|
||||||
import { PrimaryButton, TertiaryButton } from "../../../../components/Button";
|
|
||||||
|
|
||||||
import { GHOST_GOVERNANCE_ADDRESSES } from "../../../../constants/addresses";
|
|
||||||
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
|
|
||||||
|
|
||||||
import { ArgumentInput, ParsedCell } from "./index";
|
|
||||||
|
|
||||||
export const prepareSetProposalThresholdCalldata = (chainId, newThreshold) => {
|
|
||||||
const value = 0n;
|
|
||||||
const label = "setProposalThreshold";
|
|
||||||
const target = GHOST_GOVERNANCE_ADDRESSES[chainId];
|
|
||||||
const calldata = encodeFunctionData({
|
|
||||||
abi: GovernorAbi,
|
|
||||||
functionName: 'setProposalThreshold',
|
|
||||||
args: [newThreshold]
|
|
||||||
});
|
|
||||||
return { label, target, calldata, value };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const prepareSetProposalThresholdDescription = "updates the minimum amount required to create a proposal. This threshold applies only when there are no Active or Pending proposals; otherwise, a higher threshold is enforced.";
|
|
||||||
|
|
||||||
export const SetProposalThresholdParsed = (props) => {
|
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
headerContent={
|
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
|
||||||
<Typography variant="h4">View setProposalThreshold</Typography>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
open={isOpened}
|
|
||||||
onClose={() => setIsOpened(false)}
|
|
||||||
maxWidth="460px"
|
|
||||||
minHeight="80px"
|
|
||||||
>
|
|
||||||
<Box minHeight="100px" display="flex" alignItems="start" justifyContent="space-between" flexDirection="column">
|
|
||||||
<SetProposalThresholdSteps {...props} />
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SetProposalThresholdSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
|
|
||||||
const createMode = args === undefined;
|
|
||||||
|
|
||||||
const [newThreshold, setNewThreshold] = useState(args?.at(0));
|
|
||||||
|
|
||||||
const handleProceed = () => {
|
|
||||||
addCalldata(prepareSetProposalThresholdCalldata(chainId, newThreshold));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box height="100%" width="100%" display="flex" flexDirection="column" justifyContent={"space-between"}>
|
|
||||||
<ArgumentInput
|
|
||||||
disabled={!createMode}
|
|
||||||
endString="!CSPR units"
|
|
||||||
label="newProposalThreshold"
|
|
||||||
value={newThreshold ?? ""}
|
|
||||||
setValue={createMode ? setNewThreshold : () => {}}
|
|
||||||
tooltip="The new proposal threshold defines the minimum amount each proposer must lock for the duration of the proposal. This locked amount serves as the required deposit to create a proposal."
|
|
||||||
/>
|
|
||||||
{createMode && <Box width="100%" sx={{ marginTop: "20px" }} display="flex" flexDirection="column" gap="5px">
|
|
||||||
<Box display="flex" gap="10px">
|
|
||||||
<TertiaryButton onClick={toInitialStep} fullWidth>Back</TertiaryButton>
|
|
||||||
<TertiaryButton disabled fullWidth>Next</TertiaryButton>
|
|
||||||
</Box>
|
|
||||||
<PrimaryButton
|
|
||||||
disabled={!newThreshold}
|
|
||||||
onClick={() => handleProceed()}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
Create Function
|
|
||||||
</PrimaryButton>
|
|
||||||
</Box>}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -24,7 +24,7 @@ export const prepareSetAdjustmentCalldata = (chainId, rateChange, targetRate, in
|
|||||||
return { label, target, calldata, value };
|
return { label, target, calldata, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prepareSetAdjustmentDescription = "function schedules a gradual change to the staking APY. It increases or decreases the staking APY by a specified rate per epoch until a target rate reached.";
|
export const prepareSetAdjustmentDescription = "Set Adjustment function schedules a gradual change to the staking APY. It increases or decreases the staking APY by a specified rate per epoch until a target rate reached.";
|
||||||
|
|
||||||
export const SetAdjustmentParsed = (props) => {
|
export const SetAdjustmentParsed = (props) => {
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
const [isOpened, setIsOpened] = useState(false);
|
||||||
@ -45,17 +45,21 @@ export const SetAdjustmentParsed = (props) => {
|
|||||||
<SetAdjustmentSteps {...props} />
|
<SetAdjustmentSteps {...props} />
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
<ParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />
|
{props.isTable && <SetAdjustmentParsedCell isOpened={isOpened} setIsOpened={setIsOpened} {...props} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SetAdjustmentParsedCell = (props) => {
|
||||||
|
return <ParsedCell {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
|
export const SetAdjustmentSteps = ({ chainId, toInitialStep, addCalldata, args }) => {
|
||||||
const createMode = args === undefined;
|
const createMode = args === undefined;
|
||||||
|
|
||||||
const [rate, setRate] = useState(args?.at(0));
|
const [rate, setRate] = useState(args?.at(0));
|
||||||
const [target, setTarget] = useState(args?.at(1));
|
const [target, setTarget] = useState(args?.at(1));
|
||||||
const [increase, setIncrease] = useState(args === undefined ? true : args.at(2));
|
const [increase, setIncrease] = useState(args?.at(1) ?? true);
|
||||||
|
|
||||||
const handleProceed = () => {
|
const handleProceed = () => {
|
||||||
addCalldata(prepareSetAdjustmentCalldata(chainId, rate, target, increase));
|
addCalldata(prepareSetAdjustmentCalldata(chainId, rate, target, increase));
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import InfoTooltip from "../../../../components/Tooltip/InfoTooltip";
|
|||||||
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
|
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
|
||||||
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
|
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
|
||||||
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
||||||
import { abi as GovernorAbi } from "../../../../abi/GhostGovernor.json";
|
|
||||||
|
|
||||||
import { config } from "../../../../config";
|
import { config } from "../../../../config";
|
||||||
import { shorten, formatCurrency } from "../../../../helpers";
|
import { shorten, formatCurrency } from "../../../../helpers";
|
||||||
@ -18,20 +17,16 @@ import { shorten, formatCurrency } from "../../../../helpers";
|
|||||||
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
|
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
|
||||||
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
|
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
|
||||||
import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond";
|
import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond";
|
||||||
import { prepareSetProposalThresholdDescription, prepareSetProposalThresholdCalldata, SetProposalThresholdSteps, SetProposalThresholdParsed } from "./ProposalThreshold";
|
|
||||||
import { prepareSetLateQuorumExtensionDescription, prepareSetLateQuorumExtensionCalldata, SetLateQuorumExtensionSteps, SetLateQuorumExtensionParsed } from "./LateQuorumExtension";
|
|
||||||
|
|
||||||
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
|
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
|
||||||
|
|
||||||
export const allPossibleFunctions = [
|
export const allPossibleFunctions = [
|
||||||
{ value: "auditReserves", label: "auditReserves" },
|
{ value: "auditReserves", label: "auditReserves" },
|
||||||
{ value: "setAdjustment", label: "setAdjustment" },
|
{ value: "setAdjustment", label: "setAdjustment" },
|
||||||
{ value: "create", label: "create" },
|
{ value: "create", label: "create" },
|
||||||
{ value: "setProposalThreshold", label: "setProposalThreshold" },
|
|
||||||
{ value: "setLateQuorumVoteExtension", label: "setLateQuorumVoteExtension" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi, GovernorAbi];
|
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
|
||||||
|
|
||||||
const identifyAction = (calldata) => {
|
const identifyAction = (calldata) => {
|
||||||
let decoded = { functionName: "Unknown", args: [] };
|
let decoded = { functionName: "Unknown", args: [] };
|
||||||
@ -49,58 +44,78 @@ const identifyAction = (calldata) => {
|
|||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseFunctionCalldata = ({
|
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
|
||||||
metadata,
|
|
||||||
index,
|
|
||||||
chainId,
|
|
||||||
nativeCoin,
|
|
||||||
removeCalldata,
|
|
||||||
isTable = true,
|
|
||||||
}) => {
|
|
||||||
const { label, calldata, target, value } = metadata;
|
const { label, calldata, target, value } = metadata;
|
||||||
const { functionName, args } = identifyAction(calldata);
|
const { functionName, args } = identifyAction(calldata);
|
||||||
const labelOrName = label ?? (functionName ?? "Unknown");
|
const labelOrName = label ?? (functionName ?? "Unknown");
|
||||||
|
|
||||||
const remove = removeCalldata && (() => removeCalldata(index));
|
const remove = removeCalldata && (() => removeCalldata(index));
|
||||||
const props = {
|
|
||||||
isTable,
|
|
||||||
calldata,
|
|
||||||
label: labelOrName,
|
|
||||||
chainId: chainId,
|
|
||||||
remove: removeCalldata,
|
|
||||||
nativeCoin,
|
|
||||||
value,
|
|
||||||
target,
|
|
||||||
args,
|
|
||||||
id: index,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (functionName) {
|
switch (functionName) {
|
||||||
case allPossibleFunctions[0].value:
|
case "auditReserves":
|
||||||
return <AuditReservesParsed key={index} {...props} />;
|
return <AuditReservesParsed
|
||||||
case allPossibleFunctions[1].value:
|
isTable
|
||||||
return <SetAdjustmentParsed key={index} {...props} />;
|
key={index}
|
||||||
case allPossibleFunctions[2].value:
|
calldata={calldata}
|
||||||
return <CreateBondParsed {...props} />;
|
label={labelOrName}
|
||||||
case allPossibleFunctions[3].value:
|
chainId={chainId}
|
||||||
return <SetProposalThresholdParsed key={index} {...props} />;
|
remove={remove}
|
||||||
case allPossibleFunctions[4].value:
|
nativeCoin={nativeCoin}
|
||||||
return <SetLateQuorumExtensionParsed key={index} {...props} />;
|
value={value}
|
||||||
|
target={target}
|
||||||
|
id={index}
|
||||||
|
/>;
|
||||||
|
case "setAdjustment":
|
||||||
|
return <SetAdjustmentParsed
|
||||||
|
isTable
|
||||||
|
args={args}
|
||||||
|
key={index}
|
||||||
|
calldata={calldata}
|
||||||
|
label={labelOrName}
|
||||||
|
chainId={chainId}
|
||||||
|
remove={remove}
|
||||||
|
nativeCoin={nativeCoin}
|
||||||
|
value={value}
|
||||||
|
target={target}
|
||||||
|
id={index}
|
||||||
|
/>;
|
||||||
|
case "create":
|
||||||
|
return <CreateBondParsed
|
||||||
|
isTable
|
||||||
|
args={args}
|
||||||
|
key={index}
|
||||||
|
calldata={calldata}
|
||||||
|
label={labelOrName}
|
||||||
|
chainId={chainId}
|
||||||
|
remove={remove}
|
||||||
|
nativeCoin={nativeCoin}
|
||||||
|
value={value}
|
||||||
|
target={target}
|
||||||
|
id={index}
|
||||||
|
/>;
|
||||||
default:
|
default:
|
||||||
return <ParsedCell key={index} {...props} />;
|
return <ParsedCell
|
||||||
|
isTable
|
||||||
|
key={index}
|
||||||
|
calldata={calldata}
|
||||||
|
label="Unknown"
|
||||||
|
remove={remove}
|
||||||
|
nativeCoin={nativeCoin}
|
||||||
|
value={value}
|
||||||
|
target={target}
|
||||||
|
id={index}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFunctionArguments = (functionName) => {
|
export const getFunctionArguments = (functionName) => {
|
||||||
switch (functionName) {
|
switch (functionName) {
|
||||||
case allPossibleFunctions[1].value:
|
case "auditReserves":
|
||||||
|
return null;
|
||||||
|
case "setAdjustment":
|
||||||
return SetAdjustmentSteps;
|
return SetAdjustmentSteps;
|
||||||
case allPossibleFunctions[2].value:
|
case "create":
|
||||||
return CreateBondSteps;
|
return CreateBondSteps;
|
||||||
case allPossibleFunctions[3].value:
|
|
||||||
return SetProposalThresholdSteps;
|
|
||||||
case allPossibleFunctions[4].value:
|
|
||||||
return SetLateQuorumExtensionSteps;
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -108,16 +123,12 @@ export const getFunctionArguments = (functionName) => {
|
|||||||
|
|
||||||
export const getFunctionCalldata = (functionName, chainId) => {
|
export const getFunctionCalldata = (functionName, chainId) => {
|
||||||
switch (functionName) {
|
switch (functionName) {
|
||||||
case allPossibleFunctions[0].value:
|
case "auditReserves":
|
||||||
return prepareAuditReservesCalldata(chainId);
|
return prepareAuditReservesCalldata(chainId);
|
||||||
case allPossibleFunctions[1].value:
|
case "setAdjustment":
|
||||||
return prepareSetAdjustmentCalldata(chainId);
|
return prepareSetAdjustmentCalldata(chainId);
|
||||||
case allPossibleFunctions[2].value:
|
case "create":
|
||||||
return prepareCreateBondCalldata(chainId);
|
return prepareCreateBondCalldata(chainId);
|
||||||
case allPossibleFunctions[3].value:
|
|
||||||
return prepareSetProposalThresholdCalldata(chainId);
|
|
||||||
case allPossibleFunctions[4].value:
|
|
||||||
return prepareSetLateQuorumExtensionCalldata(chainId);
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -125,16 +136,12 @@ export const getFunctionCalldata = (functionName, chainId) => {
|
|||||||
|
|
||||||
export const getFunctionDescription = (functionName) => {
|
export const getFunctionDescription = (functionName) => {
|
||||||
switch (functionName) {
|
switch (functionName) {
|
||||||
case allPossibleFunctions[0].value:
|
case "auditReserves":
|
||||||
return prepareAuditReservesDescription;
|
return prepareAuditReservesDescription;
|
||||||
case allPossibleFunctions[1].value:
|
case "setAdjustment":
|
||||||
return prepareSetAdjustmentDescription;
|
return prepareSetAdjustmentDescription;
|
||||||
case allPossibleFunctions[2].value:
|
case "create":
|
||||||
return prepareCreateBondDescription;
|
return prepareCreateBondDescription;
|
||||||
case allPossibleFunctions[3].value:
|
|
||||||
return prepareSetProposalThresholdDescription;
|
|
||||||
case allPossibleFunctions[4].value:
|
|
||||||
return prepareSetLateQuorumExtensionDescription;
|
|
||||||
default:
|
default:
|
||||||
return DEFAULT_DESCRIPTION;
|
return DEFAULT_DESCRIPTION;
|
||||||
}
|
}
|
||||||
@ -268,86 +275,34 @@ export const ParsedCell = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.isTable) {
|
|
||||||
return (
|
|
||||||
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
|
||||||
<Typography>{props.label}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
|
||||||
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Typography>{shorten(props.target)}</Typography>
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
|
||||||
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Typography>{shorten(props.calldata)}</Typography>
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell align="center" style={{ padding: "8px 0" }}>
|
|
||||||
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
|
||||||
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
|
|
||||||
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
|
||||||
id={props.id + `--proposalFunction`}
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
data-testid={props.id + `--proposalFunction`}
|
|
||||||
display="flex"
|
|
||||||
flexDirection="column"
|
|
||||||
>
|
|
||||||
<Box display="flex" justifyContent="space-between">
|
|
||||||
<Typography>{`Function ${props.id + 1}`}</Typography>
|
|
||||||
<Typography>{props.label}</Typography>
|
<Typography>{props.label}</Typography>
|
||||||
</Box>
|
</TableCell>
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between">
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
<Typography>Target</Typography>
|
|
||||||
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
|
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
|
||||||
<Typography>{shorten(props.target)}</Typography>
|
<Typography>{shorten(props.target)}</Typography>
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</TableCell>
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between">
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
<Typography>Calldata</Typography>
|
|
||||||
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
|
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
|
||||||
<Typography>{shorten(props.calldata)}</Typography>
|
<Typography>{shorten(props.calldata)}</Typography>
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</TableCell>
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between">
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
<Typography>Value</Typography>
|
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
|
||||||
<Link onClick={handleCalldataCopy} target="_blank" rel="noopener noreferrer">
|
</TableCell>
|
||||||
<Typography>{formatCurrency(props.value, 4, props.nativeCoin)}</Typography>
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{(props.args || props.remove) && <Box
|
<TableCell>
|
||||||
display="flex"
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
flexDirection="row"
|
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
|
||||||
justifyContent="space-between"
|
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
|
||||||
gap="10px"
|
</div>
|
||||||
marginTop="10px"
|
</TableCell>
|
||||||
>
|
</TableRow>
|
||||||
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
|
|
||||||
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
|
|
||||||
</Box>}
|
|
||||||
|
|
||||||
<hr width="100%" style={{ margin: "20px 0" }} />
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,18 @@
|
|||||||
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
|
export const MY_PROPOSALS_PREFIX = "MY_PROPOSALS";
|
||||||
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
|
export const VOTED_PROPOSALS_PREFIX = "VOTED_PROPOSALS";
|
||||||
|
|
||||||
export const convertStatusToColor = (status) => {
|
export const convertStatusToTemplate = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1:
|
|
||||||
return "#f2e370";
|
|
||||||
case 2:
|
|
||||||
return "#f06f73";
|
|
||||||
case 3:
|
|
||||||
return "#f06f73";
|
|
||||||
case 4:
|
|
||||||
return "#60c45b";
|
|
||||||
case 5:
|
|
||||||
return "#60c45b";
|
|
||||||
case 6:
|
|
||||||
return "#f06f73";
|
|
||||||
case 7:
|
case 7:
|
||||||
return "#f2e370";
|
return 'darkGray';
|
||||||
|
case 2:
|
||||||
|
return 'warning';
|
||||||
|
case 4:
|
||||||
|
return 'success';
|
||||||
|
case 3:
|
||||||
|
return 'error';
|
||||||
default:
|
default:
|
||||||
return "#a2b7ce";
|
return 'darkGray';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export default function NotFound({
|
|||||||
});
|
});
|
||||||
setWrongNetworkToastId(toastId);
|
setWrongNetworkToastId(toastId);
|
||||||
|
|
||||||
|
|
||||||
setNetworkId(newChainId);
|
setNetworkId(newChainId);
|
||||||
switchChain({ chainId: newChainId });
|
switchChain({ chainId: newChainId });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
|
|||||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
|
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
const [upperToken, setUpperToken] = useState(ftsoSymbol);
|
const [upperToken, setUpperToken] = useState(ftsoSymbol);
|
||||||
@ -30,16 +31,28 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
case (upperToken === ftsoSymbol && bottomToken === stnkSymbol):
|
||||||
|
setAction("STAKE")
|
||||||
|
break;
|
||||||
case (upperToken === ftsoSymbol && bottomToken === ghstSymbol):
|
case (upperToken === ftsoSymbol && bottomToken === ghstSymbol):
|
||||||
setAction("STAKE")
|
setAction("STAKE")
|
||||||
break;
|
break;
|
||||||
|
case (upperToken === stnkSymbol && bottomToken === ftsoSymbol):
|
||||||
|
setAction("UNSTAKE")
|
||||||
|
break;
|
||||||
case (upperToken === ghstSymbol && bottomToken === ftsoSymbol):
|
case (upperToken === ghstSymbol && bottomToken === ftsoSymbol):
|
||||||
setAction("UNSTAKE")
|
setAction("UNSTAKE")
|
||||||
break;
|
break;
|
||||||
|
case (upperToken === stnkSymbol && bottomToken === ghstSymbol):
|
||||||
|
setAction("WRAP")
|
||||||
|
break;
|
||||||
|
case (upperToken === ghstSymbol && bottomToken === stnkSymbol):
|
||||||
|
setAction("UNWRAP")
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
setAction("STAKE")
|
setAction("STAKE")
|
||||||
}
|
}
|
||||||
}, [ftsoSymbol, ghstSymbol, upperToken, bottomToken])
|
}, [upperToken, bottomToken])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Dispatch, SetStateAction, useState, useEffect } from "react";
|
import { Dispatch, SetStateAction, useState, useEffect } from "react";
|
||||||
import { Box, Container, Grid, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Container, Grid, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import Paper from "../../components/Paper/Paper";
|
import Paper from "../../components/Paper/Paper";
|
||||||
@ -20,7 +19,6 @@ import { useEpoch } from "../../hooks/staking";
|
|||||||
import { useTokenSymbol } from "../../hooks/tokens";
|
import { useTokenSymbol } from "../../hooks/tokens";
|
||||||
|
|
||||||
export const StakeContainer = ({ chainId, address, connect }) => {
|
export const StakeContainer = ({ chainId, address, connect }) => {
|
||||||
const location = useLocation();
|
|
||||||
const [isModalOpened, handleModal] = useState(false);
|
const [isModalOpened, handleModal] = useState(false);
|
||||||
|
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
@ -33,8 +31,8 @@ export const StakeContainer = ({ chainId, address, connect }) => {
|
|||||||
const { epoch, refetch: refetchEpoch } = useEpoch(chainId);
|
const { epoch, refetch: refetchEpoch } = useEpoch(chainId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/stake" });
|
||||||
}, [location])
|
}, [])
|
||||||
|
|
||||||
if (isModalOpened) {
|
if (isModalOpened) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -30,6 +30,8 @@ const ClaimConfirmationModal = (props) => {
|
|||||||
props.ghstSymbol
|
props.ghstSymbol
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unknown action")
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
import { useState, useMemo, useCallback } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
|
|
||||||
@ -21,16 +21,14 @@ import InfoTooltip from "../../../components/Tooltip/InfoTooltip";
|
|||||||
import Paper from "../../../components/Paper/Paper";
|
import Paper from "../../../components/Paper/Paper";
|
||||||
import Token from "../../../components/Token/Token";
|
import Token from "../../../components/Token/Token";
|
||||||
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
|
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
|
||||||
|
import { Tab, Tabs } from "../../../components/Tabs/Tabs";
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
||||||
import { formatNumber, formatCurrency } from "../../../helpers";
|
import { formatNumber } from "../../../helpers";
|
||||||
import { STAKING_ADDRESSES } from "../../../constants/addresses";
|
import { STAKING_ADDRESSES } from "../../../constants/addresses";
|
||||||
import { useCurrentIndex, useWarmupInfo, claim, breakout } from "../../../hooks/staking";
|
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
|
||||||
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
|
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
|
||||||
import { useGhstPrice, useStnkPrice } from "../../../hooks/prices";
|
|
||||||
import { useBreakoutModal } from "../../../hooks/breakoutModal";
|
|
||||||
import { isNetworkLegacy } from "../../../constants";
|
|
||||||
|
|
||||||
import ClaimConfirmationModal from "./ClaimConfirmationModal";
|
import ClaimConfirmationModal from "./ClaimConfirmationModal";
|
||||||
|
|
||||||
@ -53,12 +51,11 @@ const StyledTableHeader = styled(TableHead)(({ theme }) => ({
|
|||||||
export const ClaimsArea = ({ chainId, address, epoch }) => {
|
export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 745px)");
|
const isSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
|
|
||||||
const { breakoutFromStaking } = useBreakoutModal();
|
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
|
||||||
const [confirmationModalOpen, setConfirmationModalOpenInner] = useState(false);
|
const [isPayoutGhst, setIsPayoutGhst] = useState(localStorage.getItem("stake-isGhstPayout")
|
||||||
const [isPayoutGhst, _] = useState(true);
|
? localStorage.getItem("stake-isGhstPayout")
|
||||||
|
: false
|
||||||
const ghstPrice = useGhstPrice(chainId);
|
);
|
||||||
const stnkPrice = useStnkPrice(chainId);
|
|
||||||
|
|
||||||
const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address);
|
const { warmupInfo: claim, refetch: claimRefetch } = useWarmupInfo(chainId, address);
|
||||||
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
|
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
|
||||||
@ -68,55 +65,29 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
|||||||
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
const claimableBalance = useMemo(() => {
|
|
||||||
if (isNetworkLegacy(chainId)) {
|
|
||||||
return isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares;
|
|
||||||
}
|
|
||||||
const toClaim = new DecimalBigNumber(claim.shares, 18);
|
|
||||||
return isPayoutGhst ? toClaim : toClaim.mul(currentIndex);
|
|
||||||
}, [chainId, claim, currentIndex, balanceForShares]);
|
|
||||||
|
|
||||||
const breakoutBalance = useMemo(() => {
|
|
||||||
if (isNetworkLegacy(chainId)) {
|
|
||||||
return undefined; // short circuit
|
|
||||||
}
|
|
||||||
return isPayoutGhst ? claim.shares : claim.shares.mul(currentIndex);
|
|
||||||
}, [chainId, claim, currentIndex]);
|
|
||||||
|
|
||||||
const setConfirmationModalOpen = useCallback(async (value) => {
|
|
||||||
if (isNetworkLegacy(chainId) || value) {
|
|
||||||
setConfirmationModalOpenInner(true);
|
|
||||||
} else {
|
|
||||||
const defaultFunction = async () => {
|
|
||||||
await claim(chainId, address, false, stnkSymbol, ghstSymbol);
|
|
||||||
await claimRefetch();
|
|
||||||
}
|
|
||||||
const warmupLeft = claim.expiry - epoch.number;
|
|
||||||
const toExecute = async (receiver) => {
|
|
||||||
const txHash = await breakout(chainId, address, receiver, breakoutBalance);
|
|
||||||
return txHash;
|
|
||||||
}
|
|
||||||
breakoutFromStaking({ defaultFunction, toExecute, amount: claimableBalance, warmupLeft })
|
|
||||||
}
|
|
||||||
}, [claim, epoch, address, chainId, ghstSymbol, breakoutBalance, claimableBalance]);
|
|
||||||
|
|
||||||
const closeConfirmationModal = () => {
|
const closeConfirmationModal = () => {
|
||||||
setConfirmationModalOpenInner(false);
|
setConfirmationModalOpen(false);
|
||||||
claimRefetch();
|
claimRefetch();
|
||||||
currentIndexRefetch();
|
currentIndexRefetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claim.shares === 0n) return <></>;
|
if (claim.shares === 0n) return <></>;
|
||||||
|
|
||||||
const warmupTooltip = `Your claim earns rebases during warm-up. You can emergency withdraw, but this forfeits the rebases`;
|
const setIsPayoutGhstInner = (value) => {
|
||||||
|
setIsPayoutGhst(value);
|
||||||
|
localStorage.setitem("bond-isGhstPayout", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const warmupTooltip = `Your claim earns rebases during warmup. You can emergency withdraw, but this forfeits the rebases`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ClaimConfirmationModal
|
<ClaimConfirmationModal
|
||||||
open={confirmationModalOpen}
|
open={confirmationModalOpen}
|
||||||
onClose={() => closeConfirmationModal()}
|
onClose={() => closeConfirmationModal()}
|
||||||
|
chainid={chainId}
|
||||||
receiver={address}
|
receiver={address}
|
||||||
receiveAmount={claim.expiry > epoch.number ? claim.deposit : claimableBalance}
|
receiveAmount={claim.expiry > epoch.number ? claim.deposit : isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
|
||||||
outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
|
outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
|
||||||
stnkSymbol={stnkSymbol}
|
stnkSymbol={stnkSymbol}
|
||||||
ghstSymbol={ghstSymbol}
|
ghstSymbol={ghstSymbol}
|
||||||
@ -135,6 +106,18 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
|||||||
flexDirection={isSmallScreen ? "column" : "row"}
|
flexDirection={isSmallScreen ? "column" : "row"}
|
||||||
>
|
>
|
||||||
<Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography>
|
<Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography>
|
||||||
|
<Tabs
|
||||||
|
centered
|
||||||
|
textColor="primary"
|
||||||
|
indicatorColor="primary"
|
||||||
|
value={isPayoutGhst ? 1 : 0}
|
||||||
|
aria-label="Claim token tabs"
|
||||||
|
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
|
||||||
|
TabIndicatorProps={{ style: { display: "none" } }}
|
||||||
|
>
|
||||||
|
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
|
||||||
|
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
|
||||||
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
tooltip={warmupTooltip}
|
tooltip={warmupTooltip}
|
||||||
@ -143,37 +126,33 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
|||||||
{isSmallScreen ? (
|
{isSmallScreen ? (
|
||||||
<MobileClaimInfo
|
<MobileClaimInfo
|
||||||
setConfirmationModalOpen={setConfirmationModalOpen}
|
setConfirmationModalOpen={setConfirmationModalOpen}
|
||||||
prepareBalance={claimableBalance}
|
prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
|
||||||
isPayoutGhst={isPayoutGhst}
|
isPayoutGhst={isPayoutGhst}
|
||||||
claim={claim}
|
claim={claim}
|
||||||
epoch={epoch}
|
epoch={epoch}
|
||||||
isClaimable={claim.expiry > epoch.number}
|
isClaimable={claim.expiry > epoch.number}
|
||||||
isLegacy={isNetworkLegacy(chainId)}
|
|
||||||
stnkSymbol={stnkSymbol}
|
stnkSymbol={stnkSymbol}
|
||||||
ghstSymbol={ghstSymbol}
|
ghstSymbol={ghstSymbol}
|
||||||
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Table>
|
<Table>
|
||||||
<StyledTableHeader className={classes.stakePoolHeaderText}>
|
<StyledTableHeader className={classes.stakePoolHeaderText}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell style={{ width: "200px", padding: "8px 0" }}>Asset</TableCell>
|
<TableCell style={{ width: "200px", padding: "8px 0" }}>Asset</TableCell>
|
||||||
<TableCell style={{ width: "200px", padding: "8px 0" }}>Staked Amount</TableCell>
|
<TableCell style={{ width: "200px", padding: "8px 0" }}>Amount</TableCell>
|
||||||
<TableCell style={{ width: "150px", padding: "8px 0" }}>Claimable In</TableCell>
|
<TableCell style={{ width: "150px", padding: "8px 0" }}>Claimable In</TableCell>
|
||||||
<TableCell></TableCell>
|
<TableCell></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</StyledTableHeader>
|
</StyledTableHeader>
|
||||||
<ClaimInfo
|
<ClaimInfo
|
||||||
setConfirmationModalOpen={setConfirmationModalOpen}
|
setConfirmationModalOpen={setConfirmationModalOpen}
|
||||||
prepareBalance={claimableBalance}
|
prepareBalance={isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
|
||||||
isPayoutGhst={isPayoutGhst}
|
isPayoutGhst={isPayoutGhst}
|
||||||
claim={claim}
|
claim={claim}
|
||||||
epoch={epoch}
|
epoch={epoch}
|
||||||
isClaimable={claim.expiry > epoch.number}
|
isClaimable={claim.expiry > epoch.number}
|
||||||
isLegacy={isNetworkLegacy(chainId)}
|
|
||||||
stnkSymbol={stnkSymbol}
|
stnkSymbol={stnkSymbol}
|
||||||
ghstSymbol={ghstSymbol}
|
ghstSymbol={ghstSymbol}
|
||||||
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
|
|
||||||
/>
|
/>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
@ -183,18 +162,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ClaimInfo = ({
|
const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst, stnkSymbol, ghstSymbol }) => {
|
||||||
setConfirmationModalOpen,
|
|
||||||
prepareBalance,
|
|
||||||
claim,
|
|
||||||
epoch,
|
|
||||||
isClaimable,
|
|
||||||
isPayoutGhst,
|
|
||||||
stnkSymbol,
|
|
||||||
ghstSymbol,
|
|
||||||
tokenPrice,
|
|
||||||
isLegacy,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@ -208,10 +176,7 @@ const ClaimInfo = ({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell style={{ padding: "8px 8px 8px 0" }}>
|
<TableCell style={{ padding: "8px 8px 8px 0" }}>
|
||||||
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}>
|
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}>
|
||||||
{formatCurrency(prepareBalance, 5, isPayoutGhst ? ghstSymbol : stnkSymbol)}
|
{`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? ghstSymbol : stnkSymbol}`}
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" gutterBottom={false} style={{ lineHeight: 1.4 }}>
|
|
||||||
{formatCurrency(prepareBalance * tokenPrice, 2)}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell style={{ padding: "8px 8px 8px 0" }}>
|
<TableCell style={{ padding: "8px 8px 8px 0" }}>
|
||||||
@ -221,7 +186,6 @@ const ClaimInfo = ({
|
|||||||
<ActionButtons
|
<ActionButtons
|
||||||
setConfirmationModalOpen={setConfirmationModalOpen}
|
setConfirmationModalOpen={setConfirmationModalOpen}
|
||||||
isClaimable={isClaimable}
|
isClaimable={isClaimable}
|
||||||
isLegacy={isLegacy}
|
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -229,18 +193,7 @@ const ClaimInfo = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MobileClaimInfo = ({
|
const MobileClaimInfo = ({ setConfirmationModalOpen, prepareBalance, epoch, claim, isPayoutGhst, isClaimable, ghstSymbol, stnkSymbol }) => {
|
||||||
setConfirmationModalOpen,
|
|
||||||
prepareBalance,
|
|
||||||
epoch,
|
|
||||||
claim,
|
|
||||||
isPayoutGhst,
|
|
||||||
isClaimable,
|
|
||||||
ghstSymbol,
|
|
||||||
stnkSymbol,
|
|
||||||
tokenPrice,
|
|
||||||
isLegacy,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Box mt="10px">
|
<Box mt="10px">
|
||||||
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
|
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
|
||||||
@ -251,15 +204,10 @@ const MobileClaimInfo = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<DataRow
|
<DataRow
|
||||||
title="Staked Amount"
|
title="Amount"
|
||||||
balance={formatNumber(prepareBalance, 7)}
|
balance={formatNumber(prepareBalance, 7)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataRow
|
|
||||||
title="Price Estimation"
|
|
||||||
balance={formatCurrency(prepareBalance * tokenPrice, 2)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DataRow
|
<DataRow
|
||||||
title="Claimed in"
|
title="Claimed in"
|
||||||
balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))}
|
balance={prettifySecondsInDays(epoch.length * (claim.expiry - epoch.number))}
|
||||||
@ -269,13 +217,12 @@ const MobileClaimInfo = ({
|
|||||||
isSmallScreen={true}
|
isSmallScreen={true}
|
||||||
setConfirmationModalOpen={setConfirmationModalOpen}
|
setConfirmationModalOpen={setConfirmationModalOpen}
|
||||||
isClaimable={isClaimable}
|
isClaimable={isClaimable}
|
||||||
isLegacy={isLegacy}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionButtons = ({ setConfirmationModalOpen, isLegacy, isSmallScreen = false, isClaimable = false }) => {
|
const ActionButtons = ({ setConfirmationModalOpen, isSmallScreen = false, isClaimable = false }) => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
@ -296,8 +243,8 @@ const ActionButtons = ({ setConfirmationModalOpen, isLegacy, isSmallScreen = fal
|
|||||||
fullWidth={isSmallScreen}
|
fullWidth={isSmallScreen}
|
||||||
loading={false}
|
loading={false}
|
||||||
sx={{ flexGrow: 1 }}
|
sx={{ flexGrow: 1 }}
|
||||||
onClick={() => setConfirmationModalOpen(false)}
|
onClick={() => setConfirmationModalOpen(true)}
|
||||||
disabled={isLegacy && isClaimable}
|
disabled={isClaimable}
|
||||||
>
|
>
|
||||||
Claim
|
Claim
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useNavigate, useParams, createSearchParams } from "react-router-dom";
|
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import Paper from "../../../components/Paper/Paper";
|
import Paper from "../../../components/Paper/Paper";
|
||||||
import { SecondaryButton } from "../../../components/Button";
|
import { SecondaryButton } from "../../../components/Button";
|
||||||
@ -17,15 +17,15 @@ import TokenStack from "../../../components/TokenStack/TokenStack";
|
|||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
import { formatCurrency } from "../../../helpers";
|
import { formatCurrency } from "../../../helpers";
|
||||||
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
||||||
|
import { isNetworkLegacy } from "../../../constants";
|
||||||
|
|
||||||
import { useLpValuation } from "../../../hooks/treasury";
|
import { useLpValuation } from "../../../hooks/treasury";
|
||||||
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
|
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
|
||||||
|
|
||||||
import { EMPTY_ADDRESS, FTSO_ADDRESSES } from "../../../constants/addresses";
|
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||||
|
|
||||||
const FarmPools = ({ chainId }) => {
|
const FarmPools = ({ chainId }) => {
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 775px)");
|
const isSmallScreen = useMediaQuery("(max-width: 775px)");
|
||||||
const { network } = useParams();
|
|
||||||
|
|
||||||
const { totalSupply: reserveFtsoUniTotalSupply } = useTotalSupply(chainId, "RESERVE_FTSO");
|
const { totalSupply: reserveFtsoUniTotalSupply } = useTotalSupply(chainId, "RESERVE_FTSO");
|
||||||
const reserveFtsoUniValuation = useLpValuation(chainId, "RESERVE_FTSO", reserveFtsoUniTotalSupply._value);
|
const reserveFtsoUniValuation = useLpValuation(chainId, "RESERVE_FTSO", reserveFtsoUniTotalSupply._value);
|
||||||
@ -35,14 +35,14 @@ const FarmPools = ({ chainId }) => {
|
|||||||
|
|
||||||
const pools = [
|
const pools = [
|
||||||
{
|
{
|
||||||
icons: ["FTSO", tokenNameConverter(chainId, reserveSymbol)],
|
icons: ["FTSO", isNetworkLegacy(chainId) ? "GDAI" : tokenNameConverter(chainId, reserveSymbol)],
|
||||||
name: `${ftsoSymbol}-${reserveSymbol}`,
|
name: `${ftsoSymbol}-${reserveSymbol}`,
|
||||||
dex: "Uniswap V2",
|
dex: "Uniswap V2",
|
||||||
url: `/${network}/dex/uniswap`,
|
url: "/dex/uniswap",
|
||||||
tvl: reserveFtsoUniValuation,
|
tvl: reserveFtsoUniValuation,
|
||||||
params: createSearchParams({
|
params: createSearchParams({
|
||||||
pool: "true",
|
pool: "true",
|
||||||
from: `${EMPTY_ADDRESS}`,
|
from: `${RESERVE_ADDRESSES[chainId]}`,
|
||||||
to: `${FTSO_ADDRESSES[chainId]}`,
|
to: `${FTSO_ADDRESSES[chainId]}`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const TotalDeposit = props => {
|
|||||||
const _props = {
|
const _props = {
|
||||||
...props,
|
...props,
|
||||||
label: "Total Deposit",
|
label: "Total Deposit",
|
||||||
tooltip: `Total reserves of native coins, LP tokens, and other assets in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`,
|
tooltip: `The total stablecoin reserves in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`;
|
if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`;
|
||||||
|
|||||||
@ -32,19 +32,15 @@ const StakeConfirmationModal = (props) => {
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
data-testid="acknowledge-stake-warm-up"
|
data-testid="acknowledge-warmup"
|
||||||
checked={acknowledgedWarmup}
|
checked={acknowledgedWarmup}
|
||||||
onChange={event => setAcknowledgedWarmup(event.target.checked)}
|
onChange={event => setAcknowledgedWarmup(event.target.checked)}
|
||||||
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
|
||||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={
|
label={`I understand the ${props.ftsoSymbol} I’m staking will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed`}
|
||||||
<Typography variant="body2">
|
/>
|
||||||
{`I understand the ${props.ftsoSymbol} I’m staking will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed`}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -59,7 +55,7 @@ const StakeConfirmationModal = (props) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
href="https://ghostchain.io/ghostdao_litepaper"
|
href="https://ghostchain.io/ghostdao_litepaper"
|
||||||
>
|
>
|
||||||
Why is there a warm-up?
|
Why is there a warmup?
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Avatar, Box, Link } from "@mui/material";
|
import { Avatar, Box, Link } from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
@ -23,7 +23,6 @@ import {
|
|||||||
STAKING_ADDRESSES,
|
STAKING_ADDRESSES,
|
||||||
} from "../../../constants/addresses";
|
} from "../../../constants/addresses";
|
||||||
import { useCurrentIndex } from "../../../hooks/staking";
|
import { useCurrentIndex } from "../../../hooks/staking";
|
||||||
import { useLocalStorage } from "../../../hooks/localstorage";
|
|
||||||
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
||||||
import { formatNumber } from "../../../helpers";
|
import { formatNumber } from "../../../helpers";
|
||||||
|
|
||||||
@ -83,11 +82,20 @@ export const StakeInputArea = ({
|
|||||||
const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false);
|
const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false);
|
||||||
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
|
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
|
||||||
|
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("stake-decimals")
|
||||||
|
? localStorage.getItem("stake-decimals")
|
||||||
|
: "5"
|
||||||
|
);
|
||||||
|
|
||||||
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "stake-decimals", "5"));
|
const [isClaim, setIsClaim] = useState(localStorage.getItem("stake-isClaim")
|
||||||
const [isClaim, setIsClaim] = useState(() => getStorageValue(chainId, address, "stake-isClaim", true));
|
? localStorage.getItem("stake-isClaim") === 'true'
|
||||||
const [isTrigger, setIsTrigger] = useState(() => getStorageValue(chainId, address, "stake-isTrigger", true));
|
: true
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isTrigger, setIsTrigger] = useState(localStorage.getItem("stake-isTrigger")
|
||||||
|
? localStorage.getItem("stake-isTrigger") === 'true'
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [receiveAmount, setReceiveAmount] = useState("");
|
const [receiveAmount, setReceiveAmount] = useState("");
|
||||||
@ -102,22 +110,23 @@ export const StakeInputArea = ({
|
|||||||
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
const setIsClaimInner = useCallback((value) => {
|
const setIsClaimInner = (value) => {
|
||||||
setIsClaim(value);
|
setIsClaim(value);
|
||||||
setStorageValue(chainId, address, "stake-isClaim", value);
|
localStorage.setItem("stake-isClaim", value);
|
||||||
}, [chainId, address]);
|
|
||||||
|
|
||||||
const setIsTriggerInner = useCallback((value) => {
|
}
|
||||||
|
|
||||||
|
const setIsTriggerInner = (value) => {
|
||||||
setIsTrigger(value);
|
setIsTrigger(value);
|
||||||
setStorageValue(chainId, address, "stake-isTrigger", value);
|
localStorage.setItem("stake-isTrigger", value);
|
||||||
}, [chainId, address]);
|
}
|
||||||
|
|
||||||
const setFormatDecimalsInner = useCallback((value) => {
|
const setFormatDecimalsInner = (value) => {
|
||||||
if (Number(value) <= 17) {
|
if (Number(value) <= 17) {
|
||||||
setFormatDecimals(value);
|
setFormatDecimals(value);
|
||||||
setStorageValue(chainId, address, "stake-decimals", value);
|
localStorage.setItem("staking-decimals", value);
|
||||||
}
|
}
|
||||||
}, [chainId, address]);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const innerBalance = upperToken === ghstSymbol ?
|
const innerBalance = upperToken === ghstSymbol ?
|
||||||
@ -233,6 +242,36 @@ export const StakeInputArea = ({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{upperTokenModalOpen && (
|
||||||
|
<TokenModal
|
||||||
|
open={upperTokenModalOpen}
|
||||||
|
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
|
||||||
|
handleClose={() => setUpperTokenModalOpen(false)}
|
||||||
|
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
|
||||||
|
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
|
||||||
|
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
|
||||||
|
isUpper={true}
|
||||||
|
ftsoSymbol={ftsoSymbol}
|
||||||
|
stnkSymbol={stnkSymbol}
|
||||||
|
ghstSymbol={ghstSymbol}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{bottomTokenModalOpen && (
|
||||||
|
<TokenModal
|
||||||
|
open={bottomTokenModalOpen}
|
||||||
|
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
|
||||||
|
handleClose={() => setBottomTokenModalOpen(false)}
|
||||||
|
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
|
||||||
|
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
|
||||||
|
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
|
||||||
|
tokenToExclude={upperToken}
|
||||||
|
isUpper={false}
|
||||||
|
ftsoSymbol={ftsoSymbol}
|
||||||
|
stnkSymbol={stnkSymbol}
|
||||||
|
ghstSymbol={ghstSymbol}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const StakeSettingsModal = props => {
|
|||||||
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={<Typography variant="body2">Always try to claim during stake, works only if warm-up period is zero</Typography>}
|
label={<Typography variant="body2">Always try to claim during stake, works only if warmup period is zero</Typography>}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Box, Container, Grid, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Container, Grid, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { memo, useEffect, useMemo, useState } from "react";
|
import { memo, useEffect, useMemo, useState } from "react";
|
||||||
import { Outlet, Route, Routes, useSearchParams, useLocation } from "react-router-dom";
|
import { Outlet, Route, Routes, useSearchParams } from "react-router-dom";
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
CurrentIndex,
|
CurrentIndex,
|
||||||
} from "./components/Metric";
|
} from "./components/Metric";
|
||||||
import TokenInfo from "./components/TokenInfo";
|
import TokenInfo from "./components/TokenInfo";
|
||||||
import ProtocolDetails from "./components/ProtocolDetails";
|
|
||||||
|
|
||||||
import Paper from "../../components/Paper/Paper";
|
import Paper from "../../components/Paper/Paper";
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
@ -61,7 +60,7 @@ const MetricsDashboard = ({ chainId }) => {
|
|||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Paper style={{ padding: "0px" }} fullWidth={true} >
|
<Paper style={{ padding: "0px" }} fullWidth={true} >
|
||||||
<ProtocolDetails theme={theme} isMobileScreen={isMobileScreen} chainId={chainId} />
|
<TokenInfo isMobileScreen={isMobileScreen} chainId={chainId} />
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -74,11 +73,9 @@ const TreasuryDashboard = ({ chainId }) => {
|
|||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/dashboard" });
|
||||||
}, [location]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export const FatsoBacking = props => {
|
|||||||
const _props = {
|
const _props = {
|
||||||
...props,
|
...props,
|
||||||
label: `Backing per ${props.ftsoSymbol}`,
|
label: `Backing per ${props.ftsoSymbol}`,
|
||||||
tooltip: `The total amount of native coins, LP tokens, and other assets held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.`
|
tooltip: `The total amount of stablecoins held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.`
|
||||||
};
|
};
|
||||||
|
|
||||||
if (backing) _props.metric = formatCurrency(backing, 2);
|
if (backing) _props.metric = formatCurrency(backing, 2);
|
||||||
|
|||||||
@ -1,136 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import { Grid, Box, Typography } from "@mui/material";
|
|
||||||
import { useConfig } from "wagmi";
|
|
||||||
import { useNavigate, createSearchParams } from "react-router-dom";
|
|
||||||
|
|
||||||
import { formatNumber } from "../../../helpers";
|
|
||||||
import { useLiveBonds } from "../../../hooks/bonds/index";
|
|
||||||
import { useGatekeeperApy } from "../../../hooks/staking";
|
|
||||||
import { useTokenSymbol, useBalance } from "../../../hooks/tokens";
|
|
||||||
import { SecondaryButton } from "../../../components/Button";
|
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
|
||||||
|
|
||||||
import { GATEKEEPER_ADDRESSES, EMPTY_ADDRESS } from "../../../constants/addresses";
|
|
||||||
|
|
||||||
const ProtocolDetail = ({ isMobileScreen, theme, name, sideName, url, urlParams, description }) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return (
|
|
||||||
<Box position="relative" width={`${isMobileScreen ? "100%" : "48%"}`}>
|
|
||||||
<Box
|
|
||||||
borderRadius="9px"
|
|
||||||
padding="12px"
|
|
||||||
sx={{ backgroundColor: theme.colors.paper.card }}
|
|
||||||
display="flex"
|
|
||||||
flexDirection="column"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
<Box display="flex" gap={"3px"} alignItems="center" justifyContent="space-between">
|
|
||||||
<Typography fontSize="20px" fontWeight="bold" lineHeight="33px">
|
|
||||||
{name}
|
|
||||||
</Typography>
|
|
||||||
<Typography color={theme.colors.primary[300]} fontSize="20px" fontWeight="bold" lineHeight="33px">
|
|
||||||
{sideName}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<>
|
|
||||||
<Box my="18px">
|
|
||||||
<Typography color={theme.colors.gray[40]} mt="9px">
|
|
||||||
{description}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="center" width="100%">
|
|
||||||
<SecondaryButton
|
|
||||||
onClick={() => urlParams
|
|
||||||
? navigate({
|
|
||||||
pathname: url,
|
|
||||||
search: urlParams.toString()
|
|
||||||
})
|
|
||||||
: navigate(url, { replace: true })
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</SecondaryButton>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProtocolDetails = ({ chainId, isMobileScreen, theme, }) => {
|
|
||||||
const config = useConfig();
|
|
||||||
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
|
||||||
const networkName = config?.getClient()?.chain?.name;
|
|
||||||
|
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
|
||||||
const { symbol: csprSymbol } = useTokenSymbol(chainId, "CSPR");
|
|
||||||
const { contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", EMPTY_ADDRESS);
|
|
||||||
const { liveBonds } = useLiveBonds(chainId);
|
|
||||||
const { gatekeepedApy, apyInner } = useGatekeeperApy(chainId);
|
|
||||||
|
|
||||||
const maxBondDiscountTest = useMemo(() => {
|
|
||||||
const sortedGhostBonds = liveBonds.filter((bond) => !bond.isSoldOut)
|
|
||||||
if (sortedGhostBonds?.length === 0) return "Coming Up";
|
|
||||||
const maxDiscountBond = sortedGhostBonds.reduce((prev, current) =>
|
|
||||||
(prev.discount > current.discount) ? prev : current
|
|
||||||
);
|
|
||||||
const maxDiscount = maxDiscountBond.discount.mul(new DecimalBigNumber(100, 0));
|
|
||||||
return `Up to ${formatNumber(maxDiscount, 0)}% Discount`;
|
|
||||||
}, [liveBonds]);
|
|
||||||
|
|
||||||
const bridgeNumbers = useMemo(() => {
|
|
||||||
const connectedNetworks = Object.keys(GATEKEEPER_ADDRESSES).length;
|
|
||||||
const number = 1 + connectedNetworks * 3;
|
|
||||||
return `(${number}, ${number})`;
|
|
||||||
}, [chainId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid container spacing={0} justifyContent={"center"}>
|
|
||||||
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
|
|
||||||
<ProtocolDetail
|
|
||||||
isMobileScreen={isMobileScreen}
|
|
||||||
theme={theme}
|
|
||||||
url={`/${networkName.toLowerCase()}/dex/uniswap`}
|
|
||||||
urlParams={createSearchParams({
|
|
||||||
from: EMPTY_ADDRESS,
|
|
||||||
to: `${ftsoAddress}`,
|
|
||||||
})}
|
|
||||||
name="(3, 3) Swap"
|
|
||||||
sideName="Unlock Magic"
|
|
||||||
description={`Buying strategy expands bond capacity triggering a positive loop to strengthen the Treasury and attract more stakers. Swap ${nativeSymbol} for ${ftsoSymbol} to unlock (3, 3) Stake.`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ProtocolDetail
|
|
||||||
isMobileScreen={isMobileScreen}
|
|
||||||
theme={theme}
|
|
||||||
url={`/${networkName.toLowerCase()}/bonds`}
|
|
||||||
name="(1, 1) Bond"
|
|
||||||
sideName={maxBondDiscountTest}
|
|
||||||
description={`Bonding strategy grows Treasury and builds Protocol Owned Liquidity (POL) by allowing users to acquire ${csprSymbol} at a discount. Take advantage of the next available bond.`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ProtocolDetail
|
|
||||||
isMobileScreen={isMobileScreen}
|
|
||||||
theme={theme}
|
|
||||||
url={`/${networkName.toLowerCase()}/stake`}
|
|
||||||
name="(3, 3) Stake"
|
|
||||||
sideName={`${formatNumber(apyInner, 0)}% APY`}
|
|
||||||
description={`Staking enables (3, 3) coordination by aligning long-term incentives, sustainably backed by (1, 1) Bonds, LP fees, and Farming. Stake ${ftsoSymbol} to earn rewards and unlock ${bridgeNumbers} Stake\u00B2.`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ProtocolDetail
|
|
||||||
isMobileScreen={isMobileScreen}
|
|
||||||
theme={theme}
|
|
||||||
url={`/${networkName.toLowerCase()}/bridge`}
|
|
||||||
name={`${bridgeNumbers} Stake\u00B2`}
|
|
||||||
sideName={`${formatNumber(gatekeepedApy, 0)}% APY`}
|
|
||||||
description={`Staking\u00B2 strategy further deepens long-term incentives powered by sustainable crosschain bridging revenue. ${bridgeNumbers} Stake\u00B2 your ${csprSymbol} to receive organic APY with no warm-up and no dilution.`}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProtocolDetails;
|
|
||||||
@ -1,23 +1,23 @@
|
|||||||
import { Grid, Box, Typography, useTheme } from "@mui/material";
|
import { Grid, Box, Typography, useTheme } from "@mui/material";
|
||||||
import { useAccount, useConfig, useBalance as useBalanceNative } from "wagmi";
|
import { useAccount, useConfig, useBalance as useBalanceNative } from "wagmi";
|
||||||
import { useNavigate, useParams, createSearchParams } from "react-router-dom";
|
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import Token from "../../../components/Token/Token";
|
import Token from "../../../components/Token/Token";
|
||||||
import { SecondaryButton } from "../../../components/Button";
|
import { SecondaryButton } from "../../../components/Button";
|
||||||
import { formatNumber, formatCurrency } from "../../../helpers";
|
import { formatNumber, formatCurrency } from "../../../helpers";
|
||||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||||
|
import { isNetworkLegacy } from "../../../constants"
|
||||||
|
|
||||||
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
||||||
import {
|
import {
|
||||||
useFtsoPrice,
|
useFtsoPrice,
|
||||||
|
useStnkPrice,
|
||||||
useGhstPrice,
|
useGhstPrice,
|
||||||
useReservePrice,
|
useReservePrice,
|
||||||
useNativePrice,
|
useNativePrice,
|
||||||
} from "../../../hooks/prices";
|
} from "../../../hooks/prices";
|
||||||
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
import { tokenNameConverter } from "../../../helpers/tokenConverter";
|
||||||
|
|
||||||
import { EMPTY_ADDRESS, WETH_ADDRESSES } from "../../../constants/addresses";
|
|
||||||
|
|
||||||
const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => {
|
const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams, balance, price, description }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const actualBalance = balance ? balance : new DecimalBigNumber(0, 0);
|
const actualBalance = balance ? balance : new DecimalBigNumber(0, 0);
|
||||||
@ -58,13 +58,10 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
|
|||||||
|
|
||||||
<Box display="flex" justifyContent="center" width="100%">
|
<Box display="flex" justifyContent="center" width="100%">
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
onClick={() => tokenUrlParams
|
onClick={() => navigate({
|
||||||
? navigate({
|
pathname: tokenUrl,
|
||||||
pathname: tokenUrl,
|
search: tokenUrlParams.toString()
|
||||||
search: tokenUrlParams.toString()
|
})}
|
||||||
})
|
|
||||||
: window.open(tokenUrl, '_blank')
|
|
||||||
}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
Get {tokenName}
|
Get {tokenName}
|
||||||
@ -78,7 +75,6 @@ const TokenTab = ({ isMobileScreen, theme, tokenName, tokenUrl, tokenUrlParams,
|
|||||||
|
|
||||||
const TokenInfo = ({ chainId, isMobileScreen }) => {
|
const TokenInfo = ({ chainId, isMobileScreen }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { network } = useParams();
|
|
||||||
const { address } = useAccount();
|
const { address } = useAccount();
|
||||||
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
@ -87,15 +83,18 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
|||||||
|
|
||||||
const nativePrice = useNativePrice(chainId);
|
const nativePrice = useNativePrice(chainId);
|
||||||
const ftsoPrice = useFtsoPrice(chainId);
|
const ftsoPrice = useFtsoPrice(chainId);
|
||||||
|
const stnkPrice = useStnkPrice(chainId);
|
||||||
const ghstPrice = useGhstPrice(chainId);
|
const ghstPrice = useGhstPrice(chainId);
|
||||||
const reservePrice = useReservePrice(chainId);
|
const reservePrice = useReservePrice(chainId);
|
||||||
|
|
||||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||||
|
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
|
||||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
const { data: nativeBalance } = useBalanceNative({ address });
|
const { data: nativeBalance } = useBalanceNative({ address });
|
||||||
const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", address);
|
const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", address);
|
||||||
|
const { balance: stnkBalance, contractAddress: stnkAddress } = useBalance(chainId, "STNK", address);
|
||||||
const { balance: ghstBalance, contractAddress: ghstAddress } = useBalance(chainId, "GHST", address);
|
const { balance: ghstBalance, contractAddress: ghstAddress } = useBalance(chainId, "GHST", address);
|
||||||
const { balance: reserveBalance, contractAddress: reserveAddress } = useBalance(chainId, "RESERVE", address);
|
const { balance: reserveBalance, contractAddress: reserveAddress } = useBalance(chainId, "RESERVE", address);
|
||||||
|
|
||||||
@ -104,7 +103,7 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
|||||||
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
|
<Box display="flex" flexWrap="wrap" justifyContent="space-between" mt="10px" gap="25px">
|
||||||
<TokenTab
|
<TokenTab
|
||||||
isMobileScreen={isMobileScreen}
|
isMobileScreen={isMobileScreen}
|
||||||
tokenUrl={`/${network}/dex/uniswap`}
|
tokenUrl="/dex/uniswap"
|
||||||
tokenUrlParams={createSearchParams({
|
tokenUrlParams={createSearchParams({
|
||||||
from: `${reserveAddress}`,
|
from: `${reserveAddress}`,
|
||||||
to: `${ftsoAddress}`,
|
to: `${ftsoAddress}`,
|
||||||
@ -117,7 +116,20 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
|||||||
/>
|
/>
|
||||||
<TokenTab
|
<TokenTab
|
||||||
isMobileScreen={isMobileScreen}
|
isMobileScreen={isMobileScreen}
|
||||||
tokenUrl={`/${network}/dex/uniswap`}
|
tokenUrl="/dex/uniswap"
|
||||||
|
tokenUrlParams={createSearchParams({
|
||||||
|
from: `${reserveAddress}`,
|
||||||
|
to: `${stnkAddress}`,
|
||||||
|
})}
|
||||||
|
theme={theme}
|
||||||
|
tokenName={stnkSymbol}
|
||||||
|
balance={stnkBalance}
|
||||||
|
price={stnkPrice}
|
||||||
|
description={`${stnkSymbol} is a receipt for staked ${ftsoSymbol}, growing with staking rewards. When unstaked, it’s burned for ${ftsoSymbol} at a 1:1 ratio.`}
|
||||||
|
/>
|
||||||
|
<TokenTab
|
||||||
|
isMobileScreen={isMobileScreen}
|
||||||
|
tokenUrl="/dex/uniswap"
|
||||||
tokenUrlParams={createSearchParams({
|
tokenUrlParams={createSearchParams({
|
||||||
from: `${reserveAddress}`,
|
from: `${reserveAddress}`,
|
||||||
to: `${ghstAddress}`,
|
to: `${ghstAddress}`,
|
||||||
@ -126,31 +138,36 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
|
|||||||
tokenName={ghstSymbol}
|
tokenName={ghstSymbol}
|
||||||
balance={ghstBalance}
|
balance={ghstBalance}
|
||||||
price={ghstPrice}
|
price={ghstPrice}
|
||||||
description={`${ghstSymbol} is the governance token enabling pure Web3 cross-chain magic. 1 ${ghstSymbol} = 1 ${ftsoSymbol} x Current Index.`}
|
description={`${ghstSymbol} enables ghostDAO to have on-chain governance and to be truly cross-chain. ${ghstSymbol} Price = ${ftsoSymbol} Price x Current Index.`}
|
||||||
/>
|
/>
|
||||||
<TokenTab
|
<TokenTab
|
||||||
isMobileScreen={isMobileScreen}
|
isMobileScreen={isMobileScreen}
|
||||||
tokenUrl={"https://ghostchain.io/faucet/"}
|
tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
|
||||||
theme={theme}
|
tokenUrlParams=""
|
||||||
tokenName={nativeSymbol}
|
|
||||||
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)}
|
|
||||||
price={reservePrice}
|
|
||||||
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
|
|
||||||
/>
|
|
||||||
<TokenTab
|
|
||||||
isMobileScreen={isMobileScreen}
|
|
||||||
tokenUrl={`/${network}/dex/uniswap`}
|
|
||||||
tokenUrlParams={createSearchParams({
|
|
||||||
from: `${EMPTY_ADDRESS}`,
|
|
||||||
to: `${WETH_ADDRESSES[chainId]}`,
|
|
||||||
})}
|
|
||||||
theme={theme}
|
theme={theme}
|
||||||
tokenName={reserveSymbol}
|
tokenName={reserveSymbol}
|
||||||
balance={reserveBalance}
|
balance={reserveBalance}
|
||||||
price={reservePrice}
|
price={reservePrice}
|
||||||
description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`}
|
description={isNetworkLegacy(chainId)
|
||||||
|
? `${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${reserveSymbol} being the primary and most liquid asset.`
|
||||||
|
: `${reserveSymbol} (Wrapped ${nativeSymbol}) is an ERC-20 token that represents ${nativeSymbol} and is pegged 1:1 to the value of ${nativeSymbol}.`
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
{!isNetworkLegacy(chainId) && (
|
||||||
|
<Box width="100%" mt="25px">
|
||||||
|
<TokenTab
|
||||||
|
isMobileScreen={true}
|
||||||
|
tokenUrl={isNetworkLegacy(chainId) ? "/faucet" : "/wrapper"}
|
||||||
|
tokenUrlParams=""
|
||||||
|
theme={theme}
|
||||||
|
tokenName={nativeSymbol}
|
||||||
|
balance={new DecimalBigNumber(nativeBalance?.value ?? 0n, 18)}
|
||||||
|
price={reservePrice}
|
||||||
|
description={`${nativeSymbol} is the native currency of the ${networkName} Network, functioning as the backing asset for the ghostDAO.`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { useState, useEffect, useMemo } from "react";
|
|||||||
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
import { Box, Container, Typography, useMediaQuery } from "@mui/material";
|
||||||
import { useConfig, useBalance } from "wagmi";
|
import { useConfig, useBalance } from "wagmi";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
@ -31,8 +30,6 @@ const WethWrapper = ({ chainId, address, config, connect }) => {
|
|||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const [chainName, setChainName] = useState("");
|
const [chainName, setChainName] = useState("");
|
||||||
const [isMint, setIsMint] = useState(true);
|
const [isMint, setIsMint] = useState(true);
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
@ -53,8 +50,8 @@ const WethWrapper = ({ chainId, address, config, connect }) => {
|
|||||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: location.pathname });
|
ReactGA.send({ hitType: "pageview", page: "/wrapper" });
|
||||||
}, [location])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const value = nativeBalance ? nativeBalance.value : 0n;
|
const value = nativeBalance ? nativeBalance.value : 0n;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export function shorten(str, first = 6, second = -4) {
|
export function shorten(str) {
|
||||||
if (str.length < 10) return str;
|
if (str.length < 10) return str;
|
||||||
return `${str.slice(0, first)}...${str.slice(second)}`;
|
return `${str.slice(0, 6)}...${str.slice(str.length - 4)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function capitalize(str) {
|
export function capitalize(str) {
|
||||||
@ -39,7 +39,7 @@ export const formatNumber = (number, precision = 0) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sortBondsByDiscount = (bonds) => {
|
export const sortBondsByDiscount = (bonds) => {
|
||||||
return Array.from(bonds).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1));
|
return Array.from(bonds).filter((bond) => !bond.isSoldOut).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const timeConverter = (time, max = 7200, maxText = "long ago") => {
|
export const timeConverter = (time, max = 7200, maxText = "long ago") => {
|
||||||
@ -52,16 +52,3 @@ export const timeConverter = (time, max = 7200, maxText = "long ago") => {
|
|||||||
return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`;
|
return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bigIntSqrt = (n) => {
|
|
||||||
if (n < 0n) return 0n;
|
|
||||||
if (n < 2n) return n;
|
|
||||||
|
|
||||||
let x = n / 2n + 1n;
|
|
||||||
let y = (x + n / x) / 2n;
|
|
||||||
while (y < x) {
|
|
||||||
x = y;
|
|
||||||
y = (x + n / x) / 2n;
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
export const tokenNameConverter = (chainId, name, address) => {
|
export const tokenNameConverter = (chainId, name) => {
|
||||||
if (name?.toUpperCase() === "WETH") {
|
if (name?.toUpperCase() === "WETH") {
|
||||||
switch (chainId) {
|
switch (chainId) {
|
||||||
case 63:
|
case 63:
|
||||||
name = "wmETC"
|
name = "wmETC"
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
name = "wETH";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import { useReadContract, useReadContracts } from "wagmi";
|
import { useReadContract, useReadContracts } from "wagmi";
|
||||||
|
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import { config } from "../../config";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BOND_DEPOSITORY_ADDRESSES,
|
BOND_DEPOSITORY_ADDRESSES,
|
||||||
DAO_TREASURY_ADDRESSES,
|
DAO_TREASURY_ADDRESSES,
|
||||||
@ -10,25 +13,14 @@ import { abi as BondAbi } from "../../abi/GhostBondDepository.json";
|
|||||||
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
|
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
|
||||||
import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json";
|
import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json";
|
||||||
|
|
||||||
import { useReservePrice, useFtsoPrice } from "../prices";
|
import { useFtsoPrice } from "../prices";
|
||||||
import { useOrinalCoefficient } from "../treasury";
|
|
||||||
import { useTokenSymbol, useTokenSymbols } from "../tokens";
|
import { useTokenSymbol, useTokenSymbols } from "../tokens";
|
||||||
import {
|
import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
|
||||||
getTokenAddress,
|
|
||||||
getTokenIcons,
|
|
||||||
getBondNameDisplayName,
|
|
||||||
getTokenPurchaseLink,
|
|
||||||
executeOnChainTransaction
|
|
||||||
} from "../helpers";
|
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { shorten } from "../../helpers";
|
import { shorten } from "../../helpers";
|
||||||
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
|
||||||
|
|
||||||
export const useLiveBonds = (chainId, chainName) => {
|
export const useLiveBonds = (chainId) => {
|
||||||
const ftsoPrice = useFtsoPrice(chainId);
|
const baseTokenPerUsd = useFtsoPrice(chainId);
|
||||||
const baseTokenPerUsd = useReservePrice(chainId);
|
|
||||||
const originalCoefficient = useOrinalCoefficient(chainId);
|
|
||||||
|
|
||||||
const { data: liveIndexesRaw, refetch } = useReadContract({
|
const { data: liveIndexesRaw, refetch } = useReadContract({
|
||||||
abi: BondAbi,
|
abi: BondAbi,
|
||||||
@ -134,11 +126,10 @@ export const useLiveBonds = (chainId, chainName) => {
|
|||||||
const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";
|
const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";
|
||||||
|
|
||||||
const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9);
|
const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9);
|
||||||
const priceInUsd = quoteTokenPerBaseToken.mul(baseTokenPerUsd).mul(quoteTokenPerUsd).mul(markdown).div(originalCoefficient);
|
const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken).mul(markdown);
|
||||||
|
const discount = baseTokenPerUsd._value > 0n
|
||||||
const discount = ftsoPrice._value > 0n
|
? baseTokenPerUsd.sub(priceInUsd).div(baseTokenPerUsd)
|
||||||
? ftsoPrice.sub(priceInUsd).div(ftsoPrice)
|
: new DecimalBigNumber("0", baseTokenPerUsd._decimals);
|
||||||
: new DecimalBigNumber("0", ftsoPrice._decimals);
|
|
||||||
|
|
||||||
const capacityInBaseToken = capacityInQuote
|
const capacityInBaseToken = capacityInQuote
|
||||||
? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9)
|
? new DecimalBigNumber(marketPrice > 0n ? marketCapacity / marketPrice : 0n, 9)
|
||||||
@ -154,20 +145,21 @@ export const useLiveBonds = (chainId, chainName) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const zero = new DecimalBigNumber(0n, 0);
|
const zero = new DecimalBigNumber(0n, 0);
|
||||||
|
const bondName = `${baseTokenSymbol}/${quoteTokenSymbol}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
discount,
|
discount,
|
||||||
displayName: getBondNameDisplayName(chainId, quoteTokenAddress, baseTokenSymbol),
|
displayName: getBondNameDisplayName(chainId, bondName, quoteTokenAddress),
|
||||||
baseToken: {
|
baseToken: {
|
||||||
name: baseTokenSymbol,
|
name: baseTokenSymbol,
|
||||||
purchaseUrl: getTokenPurchaseLink(chainId, "", chainName),
|
purchaseUrl: getTokenPurchaseLink(chainId, ""),
|
||||||
icons: ["FTSO"],
|
icons: ["FTSO"],
|
||||||
tokenAddress: getTokenAddress(chainId, "FTSO")
|
tokenAddress: getTokenAddress(chainId, "FTSO")
|
||||||
},
|
},
|
||||||
quoteToken: {
|
quoteToken: {
|
||||||
name: tokenNameConverter(chainId, quoteTokenSymbol),
|
name: quoteTokenSymbol,
|
||||||
purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress, chainName),
|
purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress),
|
||||||
icons: getTokenIcons(chainId, quoteTokenAddress),
|
icons: getTokenIcons(chainId, quoteTokenAddress),
|
||||||
decimals: quoteTokenDecimals,
|
decimals: quoteTokenDecimals,
|
||||||
quoteTokenAddress,
|
quoteTokenAddress,
|
||||||
@ -175,11 +167,11 @@ export const useLiveBonds = (chainId, chainName) => {
|
|||||||
duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0,
|
duration: terms?.at(index).result?.at(3) ? terms.at(index).result.at(3) : 0,
|
||||||
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
|
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
|
||||||
isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true,
|
isFixedTerm: terms?.at(index).result?.at(0) ? terms.at(index).result.at(0) : true,
|
||||||
isSoldOut: capacityInBaseToken.eq(zero),
|
isSoldOut: capacityInBaseToken.eq(zero) || capacityInBaseToken.eq(zero),
|
||||||
price: {
|
price: {
|
||||||
inUsd: priceInUsd,
|
inUsd: priceInUsd,
|
||||||
inBaseToken: quoteTokenPerBaseToken,
|
inBaseToken: quoteTokenPerBaseToken,
|
||||||
marketPriceInUsd: ftsoPrice
|
marketPriceInUsd: baseTokenPerUsd
|
||||||
},
|
},
|
||||||
capacity: {
|
capacity: {
|
||||||
inBaseToken: capacityInBaseToken,
|
inBaseToken: capacityInBaseToken,
|
||||||
@ -253,7 +245,7 @@ export const useNotes = (chainId, address) => {
|
|||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
quoteToken: {
|
quoteToken: {
|
||||||
name: tokenNameConverter(chainId, quoteTokenSymbol),
|
name: quoteTokenSymbol,
|
||||||
icons: getTokenIcons(chainId, quoteTokenAddress),
|
icons: getTokenIcons(chainId, quoteTokenAddress),
|
||||||
},
|
},
|
||||||
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
|
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,
|
||||||
@ -269,62 +261,75 @@ export const useNotes = (chainId, address) => {
|
|||||||
return { notes, refetch };
|
return { notes, refetch };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => {
|
export const purchaseBond = async (chainId, bondId, amount, maxPrice, user, sender, referral) => {
|
||||||
const args = [bondId, amount, maxPrice, user, referral];
|
const args = [
|
||||||
|
bondId,
|
||||||
|
amount,
|
||||||
|
maxPrice,
|
||||||
|
user,
|
||||||
|
referral
|
||||||
|
];
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
|
successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
|
||||||
errorMsg: "Bond transaction failed. Check logs for error detalization.",
|
errorMsg: "Bond transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
|
await executeOnChainTransaction(
|
||||||
await executeOnChainTransaction({
|
|
||||||
chainId,
|
chainId,
|
||||||
|
"deposit",
|
||||||
args,
|
args,
|
||||||
messages,
|
sender,
|
||||||
abi: BondAbi,
|
messages
|
||||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
);
|
||||||
functionName: "deposit",
|
|
||||||
account: user,
|
|
||||||
value: isNative ? amount : undefined
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const redeem = async ({ chainId, user, isGhst, indexes }) => {
|
export const redeem = async (chainId, user, isGhst, indexes) => {
|
||||||
const args = [user, isGhst, indexes];
|
const args = [
|
||||||
|
user,
|
||||||
|
isGhst,
|
||||||
|
indexes
|
||||||
|
];
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Redeem transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Redeem transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `Address ${shorten(user)} redeemed ${indexes.length} bonds in ${isGhst ? "GHST" : "STNK"}!`,
|
successMsg: `Address ${shorten(user)} redeemed ${indexes.length} bonds in ${isGhst ? "GHST" : "STNK"}!`,
|
||||||
errorMsg: `Redeem of ${indexes.length} bonds failed. Check logs for error detalization.`,
|
errorMsg: `Redeem of ${indexes.length} bonds failed. Check logs for error detalization.`,
|
||||||
};
|
};
|
||||||
|
await executeOnChainTransaction(
|
||||||
await executeOnChainTransaction({
|
|
||||||
chainId,
|
chainId,
|
||||||
|
"redeem",
|
||||||
args,
|
args,
|
||||||
messages,
|
user,
|
||||||
abi: BondAbi,
|
messages
|
||||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
);
|
||||||
functionName: "redeem",
|
|
||||||
account: user,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const forceRedeem = async ({ chainId, user, receiver, indexes }) => {
|
const executeOnChainTransaction = async (
|
||||||
const args = [receiver, indexes];
|
chainId,
|
||||||
const messages = {
|
functionName,
|
||||||
replacedMsg: "Bond breakout transaction was replaced. Wait for inclusion please.",
|
args,
|
||||||
successMsg: `Address ${shorten(user)} succesfully breakout for ${indexes.length} bonds.`,
|
account,
|
||||||
errorMsg: `Breakout of ${indexes.length} bonds failed. Check logs for error detalization.`,
|
messages
|
||||||
};
|
) => {
|
||||||
|
try {
|
||||||
|
const { request } = await simulateContract(config, {
|
||||||
|
abi: BondAbi,
|
||||||
|
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
||||||
|
functionName,
|
||||||
|
args,
|
||||||
|
account,
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
const txHash = await executeOnChainTransaction({
|
const txHash = await writeContract(config, request);
|
||||||
chainId,
|
await waitForTransactionReceipt(config, {
|
||||||
args,
|
hash: txHash,
|
||||||
messages,
|
onReplaced: () => toast(messages.replacedMsg),
|
||||||
abi: BondAbi,
|
chainId
|
||||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
});
|
||||||
functionName: "forceRedeem",
|
|
||||||
account: user,
|
|
||||||
});
|
|
||||||
|
|
||||||
return txHash;
|
toast.success(messages.successMsg);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error(messages.errorMsg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
import { createContext, useContext, useState, useMemo } from "react";
|
|
||||||
import { claim } from "../../hooks/staking";
|
|
||||||
|
|
||||||
const emptyFunction = () => {};
|
|
||||||
const BreakoutModalContext = createContext();
|
|
||||||
export const useBreakoutModal = () => useContext(BreakoutModalContext);
|
|
||||||
|
|
||||||
export const BreakoutModalProvider = ({ children }) => {
|
|
||||||
const [isStakingOpened, setIsStakingOpened] = useState(false);
|
|
||||||
const [isClaimBondOpened, setIsClaimBondOpened] = useState(false);
|
|
||||||
const [activeTxIndex, setActiveTxIndex] = useState(-1);
|
|
||||||
const [warmupPeriod, setWarmupPeriod] = useState(0);
|
|
||||||
const [estimatedAmount, setEstimatedAmount] = useState(0);
|
|
||||||
const [defaultFunction, setDefaultFunction] = useState(emptyFunction);
|
|
||||||
const [executableFunction, setExecutableFunction] = useState(emptyFunction);
|
|
||||||
|
|
||||||
const breakoutFromStaking = ({ defaultFunction, toExecute, amount, warmupLeft }) => {
|
|
||||||
setIsStakingOpened(true);
|
|
||||||
setWarmupPeriod(warmupLeft);
|
|
||||||
setEstimatedAmount(amount);
|
|
||||||
setExecutableFunction(() => () => toExecute);
|
|
||||||
setDefaultFunction(() => () => defaultFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
const breakoutFromBonding = ({ defaultFunction, toExecute, amount, warmupLeft }) => {
|
|
||||||
setIsClaimBondOpened(true);
|
|
||||||
setWarmupPeriod(warmupLeft);
|
|
||||||
setEstimatedAmount(amount);
|
|
||||||
setExecutableFunction(() => () => toExecute);
|
|
||||||
setDefaultFunction(() => () => defaultFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOpened = useMemo(() =>
|
|
||||||
isStakingOpened || isClaimBondOpened,
|
|
||||||
[isStakingOpened, isClaimBondOpened]);
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
setIsStakingOpened(false);
|
|
||||||
setIsClaimBondOpened(false);
|
|
||||||
setWarmupPeriod(0);
|
|
||||||
setDefaultFunction(emptyFunction);
|
|
||||||
setExecutableFunction(emptyFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BreakoutModalContext.Provider value={{
|
|
||||||
isStakingOpened,
|
|
||||||
isClaimBondOpened,
|
|
||||||
activeTxIndex,
|
|
||||||
setActiveTxIndex,
|
|
||||||
warmupPeriod,
|
|
||||||
isOpened,
|
|
||||||
closeModal,
|
|
||||||
breakoutFromStaking,
|
|
||||||
breakoutFromBonding,
|
|
||||||
defaultFunction,
|
|
||||||
estimatedAmount,
|
|
||||||
executableFunction
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</BreakoutModalContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -8,24 +8,15 @@ import { useUnstableProvider } from "./UnstableProvider"
|
|||||||
const MetadataProvider = createContext(null)
|
const MetadataProvider = createContext(null)
|
||||||
export const useMetadata = () => useContext(MetadataProvider)
|
export const useMetadata = () => useContext(MetadataProvider)
|
||||||
|
|
||||||
const CACHE_VERSION = "v2"
|
|
||||||
|
|
||||||
export const MetadataProviderProvider = ({ children }) => {
|
export const MetadataProviderProvider = ({ children }) => {
|
||||||
const { client, chainId } = useUnstableProvider()
|
const { client, chainId } = useUnstableProvider()
|
||||||
const { data: metadata } = useSWR(
|
const { data: metadata } = useSWR(
|
||||||
client && chainId ? ["metadata", client, chainId] : null,
|
client && chainId ? ["metadata", client, chainId] : null,
|
||||||
async ([_, client]) => {
|
async ([_, client]) => {
|
||||||
const storageKey = `metadata-${chainId}-${CACHE_VERSION}`
|
const storageKey = `metadata-${chainId}`
|
||||||
const storedMetadata = sessionStorage.getItem(storageKey)
|
const storedMetadata = sessionStorage.getItem(storageKey)
|
||||||
|
|
||||||
if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata))
|
if (storedMetadata) return unifyMetadata(decAnyMetadata(storedMetadata))
|
||||||
|
|
||||||
Object.keys(sessionStorage).forEach(key => {
|
|
||||||
if (key.startsWith("metadata-")) {
|
|
||||||
sessionStorage.removeItem(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const metadata = await new Promise((resolve, reject) =>
|
const metadata = await new Promise((resolve, reject) =>
|
||||||
client._request("state_getMetadata", [], {
|
client._request("state_getMetadata", [], {
|
||||||
onSuccess: resolve,
|
onSuccess: resolve,
|
||||||
|
|||||||
@ -1,64 +1,54 @@
|
|||||||
import { createContext, useEffect, useContext, useState, useMemo, useCallback, useRef } from "react"
|
import { createContext, useContext, useState, useMemo } from "react"
|
||||||
import { Unstable } from "@substrate/connect-discovery"
|
import { Unstable } from "@substrate/connect-discovery"
|
||||||
import { createClient } from "@polkadot-api/substrate-client"
|
import { createClient } from "@polkadot-api/substrate-client"
|
||||||
import { getObservableClient } from "@polkadot-api/observable-client"
|
import { getObservableClient } from "@polkadot-api/observable-client"
|
||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
|
|
||||||
const MAX_BLOCK_TIMEOUT = 15000
|
|
||||||
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
|
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
|
||||||
const UnstableProvider = createContext(null)
|
const UnstableProvider = createContext(null)
|
||||||
export const useUnstableProvider = () => useContext(UnstableProvider)
|
export const useUnstableProvider = () => useContext(UnstableProvider)
|
||||||
|
|
||||||
export const UnstableProviderProvider = ({ children }) => {
|
export const UnstableProviderProvider = ({ children }) => {
|
||||||
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
|
|
||||||
const [providerIndex, setProviderIndex] = useState(0);
|
|
||||||
|
|
||||||
const { data: providerDetails } = useSWR("getGhostProviders", () =>
|
const { data: providerDetails } = useSWR("getGhostProviders", () =>
|
||||||
Unstable.getSubstrateConnectExtensionProviders()
|
Unstable.getSubstrateConnectExtensionProviders()
|
||||||
);
|
);
|
||||||
|
|
||||||
const providerDetail = useMemo(() => providerDetails?.at(providerIndex), [providerDetails, providerIndex]);
|
const [providerDetail, setProviderDetail] = useState();
|
||||||
|
|
||||||
const { data: provider } = useSWR(
|
const { data: provider } = useSWR(
|
||||||
() => providerDetail ? `ghostProviderDetail.${providerDetail.info.uuid}.provider` : null,
|
() => providerDetail ? `ghostProviderDetail.${providerDetail.info.uuid}.provider` : null,
|
||||||
() => providerDetail ? providerDetail.provider : null
|
() => providerDetail ? providerDetail.provider : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const connectionState = useMemo(() => {
|
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
|
||||||
if (!providerDetail) return 'no-extension';
|
|
||||||
if (!provider) return 'loading';
|
|
||||||
|
|
||||||
const chains = provider.getChains();
|
|
||||||
if (chains[chainId]) return 'connected';
|
|
||||||
|
|
||||||
return 'wrong-network';
|
|
||||||
}, [providerDetail, provider, chainId]);
|
|
||||||
|
|
||||||
const client = useMemo(() => {
|
const client = useMemo(() => {
|
||||||
if (!provider || !chainId) return undefined;
|
if (!provider || !chainId) return undefined;
|
||||||
|
|
||||||
const chain = provider.getChains()[chainId];
|
const chain = provider.getChains()[chainId];
|
||||||
if (!chain) return undefined;
|
if (!chain) return undefined;
|
||||||
|
return createClient(chain.connect);
|
||||||
return createClient(chain.connect)
|
|
||||||
}, [provider, chainId]);
|
}, [provider, chainId]);
|
||||||
|
|
||||||
const observableClient = useMemo(() => client ? getObservableClient(client) : undefined, [client]);
|
const observableClient = useMemo(() => {
|
||||||
const chainHead$ = useMemo(() => observableClient?.chainHead$(), [observableClient]);
|
return client ? getObservableClient(client) : undefined;
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
const value = useMemo(() => ({
|
const chainHead$ = useMemo(() => {
|
||||||
isExtensionMissing: connectionState === "no-extension",
|
return observableClient ? observableClient.chainHead$() : undefined;
|
||||||
providerDetails,
|
}, [observableClient]);
|
||||||
providerDetail,
|
|
||||||
connectProviderByIndex: setProviderIndex,
|
|
||||||
chainId,
|
|
||||||
client,
|
|
||||||
setChainId,
|
|
||||||
chainHead$
|
|
||||||
}), [providerDetails, providerDetail, chainId, client, chainHead$]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnstableProvider.Provider value={value}>
|
<UnstableProvider.Provider
|
||||||
|
value={{
|
||||||
|
providerDetails,
|
||||||
|
providerDetail,
|
||||||
|
connectProviderDetail: setProviderDetail,
|
||||||
|
provider,
|
||||||
|
chainId,
|
||||||
|
client,
|
||||||
|
setChainId,
|
||||||
|
chainHead$
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</UnstableProvider.Provider>
|
</UnstableProvider.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,5 +12,3 @@ export * from "./useBlockCommitments";
|
|||||||
export * from "./useApplauseDetails";
|
export * from "./useApplauseDetails";
|
||||||
export * from "./useBabeSlots";
|
export * from "./useBabeSlots";
|
||||||
export * from "./useErasTotalStaked";
|
export * from "./useErasTotalStaked";
|
||||||
export * from "./useLatestBlockNumber";
|
|
||||||
export * from "./useEraIndex";
|
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import useSWRSubscription from "swr/subscription"
|
|
||||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
|
||||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
|
||||||
|
|
||||||
import { useUnstableProvider } from "./UnstableProvider"
|
|
||||||
import { useMetadata } from "./MetadataProvider"
|
|
||||||
|
|
||||||
export const useEraIndex = () => {
|
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
|
||||||
const metadata = useMetadata()
|
|
||||||
const { data: eraIndex } = useSWRSubscription(
|
|
||||||
chainHead$ && chainId && metadata
|
|
||||||
? ["eraIndex", chainHead$, chainId, metadata]
|
|
||||||
: null,
|
|
||||||
([_, chainHead$, chainId, metadata], { next }) => {
|
|
||||||
const { finalized$, storage$ } = chainHead$
|
|
||||||
const subscription = finalized$.pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
mergeMap((blockInfo) => {
|
|
||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
|
||||||
const eraIndex = builder.buildStorage("Staking", "ActiveEra")
|
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
|
||||||
eraIndex?.keys.enc()
|
|
||||||
).pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
distinct(),
|
|
||||||
map((value) => eraIndex?.value.dec(value))
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.subscribe({
|
|
||||||
next(eraIndex) {
|
|
||||||
next(null, eraIndex)
|
|
||||||
},
|
|
||||||
error: next,
|
|
||||||
})
|
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return eraIndex;
|
|
||||||
}
|
|
||||||
@ -5,14 +5,14 @@ import { distinct, filter, map, mergeMap } from "rxjs"
|
|||||||
import { useUnstableProvider } from "./UnstableProvider"
|
import { useUnstableProvider } from "./UnstableProvider"
|
||||||
import { useMetadata } from "./MetadataProvider"
|
import { useMetadata } from "./MetadataProvider"
|
||||||
|
|
||||||
export const useErasTotalStake = ({ epochIndex }) => {
|
export const useErasTotalStake = ({ eraIndex }) => {
|
||||||
const { chainHead$, chainId } = useUnstableProvider()
|
const { chainHead$, chainId } = useUnstableProvider()
|
||||||
const metadata = useMetadata()
|
const metadata = useMetadata()
|
||||||
const { data: eraTotalStake } = useSWRSubscription(
|
const { data: eraTotalStake } = useSWRSubscription(
|
||||||
chainHead$ && chainId && metadata
|
chainHead$ && chainId && metadata
|
||||||
? ["eraTotalStake", chainHead$, epochIndex, chainId, metadata]
|
? ["eraTotalStake", chainHead$, eraIndex, chainId, metadata]
|
||||||
: null,
|
: null,
|
||||||
([_, chainHead$, epochIndex, chainId, metadata], { next }) => {
|
([_, chainHead$, eraIndex, chainId, metadata], { next }) => {
|
||||||
const { finalized$, storage$ } = chainHead$
|
const { finalized$, storage$ } = chainHead$
|
||||||
const subscription = finalized$.pipe(
|
const subscription = finalized$.pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
@ -20,7 +20,7 @@ export const useErasTotalStake = ({ epochIndex }) => {
|
|||||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||||
const eraTotalStake = builder.buildStorage("Staking", "ErasTotalStake")
|
const eraTotalStake = builder.buildStorage("Staking", "ErasTotalStake")
|
||||||
return storage$(blockInfo?.hash, "value", () =>
|
return storage$(blockInfo?.hash, "value", () =>
|
||||||
eraTotalStake?.keys.enc(epochIndex)
|
eraTotalStake?.keys.enc(eraIndex)
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
distinct(),
|
distinct(),
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import useSWRSubscription from "swr/subscription"
|
|
||||||
import { useUnstableProvider } from "./UnstableProvider"
|
|
||||||
|
|
||||||
export const useLatestBlockNumber = () => {
|
|
||||||
const { chainHead$ } = useUnstableProvider();
|
|
||||||
const [blockNumber, setBlockNumber] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!chainHead$) return;
|
|
||||||
const subscription = chainHead$.best$.subscribe((block) => {
|
|
||||||
setBlockNumber(block.number);
|
|
||||||
});
|
|
||||||
return () => subscription.unsubscribe();
|
|
||||||
}, [chainHead$]);
|
|
||||||
|
|
||||||
return blockNumber;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import { useReadContract, useReadContracts } from "wagmi";
|
import { useReadContract, useReadContracts } from "wagmi";
|
||||||
import { keccak256, stringToBytes } from 'viem'
|
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { keccak256, stringToBytes } from 'viem'
|
||||||
|
|
||||||
|
import { config } from "../../config";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GHOST_GOVERNANCE_ADDRESSES,
|
GHOST_GOVERNANCE_ADDRESSES,
|
||||||
@ -12,24 +14,7 @@ import { abi as GovernorStorageAbi } from "../../abi/GovernorStorage.json";
|
|||||||
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
|
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { getTokenDecimals, getTokenAbi, getTokenAddress, executeOnChainTransaction } from "../helpers";
|
import { getTokenDecimals, getTokenAbi, getTokenAddress } from "../helpers";
|
||||||
|
|
||||||
export const getVoteValue = (forVotes, totalVotes) => {
|
|
||||||
if (totalVotes == 0n) return 0;
|
|
||||||
const value = forVotes * 100n / totalVotes;
|
|
||||||
return Math.floor(Number(value.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getVoteTarget = (totalVotes, totalSupply) => {
|
|
||||||
if (totalSupply == 0n) return 80;
|
|
||||||
|
|
||||||
const precision = 10n ** 3n;
|
|
||||||
const valueRaw = (totalVotes * precision) / totalSupply;
|
|
||||||
const value = Number(valueRaw) / Number(precision);
|
|
||||||
|
|
||||||
const result = (5 - Math.sqrt(1 + 80/9 * (value - 0.1) )) / 4;
|
|
||||||
return Math.floor(result * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useProposalVoteOf = (chainId, proposalId, who) => {
|
export const useProposalVoteOf = (chainId, proposalId, who) => {
|
||||||
const { data, error } = useReadContract({
|
const { data, error } = useReadContract({
|
||||||
@ -37,7 +22,7 @@ export const useProposalVoteOf = (chainId, proposalId, who) => {
|
|||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
functionName: "voteOf",
|
functionName: "voteOf",
|
||||||
args: [proposalId, who],
|
args: [proposalId, who],
|
||||||
scopeKey: `voteOf-${chainId}-${proposalId ? proposalId.toString() : "undefined"}-${who}`,
|
scopeKey: `voteOf-${chainId}-${proposalId?.toString()}-${who}`,
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
});
|
});
|
||||||
const voteOf = data ? BigInt(data) : 0n;
|
const voteOf = data ? BigInt(data) : 0n;
|
||||||
@ -111,7 +96,6 @@ export const useMinQuorum = (chainId) => {
|
|||||||
|
|
||||||
export const useProposalThreshold = (chainId, name) => {
|
export const useProposalThreshold = (chainId, name) => {
|
||||||
const decimals = getTokenDecimals(name);
|
const decimals = getTokenDecimals(name);
|
||||||
const { proposalCount } = useProposalCount(chainId);
|
|
||||||
|
|
||||||
const { data } = useReadContract({
|
const { data } = useReadContract({
|
||||||
abi: GovernorStorageAbi,
|
abi: GovernorStorageAbi,
|
||||||
@ -129,17 +113,13 @@ export const useProposalThreshold = (chainId, name) => {
|
|||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastIndex = proposalCount === 0n ? 0n : proposalCount - 1n;
|
|
||||||
const { proposalId } = useProposalDetailsAt(chainId, lastIndex, {
|
|
||||||
enabled: proposalCount !== 0n
|
|
||||||
});
|
|
||||||
const { state } = useProposalState(chainId, proposalId, {
|
|
||||||
enabled: proposalCount !== 0n && !!proposalId
|
|
||||||
});
|
|
||||||
|
|
||||||
let threshold = new DecimalBigNumber(data ?? 0n, decimals);
|
let threshold = new DecimalBigNumber(data ?? 0n, decimals);
|
||||||
|
|
||||||
if (proposalCount !== 0n && state !== undefined && state < 2) {
|
const { proposalCount } = useProposalCount(chainId);
|
||||||
|
const { proposalId } = useProposalDetailsAt(chainId, proposalCount === 0n ? 0n : proposalCount - 1n);
|
||||||
|
const { state } = useProposalState(chainId, proposalId);
|
||||||
|
|
||||||
|
if (state < 2) {
|
||||||
threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals);
|
threshold = new DecimalBigNumber(activeProposedLock ?? 0n, decimals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,22 +320,22 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalDetails } = useReadContracts({
|
const { data: proposalDetails } = useReadContracts({
|
||||||
contracts: searchedIndexes?.map(proposalId => {
|
contracts: searchedIndexes?.map(index => {
|
||||||
return {
|
return {
|
||||||
abi: GovernorStorageAbi,
|
abi: GovernorStorageAbi,
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
functionName: "proposalDetails",
|
functionName: "proposalDetails",
|
||||||
args: [proposalId],
|
args: [index],
|
||||||
scopeKey: `proposalDetails-${chainId}-${proposalId}`,
|
scopeKey: `proposalDetails-${chainId}-${index}`,
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalDeadlines } = useReadContracts({
|
const { data: proposalDeadlines } = useReadContracts({
|
||||||
contracts: indexes?.map((_, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const proposalId = searchedIndexes
|
const proposalId = searchedIndexes
|
||||||
? searchedIndexes?.at(index)
|
? searchedIndexes?.at(0)
|
||||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -370,9 +350,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalVotes } = useReadContracts({
|
const { data: proposalVotes } = useReadContracts({
|
||||||
contracts: indexes?.map((_, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const proposalId = searchedIndexes
|
const proposalId = searchedIndexes
|
||||||
? searchedIndexes?.at(index)
|
? searchedIndexes?.at(0)
|
||||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -387,9 +367,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalStates } = useReadContracts({
|
const { data: proposalStates } = useReadContracts({
|
||||||
contracts: indexes?.map((_, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const proposalId = searchedIndexes
|
const proposalId = searchedIndexes
|
||||||
? searchedIndexes?.at(index)
|
? searchedIndexes?.at(0)
|
||||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -404,9 +384,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalSnapshots } = useReadContracts({
|
const { data: proposalSnapshots } = useReadContracts({
|
||||||
contracts: indexes?.map((_, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const proposalId = searchedIndexes
|
const proposalId = searchedIndexes
|
||||||
? searchedIndexes?.at(index)
|
? searchedIndexes?.at(0)
|
||||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -421,8 +401,8 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalQuorums } = useReadContracts({
|
const { data: proposalQuorums } = useReadContracts({
|
||||||
contracts: proposalSnapshots?.map((proposal, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const timepoint = proposal?.result;
|
const timepoint = proposalSnapshots?.at(index)?.result;
|
||||||
return {
|
return {
|
||||||
abi: GovernorAbi,
|
abi: GovernorAbi,
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
@ -435,8 +415,8 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: pastTotalSupplies } = useReadContracts({
|
const { data: pastTotalSupplies } = useReadContracts({
|
||||||
contracts: proposalSnapshots?.map((proposal, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const timepoint = proposal?.result;
|
const timepoint = proposalSnapshots?.at(index)?.result;
|
||||||
return {
|
return {
|
||||||
abi: ghstAbi,
|
abi: ghstAbi,
|
||||||
address: ghstAddress,
|
address: ghstAddress,
|
||||||
@ -449,9 +429,9 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: proposalProposer } = useReadContracts({
|
const { data: proposalProposer } = useReadContracts({
|
||||||
contracts: indexes?.map((_, index) => {
|
contracts: indexes?.map(index => {
|
||||||
const proposalId = searchedIndexes
|
const proposalId = searchedIndexes
|
||||||
? searchedIndexes?.at(index)
|
? searchedIndexes?.at(0)
|
||||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -465,126 +445,116 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const hashes = useMemo(() => {
|
const hashes = indexes?.map(index => {
|
||||||
return indexes?.map((_, index) => {
|
let result = { short: index + 1, full: undefined };
|
||||||
let result = { short: index + 1, full: undefined };
|
const proposalId = searchedIndexes
|
||||||
const proposalId = searchedIndexes
|
? searchedIndexes?.at(0)
|
||||||
? searchedIndexes?.at(index)
|
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
||||||
: proposalsDetailsAt?.at(index)?.result?.at(0);
|
|
||||||
|
|
||||||
if (proposalId) {
|
if (proposalId) {
|
||||||
const hash = "0x" + proposalId.toString(16);
|
const hash = "0x" + proposalId.toString(16);
|
||||||
result.short = hash.slice(-5);
|
result.short = hash.slice(-5);
|
||||||
result.full = hash;
|
result.full = hash;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}, [indexes, searchedIndexes, proposalsDetailsAt]);
|
|
||||||
|
|
||||||
const voteValues = useMemo(() => {
|
const proposals = indexes?.map(index => ({
|
||||||
return indexes?.map((_, idx) => {
|
hashes: hashes?.at(index),
|
||||||
const index = indexes.length - idx - 1;
|
|
||||||
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
|
|
||||||
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
|
|
||||||
|
|
||||||
const totalVotes = againstVotes + forVotes;
|
|
||||||
return getVoteValue(forVotes, totalVotes);
|
|
||||||
}) ?? [];
|
|
||||||
}, [indexes, proposalVotes]);
|
|
||||||
|
|
||||||
const voteTargets = useMemo(() => {
|
|
||||||
return indexes?.map((_, idx) => {
|
|
||||||
const index = indexes.length - idx - 1;
|
|
||||||
const againstVotes = proposalVotes?.at(index)?.result?.at(0) ?? 0n
|
|
||||||
const forVotes = proposalVotes?.at(index)?.result?.at(1) ?? 0n;
|
|
||||||
const totalSupply = pastTotalSupplies?.at(index)?.result ?? 0n;
|
|
||||||
|
|
||||||
const totalVotes = againstVotes + forVotes;
|
|
||||||
return getVoteTarget(totalVotes, totalSupply);
|
|
||||||
}) ?? [];
|
|
||||||
}, [indexes, proposalVotes, pastTotalSupplies]);
|
|
||||||
|
|
||||||
const proposalDetailsPrepared = useMemo(() => {
|
|
||||||
if (!searchedIndexes) return proposalsDetailsAt;
|
|
||||||
return proposalDetails?.map((obj, index) => {
|
|
||||||
if (!obj?.result) return obj;
|
|
||||||
const proposalId = searchedIndexes.at(index);
|
|
||||||
return {
|
|
||||||
...obj,
|
|
||||||
result: [proposalId, ...obj.result],
|
|
||||||
};
|
|
||||||
}) ?? [];
|
|
||||||
}, [searchedIndexes, proposalsDetailsAt, proposalDetails]);
|
|
||||||
|
|
||||||
const proposals = indexes?.map((_, index) => ({
|
|
||||||
hashes: hashes?.at(index) ?? { short: "", full: "" },
|
|
||||||
proposer: proposalProposer?.at(index)?.result,
|
proposer: proposalProposer?.at(index)?.result,
|
||||||
details: proposalDetailsPrepared?.at(index)?.result,
|
details: proposalsDetailsAt?.at(index)?.result,
|
||||||
deadline: proposalDeadlines?.at(index)?.result ?? 0n,
|
deadline: proposalDeadlines?.at(index)?.result ?? 0n,
|
||||||
snapshot: proposalSnapshots?.at(index)?.result ?? 0n,
|
|
||||||
state: proposalStates?.at(index)?.result ?? 0,
|
state: proposalStates?.at(index)?.result ?? 0,
|
||||||
pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.at(index)?.result ?? 0n, decimals),
|
pastTotalSupply: new DecimalBigNumber(pastTotalSupplies?.at(index)?.result ?? 0n, decimals),
|
||||||
quorum: new DecimalBigNumber(proposalQuorums?.at(index)?.result ?? 0n, decimals),
|
quorum: new DecimalBigNumber(proposalQuorums?.at(index)?.result ?? 0n, decimals),
|
||||||
votes: proposalVotes?.at(index)?.result?.map(vote => new DecimalBigNumber(vote ?? 0n, decimals)),
|
snapshot: new DecimalBigNumber(proposalSnapshots?.at(index)?.result ?? 0n, decimals),
|
||||||
voteValue: voteValues?.at(index),
|
votes: proposalVotes?.at(index)?.result?.map(
|
||||||
voteTarget: voteTargets?.at(index),
|
vote => new DecimalBigNumber(vote ?? 0n, decimals),
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { proposals };
|
return { proposals };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const releaseLocked = async (chainId, account, proposalId) => {
|
export const releaseLocked = async (chainId, account, proposalId) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Release locked transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "Successfully release locked funds from the governor.",
|
abi: GovernorAbi,
|
||||||
errorMsg: "Release locked funds failed. Check logs for error detalization.",
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
};
|
functionName: 'releaseLocked',
|
||||||
|
args: [proposalId],
|
||||||
|
account: account,
|
||||||
|
chainId: chainId
|
||||||
|
});
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await writeContract(config, request);
|
||||||
chainId,
|
await waitForTransactionReceipt(config, {
|
||||||
args: [proposalId],
|
hash: txHash,
|
||||||
abi: GovernorAbi,
|
onReplaced: () => toast("Release locked transaction was replaced. Wait for inclusion please."),
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
chainId
|
||||||
functionName: "releaseLocked",
|
});
|
||||||
account,
|
|
||||||
messages,
|
toast.success("Successfully release locked funds from the governor.");
|
||||||
});
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Release locked funds failed. Check logs for error detalization.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executeProposal = async (chainId, account, proposalId) => {
|
export const executeProposal = async (chainId, account, proposalId) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Proposal execution transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "Proposal execution was successful, wait for updates.",
|
abi: GovernorAbi,
|
||||||
errorMsg: "Proposal execution failed. Check logs for error detalization.",
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
};
|
functionName: 'execute',
|
||||||
|
args: [proposalId],
|
||||||
|
account: account,
|
||||||
|
chainId: chainId
|
||||||
|
});
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await writeContract(config, request);
|
||||||
chainId,
|
await waitForTransactionReceipt(config, {
|
||||||
args: [proposalId],
|
hash: txHash,
|
||||||
abi: GovernorAbi,
|
onReplaced: () => toast("Proposal execution transaction was replaced. Wait for inclusion please."),
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
chainId
|
||||||
functionName: "execute",
|
});
|
||||||
account,
|
|
||||||
messages,
|
toast.success("Proposal execution was successful, wait for updates.");
|
||||||
});
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Proposal execution failed. Check logs for error detalization.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const castVote = async (chainId, account, proposalId, support) => {
|
export const castVote = async (chainId, account, proposalId, support) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Cast vote transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "Successfully casted a vote, should be applied the proposal.",
|
abi: GovernorAbi,
|
||||||
errorMsg: "Vote cast failed. Check logs for error detalization.",
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
};
|
functionName: 'castVote',
|
||||||
|
args: [proposalId, support],
|
||||||
|
account: account,
|
||||||
|
chainId: chainId
|
||||||
|
});
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await writeContract(config, request);
|
||||||
chainId,
|
await waitForTransactionReceipt(config, {
|
||||||
args: [proposalId, support],
|
hash: txHash,
|
||||||
abi: GovernorAbi,
|
onReplaced: () => toast("Cast vote transaction was replaced. Wait for inclusion please."),
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
chainId
|
||||||
functionName: "castVote",
|
});
|
||||||
account,
|
|
||||||
messages,
|
toast.success("Successfully casted a vote, should be applied the proposal.");
|
||||||
});
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Vote cast failed. Check logs for error detalization.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const propose = async (chainId, account, functions, description) => {
|
export const propose = async (chainId, account, functions, description) => {
|
||||||
@ -592,19 +562,28 @@ export const propose = async (chainId, account, functions, description) => {
|
|||||||
const values = functions.map(f => f.value);
|
const values = functions.map(f => f.value);
|
||||||
const calldatas = functions.map(f => f.calldata);
|
const calldatas = functions.map(f => f.calldata);
|
||||||
|
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Proposal transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "Successfully proposed a set of functions to be executed.",
|
abi: GovernorAbi,
|
||||||
errorMsg: "Proposal creation failed. Check logs for error detalization.",
|
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||||
};
|
functionName: 'propose',
|
||||||
|
args: [targets, values, calldatas, description],
|
||||||
|
account: account,
|
||||||
|
chainId: chainId
|
||||||
|
});
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await writeContract(config, request);
|
||||||
chainId,
|
await waitForTransactionReceipt(config, {
|
||||||
args: [targets, values, calldatas, description],
|
hash: txHash,
|
||||||
abi: GovernorAbi,
|
onReplaced: () => toast("Proposal transaction was replaced. Wait for inclusion please."),
|
||||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
chainId
|
||||||
functionName: "propose",
|
});
|
||||||
account,
|
|
||||||
messages,
|
toast.success("Successfully proposed a set of functions to be executed.");
|
||||||
});
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Proposal creation failed. Check logs for error detalization.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
import toast from "react-hot-toast";
|
|
||||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RESERVE_ADDRESSES,
|
RESERVE_ADDRESSES,
|
||||||
FTSO_ADDRESSES,
|
FTSO_ADDRESSES,
|
||||||
@ -9,9 +6,6 @@ import {
|
|||||||
FTSO_DAI_LP_ADDRESSES,
|
FTSO_DAI_LP_ADDRESSES,
|
||||||
WETH_ADDRESSES,
|
WETH_ADDRESSES,
|
||||||
} from "../constants/addresses";
|
} from "../constants/addresses";
|
||||||
import { config } from "../config";
|
|
||||||
|
|
||||||
import { tokenNameConverter } from "../helpers/tokenConverter";
|
|
||||||
|
|
||||||
import { abi as DaiAbi } from "../abi/Reserve.json";
|
import { abi as DaiAbi } from "../abi/Reserve.json";
|
||||||
import { abi as FatsoAbi } from "../abi/Fatso.json";
|
import { abi as FatsoAbi } from "../abi/Fatso.json";
|
||||||
@ -148,12 +142,6 @@ export const getTokenAddress = (chainId, name) => {
|
|||||||
case "WMETC":
|
case "WMETC":
|
||||||
address = WETH_ADDRESSES[chainId];
|
address = WETH_ADDRESSES[chainId];
|
||||||
break;
|
break;
|
||||||
case "ETH":
|
|
||||||
address = undefined;
|
|
||||||
break;
|
|
||||||
case "METC":
|
|
||||||
address = undefined;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
@ -162,7 +150,7 @@ export const getTokenIcons = (chainId, address) => {
|
|||||||
let icons = [""];
|
let icons = [""];
|
||||||
switch (address) {
|
switch (address) {
|
||||||
case RESERVE_ADDRESSES[chainId]:
|
case RESERVE_ADDRESSES[chainId]:
|
||||||
icons = [tokenNameConverter(chainId, "WETH")];
|
icons = ["WETH"];
|
||||||
break;
|
break;
|
||||||
case FTSO_ADDRESSES[chainId]:
|
case FTSO_ADDRESSES[chainId]:
|
||||||
icons = ["FTSO"];
|
icons = ["FTSO"];
|
||||||
@ -176,27 +164,24 @@ export const getTokenIcons = (chainId, address) => {
|
|||||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
||||||
icons = ["FTSO", "WETH"];
|
icons = ["FTSO", "WETH"];
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
icons = [""]
|
|
||||||
}
|
}
|
||||||
return icons;
|
return icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => {
|
export const getBondNameDisplayName = (chainId, stringValue, tokenAddress) => {
|
||||||
let stringValue = tokenNameConverter(chainId, "WETH")
|
|
||||||
if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
|
if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
|
||||||
stringValue = `${baseTokenSymbol}-${stringValue} LP`;
|
stringValue = `LP ${stringValue}`;
|
||||||
}
|
}
|
||||||
return stringValue;
|
return stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => {
|
export const getTokenPurchaseLink = (chainId, tokenAddress) => {
|
||||||
let purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/dex/uniswap`;
|
let purchaseUrl = "https://app.dao.ghostchain.io/#/dex/uniswap";
|
||||||
switch (tokenAddress) {
|
switch (tokenAddress) {
|
||||||
case RESERVE_ADDRESSES[chainId]:
|
case RESERVE_ADDRESSES[chainId]:
|
||||||
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/faucet`;
|
purchaseUrl = "https://app.dao.ghostchain.io/#/faucet";
|
||||||
if (chainId == 63) {
|
if (chainId == 63) {
|
||||||
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`;
|
purchaseUrl = "https://app.dao.ghostchain.io/#/wrapper";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
||||||
@ -205,41 +190,3 @@ export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => {
|
|||||||
}
|
}
|
||||||
return purchaseUrl;
|
return purchaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executeOnChainTransaction = async ({
|
|
||||||
chainId,
|
|
||||||
abi,
|
|
||||||
address,
|
|
||||||
functionName,
|
|
||||||
args,
|
|
||||||
account,
|
|
||||||
messages,
|
|
||||||
value
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
const { request } = await simulateContract(config, {
|
|
||||||
abi,
|
|
||||||
address,
|
|
||||||
functionName,
|
|
||||||
args,
|
|
||||||
account,
|
|
||||||
chainId,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const txHash = await writeContract(config, request);
|
|
||||||
await waitForTransactionReceipt(config, {
|
|
||||||
hash: txHash,
|
|
||||||
onReplaced: () => toast(messages.replacedMsg),
|
|
||||||
chainId
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.success(messages.successMsg);
|
|
||||||
|
|
||||||
return txHash;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
toast.error(messages.errorMsg)
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { createContext, useContext, useState, useEffect } from "react";
|
|
||||||
|
|
||||||
const LocalStorageContext = createContext();
|
|
||||||
export const useLocalStorage = () => useContext(LocalStorageContext);
|
|
||||||
|
|
||||||
export const LocalStorageProvider = ({ children }) => {
|
|
||||||
const getStorageKey = (chainId, address, target) => {
|
|
||||||
return `${chainId}:${address}:${target}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStorageValue = (chainId, address, target, defaultValue) => {
|
|
||||||
const key = getStorageKey(chainId, address, target);
|
|
||||||
const stored = localStorage.getItem(key);
|
|
||||||
return stored ? JSON.parse(stored) : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setStorageValue = (chainId, address, target, value) => {
|
|
||||||
const key = getStorageKey(chainId, address, target);
|
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LocalStorageContext.Provider value={{ getStorageValue, setStorageValue }}>
|
|
||||||
{children}
|
|
||||||
</LocalStorageContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -116,7 +116,7 @@ export const useReservePrice = (chainId) => {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
if ('data' in response) {
|
if ('data' in response) {
|
||||||
const coinPrice = Number(response?.data?.amount ?? 0.0);
|
const coinPrice = Number(response?.data?.amount ?? 0.0);
|
||||||
const priceInWei = Math.floor(coinPrice * 1e18);
|
const priceInWei = Math.floor(coinPrice * 1e18 / 0.99);
|
||||||
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
|
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
|
||||||
} else if ('price' in response) {
|
} else if ('price' in response) {
|
||||||
const coinPrice = Number(response?.price ?? 0.0);
|
const coinPrice = Number(response?.price ?? 0.0);
|
||||||
|
|||||||
@ -1,75 +1,14 @@
|
|||||||
import { useReadContract } from "wagmi";
|
import { useReadContract } from "wagmi";
|
||||||
|
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
import {
|
import { config } from "../../config";
|
||||||
amountInHistory,
|
|
||||||
amountOutHistory,
|
|
||||||
feeIn,
|
|
||||||
feeOut,
|
|
||||||
stakeRatio
|
|
||||||
} from "../../constants/gatekeeper";
|
|
||||||
import { STAKING_ADDRESSES } from "../../constants/addresses";
|
import { STAKING_ADDRESSES } from "../../constants/addresses";
|
||||||
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
|
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
|
||||||
import { abi as GatekeeperAbi } from "../../abi/GhostGatekeeper.json";
|
|
||||||
|
|
||||||
import { shorten } from "../../helpers";
|
import { shorten } from "../../helpers";
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { executeOnChainTransaction } from "../helpers";
|
|
||||||
import { useCirculatingSupply } from "../tokens";
|
|
||||||
|
|
||||||
export const useGatekeeperApy = (chainId) => {
|
|
||||||
const circulatingSupply = useCirculatingSupply(chainId, "STNK");
|
|
||||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
|
||||||
const { epoch } = useEpoch(chainId);
|
|
||||||
|
|
||||||
const { data: metadata, error } = useReadContract({
|
|
||||||
abi: GatekeeperAbi,
|
|
||||||
address: gatekeeperAddress,
|
|
||||||
functionName: "metadata",
|
|
||||||
scopeKey: `gatekeeperMetadata-${chainId}-${gatekeeperAddress}`,
|
|
||||||
chainId: chainId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const amountIn = new DecimalBigNumber(
|
|
||||||
(metadata?.amountIn ?? 0n) + (amountInHistory[chainId] ?? 0n),
|
|
||||||
18,
|
|
||||||
);
|
|
||||||
const amountOut = new DecimalBigNumber(
|
|
||||||
(metadata?.amountOut ?? 0n) + (amountOutHistory[chainId] ?? 0n),
|
|
||||||
18,
|
|
||||||
);
|
|
||||||
|
|
||||||
const deployedAt = metadata?.deployedAt ?? 0;
|
|
||||||
const unixSeconds = Math.floor(Date.now() / 1000);
|
|
||||||
const power = 365 * 86400 / (unixSeconds - deployedAt);
|
|
||||||
|
|
||||||
const feeInBig = new DecimalBigNumber((feeIn[chainId] ?? 0n), 5);
|
|
||||||
const feeOutBig = new DecimalBigNumber((feeOut[chainId] ?? 0n), 5);
|
|
||||||
const stakeRatioBig = new DecimalBigNumber((stakeRatio ?? 0n), 5);
|
|
||||||
|
|
||||||
const numerator = amountIn.mul(feeInBig).add(amountOut.mul(feeOutBig));
|
|
||||||
const denominator = amountIn.mul(stakeRatioBig).sub(amountOut.mul(stakeRatioBig));
|
|
||||||
|
|
||||||
let apyInner = Infinity;
|
|
||||||
if ((circulatingSupply?._value ?? 0n) === 0n) {
|
|
||||||
return { gatekeepedApy: apyInner, apyInner};
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = epoch.distribute.div(circulatingSupply);
|
|
||||||
apyInner = 100 * (Math.pow(1 + parseFloat(value.toString()), 1095) - 1);
|
|
||||||
if (apyInner === 0) apyInner = Infinity;
|
|
||||||
|
|
||||||
if (!denominator?._value || denominator._value.isZero()) {
|
|
||||||
return { gatekeepedApy: apyInner, apyInner };
|
|
||||||
}
|
|
||||||
const result = Number(numerator.div(denominator).toString());
|
|
||||||
const gatekeepedApy = Math.pow(1 + result, power) * 100;
|
|
||||||
|
|
||||||
return {
|
|
||||||
gatekeepedApy: apyInner + gatekeepedApy + (apyInner * gatekeepedApy) / 100,
|
|
||||||
apyInner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCurrentIndex = (chainId) => {
|
export const useCurrentIndex = (chainId) => {
|
||||||
const { data: index, refetch } = useReadContract({
|
const { data: index, refetch } = useReadContract({
|
||||||
@ -80,7 +19,7 @@ export const useCurrentIndex = (chainId) => {
|
|||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentIndexRaw = index ? index : 1000000000n;
|
const currentIndexRaw = index ? index : 0n;
|
||||||
const currentIndex = new DecimalBigNumber(currentIndexRaw, 9);
|
const currentIndex = new DecimalBigNumber(currentIndexRaw, 9);
|
||||||
|
|
||||||
return { currentIndex, refetch };
|
return { currentIndex, refetch };
|
||||||
@ -129,16 +68,16 @@ export const useWarmupInfo = (chainId, address) => {
|
|||||||
const { data: info, refetch } = useReadContract({
|
const { data: info, refetch } = useReadContract({
|
||||||
abi: StakingAbi,
|
abi: StakingAbi,
|
||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "warmupInfo",
|
functionName: "getWarmupInfo",
|
||||||
args: [address],
|
args: [address],
|
||||||
scopeKey: `warmupInfo-${address}-${chainId}`,
|
scopeKey: `getWarmupInfo-${address}-${chainId}`,
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const warmupInfo = {
|
const warmupInfo = {
|
||||||
deposit: info ? new DecimalBigNumber(info.at(0), 9) : new DecimalBigNumber(0n, 9),
|
deposit: info ? new DecimalBigNumber(info.deposit, 9) : new DecimalBigNumber(0n, 9),
|
||||||
shares: info ? info.at(1) : 0n,
|
shares: info ? info.shares : 0n,
|
||||||
expiry: info ? Number(info.at(2)) : 0,
|
expiry: info ? Number(info.expiry) : 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
return { warmupInfo, refetch }
|
return { warmupInfo, refetch }
|
||||||
@ -175,39 +114,45 @@ export const useGhostedSupply = (chainId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSymbol, stnkSymbol, ghstSymbol) => {
|
export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSymbol, stnkSymbol, ghstSymbol) => {
|
||||||
const args = [amount, account, isRebase, isClaim];
|
const args = [
|
||||||
|
amount,
|
||||||
|
account,
|
||||||
|
isRebase,
|
||||||
|
isClaim
|
||||||
|
];
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Staking transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Staking transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `${ftsoSymbol} tokens staked successfully! Wait for the warm-up period to claim your ${isRebase ? stnkSymbol : ghstSymbol}.`,
|
successMsg: `${ftsoSymbol} tokens staked successfully! Wait for the warmup period to claim your ${isRebase ? stnkSymbol : ghstSymbol}.`,
|
||||||
errorMsg: "Staking transaction failed. Check logs for error detalization.",
|
errorMsg: "Staking transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
|
"stake",
|
||||||
args,
|
args,
|
||||||
messages,
|
|
||||||
account,
|
account,
|
||||||
abi: StakingAbi,
|
messages
|
||||||
address: STAKING_ADDRESSES[chainId],
|
);
|
||||||
functionName: "stake",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unstake = async (chainId, account, amount, isTrigger, isRebase, ftsoSymbol, stnkSymbol, ghstSymbol) => {
|
export const unstake = async (chainId, account, amount, isTrigger, isRebase, ftsoSymbol, stnkSymbol, ghstSymbol) => {
|
||||||
const args = [amount, account, isTrigger, isRebase];
|
const args = [
|
||||||
|
amount,
|
||||||
|
account,
|
||||||
|
isTrigger,
|
||||||
|
isRebase
|
||||||
|
];
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Unstake transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Unstake transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `${isRebase ? stnkSymbol : ghstSymbol} tokens unstaked successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
successMsg: `${isRebase ? stnkSymbol : ghstSymbol} tokens unstaked successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
||||||
errorMsg: "Unstake transaction failed. Check logs for error detalization.",
|
errorMsg: "Unstake transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
|
"unstake",
|
||||||
args,
|
args,
|
||||||
messages,
|
|
||||||
account,
|
account,
|
||||||
abi: StakingAbi,
|
messages
|
||||||
address: STAKING_ADDRESSES[chainId],
|
);
|
||||||
functionName: "unstake",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const forfeit = async (chainId, account, ftsoSymbol) => {
|
export const forfeit = async (chainId, account, ftsoSymbol) => {
|
||||||
@ -216,15 +161,13 @@ export const forfeit = async (chainId, account, ftsoSymbol) => {
|
|||||||
successMsg: `Tokens forfeited successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
successMsg: `Tokens forfeited successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
||||||
errorMsg: "Forfeit transaction failed. Check logs for error detalization.",
|
errorMsg: "Forfeit transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
messages,
|
"forfeit",
|
||||||
|
[],
|
||||||
account,
|
account,
|
||||||
args: [],
|
messages
|
||||||
abi: StakingAbi,
|
);
|
||||||
address: STAKING_ADDRESSES[chainId],
|
|
||||||
functionName: "forfeit",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) => {
|
export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) => {
|
||||||
@ -234,89 +177,93 @@ export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) =>
|
|||||||
successMsg: `${isStnk ? stnkSymbol : ghstSymbol} tokens claimed successfully to ${shorten(account)}.`,
|
successMsg: `${isStnk ? stnkSymbol : ghstSymbol} tokens claimed successfully to ${shorten(account)}.`,
|
||||||
errorMsg: "Claim transaction failed. Check logs for error detalization.",
|
errorMsg: "Claim transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
|
"claim",
|
||||||
args,
|
args,
|
||||||
messages,
|
|
||||||
account,
|
account,
|
||||||
abi: StakingAbi,
|
messages
|
||||||
address: STAKING_ADDRESSES[chainId],
|
);
|
||||||
functionName: "claim",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const breakout = async (chainId, account, receiver, amount) => {
|
|
||||||
const args = [receiver, amount];
|
|
||||||
const messages = {
|
|
||||||
replacedMsg: "Breakout transaction was replaced. Wait for inclusion please.",
|
|
||||||
successMsg: `Staking breakout succesfully done. Check tx hash status or wait for slow clap finalization.`,
|
|
||||||
errorMsg: "Breakout transaction failed. Check logs for error detalization.",
|
|
||||||
};
|
|
||||||
const txHash = await executeOnChainTransaction({
|
|
||||||
chainId,
|
|
||||||
args,
|
|
||||||
messages,
|
|
||||||
account,
|
|
||||||
abi: StakingAbi,
|
|
||||||
address: STAKING_ADDRESSES[chainId],
|
|
||||||
functionName: "breakout",
|
|
||||||
});
|
|
||||||
|
|
||||||
return txHash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unwrap = async (chainId, account, amount, stnkSymbol) => {
|
export const unwrap = async (chainId, account, amount, stnkSymbol) => {
|
||||||
const args = [account, amount];
|
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Unwrap transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Unwrap transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `Tokens unwrapped successfully! Check your ${stnkSymbol} balance on ${shorten(account)}.`,
|
successMsg: `Tokens unwrapped successfully! Check your ${stnkSymbol} balance on ${shorten(account)}.`,
|
||||||
errorMsg: "Unwrap transaction failed. Check logs for error detalization.",
|
errorMsg: "Unwrap transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
const txHash = await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
"unwrap",
|
||||||
messages,
|
[account, amount],
|
||||||
account,
|
account,
|
||||||
abi: StakingAbi,
|
messages
|
||||||
address: STAKING_ADDRESSES[chainId],
|
);
|
||||||
functionName: "unwrap",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wrap = async (chainId, account, amount, ghstSymbol) => {
|
export const wrap = async (chainId, account, amount, ghstSymbol) => {
|
||||||
const args = [account, amount];
|
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Wrap transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Wrap transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `Tokens wrapped successfully! Check your ${ghstSymbol} balance on ${shorten(account)}.`,
|
successMsg: `Tokens wrapped successfully! Check your ${ghstSymbol} balance on ${shorten(account)}.`,
|
||||||
errorMsg: "Wrap transaction failed. Check logs for error detalization.",
|
errorMsg: "Wrap transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
const txHash = await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
"wrap",
|
||||||
messages,
|
[account, amount],
|
||||||
account,
|
account,
|
||||||
abi: StakingAbi,
|
messages
|
||||||
address: STAKING_ADDRESSES[chainId],
|
);
|
||||||
functionName: "wrap",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ghost = async (chainId, account, receiver, amount) => {
|
export const ghost = async (chainId, account, receiver, amount) => {
|
||||||
const args = [receiver, amount];
|
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Bridge transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Bridge transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `Amount successfully bridged! Check tx hash status or wait for slow clap finalization.`,
|
successMsg: `Amount successfully bridged! Check tx hash status or wait for slow clap finalization.`,
|
||||||
errorMsg: "Bridge transaction failed. Check logs for error detalization.",
|
errorMsg: "Bridge transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
const txHash = await executeOnChainTransaction({
|
|
||||||
|
const txHash = await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
"ghost",
|
||||||
messages,
|
[receiver, amount],
|
||||||
account,
|
account,
|
||||||
abi: StakingAbi,
|
messages
|
||||||
address: STAKING_ADDRESSES[chainId],
|
);
|
||||||
functionName: "ghost",
|
|
||||||
});
|
|
||||||
|
|
||||||
return txHash;
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executeOnChainTransaction = async (
|
||||||
|
chainId,
|
||||||
|
functionName,
|
||||||
|
args,
|
||||||
|
account,
|
||||||
|
messages
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const { request } = await simulateContract(config, {
|
||||||
|
abi: StakingAbi,
|
||||||
|
address: STAKING_ADDRESSES[chainId],
|
||||||
|
functionName,
|
||||||
|
args,
|
||||||
|
account,
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
const txHash = await writeContract(config, request);
|
||||||
|
await waitForTransactionReceipt(config, {
|
||||||
|
hash: txHash,
|
||||||
|
onReplaced: () => toast(messages.replacedMsg),
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(messages.successMsg);
|
||||||
|
|
||||||
|
return txHash;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error(messages.errorMsg)
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
import { useReadContract, useReadContracts, useToken, useBalance as useInnerBalance } from "wagmi";
|
import { useReadContract, useReadContracts, useToken, useBalance as useInnerBalance } from "wagmi";
|
||||||
|
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
import {
|
import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers";
|
||||||
getTokenAbi,
|
|
||||||
getTokenAddress,
|
|
||||||
getTokenDecimals,
|
|
||||||
executeOnChainTransaction
|
|
||||||
} from "../helpers";
|
|
||||||
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { shorten } from "../../helpers";
|
import { shorten } from "../../helpers";
|
||||||
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
||||||
import { WETH_ADDRESSES } from "../../constants/addresses";
|
import { config } from "../../config";
|
||||||
|
|
||||||
export const usePastVotes = (chainId, name, timepoint, address) => {
|
export const usePastVotes = (chainId, name, timepoint, address) => {
|
||||||
const decimals = getTokenDecimals(name);
|
const decimals = getTokenDecimals(name);
|
||||||
@ -63,29 +59,20 @@ export const useTotalSupply = (chainId, name) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useBalance = (chainId, name, address) => {
|
export const useBalance = (chainId, name, address) => {
|
||||||
let contractAddress = getTokenAddress(chainId, name);
|
const contractAddress = getTokenAddress(chainId, name);
|
||||||
let isNative = false;
|
const { data, refetch, error } = useInnerBalance({
|
||||||
|
|
||||||
let requestObj = {
|
|
||||||
address,
|
address,
|
||||||
chainId,
|
chainId,
|
||||||
scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
|
scopeKey: `balance-${contractAddress}-${address}-${chainId}`,
|
||||||
};
|
token: contractAddress,
|
||||||
|
});
|
||||||
|
|
||||||
if (contractAddress !== undefined) {
|
|
||||||
requestObj.token = contractAddress;
|
|
||||||
} else {
|
|
||||||
contractAddress = WETH_ADDRESSES[chainId];
|
|
||||||
isNative = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data, refetch, error } = useInnerBalance(requestObj);
|
|
||||||
const balancePrepared = data ? data.value : 0n;
|
const balancePrepared = data ? data.value : 0n;
|
||||||
const decimals = data ? data.decimals : getTokenDecimals(name);
|
const decimals = data ? data.decimals : getTokenDecimals(name);
|
||||||
|
|
||||||
const balance = new DecimalBigNumber(balancePrepared, decimals);
|
const balance = new DecimalBigNumber(balancePrepared, decimals);
|
||||||
|
|
||||||
return { balance, refetch, contractAddress, isNative };
|
return { balance, refetch, contractAddress };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAllowance = (chainId, name, owner, spender, decimals) => {
|
export const useAllowance = (chainId, name, owner, spender, decimals) => {
|
||||||
@ -108,7 +95,6 @@ export const useAllowance = (chainId, name, owner, spender, decimals) => {
|
|||||||
|
|
||||||
export const useTokenSymbol = (chainId, name) => {
|
export const useTokenSymbol = (chainId, name) => {
|
||||||
const contractAddress = getTokenAddress(chainId, name);
|
const contractAddress = getTokenAddress(chainId, name);
|
||||||
|
|
||||||
const { data, refetch } = useReadContract({
|
const { data, refetch } = useReadContract({
|
||||||
abi: getTokenAbi(name),
|
abi: getTokenAbi(name),
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
@ -206,86 +192,127 @@ export const useBalanceForShares = (chainId, name, amount) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const approveTokens = async (chainId, name, owner, spender, value) => {
|
export const approveTokens = async (chainId, name, owner, spender, value) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Approve transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: `${name} tokens successfully approved to ${shorten(spender)}`,
|
abi: getTokenAbi(name),
|
||||||
errorMsg: `${name} tokens approval failed. Check logs for error detalization.`
|
address: getTokenAddress(chainId, name),
|
||||||
};
|
functionName: 'approve',
|
||||||
await executeOnChainTransaction({
|
args: [spender, value],
|
||||||
chainId,
|
account: owner,
|
||||||
args: [spender, value],
|
chainId: chainId
|
||||||
abi: getTokenAbi(name),
|
});
|
||||||
address: getTokenAddress(chainId, name),
|
|
||||||
functionName: "approve",
|
const txHash = await writeContract(config, request);
|
||||||
account: owner,
|
await waitForTransactionReceipt(config, {
|
||||||
messages,
|
hash: txHash,
|
||||||
});
|
onReplaced: () => toast("Approve transaction was replaced. Wait for inclusion please."),
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(name + " tokens successfully approved to " + shorten(spender));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error(name + " tokens approval failed. Check logs for error detalization.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mintDai = async (chainId, account, value) => {
|
export const mintDai = async (chainId, account, value) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Mint transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "gDAI successfully minted to your wallet! Funds will be used on Ghost Faucet.",
|
abi: getTokenAbi("GDAI"),
|
||||||
errorMsg: "Minting gDAI from the faucet failed. Check logs for error detalization."
|
address: getTokenAddress(chainId, "GDAI"),
|
||||||
};
|
functionName: 'mint',
|
||||||
await executeOnChainTransaction({
|
args: [account],
|
||||||
chainId,
|
account: account,
|
||||||
args: [account],
|
value: value,
|
||||||
abi: getTokenAbi("GDAI"),
|
chainId: chainId
|
||||||
address: getTokenAddress(chainId, "GDAI"),
|
});
|
||||||
functionName: "mint",
|
|
||||||
account,
|
const txHash = await writeContract(config, request);
|
||||||
messages,
|
await waitForTransactionReceipt(config, {
|
||||||
});
|
hash: txHash,
|
||||||
|
onReplaced: () => toast("Mint transaction was replaced. Wait for inclusion please."),
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("gDAI successfully minted to your wallet! Funds will be used on Ghost Faucet.");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Minting gDAI from the faucet failed. Check logs for error detalization.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const burnDai = async (chainId, account, value) => {
|
export const burnDai = async (chainId, account, value) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "Burn transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "gDAI successfully burned for native coins! Check your wallet balance.",
|
abi: getTokenAbi("GDAI"),
|
||||||
errorMsg: "Burning gDAI from the faucet failed. Check logs for error detalization."
|
address: getTokenAddress(chainId, "GDAI"),
|
||||||
};
|
functionName: 'burn',
|
||||||
await executeOnChainTransaction({
|
args: [value],
|
||||||
chainId,
|
account: account,
|
||||||
abi: getTokenAbi("GDAI"),
|
chainId: chainId
|
||||||
address: getTokenAddress(chainId, "GDAI"),
|
});
|
||||||
functionName: 'burn',
|
|
||||||
args: [value],
|
const txHash = await writeContract(config, request);
|
||||||
account,
|
await waitForTransactionReceipt(config, {
|
||||||
messages,
|
hash: txHash,
|
||||||
});
|
onReplaced: () => toast("Burn transaction was replaced. Wait for inclusion please."),
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("gDAI successfully burned for native coins! Check your wallet balance.");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("Burning gDAI from the faucet failed. Check logs for error detalization.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const depositNative = async (chainId, account, value) => {
|
export const depositNative = async (chainId, account, value) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "WETH9 deposit transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "WETH9 successfully minted to your wallet! Check your wallet balance.",
|
abi: getTokenAbi("WETH"),
|
||||||
errorMsg: "WETH9 wrapping failed. Check logs for error detalization."
|
address: getTokenAddress(chainId, "WETH"),
|
||||||
};
|
functionName: 'deposit',
|
||||||
await executeOnChainTransaction({
|
account: account,
|
||||||
chainId,
|
chainId: chainId,
|
||||||
abi: getTokenAbi("WETH"),
|
value: value
|
||||||
address: getTokenAddress(chainId, "WETH"),
|
});
|
||||||
functionName: 'deposit',
|
const txHash = await writeContract(config, request);
|
||||||
value,
|
|
||||||
account,
|
await waitForTransactionReceipt(config, {
|
||||||
messages,
|
hash: txHash,
|
||||||
});
|
onReplaced: () => toast("WETH9 deposit transaction was replaced. Wait for inclusion please."),
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("WETH9 successfully minted to your wallet! Check your wallet balance.");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("WETH9 wrapping failed. Check logs for error detalization.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const withdrawWeth = async (chainId, account, value) => {
|
export const withdrawWeth = async (chainId, account, value) => {
|
||||||
const messages = {
|
try {
|
||||||
replacedMsg: "WETH9 withdraw transaction was replaced. Wait for inclusion please.",
|
const { request } = await simulateContract(config, {
|
||||||
successMsg: "WETH9 successfully burned for native coins! Check your wallet balance.",
|
abi: getTokenAbi("WETH"),
|
||||||
errorMsg: "WETH9 unwrapping failed. Check logs for error detalization."
|
address: getTokenAddress(chainId, "WETH"),
|
||||||
};
|
functionName: 'withdraw',
|
||||||
await executeOnChainTransaction({
|
args: [value],
|
||||||
chainId,
|
account: account,
|
||||||
abi: getTokenAbi("WETH"),
|
chainId: chainId
|
||||||
address: getTokenAddress(chainId, "WETH"),
|
});
|
||||||
functionName: 'withdraw',
|
|
||||||
args: [value],
|
const txHash = await writeContract(config, request);
|
||||||
account,
|
await waitForTransactionReceipt(config, {
|
||||||
messages,
|
hash: txHash,
|
||||||
});
|
onReplaced: () => toast("WETH9 withdraw transaction was replaced. Wait for inclusion please."),
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("WETH9 successfully burned for native coins! Check your wallet balance.");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error("WETH9 unwrapping failed. Check logs for error detalization.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
|
|||||||
|
|
||||||
import { useReservePrice } from "../prices/index";
|
import { useReservePrice } from "../prices/index";
|
||||||
|
|
||||||
import { bigIntSqrt } from "../../helpers";
|
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import { getTokenAddress } from "../helpers";
|
import { getTokenAddress } from "../helpers";
|
||||||
|
|
||||||
@ -41,9 +40,6 @@ export const useTotalReserves = (chainId) => {
|
|||||||
|
|
||||||
export const useLpValuation = (chainId, pairAddress, pairSupply) => {
|
export const useLpValuation = (chainId, pairAddress, pairSupply) => {
|
||||||
const convertedPairAddress = getTokenAddress(chainId, pairAddress);
|
const convertedPairAddress = getTokenAddress(chainId, pairAddress);
|
||||||
const originalCoefficient = useOrinalCoefficient(chainId);
|
|
||||||
const reservePrice = useReservePrice(chainId);
|
|
||||||
|
|
||||||
const { data: valuationRaw } = useReadContract({
|
const { data: valuationRaw } = useReadContract({
|
||||||
abi: TreasuryAbi,
|
abi: TreasuryAbi,
|
||||||
address: DAO_TREASURY_ADDRESSES[chainId],
|
address: DAO_TREASURY_ADDRESSES[chainId],
|
||||||
@ -56,18 +52,7 @@ export const useLpValuation = (chainId, pairAddress, pairSupply) => {
|
|||||||
chainId,
|
chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sqrtReservePrice = reservePrice?._value
|
const valuationPrepared = valuationRaw ? valuationRaw : 0n;
|
||||||
? bigIntSqrt(reservePrice._value)
|
|
||||||
: 0n;
|
|
||||||
|
|
||||||
const sqrtOriginalCoefficient = originalCoefficient?._value
|
|
||||||
? bigIntSqrt(originalCoefficient._value)
|
|
||||||
: 1n;
|
|
||||||
|
|
||||||
const valuationPrepared = valuationRaw
|
|
||||||
? valuationRaw * sqrtReservePrice / sqrtOriginalCoefficient
|
|
||||||
: 0n;
|
|
||||||
|
|
||||||
const valuation = new DecimalBigNumber(valuationPrepared, 9);
|
const valuation = new DecimalBigNumber(valuationPrepared, 9);
|
||||||
|
|
||||||
return valuation;
|
return valuation;
|
||||||
|
|||||||
@ -4,12 +4,14 @@ import { abi as UniswapV2Factory } from "../../abi/UniswapV2Factory.json";
|
|||||||
import { UNISWAP_V2_FACTORY } from "../../constants/addresses";
|
import { UNISWAP_V2_FACTORY } from "../../constants/addresses";
|
||||||
|
|
||||||
export const useUniswapV2Pair = (chainId, factoryAddress, token0, token1) => {
|
export const useUniswapV2Pair = (chainId, factoryAddress, token0, token1) => {
|
||||||
|
const t0 = token0 > token1 ? token0 : token1;
|
||||||
|
const t1 = token0 > token1 ? token1 : token0;
|
||||||
const { data, refetch } = useReadContract({
|
const { data, refetch } = useReadContract({
|
||||||
abi: UniswapV2Factory,
|
abi: UniswapV2Factory,
|
||||||
address: factoryAddress,
|
address: factoryAddress,
|
||||||
functionName: "getPair",
|
functionName: "getPair",
|
||||||
args: [token0, token1],
|
args: [token0, token1],
|
||||||
scopeKey: `getPair-${token0}-${token1}-${chainId}`,
|
scopeKey: `getPair-${t0}-${t1}-${chainId}`,
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,7 @@ import { abi as Erc20Abi } from "../../abi/ERC20.json";
|
|||||||
|
|
||||||
import { getTokenAddress } from "../helpers";
|
import { getTokenAddress } from "../helpers";
|
||||||
|
|
||||||
export const useUniswapV2PairReserves = (chainId, rawContractAddress) => {
|
export const useUniswapV2PairReserves = (chainId, contractAddress) => {
|
||||||
const contractAddress = getTokenAddress(chainId, rawContractAddress);
|
|
||||||
|
|
||||||
const { data: pairReserves, refetch: pairReservesRefetch } = useReadContract({
|
const { data: pairReserves, refetch: pairReservesRefetch } = useReadContract({
|
||||||
abi: UniswapV2Pair,
|
abi: UniswapV2Pair,
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
|
|||||||
@ -1,185 +1,111 @@
|
|||||||
|
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
import { UNISWAP_V2_ROUTER, WETH_ADDRESSES } from "../../constants/addresses";
|
import { UNISWAP_V2_ROUTER } from "../../constants/addresses";
|
||||||
import { abi as RouterAbi } from "../../abi/UniswapV2Router.json";
|
import { abi as RouterAbi } from "../../abi/UniswapV2Router.json";
|
||||||
import { getTokenAddress, executeOnChainTransaction } from "../helpers";
|
import { getTokenAddress } from "../helpers";
|
||||||
|
|
||||||
const swapMessages = {
|
import { config } from "../../config";
|
||||||
replacedMsg: "Swap transaction was replaced. Wait for inclusion please.",
|
|
||||||
successMsg: "Swap executed successfully! Wait for balances update.",
|
|
||||||
errorMsg: "Swap tokens failed. Check logs for error detalization.",
|
|
||||||
};
|
|
||||||
|
|
||||||
const addMessages = {
|
export const swapExactTokensForTokens = async (
|
||||||
replacedMsg: "Add liquidity transaction was replaced. Wait for inclusion please.",
|
|
||||||
successMsg: "Liquidity added successfully! You should get LP tokens to your wallet.",
|
|
||||||
errorMsg: "Adding liquidity failed. Check logs for error detalization.",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const swapExactETHForTokens = async ({
|
|
||||||
chainId,
|
chainId,
|
||||||
amountADesired,
|
amountDesired,
|
||||||
amountBMin,
|
amountMin,
|
||||||
tokenNameTop,
|
pathRaw,
|
||||||
tokenNameBottom,
|
|
||||||
destination,
|
destination,
|
||||||
address,
|
|
||||||
deadline
|
deadline
|
||||||
}) => {
|
) => {
|
||||||
|
|
||||||
|
const path = pathRaw.map(tokenName => getTokenAddress(chainId, tokenName));
|
||||||
const args = [
|
const args = [
|
||||||
amountBMin,
|
amountDesired,
|
||||||
[WETH_ADDRESSES[chainId], getTokenAddress(chainId, tokenNameBottom)],
|
amountMin,
|
||||||
|
path,
|
||||||
destination,
|
destination,
|
||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const messages = {
|
||||||
chainId,
|
replacedMsg: "Swap transaction was replaced. Wait for inclusion please.",
|
||||||
args,
|
successMsg: "Swap executed successfully! Wait for balances update.",
|
||||||
abi: RouterAbi,
|
errorMsg: "Swap tokens failed. Check logs for error detalization.",
|
||||||
address: UNISWAP_V2_ROUTER[chainId],
|
};
|
||||||
functionName: "swapExactETHForTokens",
|
|
||||||
account: address,
|
|
||||||
messages: swapMessages,
|
|
||||||
value: amountADesired,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const swapExactTokensForETH = async ({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
amountADesired,
|
"swapExactTokensForTokens",
|
||||||
amountBMin,
|
args,
|
||||||
tokenNameTop,
|
|
||||||
tokenNameBottom,
|
|
||||||
destination,
|
|
||||||
address,
|
|
||||||
deadline
|
|
||||||
}) => {
|
|
||||||
const args = [
|
|
||||||
amountADesired,
|
|
||||||
amountBMin,
|
|
||||||
[getTokenAddress(chainId, tokenNameTop), WETH_ADDRESSES[chainId]],
|
|
||||||
destination,
|
destination,
|
||||||
deadline
|
messages
|
||||||
];
|
);
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
|
||||||
chainId,
|
|
||||||
args,
|
|
||||||
abi: RouterAbi,
|
|
||||||
address: UNISWAP_V2_ROUTER[chainId],
|
|
||||||
functionName: "swapExactTokensForETH",
|
|
||||||
account: address,
|
|
||||||
messages: swapMessages,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const swapExactTokensForTokens = async ({
|
export const addLiquidity = async (
|
||||||
chainId,
|
chainId,
|
||||||
amountADesired,
|
tokenA,
|
||||||
amountBMin,
|
tokenB,
|
||||||
tokenNameTop,
|
|
||||||
tokenNameBottom,
|
|
||||||
destination,
|
|
||||||
address,
|
|
||||||
deadline
|
|
||||||
}) => {
|
|
||||||
const args = [
|
|
||||||
amountADesired,
|
|
||||||
amountBMin,
|
|
||||||
[getTokenAddress(chainId, tokenNameTop), getTokenAddress(chainId, tokenNameBottom)],
|
|
||||||
destination,
|
|
||||||
deadline
|
|
||||||
];
|
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
|
||||||
chainId,
|
|
||||||
args,
|
|
||||||
abi: RouterAbi,
|
|
||||||
address: UNISWAP_V2_ROUTER[chainId],
|
|
||||||
functionName: "swapExactTokensForTokens",
|
|
||||||
account: address,
|
|
||||||
messages: swapMessages,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addLiquidity = async ({
|
|
||||||
chainId,
|
|
||||||
tokenNameTop,
|
|
||||||
tokenNameBottom,
|
|
||||||
amountADesired,
|
amountADesired,
|
||||||
amountBDesired,
|
amountBDesired,
|
||||||
amountAMin,
|
amountAMin,
|
||||||
amountBMin,
|
amountBMin,
|
||||||
address,
|
to,
|
||||||
destination,
|
|
||||||
deadline,
|
deadline,
|
||||||
}) => {
|
) => {
|
||||||
|
const token1 = getTokenAddress(chainId, tokenA);
|
||||||
|
const token2 = getTokenAddress(chainId, tokenB);
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
getTokenAddress(chainId, tokenNameTop),
|
token1,
|
||||||
getTokenAddress(chainId, tokenNameBottom),
|
token2,
|
||||||
amountADesired,
|
amountADesired,
|
||||||
amountBDesired,
|
amountBDesired,
|
||||||
amountAMin,
|
amountAMin,
|
||||||
amountBMin,
|
amountBMin,
|
||||||
destination,
|
to,
|
||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
const messages = {
|
||||||
|
replacedMsg: "Add liquidity transaction was replaced. Wait for inclusion please.",
|
||||||
|
successMsg: "Liquidity added successfully! You should get LP tokens to your wallet.",
|
||||||
|
errorMsg: "Adding liquidity failed. Check logs for error detalization.",
|
||||||
|
};
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
await executeOnChainTransaction(
|
||||||
chainId,
|
chainId,
|
||||||
|
"addLiquidity",
|
||||||
args,
|
args,
|
||||||
abi: RouterAbi,
|
to,
|
||||||
address: UNISWAP_V2_ROUTER[chainId],
|
messages
|
||||||
functionName: "addLiquidity",
|
);
|
||||||
account: address,
|
|
||||||
messages: addMessages,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addLiquidityETH = async ({
|
const executeOnChainTransaction = async (
|
||||||
chainId,
|
chainId,
|
||||||
tokenNameTop,
|
functionName,
|
||||||
tokenNameBottom,
|
args,
|
||||||
amountADesired,
|
account,
|
||||||
amountBDesired,
|
messages
|
||||||
amountAMin,
|
) => {
|
||||||
amountBMin,
|
try {
|
||||||
address,
|
const { request } = await simulateContract(config, {
|
||||||
destination,
|
abi: RouterAbi,
|
||||||
deadline,
|
address: UNISWAP_V2_ROUTER[chainId],
|
||||||
}) => {
|
functionName,
|
||||||
let token = getTokenAddress(chainId, tokenNameTop);
|
args,
|
||||||
let amountTokenDesired = amountADesired;
|
account,
|
||||||
let amountETHDesired = amountBDesired;
|
chainId
|
||||||
let amountTokenMin = amountAMin;
|
});
|
||||||
let amountETHMin = amountBMin;
|
|
||||||
|
|
||||||
if (token === undefined) {
|
const txHash = await writeContract(config, request);
|
||||||
token = getTokenAddress(chainId, tokenNameBottom);
|
await waitForTransactionReceipt(config, {
|
||||||
amountTokenDesired = amountBDesired;
|
hash: txHash,
|
||||||
amountETHDesired = amountADesired;
|
onReplaced: () => toast(messages.replacedMsg),
|
||||||
amountTokenMin = amountBMin;
|
chainId
|
||||||
amountETHMin = amountAMin;
|
});
|
||||||
|
|
||||||
|
toast.success(messages.successMsg);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error(messages.errorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = [
|
|
||||||
token,
|
|
||||||
amountTokenDesired,
|
|
||||||
amountTokenMin,
|
|
||||||
amountETHMin,
|
|
||||||
destination,
|
|
||||||
deadline
|
|
||||||
];
|
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
|
||||||
chainId,
|
|
||||||
args,
|
|
||||||
abi: RouterAbi,
|
|
||||||
address: UNISWAP_V2_ROUTER[chainId],
|
|
||||||
functionName: "addLiquidityETH",
|
|
||||||
account: address,
|
|
||||||
messages: addMessages,
|
|
||||||
value: amountETHDesired,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import { WagmiProvider } from "wagmi";
|
import { WagmiProvider } from "wagmi";
|
||||||
import { config } from "./config";
|
import { config } from "./config";
|
||||||
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
|
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
|
||||||
import { LocalStorageProvider } from "./hooks/localstorage";
|
|
||||||
import { BreakoutModalProvider } from "./hooks/breakoutModal";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
@ -26,11 +24,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
|||||||
<StyledEngineProvider injectFirst>
|
<StyledEngineProvider injectFirst>
|
||||||
<UnstableProviderProvider>
|
<UnstableProviderProvider>
|
||||||
<MetadataProviderProvider>
|
<MetadataProviderProvider>
|
||||||
<LocalStorageProvider>
|
<App />
|
||||||
<BreakoutModalProvider>
|
|
||||||
<App />
|
|
||||||
</BreakoutModalProvider>
|
|
||||||
</LocalStorageProvider>
|
|
||||||
</MetadataProviderProvider>
|
</MetadataProviderProvider>
|
||||||
</UnstableProviderProvider>
|
</UnstableProviderProvider>
|
||||||
</StyledEngineProvider>
|
</StyledEngineProvider>
|
||||||
|
|||||||