Compare commits
No commits in common. "787b7e632254eb326e53ced6ef82b23bfd982964" and "5bb5b7d7e061d5da6e04ec8fa103409d09a63f74" have entirely different histories.
787b7e6322
...
5bb5b7d7e0
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ghost-dao-interface",
|
||||
"private": true,
|
||||
"version": "0.7.40",
|
||||
"version": "0.7.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
36
src/App.jsx
36
src/App.jsx
@ -15,17 +15,16 @@ import {
|
||||
useSwitchChain,
|
||||
injected
|
||||
} from "wagmi";
|
||||
import { watchChainId } from '@wagmi/core'
|
||||
|
||||
import Messages from "./components/Messages/Messages";
|
||||
import NavDrawer from "./components/Sidebar/NavDrawer";
|
||||
import Sidebar from "./components/Sidebar/Sidebar";
|
||||
import TopBar from "./components/TopBar/TopBar";
|
||||
import BreakoutModal from "./containers/Breakout/BreakoutModal";
|
||||
|
||||
import { shouldTriggerSafetyCheck } from "./helpers";
|
||||
import { isNetworkAvailable } from "./constants";
|
||||
import { isNetworkAvailable, isGovernanceAvailable } from "./constants";
|
||||
import useTheme from "./hooks/useTheme";
|
||||
import { useUnstableProvider } from "./hooks/ghost";
|
||||
import { dark as darkTheme } from "./themes/dark.js";
|
||||
import { girth as gTheme } from "./themes/girth.js";
|
||||
import { light as lightTheme } from "./themes/light.js";
|
||||
@ -122,9 +121,27 @@ function App() {
|
||||
const provider = usePublicClient();
|
||||
const chainId = useChainId();
|
||||
|
||||
const isSmallerScreen = useMediaQuery("(max-width: 1047px)");
|
||||
const isSmallerScreen = useMediaQuery("(max-width: 1130px)");
|
||||
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(() => {
|
||||
if (shouldTriggerSafetyCheck()) {
|
||||
toast.success("Safety Check: Always verify you're on app.dao.ghostchain.io!", { duration: 5000 });
|
||||
@ -133,16 +150,16 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && chainId !== addressChainId) {
|
||||
if (wrongNetworkToastId === null) {
|
||||
const toastId = toast.loading("You are connected to wrong network. Use one of the deployed networks please.", {
|
||||
position: 'bottom-right'
|
||||
});
|
||||
setWrongNetworkToastId(toastId);
|
||||
}
|
||||
} else {
|
||||
if (wrongNetworkToastId) {
|
||||
toast.dismiss(wrongNetworkToastId);
|
||||
setWrongNetworkToastId(null);
|
||||
}
|
||||
}
|
||||
}, [chainId, addressChainId, isConnected, wrongNetworkToastId])
|
||||
|
||||
useEffect(() => {
|
||||
@ -192,7 +209,6 @@ function App() {
|
||||
|
||||
<div className={`${classes.content} ${isSmallerScreen && classes.contentShift}`}>
|
||||
<Suspense fallback={<div></div>}>
|
||||
<BreakoutModal chainId={chainId} address={address} />
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to={chainExists ? `/${chains.at(0).name.toLowerCase()}/dashboard` : "/empty"} />} />
|
||||
{chainExists &&
|
||||
@ -203,9 +219,9 @@ function App() {
|
||||
<Route path="stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="dex/:name" element={<Dex config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||
<Route path="governance" element={<Governance 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} />} />
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <Route path="governance/create" element={<NewProposal config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />}
|
||||
</Route>
|
||||
}
|
||||
<Route path="/empty" element={<NotFound
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@ import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
const PageTitle = ({ name, subtitle, noMargin }) => {
|
||||
const theme = useTheme();
|
||||
const mobile = useMediaQuery(theme.breakpoints.down("700"));
|
||||
const mobile = useMediaQuery(theme.breakpoints.down("900"));
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMemo, useEffect } from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import "./Sidebar.scss";
|
||||
|
||||
@ -17,7 +17,6 @@ import {
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { useSwitchChain } from "wagmi";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
@ -41,7 +40,7 @@ import BondIcon from "../Icon/BondIcon";
|
||||
import StakeIcon from "../Icon/StakeIcon";
|
||||
import WrapIcon from "../Icon/WrapIcon";
|
||||
|
||||
import { isNetworkAvailable } from "../../constants";
|
||||
import { isNetworkAvailable, isGovernanceAvailable } from "../../constants";
|
||||
import { AVAILABLE_DEXES } from "../../constants/dexes";
|
||||
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
|
||||
import { ECOSYSTEM } from "../../constants/ecosystem";
|
||||
@ -80,17 +79,12 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.send({ hitType: "pageview", page: "/sidebar" });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Paper className="dapp-sidebar">
|
||||
<Box className="dapp-sidebar-inner" display="flex" justifyContent="space-between" flexDirection="column">
|
||||
@ -150,10 +144,10 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
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">
|
||||
{ghostBonds.length > 0 && <Box width="180px" mb="10px" ml="auto">
|
||||
<Typography component="span" variant="body2">Bond Discounts</Typography>
|
||||
</Box>}
|
||||
{sortedGhostBonds.map((bond, index) => {
|
||||
{sortBondsByDiscount(ghostBonds).map((bond, index) => {
|
||||
return (
|
||||
<Link
|
||||
component={NavLink}
|
||||
@ -174,7 +168,7 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
variant="body2"
|
||||
>
|
||||
{bond.displayName}
|
||||
{bond.isSoldOut
|
||||
{bond.soldOut
|
||||
? <Chip label="Sold Out" template="darkGray" />
|
||||
: <BondDiscount discount={bond.discount} />
|
||||
}
|
||||
@ -187,7 +181,7 @@ const NavContent = ({ chainId, addressChainId }) => {
|
||||
}
|
||||
/>
|
||||
<NavItem icon={ForkRightIcon} label={`${bridgeNumbers} Stake\u00B2`} to={`/${chainName}/bridge`} />
|
||||
<NavItem icon={GavelIcon} label={`Governance`} to={`/${chainName}/governance`} />
|
||||
{isGovernanceAvailable(chainId, addressChainId) && <NavItem icon={GavelIcon} label={`Governance`} to={`/${chainName}/governance`} />}
|
||||
<Box className="menu-divider">
|
||||
<Divider />
|
||||
</Box>
|
||||
|
||||
@ -86,8 +86,8 @@ const Token = ({ chainTokenName, name, viewBox = "0 0 260 260", fontSize = "larg
|
||||
position: "absolute",
|
||||
marginLeft: "70%",
|
||||
marginTop: "45%",
|
||||
width: "45%",
|
||||
height: "45%",
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
border: "1px solid #fff",
|
||||
borderRadius: "100%"
|
||||
}}
|
||||
|
||||
@ -4,7 +4,7 @@ import { parseKnownToken } from "../../components/Token/Token";
|
||||
import { useUnstableProvider } from "../../hooks/ghost";
|
||||
import { PrimaryButton } from "../Button"
|
||||
|
||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
||||
const GHOST_CONNECT = 'https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases';
|
||||
|
||||
function GhostChainSelect({ small }) {
|
||||
const { providerDetail, isConnected } = useUnstableProvider();
|
||||
|
||||
@ -71,7 +71,7 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
|
||||
}
|
||||
|
||||
return(
|
||||
<FormControl sx={{ width: small ? "100px" : "155px" }}>
|
||||
<FormControl sx={{ width: small ? "auto" : "155px" }}>
|
||||
<Select
|
||||
labelId="network-select-helper-label"
|
||||
id="network-select-helper"
|
||||
|
||||
@ -2,6 +2,7 @@ import { Box, Button, SvgIcon, useMediaQuery, useTheme } from "@mui/material";
|
||||
import MenuIcon from "../../assets/icons/hamburger.svg?react";
|
||||
import Wallet from "./Wallet"
|
||||
import SelectNetwork from "./SelectNetwork";
|
||||
import GhostChainSelect from "./GhostChainSelect";
|
||||
|
||||
const PREFIX = "TopBar";
|
||||
|
||||
@ -22,8 +23,8 @@ function TopBar({
|
||||
setWrongNetworkToastId
|
||||
}) {
|
||||
const themeColor = useTheme();
|
||||
const desktop = useMediaQuery(themeColor.breakpoints.up(1048));
|
||||
const small = useMediaQuery(themeColor.breakpoints.down(400));
|
||||
const desktop = useMediaQuery(themeColor.breakpoints.up(1130));
|
||||
const small = useMediaQuery(themeColor.breakpoints.down(600));
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@ -37,9 +38,10 @@ function TopBar({
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
width={small ? "calc(100vw - 78px)" : "320px"}
|
||||
width={small ? "calc(100vw - 78px)" : "500px"}
|
||||
height="40px"
|
||||
>
|
||||
<GhostChainSelect small={small} />
|
||||
<SelectNetwork
|
||||
wrongNetworkToastId={wrongNetworkToastId}
|
||||
setWrongNetworkToastId={setWrongNetworkToastId}
|
||||
|
||||
@ -205,10 +205,7 @@ export const useWallet = (chainId, userAddress) => {
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const nativeSymbol = useMemo(() => {
|
||||
return config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
}, [config]);
|
||||
|
||||
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
|
||||
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
|
||||
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||
|
||||
@ -11,7 +11,7 @@ const WalletButton = ({ openWallet, connect }) => {
|
||||
const { isConnected, chain } = useAccount();
|
||||
const theme = useTheme();
|
||||
const onClick = isConnected ? openWallet : connect;
|
||||
const label = `${isConnected ? "Open" : "Connect"} Wallet`;
|
||||
const label = isConnected ? "Wallet" : `Connect`;
|
||||
return (
|
||||
<Button
|
||||
id="fatso-menu-button"
|
||||
|
||||
@ -4,6 +4,22 @@ export enum NetworkId {
|
||||
TESTNET_MORDOR = 63,
|
||||
}
|
||||
|
||||
export const isGovernanceAvailable = (chainId, addressChainId) => {
|
||||
chainId = addressChainId ? addressChainId : chainId;
|
||||
let exists = false;
|
||||
switch (chainId) {
|
||||
case 11155111:
|
||||
exists = true
|
||||
break;
|
||||
case 63:
|
||||
exists = true
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
export const isNetworkAvailable = (chainId, addressChainId) => {
|
||||
chainId = addressChainId ? addressChainId : chainId;
|
||||
let exists = false;
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { NetworkId } from "../constants";
|
||||
|
||||
export const GHOST_CONNECT = "https://connect.ghostchain.io/";
|
||||
|
||||
export const ECOSYSTEM = [
|
||||
{
|
||||
name: "GHOST chain",
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Box, Typography, Checkbox, FormControlLabel } from "@mui/material";
|
||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||
import { useState } from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { styled, useTheme } from "@mui/material/styles";
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
@ -11,14 +10,13 @@ import Metric from "../../../components/Metric/Metric";
|
||||
import Modal from "../../../components/Modal/Modal";
|
||||
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||
import DataRow from "../../../components/DataRow/DataRow";
|
||||
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
|
||||
import { PrimaryButton } from "../../../components/Button";
|
||||
|
||||
import BondDiscount from "./BondDiscount";
|
||||
import BondVesting from "./BondVesting";
|
||||
import BondSlippage from "./BondSlippage";
|
||||
|
||||
import { purchaseBond } from "../../../hooks/bonds";
|
||||
import { useWarmupLength } from "../../../hooks/staking";
|
||||
|
||||
const StyledBox = styled(Box, {
|
||||
shouldForwardProp: prop => prop !== "template",
|
||||
@ -45,57 +43,8 @@ const BondConfirmModal = ({
|
||||
handleConfirmClose
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { warmupLength, warmupExists: needsWarmup } = useWarmupLength(chainId);
|
||||
const [acknowledgedWarmup, setAcknowledgedWarmup] = 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 () => {
|
||||
setIsPending(true);
|
||||
|
||||
@ -119,7 +68,7 @@ const BondConfirmModal = ({
|
||||
});
|
||||
|
||||
setIsPending(false);
|
||||
handleConfirmCloseMaster();
|
||||
handleConfirmClose();
|
||||
}
|
||||
|
||||
return (
|
||||
@ -135,7 +84,7 @@ const BondConfirmModal = ({
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
onClose={!isPending && handleConfirmCloseMaster}
|
||||
onClose={!isPending && handleConfirmClose}
|
||||
topLeft={<GhostStyledIcon viewBox="0 0 23 23" component={SettingsIcon} style={{ cursor: "pointer" }} onClick={handleSettingsOpen} />}
|
||||
>
|
||||
<>
|
||||
@ -161,13 +110,9 @@ const BondConfirmModal = ({
|
||||
<DataRow title="ROI" balance={<BondDiscount discount={bond.discount} textOnly />} />
|
||||
<DataRow title="Bond Slippage" balance={<BondSlippage slippage={slippage} textOnly />} />
|
||||
<DataRow title="Vesting Term" balance={<BondVesting vesting={bond.vesting} />} />
|
||||
{!acknowledgedWarmup && <Box>
|
||||
<Box mt="21px" mb="21px" borderTop={`1px solid ${theme.colors.gray[500]}`}></Box>
|
||||
<NeedsWarmupDetails />
|
||||
</Box>}
|
||||
{acknowledgedWarmup && <PrimaryButton fullWidth onClick={onSubmit} disabled={isPending} loading={isPending}>
|
||||
<PrimaryButton fullWidth onClick={onSubmit} disabled={isPending} loading={isPending}>
|
||||
{isPending ? "Bonding..." : "Confirm Bond Purchase"}
|
||||
</PrimaryButton>}
|
||||
</PrimaryButton>
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
||||
import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||
@ -66,7 +66,7 @@ const BondInputArea = ({
|
||||
refetch();
|
||||
}
|
||||
|
||||
const setMax = useCallback(() => {
|
||||
const setMax = () => {
|
||||
if (!balance) return;
|
||||
|
||||
if (bond.capacity.inQuoteToken.lt(bond.maxPayout.inQuoteToken)) {
|
||||
@ -82,17 +82,12 @@ const BondInputArea = ({
|
||||
? bond.maxPayout.inQuoteToken.toString() // Payout is the smallest
|
||||
: 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.capacity.inBaseToken
|
||||
), [bond]);
|
||||
|
||||
const incorrectInputAmount = useMemo(() => {
|
||||
if (!balance) return false;
|
||||
return balance.lt(parsedAmount) || baseTokenString.lt(amountInBaseToken);
|
||||
}, [amountInBaseToken, baseTokenString, balance, parsedAmount]);
|
||||
);
|
||||
|
||||
return (
|
||||
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
|
||||
@ -108,7 +103,7 @@ const BondInputArea = ({
|
||||
token={<TokenStack tokens={bond.quoteToken.icons} sx={{ fontSize: "21px" }} />}
|
||||
tokenName={preparedQuoteToken.name}
|
||||
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
|
||||
endString="Max"
|
||||
endString={preparedQuoteToken.address && "Max"}
|
||||
endStringOnClick={setMax}
|
||||
value={amount}
|
||||
onChange={event => setAmount(event.currentTarget.value)}
|
||||
@ -163,7 +158,7 @@ const BondInputArea = ({
|
||||
)}
|
||||
<PrimaryButton
|
||||
fullWidth
|
||||
disabled={incorrectInputAmount || bond.isSoldOut || (showDisclaimer && !checked)}
|
||||
disabled={bond.isSoldOut || (showDisclaimer && !checked)}
|
||||
onClick={() => setConfirmOpen(true)}
|
||||
>
|
||||
Bond
|
||||
|
||||
@ -13,27 +13,23 @@ import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||
|
||||
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||
import { isNetworkLegacy } from "../../../constants";
|
||||
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { formatCurrency } from "../../../helpers";
|
||||
|
||||
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 { useGhstPrice } from "../../../hooks/prices";
|
||||
import { useBreakoutModal } from "../../../hooks/breakoutModal";
|
||||
|
||||
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
const isSmallScreen = useScreenSize("md");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [pendingIndexes, setPendingIndexes] = useState([]);
|
||||
const [isWarmup, setIsWapmup] = useState(false);
|
||||
const [isPreClaimConfirmed, setPreClaimConfirmed] = useState(false);
|
||||
const [isPayoutGhst, _] = useState(true);
|
||||
|
||||
const ghstPrice = useGhstPrice(chainId);
|
||||
const { breakoutFromBonding } = useBreakoutModal();
|
||||
const { epoch } = useEpoch(chainId);
|
||||
const { warmupExists } = useWarmupLength(chainId);
|
||||
const { warmupInfo } = useWarmupInfo(chainId, BOND_DEPOSITORY_ADDRESSES[chainId]);
|
||||
@ -55,38 +51,21 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
);
|
||||
|
||||
const onSubmit = async (indexes) => {
|
||||
setIsPending(true);
|
||||
setPendingIndexes(indexes);
|
||||
|
||||
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 {
|
||||
const warmupLeft = warmupInfo.expiry - epoch.number;
|
||||
const amountRaw = notes
|
||||
.filter(note => indexes.includes(note.id))
|
||||
.reduce((sum, note) => sum + note.payout._value, 0n);
|
||||
const amount = new DecimalBigNumber(amountRaw, 18);
|
||||
|
||||
const toExecute = async (receiver) => {
|
||||
const txHash = await forceRedeem({ chainId, user: address, receiver, indexes });
|
||||
return txHash;
|
||||
}
|
||||
|
||||
breakoutFromBonding({ defaultFunction, toExecute, amount, warmupLeft })
|
||||
}
|
||||
setPendingIndexes([]);
|
||||
setIsPending(true);
|
||||
await redeem({
|
||||
chainId,
|
||||
user: address,
|
||||
isGhst: isPayoutGhst,
|
||||
indexes
|
||||
});
|
||||
await notesRefetch();
|
||||
setIsPending(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -180,10 +159,9 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
<TertiaryButton
|
||||
fullWidth
|
||||
disabled={isPending || secondsTo < note.matured}
|
||||
loading={isPending && pendingIndexes.includes(note.id)}
|
||||
onClick={() => onSubmit([note.id])}
|
||||
>
|
||||
{isPending && pendingIndexes.includes(note.id) ? "Claiming" : "Claim"}
|
||||
Claim
|
||||
</TertiaryButton>
|
||||
</Box>
|
||||
</Box>
|
||||
@ -240,10 +218,9 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
|
||||
<TertiaryButton
|
||||
fullWidth
|
||||
disabled={isPending || secondsTo < note.matured}
|
||||
loading={isPending && pendingIndexes.includes(note.id)}
|
||||
onClick={() => onSubmit([note.id])}
|
||||
>
|
||||
{isPending && pendingIndexes.includes(note.id) ? "Claiming" : "Claim"}
|
||||
Claim
|
||||
</TertiaryButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@ -56,14 +56,14 @@ const WarmupConfirmModal = ({
|
||||
? <FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="acknowledge-warm-up"
|
||||
data-testid="acknowledge-warmup"
|
||||
checked={isChecked}
|
||||
onChange={event => setIsChecked(event.target.checked)}
|
||||
icon={<CheckBoxOutlineBlank 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.`
|
||||
}
|
||||
|
||||
@ -1,506 +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, useCirculatingSupply } 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 [isPending, setIsPending] = useState(false);
|
||||
|
||||
const { epoch } = useEpoch(chainId);
|
||||
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
|
||||
const circulatingSupply = useCirculatingSupply(chainId, "STNK");
|
||||
const gatekeepedApy = useGatekeeperApy(chainId);
|
||||
|
||||
const { isExtensionMissing } = useUnstableProvider();
|
||||
|
||||
const getConnect = () => {
|
||||
window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer');
|
||||
closeModal();
|
||||
}
|
||||
|
||||
const apyInner = useMemo(() => {
|
||||
let apy = Infinity;
|
||||
if (circulatingSupply._value > 0n) {
|
||||
const value = epoch.distribute.div(circulatingSupply);
|
||||
apy = 100 * (Math.pow(1 + parseFloat(value.toString()), 1095) - 1);
|
||||
if (apy === 0) apy = Infinity;
|
||||
}
|
||||
return apy;
|
||||
}, [circulatingSupply, epoch]);
|
||||
|
||||
const callDefaultFunction = useCallback(async () => {
|
||||
setIsPending(true);
|
||||
await defaultFunction()();
|
||||
setIsPending(false);
|
||||
closeModal();
|
||||
}, [defaultFunction]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>{warmupPeriod <= 0
|
||||
? `You've 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(apyInner * gatekeepedApy, 2)}% APY</Typography>
|
||||
</Box>
|
||||
|
||||
<PrimaryButton
|
||||
disabled={isPending || gatekeeperAddress === EMPTY_ADDRESS}
|
||||
onClick={isExtensionMissing ? getConnect : goNext}
|
||||
fullWidth
|
||||
>
|
||||
{isExtensionMissing ? "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: 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;
|
||||
@ -13,7 +13,7 @@ import { decodeAddress } from "@polkadot/util-crypto";
|
||||
import { fromHex } from "@polkadot-api/utils";
|
||||
import { getBlockNumber } from "@wagmi/core";
|
||||
import { useTransaction } from "wagmi";
|
||||
import { keccak256, decodeFunctionData } from "viem";
|
||||
import { keccak256 } from "viem";
|
||||
import { u32, u64, u128 } from "scale-ts";
|
||||
|
||||
import PendingActionsIcon from '@mui/icons-material/PendingActions';
|
||||
@ -23,7 +23,6 @@ import PageTitle from "../../components/PageTitle/PageTitle";
|
||||
import Paper from "../../components/Paper/Paper";
|
||||
import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
|
||||
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
|
||||
import { networkAvgBlockSpeed } from "../../constants";
|
||||
import { timeConverter } from "../../helpers";
|
||||
|
||||
@ -48,7 +47,6 @@ import {
|
||||
useEraIndex,
|
||||
} from "../../hooks/ghost";
|
||||
import { useLocalStorage } from "../../hooks/localstorage";
|
||||
import { useBreakoutModal } from "../../hooks/breakoutModal";
|
||||
|
||||
import { ValidatorTable } from "./ValidatorTable";
|
||||
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
|
||||
@ -63,12 +61,12 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
||||
|
||||
const [bridgeModalOpen, setBridgeModalOpen] = useState(false);
|
||||
const [isConfirmed, setIsConfirmed] = useState(false);
|
||||
const [activeTxIndex, setActiveTxIndex] = useState(-1);
|
||||
const [blockNumber, setBlockNumber] = useState(0n);
|
||||
const [bridgeAction, setBridgeAction] = useState(true);
|
||||
const [currentTime, setCurrentTime] = useState(Date.now());
|
||||
|
||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
||||
const { activeTxIndex, setActiveTxIndex } = useBreakoutModal();
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
|
||||
@ -102,7 +100,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
||||
});
|
||||
|
||||
const hashedArguments = useMemo(() => {
|
||||
if (!watchTransaction) return undefined;
|
||||
if (!watchTransaction) return undefined
|
||||
|
||||
const networkIdEncoded = u64.enc(BigInt(chainId));
|
||||
const amountEncoded = u128.enc(BigInt(watchTransaction.amount));
|
||||
@ -184,8 +182,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
||||
return sum + countOnesInBigInt(bigIntValue);
|
||||
}, 0);
|
||||
|
||||
const storedBlockNumber = watchTransactionInfo ? Number(watchTransactionInfo.blockNumber) : 0;
|
||||
const finalization = Math.max(0, (finalityDelay + storedBlockNumber) - Number(blockNumber));
|
||||
const finalization = Math.max(0, (finalityDelay + watchTransaction.blockNumber) - Number(blockNumber));
|
||||
const applaused = transactionApplaused?.finalized ?? false;
|
||||
const clappedAmount = transactionApplaused?.clapped_amount ?? 0n;
|
||||
const clappedPercentage = clappedAmount * 100n / (totalStakedAmount ?? 1n);
|
||||
@ -201,10 +198,9 @@ const Bridge = ({ chainId, address, config, connect }) => {
|
||||
clapsPercentage,
|
||||
}
|
||||
}, [
|
||||
watchTransaction,
|
||||
watchTransactionInfo,
|
||||
transactionApplaused,
|
||||
finalityDelay,
|
||||
watchTransaction,
|
||||
blockNumber,
|
||||
totalStakedAmount,
|
||||
authorities
|
||||
|
||||
@ -246,7 +246,7 @@ export const BridgeCardAction = ({
|
||||
loading={isPending}
|
||||
onClick={() => ghostOrConnect()}
|
||||
>
|
||||
{address === "" ? "Connect" : isPending ? "Bridging..." : "Bridge" }
|
||||
{address === "" ? "Connect" : "Bridge" }
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
)
|
||||
@ -307,7 +307,7 @@ export const BridgeCardHistory = ({
|
||||
<Box display="flex" flexDirection="column" justifyContent="center">
|
||||
<Typography variant="caption">
|
||||
{formatCurrency(
|
||||
new DecimalBigNumber(BigInt(obj.amount ?? "0"), 18).toString(),
|
||||
new DecimalBigNumber(BigInt(obj.amount), 18).toString(),
|
||||
isSemiSmallScreen ? 3 : 8,
|
||||
ghstSymbol
|
||||
)}
|
||||
|
||||
@ -24,7 +24,6 @@ import { PrimaryButton, TertiaryButton, SecondaryButton } from "../../components
|
||||
import { formatCurrency } from "../../helpers";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
|
||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
||||
|
||||
export const BridgeModal = ({
|
||||
providerDetail,
|
||||
@ -114,8 +113,15 @@ export const BridgeModal = ({
|
||||
>
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
sx={{ marginTop: "0 !important", marginBottom: "0 !important" }}
|
||||
onClick={() => window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}
|
||||
sx={{
|
||||
marginTop: "0 !important",
|
||||
marginBottom: "0 !important"
|
||||
}}
|
||||
onClick={() => window.open(
|
||||
'https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases',
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
)}
|
||||
>
|
||||
Get GHOST Connect
|
||||
</SecondaryButton>
|
||||
@ -369,7 +375,7 @@ export const BridgeModal = ({
|
||||
<Typography variant="body2">Bridged Amount:</Typography>
|
||||
<Typography variant="body2">{formatCurrency(
|
||||
new DecimalBigNumber(
|
||||
BigInt(currentRecord && currentRecord.amount ? currentRecord.amount : "0"),
|
||||
BigInt(currentRecord ? currentRecord.amount : "0"),
|
||||
18
|
||||
).toString(), 9, ghstSymbol)
|
||||
}</Typography>
|
||||
|
||||
@ -25,8 +25,6 @@ import GhostStyledIcon from "../../components/Icon/GhostIcon";
|
||||
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||
import { PrimaryButton } from "../../components/Button";
|
||||
|
||||
import { GHOST_CONNECT } from "../../constants/ecosystem";
|
||||
|
||||
export const ValidatorTable = ({
|
||||
currentTime,
|
||||
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="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>
|
||||
<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
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
|
||||
@ -74,7 +74,7 @@ const Dex = ({ chainId, address, connect, config }) => {
|
||||
const chainSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
|
||||
if (chainSymbol) return chainSymbol;
|
||||
return "WTF";
|
||||
}, [config, chainId])
|
||||
}, [config])
|
||||
|
||||
const tokenNameTop = useMemo(() => {
|
||||
if (chainSymbol && tokenAddressTop === EMPTY_ADDRESS) {
|
||||
|
||||
@ -317,9 +317,9 @@ const PoolContainer = ({
|
||||
"Connect"
|
||||
:
|
||||
pairAddress === "0x0000000000000000000000000000000000000000" ?
|
||||
isPending ? "Creating Pool..." : "Create Pool"
|
||||
"Create Pool"
|
||||
:
|
||||
isPending ? "Adding Liquidity..." : "Add Liquidity"
|
||||
"Add Liquidity"
|
||||
}
|
||||
</SecondaryButton>
|
||||
</TokenAllowanceGuard>
|
||||
|
||||
@ -147,12 +147,8 @@ const SwapContainer = ({
|
||||
if (isWrapping) text = "Wrap";
|
||||
else if (isUnwrapping) text = "Unwrap";
|
||||
else if (pairAddress === EMPTY_ADDRESS) text = "Create Pool";
|
||||
|
||||
if (isPending) text = `${text}ping...`
|
||||
if (pairAddress === EMPTY_ADDRESS && isPending) text = "Creating Pool..."
|
||||
|
||||
return text;
|
||||
}, [isPending, isWrapping, isUnwrapping, pairAddress]);
|
||||
}, [isWrapping, isUnwrapping, pairAddress]);
|
||||
|
||||
const swapTokens = async () => {
|
||||
setIsPending(true);
|
||||
|
||||
@ -178,7 +178,6 @@ const NewProposal = ({ config, address, connect, chainId }) => {
|
||||
isPending
|
||||
}
|
||||
fullWidth
|
||||
loading={isPending}
|
||||
onClick={() => submitProposal()}
|
||||
>
|
||||
{isPending ? "Submitting..." : "Submit Proposal"}
|
||||
|
||||
@ -80,9 +80,7 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
const { id } = useParams();
|
||||
const proposalId = BigInt(id);
|
||||
|
||||
const [isPendingVote, setIsPendingVote] = useState(-1);
|
||||
const [isPendingExecute, setIsPendingExecute] = useState(false);
|
||||
const [isPendingRelease, setIsPendingRelease] = useState(false);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
||||
|
||||
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||
@ -152,12 +150,8 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
return url;
|
||||
}, [proposalProposer, config]);
|
||||
|
||||
const isPending = useMemo(() => {
|
||||
return isPendingExecute || isPendingRelease || isPendingVote > -1;
|
||||
}, [isPendingExecute, isPendingRelease, isPendingVote]);
|
||||
|
||||
const handleVote = useCallback(async (support) => {
|
||||
setIsPendingVote(support);
|
||||
setIsPending(true);
|
||||
const result = await castVote(chainId, address, proposalId, support);
|
||||
|
||||
if (result) {
|
||||
@ -167,21 +161,21 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
|
||||
await queryClient.invalidateQueries();
|
||||
}
|
||||
setIsPendingVote(-1);
|
||||
setIsPending(false);
|
||||
}, [chainId, address, proposalId]);
|
||||
|
||||
const handleExecute = async () => {
|
||||
setIsPendingExecute(true);
|
||||
setIsPending(true);
|
||||
await executeProposal(chainId, address, proposalId);
|
||||
await queryClient.invalidateQueries();
|
||||
setIsPendingExecute(false);
|
||||
setIsPending(false);
|
||||
}
|
||||
|
||||
const handleRelease = async () => {
|
||||
setIsPendingRelease(true);
|
||||
setIsPending(true);
|
||||
await releaseLocked(chainId, address, proposalId);
|
||||
await queryClient.invalidateQueries();
|
||||
setIsPendingRelease(false);
|
||||
setIsPending(false);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -298,18 +292,16 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
||||
loading={isPendingVote === 1}
|
||||
onClick={() => handleVote(1)}
|
||||
>
|
||||
{isPendingVote === 1 ? "Voting For..." : "For"}
|
||||
{isPending ? "Voting..." : "For"}
|
||||
</SecondaryButton>
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
disabled={proposalState !== 1 || pastVotes?._value === 0n || isPending}
|
||||
loading={isPendingVote === 0}
|
||||
onClick={() => handleVote(0)}
|
||||
>
|
||||
{isPendingVote === 0 ? "Voting Against..." : "Against"}
|
||||
{isPending ? "Voting..." : "Against"}
|
||||
</SecondaryButton>
|
||||
</>
|
||||
: <SecondaryButton
|
||||
@ -345,8 +337,6 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
chainId={chainId}
|
||||
proposalId={id}
|
||||
isPending={isPending}
|
||||
isPendingExecute={isPendingExecute}
|
||||
isPendingRelease={isPendingRelease}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
@ -402,20 +392,7 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const VotingTimeline = ({
|
||||
connect,
|
||||
handleExecute,
|
||||
handleRelease,
|
||||
proposalLocked,
|
||||
proposalId,
|
||||
chainId,
|
||||
state,
|
||||
address,
|
||||
isProposer,
|
||||
isPending,
|
||||
isPendingExecute,
|
||||
isPendingRelease,
|
||||
}) => {
|
||||
const VotingTimeline = ({ connect, handleExecute, handleRelease, proposalLocked, proposalId, chainId, state, address, isProposer, isPending }) => {
|
||||
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
||||
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
||||
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
||||
@ -437,15 +414,13 @@ const VotingTimeline = ({
|
||||
<Box width="100%" display="flex" gap="10px">
|
||||
{(isProposer && (proposalLocked?._value ?? 0n) > 0n) && <SecondaryButton
|
||||
fullWidth
|
||||
loading={isPendingRelease}
|
||||
disabled={isPending || state < 2}
|
||||
onClick={() => address === "" ? connect() : handleRelease()}
|
||||
>
|
||||
{address === "" ? "Connect" : isPendingRelease ? "Releasing..." : "Release"}
|
||||
{address === "" ? "Connect" : "Release"}
|
||||
</SecondaryButton>}
|
||||
<SecondaryButton
|
||||
fullWidth
|
||||
loading={isPending}
|
||||
disabled={isPending || state !== 4}
|
||||
onClick={() => address === "" ? connect() : handleExecute()}
|
||||
>
|
||||
@ -453,7 +428,7 @@ const VotingTimeline = ({
|
||||
? "Connect"
|
||||
: state !== 4
|
||||
? convertStatusToLabel(state)
|
||||
: isPendingExecute ? "Executing..." : "Execute"
|
||||
: isPending ? "Executing..." : "Execute"
|
||||
}
|
||||
</SecondaryButton>
|
||||
</Box>
|
||||
|
||||
@ -30,6 +30,8 @@ const ClaimConfirmationModal = (props) => {
|
||||
props.ghstSymbol
|
||||
);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown action")
|
||||
}
|
||||
|
||||
setIsPending(false);
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
Skeleton,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
|
||||
@ -26,10 +26,9 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
||||
import { formatNumber, formatCurrency } from "../../../helpers";
|
||||
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 { useGhstPrice, useStnkPrice } from "../../../hooks/prices";
|
||||
import { useBreakoutModal } from "../../../hooks/breakoutModal";
|
||||
import { isNetworkLegacy } from "../../../constants";
|
||||
|
||||
import ClaimConfirmationModal from "./ClaimConfirmationModal";
|
||||
@ -53,8 +52,7 @@ const StyledTableHeader = styled(TableHead)(({ theme }) => ({
|
||||
export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
const isSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||
|
||||
const { breakoutFromStaking } = useBreakoutModal();
|
||||
const [confirmationModalOpen, setConfirmationModalOpenInner] = useState(false);
|
||||
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
|
||||
const [isPayoutGhst, _] = useState(true);
|
||||
|
||||
const ghstPrice = useGhstPrice(chainId);
|
||||
@ -76,45 +74,22 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
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 = () => {
|
||||
setConfirmationModalOpenInner(false);
|
||||
setConfirmationModalOpen(false);
|
||||
claimRefetch();
|
||||
currentIndexRefetch();
|
||||
}
|
||||
|
||||
if (claim.shares === 0n) return <></>;
|
||||
|
||||
const warmupTooltip = `Your claim earns rebases during warm-up. You can emergency withdraw, but this forfeits the rebases`;
|
||||
const warmupTooltip = `Your claim earns rebases during warmup. You can emergency withdraw, but this forfeits the rebases`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ClaimConfirmationModal
|
||||
open={confirmationModalOpen}
|
||||
onClose={() => closeConfirmationModal()}
|
||||
chainid={chainId}
|
||||
receiver={address}
|
||||
receiveAmount={claim.expiry > epoch.number ? claim.deposit : claimableBalance}
|
||||
outputToken={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
|
||||
@ -148,7 +123,6 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
claim={claim}
|
||||
epoch={epoch}
|
||||
isClaimable={claim.expiry > epoch.number}
|
||||
isLegacy={isNetworkLegacy(chainId)}
|
||||
stnkSymbol={stnkSymbol}
|
||||
ghstSymbol={ghstSymbol}
|
||||
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
|
||||
@ -170,7 +144,6 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
|
||||
claim={claim}
|
||||
epoch={epoch}
|
||||
isClaimable={claim.expiry > epoch.number}
|
||||
isLegacy={isNetworkLegacy(chainId)}
|
||||
stnkSymbol={stnkSymbol}
|
||||
ghstSymbol={ghstSymbol}
|
||||
tokenPrice={isPayoutGhst ? ghstPrice : stnkPrice}
|
||||
@ -192,8 +165,7 @@ const ClaimInfo = ({
|
||||
isPayoutGhst,
|
||||
stnkSymbol,
|
||||
ghstSymbol,
|
||||
tokenPrice,
|
||||
isLegacy,
|
||||
tokenPrice
|
||||
}) => {
|
||||
return (
|
||||
<TableBody>
|
||||
@ -221,7 +193,6 @@ const ClaimInfo = ({
|
||||
<ActionButtons
|
||||
setConfirmationModalOpen={setConfirmationModalOpen}
|
||||
isClaimable={isClaimable}
|
||||
isLegacy={isLegacy}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -239,7 +210,6 @@ const MobileClaimInfo = ({
|
||||
ghstSymbol,
|
||||
stnkSymbol,
|
||||
tokenPrice,
|
||||
isLegacy,
|
||||
}) => {
|
||||
return (
|
||||
<Box mt="10px">
|
||||
@ -269,13 +239,12 @@ const MobileClaimInfo = ({
|
||||
isSmallScreen={true}
|
||||
setConfirmationModalOpen={setConfirmationModalOpen}
|
||||
isClaimable={isClaimable}
|
||||
isLegacy={isLegacy}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionButtons = ({ setConfirmationModalOpen, isLegacy, isSmallScreen = false, isClaimable = false }) => {
|
||||
const ActionButtons = ({ setConfirmationModalOpen, isSmallScreen = false, isClaimable = false }) => {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@ -296,8 +265,8 @@ const ActionButtons = ({ setConfirmationModalOpen, isLegacy, isSmallScreen = fal
|
||||
fullWidth={isSmallScreen}
|
||||
loading={false}
|
||||
sx={{ flexGrow: 1 }}
|
||||
onClick={() => setConfirmationModalOpen(false)}
|
||||
disabled={isLegacy && isClaimable}
|
||||
onClick={() => setConfirmationModalOpen(true)}
|
||||
disabled={isClaimable}
|
||||
>
|
||||
Claim
|
||||
</PrimaryButton>
|
||||
|
||||
@ -32,18 +32,14 @@ const StakeConfirmationModal = (props) => {
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="acknowledge-stake-warm-up"
|
||||
data-testid="acknowledge-warmup"
|
||||
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 ${props.ftsoSymbol} I’m staking will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed`}
|
||||
</Typography>
|
||||
}
|
||||
label={`I understand the ${props.ftsoSymbol} I’m staking will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed`}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
@ -59,7 +55,7 @@ const StakeConfirmationModal = (props) => {
|
||||
fullWidth
|
||||
href="https://ghostchain.io/ghostdao_litepaper"
|
||||
>
|
||||
Why is there a warm-up?
|
||||
Why is there a warmup?
|
||||
</SecondaryButton>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -35,7 +35,7 @@ const StakeSettingsModal = props => {
|
||||
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>
|
||||
|
||||
@ -4,7 +4,6 @@ import { useConfig } from "wagmi";
|
||||
import { useNavigate, createSearchParams } from "react-router-dom";
|
||||
|
||||
import { formatNumber } from "../../../helpers";
|
||||
import { useLiveBonds } from "../../../hooks/bonds/index";
|
||||
import { useEpoch, useGatekeeperApy } from "../../../hooks/staking";
|
||||
import { useTokenSymbol, useBalance, useCirculatingSupply } from "../../../hooks/tokens";
|
||||
import { SecondaryButton } from "../../../components/Button";
|
||||
@ -67,22 +66,11 @@ const ProtocolDetails = ({ chainId, isMobileScreen, theme, }) => {
|
||||
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 circulatingSupply = useCirculatingSupply(chainId, "STNK");
|
||||
const gatekeepedApy = useGatekeeperApy(chainId);
|
||||
const { epoch } = useEpoch(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 apyInner = useMemo(() => {
|
||||
let apy = Infinity;
|
||||
if (circulatingSupply._value > 0n) {
|
||||
@ -120,8 +108,8 @@ const ProtocolDetails = ({ chainId, isMobileScreen, theme, }) => {
|
||||
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.`}
|
||||
sideName="Up to 40% Discount"
|
||||
description={`Bonding strategy grows Treasury and builds Protocol Owned Liquidity (POL) by allowing usersto acquire ${csprSymbol} at a discount. Take advantage of the next available bond.`}
|
||||
/>
|
||||
|
||||
<ProtocolDetail
|
||||
@ -130,7 +118,7 @@ const ProtocolDetails = ({ chainId, isMobileScreen, 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.`}
|
||||
description={`Staking enables (3, 3) coordination by aligning long-term incentives, sustainably backed (1, 1) Bonds, LP fees, and Farming. Stake ${ftsoSymbol} to earn rewards and unlock ${bridgeNumbers} Stake\u00B2.`}
|
||||
/>
|
||||
|
||||
<ProtocolDetail
|
||||
@ -139,7 +127,7 @@ const ProtocolDetails = ({ chainId, isMobileScreen, theme, }) => {
|
||||
url={`/${networkName.toLowerCase()}/bridge`}
|
||||
name={`${bridgeNumbers} Stake\u00B2`}
|
||||
sideName={`${formatNumber(apyInner * 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.`}
|
||||
description={`Staking\u00B2 strategy further deepens long-term incentives powered by sustainable cross-chain bridging revenue. ${bridgeNumbers} Stake\u00B2 your ${csprSymbol} to receive organic APY with no warmup and dillution.`}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export function shorten(str, first = 6, second = -4) {
|
||||
export function shorten(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) {
|
||||
@ -39,7 +39,7 @@ export const formatNumber = (number, precision = 0) => {
|
||||
};
|
||||
|
||||
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") => {
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { useReadContract, useReadContracts } from "wagmi";
|
||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { isNetworkLegacyType } from "../../constants";
|
||||
import { config } from "../../config";
|
||||
|
||||
import {
|
||||
BOND_DEPOSITORY_ADDRESSES,
|
||||
DAO_TREASURY_ADDRESSES,
|
||||
@ -13,14 +17,7 @@ import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.js
|
||||
import { useReservePrice, useFtsoPrice } from "../prices";
|
||||
import { useOrinalCoefficient } from "../treasury";
|
||||
import { useTokenSymbol, useTokenSymbols } from "../tokens";
|
||||
import {
|
||||
getTokenAddress,
|
||||
getTokenIcons,
|
||||
getBondNameDisplayName,
|
||||
getTokenPurchaseLink,
|
||||
executeOnChainTransaction
|
||||
} from "../helpers";
|
||||
|
||||
import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { shorten } from "../../helpers";
|
||||
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
||||
@ -270,61 +267,78 @@ export const useNotes = (chainId, address) => {
|
||||
}
|
||||
|
||||
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => {
|
||||
const args = [bondId, amount, maxPrice, user, referral];
|
||||
const args = [
|
||||
bondId,
|
||||
amount,
|
||||
maxPrice,
|
||||
user,
|
||||
referral
|
||||
];
|
||||
const messages = {
|
||||
replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
|
||||
errorMsg: "Bond transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
|
||||
await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
"deposit",
|
||||
args,
|
||||
sender,
|
||||
messages,
|
||||
abi: BondAbi,
|
||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
||||
functionName: "deposit",
|
||||
account: user,
|
||||
value: isNative ? amount : undefined
|
||||
});
|
||||
isNative ? amount : undefined
|
||||
);
|
||||
}
|
||||
|
||||
export const redeem = async ({ chainId, user, isGhst, indexes }) => {
|
||||
const args = [user, isGhst, indexes];
|
||||
const args = [
|
||||
user,
|
||||
isGhst,
|
||||
indexes
|
||||
];
|
||||
const messages = {
|
||||
replacedMsg: "Redeem transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `Address ${shorten(user)} redeemed ${indexes.length} bonds in ${isGhst ? "GHST" : "STNK"}!`,
|
||||
errorMsg: `Redeem of ${indexes.length} bonds failed. Check logs for error detalization.`,
|
||||
};
|
||||
|
||||
await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
"redeem",
|
||||
args,
|
||||
messages,
|
||||
abi: BondAbi,
|
||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
||||
functionName: "redeem",
|
||||
account: user,
|
||||
});
|
||||
user,
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
export const forceRedeem = async ({ chainId, user, receiver, indexes }) => {
|
||||
const args = [receiver, indexes];
|
||||
const messages = {
|
||||
replacedMsg: "Bond breakout transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `Address ${shorten(user)} succesfully breakout for ${indexes.length} bonds.`,
|
||||
errorMsg: `Breakout of ${indexes.length} bonds failed. Check logs for error detalization.`,
|
||||
};
|
||||
|
||||
const txHash = await executeOnChainTransaction({
|
||||
const executeOnChainTransaction = async (
|
||||
chainId,
|
||||
functionName,
|
||||
args,
|
||||
account,
|
||||
messages,
|
||||
value
|
||||
) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: BondAbi,
|
||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
||||
functionName: "forceRedeem",
|
||||
account: user,
|
||||
functionName,
|
||||
args,
|
||||
account,
|
||||
chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
value: value,
|
||||
});
|
||||
|
||||
return txHash;
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast(messages.replacedMsg),
|
||||
chainId
|
||||
});
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@ -11,29 +11,19 @@ export const useUnstableProvider = () => useContext(UnstableProvider)
|
||||
|
||||
export const UnstableProviderProvider = ({ children }) => {
|
||||
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
|
||||
const [providerIndex, setProviderIndex] = useState(0);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [reconnectTicket, setReconnectTicket] = useState(0);
|
||||
|
||||
const { data: providerDetails } = useSWR("getGhostProviders", () =>
|
||||
Unstable.getSubstrateConnectExtensionProviders()
|
||||
);
|
||||
|
||||
const providerDetail = useMemo(() => providerDetails?.at(providerIndex), [providerDetails, providerIndex]);
|
||||
|
||||
const [providerDetail, setProviderDetail] = useState();
|
||||
const { data: provider } = useSWR(
|
||||
() => providerDetail ? `ghostProviderDetail.${providerDetail.info.uuid}.provider` : null,
|
||||
() => providerDetail ? providerDetail.provider : null
|
||||
);
|
||||
|
||||
const connectionState = useMemo(() => {
|
||||
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(() => {
|
||||
if (!provider || !chainId) return undefined;
|
||||
|
||||
@ -41,21 +31,56 @@ export const UnstableProviderProvider = ({ children }) => {
|
||||
if (!chain) return undefined;
|
||||
|
||||
return createClient(chain.connect)
|
||||
}, [provider, chainId]);
|
||||
}, [provider, chainId, reconnectTicket]);
|
||||
|
||||
const observableClient = useMemo(() => client ? getObservableClient(client) : undefined, [client]);
|
||||
const chainHead$ = useMemo(() => observableClient?.chainHead$(), [observableClient]);
|
||||
|
||||
const lastBlockNumber = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chainHead$) return;
|
||||
|
||||
lastBlockNumber.current = 0;
|
||||
let timeoutId;
|
||||
|
||||
const sub = chainHead$.bestBlocks$.subscribe({
|
||||
next: (blocks) => {
|
||||
const currentHeight = blocks.at(0)?.number ?? -1;
|
||||
|
||||
if (currentHeight > lastBlockNumber.current) {
|
||||
lastBlockNumber.current = currentHeight;
|
||||
setIsConnected(true);
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
setIsConnected(false);
|
||||
setReconnectTicket(t => t + 1);
|
||||
}, MAX_BLOCK_TIMEOUT);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
setIsConnected(false);
|
||||
setTimeout(() => setReconnectTicket(t => t + 1), 1000);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [chainHead$]);
|
||||
|
||||
const value = useMemo(() => ({
|
||||
isExtensionMissing: connectionState === "no-extension",
|
||||
isConnected,
|
||||
providerDetails,
|
||||
providerDetail,
|
||||
connectProviderByIndex: setProviderIndex,
|
||||
connectProviderDetail: setProviderDetail,
|
||||
chainId,
|
||||
client,
|
||||
setChainId,
|
||||
chainHead$
|
||||
}), [providerDetails, providerDetail, chainId, client, chainHead$]);
|
||||
}), [isConnected, providerDetails, providerDetail, chainId, client, chainHead$]);
|
||||
|
||||
return (
|
||||
<UnstableProvider.Provider value={value}>
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { useMemo } from "react";
|
||||
import { useReadContract, useReadContracts } from "wagmi";
|
||||
import { keccak256, stringToBytes } from 'viem'
|
||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||
import toast from "react-hot-toast";
|
||||
import { keccak256, stringToBytes } from 'viem'
|
||||
|
||||
import { isNetworkLegacyType } from "../../constants";
|
||||
import { config } from "../../config";
|
||||
|
||||
import {
|
||||
GHOST_GOVERNANCE_ADDRESSES,
|
||||
@ -12,7 +16,7 @@ import { abi as GovernorStorageAbi } from "../../abi/GovernorStorage.json";
|
||||
import { abi as GovernorVotesQuorumFractionAbi } from "../../abi/GovernorVotesQuorumFraction.json";
|
||||
|
||||
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;
|
||||
@ -37,7 +41,7 @@ export const useProposalVoteOf = (chainId, proposalId, who) => {
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "voteOf",
|
||||
args: [proposalId, who],
|
||||
scopeKey: `voteOf-${chainId}-${proposalId ? proposalId.toString() : "undefined"}-${who}`,
|
||||
scopeKey: `voteOf-${chainId}-${proposalId?.toString()}-${who}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
const voteOf = data ? BigInt(data) : 0n;
|
||||
@ -534,57 +538,87 @@ export const useProposals = (chainId, depth, searchedIndexes) => {
|
||||
}
|
||||
|
||||
export const releaseLocked = async (chainId, account, proposalId) => {
|
||||
const messages = {
|
||||
replacedMsg: "Release locked transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "Successfully release locked funds from the governor.",
|
||||
errorMsg: "Release locked funds failed. Check logs for error detalization.",
|
||||
};
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args: [proposalId],
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "releaseLocked",
|
||||
account,
|
||||
messages,
|
||||
functionName: 'releaseLocked',
|
||||
args: [proposalId],
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Release locked transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
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) => {
|
||||
const messages = {
|
||||
replacedMsg: "Proposal execution transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "Proposal execution was successful, wait for updates.",
|
||||
errorMsg: "Proposal execution failed. Check logs for error detalization.",
|
||||
};
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args: [proposalId],
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "execute",
|
||||
account,
|
||||
messages,
|
||||
functionName: 'execute',
|
||||
args: [proposalId],
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Proposal execution transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
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) => {
|
||||
const messages = {
|
||||
replacedMsg: "Cast vote transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "Successfully casted a vote, should be applied the proposal.",
|
||||
errorMsg: "Vote cast failed. Check logs for error detalization.",
|
||||
};
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args: [proposalId, support],
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "castVote",
|
||||
account,
|
||||
messages,
|
||||
functionName: 'castVote',
|
||||
args: [proposalId, support],
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Cast vote transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
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) => {
|
||||
@ -592,19 +626,29 @@ export const propose = async (chainId, account, functions, description) => {
|
||||
const values = functions.map(f => f.value);
|
||||
const calldatas = functions.map(f => f.calldata);
|
||||
|
||||
const messages = {
|
||||
replacedMsg: "Proposal transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "Successfully proposed a set of functions to be executed.",
|
||||
errorMsg: "Proposal creation failed. Check logs for error detalization.",
|
||||
};
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args: [targets, values, calldatas, description],
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: GovernorAbi,
|
||||
address: GHOST_GOVERNANCE_ADDRESSES[chainId],
|
||||
functionName: "propose",
|
||||
account,
|
||||
messages,
|
||||
functionName: 'propose',
|
||||
args: [targets, values, calldatas, description],
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast("Proposal transaction was replaced. Wait for inclusion please."),
|
||||
chainId
|
||||
});
|
||||
|
||||
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 {
|
||||
RESERVE_ADDRESSES,
|
||||
FTSO_ADDRESSES,
|
||||
@ -9,8 +6,6 @@ import {
|
||||
FTSO_DAI_LP_ADDRESSES,
|
||||
WETH_ADDRESSES,
|
||||
} from "../constants/addresses";
|
||||
import { isNetworkLegacyType } from "../constants";
|
||||
import { config } from "../config";
|
||||
|
||||
import { tokenNameConverter } from "../helpers/tokenConverter";
|
||||
|
||||
@ -206,55 +201,3 @@ export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => {
|
||||
}
|
||||
return purchaseUrl;
|
||||
}
|
||||
|
||||
const sanitizeTransactionRequest = (request, isLegacy) => {
|
||||
const finalRequest = { ...request };
|
||||
if (isLegacy) {
|
||||
delete finalRequest.maxFeePerGas;
|
||||
delete finalRequest.maxPriorityFeePerGas;
|
||||
} else {
|
||||
delete finalRequest.gasPrice;
|
||||
}
|
||||
return finalRequest;
|
||||
}
|
||||
|
||||
export const executeOnChainTransaction = async ({
|
||||
chainId,
|
||||
abi,
|
||||
address,
|
||||
functionName,
|
||||
args,
|
||||
account,
|
||||
messages,
|
||||
value
|
||||
}) => {
|
||||
try {
|
||||
const isLegacy = isNetworkLegacyType(chainId);
|
||||
const { request } = await simulateContract(config, {
|
||||
abi,
|
||||
address,
|
||||
functionName,
|
||||
args,
|
||||
account,
|
||||
chainId,
|
||||
value,
|
||||
type: isLegacy ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const finalRequest = sanitizeTransactionRequest(request, isLegacy);
|
||||
const txHash = await writeContract(config, { ...finalRequest });
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export const LocalStorageProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
const setStorageValue = (chainId, address, target, value) => {
|
||||
const key = getStorageKey(chainId, address, target);
|
||||
const key = getStorageKey(prefix, chainId, address, target);
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import { useReadContract } from "wagmi";
|
||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { config } from "../../config";
|
||||
|
||||
import { isNetworkLegacyType } from "../../constants";
|
||||
import { STAKING_ADDRESSES } from "../../constants/addresses";
|
||||
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
|
||||
import { abi as GatekeeperAbi } from "../../abi/GhostGatekeeper.json";
|
||||
|
||||
import { shorten } from "../../helpers";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { executeOnChainTransaction } from "../helpers";
|
||||
|
||||
export const useGatekeeperApy = (chainId) => {
|
||||
const { data: gatekeeper, refetch } = useReadContract({
|
||||
@ -21,7 +24,7 @@ export const useGatekeeperApy = (chainId) => {
|
||||
const { data: metadata, error } = useReadContract({
|
||||
abi: GatekeeperAbi,
|
||||
address: gatekeeper,
|
||||
functionName: "metadata",
|
||||
functionName: "gatekeeperMetadata",
|
||||
scopeKey: `gatekeeperMetadata-${chainId}-${gatekeeper}`,
|
||||
chainId: chainId,
|
||||
});
|
||||
@ -152,39 +155,45 @@ export const useGhostedSupply = (chainId) => {
|
||||
}
|
||||
|
||||
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 = {
|
||||
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.",
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
"stake",
|
||||
args,
|
||||
messages,
|
||||
account,
|
||||
abi: StakingAbi,
|
||||
address: STAKING_ADDRESSES[chainId],
|
||||
functionName: "stake",
|
||||
});
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
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 = {
|
||||
replacedMsg: "Unstake transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `${isRebase ? stnkSymbol : ghstSymbol} tokens unstaked successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
||||
errorMsg: "Unstake transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
"unstake",
|
||||
args,
|
||||
messages,
|
||||
account,
|
||||
abi: StakingAbi,
|
||||
address: STAKING_ADDRESSES[chainId],
|
||||
functionName: "unstake",
|
||||
});
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
export const forfeit = async (chainId, account, ftsoSymbol) => {
|
||||
@ -193,15 +202,13 @@ export const forfeit = async (chainId, account, ftsoSymbol) => {
|
||||
successMsg: `Tokens forfeited successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
||||
errorMsg: "Forfeit transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
messages,
|
||||
"forfeit",
|
||||
[],
|
||||
account,
|
||||
args: [],
|
||||
abi: StakingAbi,
|
||||
address: STAKING_ADDRESSES[chainId],
|
||||
functionName: "forfeit",
|
||||
});
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) => {
|
||||
@ -211,89 +218,94 @@ export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) =>
|
||||
successMsg: `${isStnk ? stnkSymbol : ghstSymbol} tokens claimed successfully to ${shorten(account)}.`,
|
||||
errorMsg: "Claim transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
"claim",
|
||||
args,
|
||||
messages,
|
||||
account,
|
||||
abi: StakingAbi,
|
||||
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;
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
export const unwrap = async (chainId, account, amount, stnkSymbol) => {
|
||||
const args = [account, amount];
|
||||
const messages = {
|
||||
replacedMsg: "Unwrap transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `Tokens unwrapped successfully! Check your ${stnkSymbol} balance on ${shorten(account)}.`,
|
||||
errorMsg: "Unwrap transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
const txHash = await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
args,
|
||||
messages,
|
||||
"unwrap",
|
||||
[account, amount],
|
||||
account,
|
||||
abi: StakingAbi,
|
||||
address: STAKING_ADDRESSES[chainId],
|
||||
functionName: "unwrap",
|
||||
});
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
export const wrap = async (chainId, account, amount, ghstSymbol) => {
|
||||
const args = [account, amount];
|
||||
const messages = {
|
||||
replacedMsg: "Wrap transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `Tokens wrapped successfully! Check your ${ghstSymbol} balance on ${shorten(account)}.`,
|
||||
errorMsg: "Wrap transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
const txHash = await executeOnChainTransaction({
|
||||
await executeOnChainTransaction(
|
||||
chainId,
|
||||
args,
|
||||
messages,
|
||||
"wrap",
|
||||
[account, amount],
|
||||
account,
|
||||
abi: StakingAbi,
|
||||
address: STAKING_ADDRESSES[chainId],
|
||||
functionName: "wrap",
|
||||
});
|
||||
messages
|
||||
);
|
||||
}
|
||||
|
||||
export const ghost = async (chainId, account, receiver, amount) => {
|
||||
const ars = [receiver, amount];
|
||||
const messages = {
|
||||
replacedMsg: "Bridge transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `Amount successfully bridged! Check tx hash status or wait for slow clap finalization.`,
|
||||
errorMsg: "Bridge transaction failed. Check logs for error detalization.",
|
||||
};
|
||||
const txHash = await executeOnChainTransaction({
|
||||
|
||||
const txHash = await executeOnChainTransaction(
|
||||
chainId,
|
||||
args,
|
||||
messages,
|
||||
"ghost",
|
||||
[receiver, amount],
|
||||
account,
|
||||
abi: StakingAbi,
|
||||
address: STAKING_ADDRESSES[chainId],
|
||||
functionName: "ghost",
|
||||
});
|
||||
messages
|
||||
);
|
||||
|
||||
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,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
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,16 +1,14 @@
|
||||
import { useReadContract, useReadContracts, useToken, useBalance as useInnerBalance } from "wagmi";
|
||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import {
|
||||
getTokenAbi,
|
||||
getTokenAddress,
|
||||
getTokenDecimals,
|
||||
executeOnChainTransaction
|
||||
} from "../helpers";
|
||||
import { getTokenAbi, getTokenAddress, getTokenDecimals } from "../helpers";
|
||||
|
||||
import { isNetworkLegacyType } from "../../constants";
|
||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||
import { shorten } from "../../helpers";
|
||||
import { tokenNameConverter } from "../../helpers/tokenConverter";
|
||||
import { config } from "../../config";
|
||||
import { WETH_ADDRESSES } from "../../constants/addresses";
|
||||
|
||||
export const usePastVotes = (chainId, name, timepoint, address) => {
|
||||
@ -206,86 +204,132 @@ export const useBalanceForShares = (chainId, name, amount) => {
|
||||
};
|
||||
|
||||
export const approveTokens = async (chainId, name, owner, spender, value) => {
|
||||
const messages = {
|
||||
replacedMsg: "Approve transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: `${name} tokens successfully approved to ${shorten(spender)}`,
|
||||
errorMsg: `${name} tokens approval failed. Check logs for error detalization.`
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args: [spender, value],
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi(name),
|
||||
address: getTokenAddress(chainId, name),
|
||||
functionName: "approve",
|
||||
functionName: 'approve',
|
||||
args: [spender, value],
|
||||
account: owner,
|
||||
messages,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
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) => {
|
||||
const messages = {
|
||||
replacedMsg: "Mint transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "gDAI successfully minted to your wallet! Funds will be used on Ghost Faucet.",
|
||||
errorMsg: "Minting gDAI from the faucet failed. Check logs for error detalization."
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args: [account],
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi("GDAI"),
|
||||
address: getTokenAddress(chainId, "GDAI"),
|
||||
functionName: "mint",
|
||||
account,
|
||||
messages,
|
||||
functionName: 'mint',
|
||||
args: [account],
|
||||
account: account,
|
||||
value: value,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
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) => {
|
||||
const messages = {
|
||||
replacedMsg: "Burn transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "gDAI successfully burned for native coins! Check your wallet balance.",
|
||||
errorMsg: "Burning gDAI from the faucet failed. Check logs for error detalization."
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi("GDAI"),
|
||||
address: getTokenAddress(chainId, "GDAI"),
|
||||
functionName: 'burn',
|
||||
args: [value],
|
||||
account,
|
||||
messages,
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
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) => {
|
||||
const messages = {
|
||||
replacedMsg: "WETH9 deposit transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "WETH9 successfully minted to your wallet! Check your wallet balance.",
|
||||
errorMsg: "WETH9 wrapping failed. Check logs for error detalization."
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi("WETH"),
|
||||
address: getTokenAddress(chainId, "WETH"),
|
||||
functionName: 'deposit',
|
||||
value,
|
||||
account,
|
||||
messages,
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
value: value,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
const txHash = await writeContract(config, request);
|
||||
|
||||
await waitForTransactionReceipt(config, {
|
||||
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) => {
|
||||
const messages = {
|
||||
replacedMsg: "WETH9 withdraw transaction was replaced. Wait for inclusion please.",
|
||||
successMsg: "WETH9 successfully burned for native coins! Check your wallet balance.",
|
||||
errorMsg: "WETH9 unwrapping failed. Check logs for error detalization."
|
||||
};
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: getTokenAbi("WETH"),
|
||||
address: getTokenAddress(chainId, "WETH"),
|
||||
functionName: 'withdraw',
|
||||
args: [value],
|
||||
account,
|
||||
messages,
|
||||
account: account,
|
||||
chainId: chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { isNetworkLegacyType } from "../../constants";
|
||||
import { UNISWAP_V2_ROUTER, WETH_ADDRESSES } from "../../constants/addresses";
|
||||
import { abi as RouterAbi } from "../../abi/UniswapV2Router.json";
|
||||
import { getTokenAddress, executeOnChainTransaction } from "../helpers";
|
||||
import { getTokenAddress } from "../helpers";
|
||||
|
||||
import { config } from "../../config";
|
||||
|
||||
const swapMessages = {
|
||||
replacedMsg: "Swap transaction was replaced. Wait for inclusion please.",
|
||||
@ -23,7 +27,6 @@ export const swapExactETHForTokens = async ({
|
||||
tokenNameTop,
|
||||
tokenNameBottom,
|
||||
destination,
|
||||
address,
|
||||
deadline
|
||||
}) => {
|
||||
const args = [
|
||||
@ -35,11 +38,9 @@ export const swapExactETHForTokens = async ({
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args,
|
||||
abi: RouterAbi,
|
||||
address: UNISWAP_V2_ROUTER[chainId],
|
||||
functionName: "swapExactETHForTokens",
|
||||
account: address,
|
||||
args,
|
||||
account: destination,
|
||||
messages: swapMessages,
|
||||
value: amountADesired,
|
||||
});
|
||||
@ -65,10 +66,8 @@ export const swapExactTokensForETH = async ({
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args,
|
||||
abi: RouterAbi,
|
||||
address: UNISWAP_V2_ROUTER[chainId],
|
||||
functionName: "swapExactTokensForETH",
|
||||
args,
|
||||
account: address,
|
||||
messages: swapMessages,
|
||||
});
|
||||
@ -94,12 +93,10 @@ export const swapExactTokensForTokens = async ({
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args,
|
||||
abi: RouterAbi,
|
||||
address: UNISWAP_V2_ROUTER[chainId],
|
||||
functionName: "swapExactTokensForTokens",
|
||||
args,
|
||||
account: address,
|
||||
messages: swapMessages,
|
||||
messages: swapMessages
|
||||
});
|
||||
}
|
||||
|
||||
@ -128,12 +125,10 @@ export const addLiquidity = async ({
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args,
|
||||
abi: RouterAbi,
|
||||
address: UNISWAP_V2_ROUTER[chainId],
|
||||
functionName: "addLiquidity",
|
||||
args,
|
||||
account: address,
|
||||
messages: addMessages,
|
||||
messages: addMessages
|
||||
});
|
||||
}
|
||||
|
||||
@ -174,12 +169,44 @@ export const addLiquidityETH = async ({
|
||||
|
||||
await executeOnChainTransaction({
|
||||
chainId,
|
||||
args,
|
||||
abi: RouterAbi,
|
||||
address: UNISWAP_V2_ROUTER[chainId],
|
||||
functionName: "addLiquidityETH",
|
||||
args,
|
||||
account: address,
|
||||
messages: addMessages,
|
||||
value: amountETHDesired,
|
||||
});
|
||||
}
|
||||
|
||||
const executeOnChainTransaction = async ({
|
||||
chainId,
|
||||
functionName,
|
||||
args,
|
||||
account,
|
||||
messages,
|
||||
value,
|
||||
}) => {
|
||||
try {
|
||||
const { request } = await simulateContract(config, {
|
||||
abi: RouterAbi,
|
||||
address: UNISWAP_V2_ROUTER[chainId],
|
||||
functionName,
|
||||
args,
|
||||
account,
|
||||
chainId,
|
||||
type: isNetworkLegacyType(chainId) ? 'legacy' : 'eip1559',
|
||||
value: value ?? 0n,
|
||||
});
|
||||
|
||||
const txHash = await writeContract(config, request);
|
||||
await waitForTransactionReceipt(config, {
|
||||
hash: txHash,
|
||||
onReplaced: () => toast(messages.replacedMsg),
|
||||
chainId
|
||||
});
|
||||
|
||||
toast.success(messages.successMsg);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(messages.errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import { WagmiProvider } from "wagmi";
|
||||
import { config } from "./config";
|
||||
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
|
||||
import { LocalStorageProvider } from "./hooks/localstorage";
|
||||
import { BreakoutModalProvider } from "./hooks/breakoutModal";
|
||||
import ReactGA from "react-ga4";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
@ -27,9 +26,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<UnstableProviderProvider>
|
||||
<MetadataProviderProvider>
|
||||
<LocalStorageProvider>
|
||||
<BreakoutModalProvider>
|
||||
<App />
|
||||
</BreakoutModalProvider>
|
||||
</LocalStorageProvider>
|
||||
</MetadataProviderProvider>
|
||||
</UnstableProviderProvider>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user