additional UI revision; change of the bond purchase and history added

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-11-04 13:03:18 +03:00
parent a911859841
commit 314abe1aba
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
26 changed files with 1276 additions and 284 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.7.58",
"version": "0.7.59-beta-2",
"type": "module",
"scripts": {
"dev": "vite",
@ -55,5 +55,6 @@
"sass-embedded": "^1.85.1",
"vite": "^6.2.0",
"vite-plugin-svgr": "^4.3.0"
}
},
"packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
}

View File

@ -41,6 +41,7 @@ const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
const Governance = lazy(() => import("./containers/Governance/Governance"));
const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails"));
const NewProposal = lazy(() => import("./containers/Governance/NewProposal"));
const History = lazy(() => import("./containers/History/History"));
const PREFIX = "App";
@ -204,6 +205,7 @@ function App() {
<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} />} />
<Route path="history" element={<History config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
</Route>
}
<Route path="/empty" element={<NotFound

View File

@ -27,6 +27,7 @@ import HubIcon from '@mui/icons-material/Hub';
import PublicIcon from '@mui/icons-material/Public';
import ForkRightIcon from '@mui/icons-material/ForkRight';
import GavelIcon from '@mui/icons-material/Gavel';
import HistoryIcon from '@mui/icons-material/History';
import CasinoIcon from '@mui/icons-material/Casino';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import BookIcon from '@mui/icons-material/Book';
@ -145,9 +146,6 @@ 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">
<Typography component="span" variant="body2">Bond Discounts</Typography>
</Box>}
{sortedGhostBonds.map((bond, index) => {
return (
<Link
@ -194,7 +192,7 @@ const NavContent = ({ chainId, addressChainId }) => {
<Box>
<NavItem href="https://ghostchain.io/game-theory-3-3" icon={CasinoIcon} label={`${bridgeNumbers} Game Theory`} />
<NavItem href="http://ecosystem.ghostchain.io" icon={PublicIcon} label={`Ecosystem`} />
<NavItem icon={HistoryIcon} label={`History`} to={`/${chainName}/history`} />
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Documentation`} />
<StyledBox display="flex" justifyContent="space-around" paddingY="24px">
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">

View File

@ -1,45 +1,46 @@
import { defineChain } from 'viem'
import { http, fallback, createConfig } from 'wagmi'
import { sepolia, hoodi } from 'wagmi/chains'
import { hoodi } from 'wagmi/chains'
const mordor = defineChain({
id: 63,
name: 'Mordor',
nativeCurrency: {
decimals: 18,
name: 'METC',
symbol: 'mETC',
},
rpcUrls: {
default: { http: ['https://rpc.mordor.etccooperative.org'] },
},
blockExplorers: {
default: {
name: 'Blockscout',
url: 'https://etc-mordor.blockscout.com/'
},
},
})
// const mordor = defineChain({
// id: 63,
// name: 'Mordor',
// nativeCurrency: {
// decimals: 18,
// name: 'METC',
// symbol: 'mETC',
// },
// rpcUrls: {
// default: { http: ['https://rpc.mordor.etccooperative.org'] },
// },
// blockExplorers: {
// default: {
// name: 'Blockscout',
// url: 'https://etc-mordor.blockscout.com/'
// },
// },
// })
export const config = createConfig({
chains: [sepolia, hoodi, mordor],
chains: [hoodi],
transports: {
[sepolia.id]: fallback([
http('https://ethereum-sepolia-rpc.publicnode.com'),
http('https://1rpc.io/sepolia'),
http('https://eth-sepolia.public.blastapi.io'),
http('https://0xrpc.io/sep'),
http('https://api.zan.top/eth-sepolia'),
http('https://eth-sepolia.api.onfinality.io/public'),
http('https://sepolia.drpc.org')
]),
// [sepolia.id]: fallback([
// http('https://ethereum-sepolia-rpc.publicnode.com'),
// http('https://1rpc.io/sepolia'),
// http('https://eth-sepolia.public.blastapi.io'),
// http('https://0xrpc.io/sep'),
// http('https://api.zan.top/eth-sepolia'),
// http('https://eth-sepolia.api.onfinality.io/public'),
// http('https://sepolia.drpc.org')
// ]),
[hoodi.id]: fallback([
http('https://rpc.hoodi.ethpandaops.io'),
http('https://rpc.sentio.xyz/hoodi'),
http('https://0xrpc.io/hoodi'),
]),
[mordor.id]: fallback([
http('https://rpc.mordor.etccooperative.org'),
http('https://geth-mordor.etc-network.info'),
])
// [mordor.id]: fallback([
// http('https://rpc.mordor.etccooperative.org'),
// http('https://geth-mordor.etc-network.info'),
// ])
},
})

View File

@ -4,25 +4,25 @@ export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
[NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
[NetworkId.TESTNET_HOODI]: "0x5d659FDEFc9c1552A3a53f661Cf4F52b0d7dDb40",
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
};
export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
[NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
[NetworkId.TESTNET_HOODI]: "0x4105D6b48f0fEfA8033b8B01d89972c2CBd787bD",
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
};
export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
[NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E",
[NetworkId.TESTNET_HOODI]: "0xeA5113bF7e0af48B6D0Cf1a4B1e4dD0184708a09",
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
};
export const FTSO_DAI_LP_ADDRESSES = {
export const FTSO_RESERVE_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
[NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D",
[NetworkId.TESTNET_HOODI]: "0xf3F2cc4DaB20F09eee0d07d8456924927ceA1847",
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
};
@ -46,43 +46,43 @@ export const WETH_ADDRESSES = {
export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
[NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
[NetworkId.TESTNET_HOODI]: "0x594a5CD093859B3FD48aA901d86F71a7D537834a",
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
};
export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
[NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
[NetworkId.TESTNET_HOODI]: "0x7a7d0F214b8C03bD962B88AC0a7F543085aB817B",
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
};
export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
[NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
[NetworkId.TESTNET_HOODI]: "0x07427B220934B3421223969eA61a247bFA37cF03",
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
};
export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
[NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
[NetworkId.TESTNET_HOODI]: "0xe503d65C9574bD3C18c8d725cD346eCBFAE24b4d",
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
};
export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
[NetworkId.TESTNET_HOODI]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
[NetworkId.TESTNET_HOODI]: "0x0000000000000000000000000000000000000000",
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
};
export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
[NetworkId.TESTNET_HOODI]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
[NetworkId.TESTNET_HOODI]: "0xf13A5319aD8071e4dcE7776728Ef9Cc221A5c098",
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
}
export const GATEKEEPER_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B",
[NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
[NetworkId.TESTNET_HOODI]: "0xc9D5f50e17eeEAF39E0D0cDbbcF4B04FA4A4Bbd6",
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
}

View File

@ -1,13 +1,15 @@
import { ArrowBack } from "@mui/icons-material";
import { Box, Link, Skeleton, Typography } from "@mui/material";
import { Box, Container, Skeleton, Typography, useMediaQuery } from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react";
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom";
import { useLocation, useParams, useNavigate } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
import Countdown from "../../components/Countdown/Countdown";
import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import Metric from "../../components/Metric/Metric";
import MetricCollection from "../../components/Metric/MetricCollection";
import TokenStack from "../../components/TokenStack/TokenStack";
import BondPrice from "./components/BondPrice";
@ -40,6 +42,9 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
const { network } = useParams();
const { pathname } = useLocation();
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const [isSettingsOpen, setSettingsOpen] = useState(false);
const { getStorageValue, setStorageValue } = useLocalStorage();
@ -102,17 +107,9 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
return (
<Box>
<PageTitle
subtitle="We need this one just for alignment"
name={
<Box display="flex" flexDirection="row" alignItems="center">
<Link component={RouterLink} to={`/${network}/bonds`}>
<Box display="flex" flexDirection="row">
<ArrowBack />
<Typography fontWeight="500" marginLeft="9.5px" marginRight="18px">
Back
</Typography>
</Box>
</Link>
<TokenStack
chainTokenNames={preparedQuoteToken.chainNames}
tokens={preparedQuoteToken.icons}
@ -120,7 +117,7 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
/>
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
<Typography variant="h4" fontWeight={500}>
{preparedQuoteToken.name}
{preparedQuoteToken.name} Bond
</Typography>
</Box>
</Box>
@ -139,30 +136,45 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
onDecimalsChange={event => setFormatDecimalsInner(event.currentTarget.value)}
/>
<Box display="flex" flexDirection="row" justifyContent="space-between" width={["100%", "70%"]} mt="24px">
<Metric
label={`Bond Price`}
metric={
bond.isSoldOut ? (
"--"
) : (
<BondPrice
price={bond.price.inUsd}
/>
)
<Container
style={{
marginTop: "15px",
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
}}
>
<Paper
fullWidth
enableBackground
headerContent={
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
<Typography variant="h6">
Bond Expires&nbsp;
<Countdown otherwise="[EXPIRED]" targetTime={bond.duration * 1000} />
</Typography>
</Box>
}
/>
<Metric
label="Market Price"
metric={<TokenPrice priceInUsd={bond.price.marketPriceInUsd} />}
/>
<Metric
label="Discount"
metric={<BondDiscount discount={bond.discount} textOnly />}
/>
</Box>
>
<MetricCollection>
<Metric
label={`Bond Price`}
tooltip="Some description about what the bond price is"
metric={bond.isSoldOut ? ( "--" ) : ( <BondPrice price={bond.price.inUsd} /> )}
/>
<Metric
label="Market Price"
metric={<TokenPrice priceInUsd={bond.price.marketPriceInUsd} />}
/>
<Metric
label="Discount"
tooltip="Some description about what the discount is"
metric={<BondDiscount discount={bond.discount} textOnly />}
/>
</MetricCollection>
</Paper>
</Container>
<Box width="100%" mt="24px">
<Box width="100%" display="flex" justifyContent="center" alignItems="center">
<BondInputArea
chainId={chainId}
bond={bond}

View File

@ -17,6 +17,7 @@ import BondDiscount from "./BondDiscount";
import BondVesting from "./BondVesting";
import BondSlippage from "./BondSlippage";
import { useLocalStorage } from "../../../hooks/localstorage";
import { purchaseBond } from "../../../hooks/bonds";
import { useWarmupLength } from "../../../hooks/staking";
@ -28,7 +29,32 @@ const StyledBox = styled(Box, {
};
});
const BondConfirmModal = ({
export const DiscountConfirmModal = ({ isOpen, isPending, onSubmit, handleConfirmClose, discount }) => {
const theme = useTheme();
return (
<Modal
maxWidth="380px"
minHeight="200px"
open={isOpen}
headerContent={<Typography variant="h4">Confirm Bond Purchase</Typography>}
onClose={!isPending && handleConfirmClose}
>
<Box display="flex" flexDirection="column" justifyContent="space-between" alignItems="center" gap="10px">
<Typography>I confirm the bond purchase with a negative discount</Typography>
<Box display="flex" flexDirection="row" alignItems="center" justifyContent="center">
<Typography color={theme.colors.feedback.error} variant="h6">{discount}%</Typography>
</Box>
<PrimaryButton fullWidth onClick={onSubmit} disabled={isPending} loading={isPending}>
Confirm
</PrimaryButton>
</Box>
</Modal>
)
}
export const BondConfirmModal = ({
chainId,
bond,
slippage,
@ -51,6 +77,8 @@ const BondConfirmModal = ({
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
const [isPending, setIsPending] = useState(false);
const { tryStoreBondInHistory } = useLocalStorage();
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup]);
const AcknowledgeWarmupCheckbox = () => {
@ -104,21 +132,19 @@ const BondConfirmModal = ({
const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
const bigIntSlippage = one + BigInt(Math.round(floatSlippage * shares));
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
await purchaseBond({
const txHash = await purchaseBond({
chainId,
bondId: bond.id,
amount: spendAmountValue._value.toBigInt(),
maxPrice,
user: recipientAddress,
sender,
referral,
isNative
});
tryStoreBondInHistory(chainId, sender, txHash);
setIsPending(false);
handleConfirmCloseMaster();
}
@ -212,5 +238,3 @@ const BondConfirmModal = ({
</Modal>
);
};
export default BondConfirmModal;

View File

@ -1,7 +1,8 @@
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material";
import { Box, Container, Checkbox, FormControlLabel, Typography, useMediaQuery } from "@mui/material";
import { useState, useMemo, useCallback } from "react";
import { useLocation } from "react-router-dom";
import SettingsIcon from '@mui/icons-material/Settings';
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
import { shorten, formatNumber, formatCurrency } from "../../../helpers";
@ -9,10 +10,11 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import BondDiscount from "./BondDiscount";
import BondVesting from "./BondVesting";
import BondConfirmModal from "./BondConfirmModal";
import { BondConfirmModal, DiscountConfirmModal } from "./BondConfirmModal";
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { PrimaryButton } from "../../../components/Button";
import GhostStyledIcon from "../../../components/Icon/GhostIcon";
import SwapCard from "../../../components/Swap/SwapCard";
import SwapCollection from "../../../components/Swap/SwapCollection";
import TokenStack from "../../../components/TokenStack/TokenStack";
@ -33,6 +35,7 @@ const BondInputArea = ({
preparedQuoteToken,
connect
}) => {
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { pathname } = useLocation();
@ -45,7 +48,9 @@ const BondInputArea = ({
const [amount, setAmount] = useState("");
const [checked, setChecked] = useState(false);
const [discountConfirmed, setDiscountConfirmed] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [discountConfirmOpen, setDiscountConfirmOpen] = useState(false);
const parsedAmount = useMemo(() => {
return new DecimalBigNumber(amount, bond.quoteToken.decimals);
@ -60,12 +65,24 @@ const BondInputArea = ({
return new DecimalBigNumber("0").gt(bond.discount);
}, [bond]);
const setModalOpen = () => {
if (showDisclaimer && !discountConfirmOpen && !discountConfirmed) setDiscountConfirmOpen(true);
else setConfirmOpen(true);
}
const handleConfirmClose = () => {
setAmount("");
setConfirmOpen(false);
setDiscountConfirmOpen(false);
refetch();
}
const confirmDiscount = () => {
setConfirmOpen(false);
setDiscountConfirmOpen(false);
setDiscountConfirmed(true);
}
const setMax = useCallback(() => {
if (!balance) return;
@ -95,143 +112,32 @@ const BondInputArea = ({
}, [amountInBaseToken, baseTokenString, balance, parsedAmount]);
return (
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
<Box display="flex" flexDirection="row" width="100%" justifyContent="center" mt="10px">
<Box display="flex" flexDirection="column" width="100%" maxWidth="476px">
<Box mb="21px">
<SwapCollection
iconNotNeeded
UpperSwapCard={
<SwapCard
maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
id="from"
token={
<TokenStack
chainTokenNames={bond.quoteToken.chainNames}
tokens={bond.quoteToken.icons}
sx={{ fontSize: "25px", height: "30px" }}
/>
}
tokenName={preparedQuoteToken.name}
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
endString="Max"
endStringOnClick={setMax}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
/>}
LowerSwapCard={
<SwapCard
maxWidth="476px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
id="to"
token={
<TokenStack
chainTokenNames={bond.baseToken.chainNames}
tokens={bond.baseToken.icons}
sx={{ fontSize: "25px", height: "30px" }}
/>
}
tokenName={bond.baseToken.name}
value={amountInBaseToken.toString({ decimals: 9 })}
inputProps={{ "data-testid": "toInput" }}
/>
}
/>
</Box>
<Box sx={{ width: "100%", maxWidth: "420px", mt: "15px" }}>
<Paper
fullWidth
enableBackground
topRight={
<GhostStyledIcon
component={SettingsIcon}
viewBox="0 0 23 23"
style={{ display: "flex", alignItems: "center", cursor: "pointer" }}
onClick={handleSettingsOpen}
/>
}
headerContent={
<Typography variant="h6">Bond</Typography>
}
>
<DiscountConfirmModal
isOpen={discountConfirmOpen}
onSubmit={() => confirmDiscount()}
handleConfirmClose={() => handleConfirmClose()}
discount={bond
? formatNumber(Number(bond.discount.mul(new DecimalBigNumber("100"))).toString(), 2)
: "Unknown"
}
/>
<TokenAllowanceGuard
spendAmount={parsedAmount}
tokenName={bond.quoteToken.quoteTokenAddress}
owner={address}
spender={BOND_DEPOSITORY_ADDRESSES[chainId]}
decimals={parsedAmount._decimals}
approvalText={`Approve ${bond.quoteToken.name} to Bond`}
approvalPendingText={"Approving..."}
connect={connect}
width="100%"
height="60px"
isNative={preparedQuoteToken.address === undefined}
isVertical
message={
<>
First time bonding <b>{bond.quoteToken.name}</b>? <br /> Please approve ghostDAO to use your{" "}
<b>{bond.quoteToken.name}</b> for bonding.
</>
}
>
{showDisclaimer && (
<FormControlLabel
control={
<Checkbox
checked={checked}
onChange={event => setChecked(event.target.checked)}
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
/>
}
label="I understand that I'm buying a negative discount bond"
/>
)}
<PrimaryButton
fullWidth
disabled={incorrectInputAmount || bond.isSoldOut || (showDisclaimer && !checked)}
onClick={() => setConfirmOpen(true)}
>
Bond
</PrimaryButton>
</TokenAllowanceGuard>
<Paper style={{ marginBottom: "7px", marginTop: "21px" }} fullWidth enableBackground>
<Box mt="24px">
<DataRow
title={"You Will Get"}
balance={
<span>
{formatCurrency(amountInBaseToken, formatDecimals, ftsoSymbol)}
{" "}
{!!currentIndex && (
<span>
(≈{formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, ghstSymbol)})
</span>
)}
</span>
}
tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
/>
<DataRow
title="Max You Can Buy"
tooltip={`Maximum ${ftsoSymbol} that can be purchased in a single transaction. Prevents whales from buying entire bond supply at once.`}
balance={
<span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
? `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)}`
: `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, ghstSymbol)})`}
</span>
}
/>
<DataRow
title="Discount"
balance={<BondDiscount discount={bond.discount} textOnly />}
tooltip={`The discount (or premium) between ${ftsoSymbol} market price and bond price. Higher discount = better deal for bond buyers.`}
/>
<DataRow
title={`Vesting Term`}
balance={<BondVesting vesting={bond.vesting} />}
tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
/>
{recipientAddress !== address && (
<DataRow title={`Recipient`} balance={shorten(recipientAddress)} />
)}
</Box>
</Paper>
</Box>
</Box>
<BondConfirmModal
chainId={chainId}
bond={bond}
@ -249,8 +155,139 @@ const BondInputArea = ({
sender={address}
handleConfirmClose={() => handleConfirmClose()}
/>
</Box>
);
};
<Box maxWidth="390px" display="flex" flexDirection="column" justifyContent="center" gap="15px">
<SwapCollection
iconNotNeeded
UpperSwapCard={
<SwapCard
maxWidth="390px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
id="from"
token={
<TokenStack
chainTokenNames={bond.quoteToken.chainNames}
tokens={bond.quoteToken.icons}
sx={{ fontSize: "25px", height: "30px" }}
/>
}
tokenName={preparedQuoteToken.name}
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
endString="Max"
endStringOnClick={setMax}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
/>}
LowerSwapCard={
<SwapCard
maxWidth="390px"
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
id="to"
token={
<TokenStack
chainTokenNames={bond.baseToken.chainNames}
tokens={bond.baseToken.icons}
sx={{ fontSize: "25px", height: "30px" }}
/>
}
tokenName={bond.baseToken.name}
value={amountInBaseToken.toString({ decimals: 9 })}
inputProps={{ "data-testid": "toInput" }}
/>
}
/>
<Box
display="flex"
flexDirection="column"
sx={{
"& .DataRow-root": {
margin: "0px !important",
},
"& .MuiTypography-root": {
fontSize: "13px !important",
},
"& span": {
fontSize: "13px !important",
}
}}
>
<DataRow
title={"Bond Payout"}
balance={
<span>
{formatCurrency(amountInBaseToken, formatDecimals, ftsoSymbol)}
{" "}
{!!currentIndex && (
<span>
(≈{formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, ghstSymbol)})
</span>
)}
</span>
}
tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
/>
<DataRow
title="Max Payout"
tooltip={`Maximum ${ftsoSymbol} that can be purchased in a single transaction. Prevents whales from buying entire bond supply at once.`}
balance={
<span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
? `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)}`
: `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, ghstSymbol)})`}
</span>
}
/>
<DataRow
title={`Vesting Term`}
balance={<span><BondVesting vesting={bond.vesting} /></span>}
tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
/>
{recipientAddress !== address && (
<DataRow
title={`Recipient`}
balance={<span>{shorten(recipientAddress)}</span>}
tooltip="The address that will receive the bond. To change it from your connected account, update the settings via the gear icon."
/>
)}
</Box>
<TokenAllowanceGuard
spendAmount={parsedAmount}
tokenName={bond.quoteToken.quoteTokenAddress}
owner={address}
spender={BOND_DEPOSITORY_ADDRESSES[chainId]}
decimals={parsedAmount._decimals}
approvalText={`Approve ${bond.quoteToken.name} to Bond`}
approvalPendingText={"Approving..."}
connect={connect}
width="100%"
height="60px"
isNative={preparedQuoteToken.address === undefined}
isVertical
message={
<>
First time bonding <b>{bond.quoteToken.name}</b>? <br /> Please approve ghostDAO to use your{" "}
<b>{bond.quoteToken.name}</b> for bonding.
</>
}
>
<PrimaryButton
fullWidth
disabled={incorrectInputAmount || bond.isSoldOut || (!showDisclaimer && !checked)}
onClick={() => setModalOpen(true)}
>
Bond
</PrimaryButton>
</TokenAllowanceGuard>
</Box>
</Paper>
</Box>
)
};
export default BondInputArea;

View File

@ -18,6 +18,7 @@ import {
addLiquidity,
addLiquidityETH,
} from "../../hooks/uniswapv2";
import { useLocalStorage } from "../../hooks/localstorage";
const PoolContainer = ({
chainSymbol,
@ -37,6 +38,7 @@ const PoolContainer = ({
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)");
const { tryStoreDexInHistory } = useLocalStorage();
const [isPending, setIsPending] = useState(false);
const [amountTop, setAmountTop] = useState("");
@ -191,12 +193,16 @@ const PoolContainer = ({
deadline,
}
let txHash;
if (topIsNative || bottomIsNative) {
await addLiquidityETH(params)
txHash = await addLiquidityETH(params)
} else {
await addLiquidity(params);
txHash = await addLiquidity(params);
}
if (txHash) tryStoreDexInHistory(chainId, address, txHash);
await balanceRefetchTop();
await balanceRefetchBottom();
await pairAddressRefetch();
@ -215,7 +221,7 @@ const PoolContainer = ({
}, [pairAddress])
return (
<Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
<Box maxWidth="390px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
<SwapCollection
UpperSwapCard={
<SwapCard
@ -256,7 +262,7 @@ const PoolContainer = ({
justifyContent="space-between"
alignItems="center"
gap="0px"
maxWidth="356px"
maxWidth="390px"
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
>
<Box width="100%" display="flex" justifyContent="space-between">

View File

@ -21,6 +21,7 @@ import {
swapExactETHForTokens,
swapExactTokensForETH,
} from "../../hooks/uniswapv2";
import { useLocalStorage } from "../../hooks/localstorage";
import { EMPTY_ADDRESS } from "../../constants/addresses";
@ -45,6 +46,7 @@ const SwapContainer = ({
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)");
const { tryStoreDexInHistory } = useLocalStorage();
const [isPending, setIsPending] = useState(false);
const [amountBottom, setAmountBottom] = useState("");
@ -173,10 +175,12 @@ const SwapContainer = ({
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
const amountBMin = amountBDesired * bigIntSlippage / one;
let txHash;
if (isWrapping) {
await depositNative(chainId, address, amountADesired);
txHash = await depositNative(chainId, address, amountADesired);
} else if (isUnwrapping) {
await withdrawWeth(chainId, address, amountADesired);
txHash = await withdrawWeth(chainId, address, amountADesired);
} else {
const params = {
chainId,
@ -190,14 +194,16 @@ const SwapContainer = ({
};
if (topIsNative) {
await swapExactETHForTokens(params)
txHash = await swapExactETHForTokens(params)
} else if (bottomIsNative) {
await swapExactTokensForETH(params)
txHash = await swapExactTokensForETH(params)
} else {
await swapExactTokensForTokens(params);
txHash = await swapExactTokensForTokens(params);
}
}
if (txHash) tryStoreDexInHistory(chainId, address, txHash);
await balanceRefetchTop();
await balanceRefetchBottom();
await pairReservesRefetch();
@ -207,11 +213,11 @@ const SwapContainer = ({
}
return (
<Box maxWidth="356px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
<Box maxWidth="390px" display="flex" flexDirection="column" justifyContent="space-between" height="100%">
<SwapCollection
UpperSwapCard={
<SwapCard
maxWidth="356px"
maxWidth="390px"
id="from"
token={tokenNameTop}
chainTokenName={tokenNameTop !== chainSymbol && chainSymbol}
@ -230,7 +236,7 @@ const SwapContainer = ({
}
LowerSwapCard={
<SwapCard
maxWidth="356px"
maxWidth="390px"
id="to"
token={tokenNameBottom}
chainTokenName={tokenNameBottom !== chainSymbol && chainSymbol}
@ -252,7 +258,7 @@ const SwapContainer = ({
justifyContent="space-between"
alignItems="center"
gap="0px"
maxWidth="356px"
maxWidth="390px"
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
>
<Box width="100%" display="flex" justifyContent="space-between">

View File

@ -12,7 +12,7 @@ import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./i
import {
RESERVE_ADDRESSES,
FTSO_DAI_LP_ADDRESSES,
FTSO_RESERVE_LP_ADDRESSES,
BOND_DEPOSITORY_ADDRESSES,
} from "../../../../constants/addresses";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
@ -114,7 +114,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
const possibleTokens = [
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
{ value: FTSO_RESERVE_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_RESERVE_LP_ADDRESSES[chainId])}` },
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
];

View File

@ -0,0 +1,191 @@
import { useTransactionReceipt, useToken } from 'wagmi';
import { decodeEventLog } from 'viem';
import { useMemo } from "react";
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import { Box, Typography, Link } from "@mui/material";
import Token from "../../components/Token/Token";
import Modal from "../../components/Modal/Modal";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { PrimaryButton } from "../../components/Button";
import { abi as UniswapV2PairAbi } from "../../abi/UniswapV2Pair.json";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, shorten } from "../../helpers";
const TokenBox = ({ tokenAmount, tokenSymbol, decimals, chainTokenSymbol }) => {
return (
<Box display="flex" flexDirection="column" width="70px">
<Token
parentSx={{
height: "80px",
"& svg:nth-of-type(2)": {
transform: "translate(10%, -50%)",
zIndex: 100,
}
}}
sx={{ transform: "scale(1.65)", }}
chainTokenName={chainTokenSymbol}
name={tokenSymbol}
/>
<Box display="flex" flexDirection="column" alignItems="center">
<Typography>{formatNumber(
new DecimalBigNumber(tokenAmount ? tokenAmount : 0n, decimals),
5
)}</Typography>
</Box>
</Box>
)
}
const DexViewModal = ({ chainId, isOpen, close, config, historyRecord }) => {
if (historyRecord === null) return null;
const { data: receipt, isLoading, error } = useTransactionReceipt({
hash: historyRecord?.txHash,
query: {
enabled: !!historyRecord?.txHash && isOpen,
}
});
const allDecodedLogs = useMemo(() => {
if (!receipt?.logs) return [];
return receipt.logs.map((log) => {
try {
const decoded = decodeEventLog({
abi: UniswapV2PairAbi,
data: log.data,
topics: log.topics,
});
return {
eventName: decoded.eventName,
args: decoded.args,
address: log.address
};
} catch (e) {
return null;
}
}).filter(Boolean);
}, [receipt]);
const swapInfo = useMemo(() => {
const swapEvent = allDecodedLogs.find(l => l.eventName === 'Swap');
if (!swapEvent) return null;
const isZeroForOne = swapEvent.args.amount0In > 0n;
const transferLogs = allDecodedLogs.filter(l => l.eventName === 'Transfer');
const tokenInAddress = isZeroForOne
? transferLogs.find(t => t.args.value === swapEvent.args.amount0In)?.address
: transferLogs.find(t => t.args.value === swapEvent.args.amount1In)?.address;
const tokenOutAddress = isZeroForOne
? transferLogs.find(t => t.args.value === swapEvent.args.amount1Out)?.address
: transferLogs.find(t => t.args.value === swapEvent.args.amount0Out)?.address;
return {
from: swapEvent.args.sender,
to: swapEvent.args.to,
tokenInAddress,
tokenOutAddress,
amountIn: isZeroForOne ? swapEvent.args.amount0In : swapEvent.args.amount1In,
amountOut: isZeroForOne ? swapEvent.args.amount1Out : swapEvent.args.amount0Out,
};
}, [allDecodedLogs]);
const { data: tokenIn } = useToken({
address: swapInfo?.tokenInAddress,
scopeKey: `token-${swapInfo ? swapInfo.tokenInAddress : ""}-${chainId}`,
chainId
});
const { data: tokenOut } = useToken({
address: swapInfo?.tokenOutAddress,
scopeKey: `token-${swapInfo ? swapInfo.tokenOutAddress : ""}-${chainId}`,
chainId
});
const chainInfo = useMemo(() => {
const client = config?.getClient();
return {
url: client?.chain?.blockExplorers?.default?.url,
symbol: client?.chain?.nativeCurrency?.symbol,
};
}, [config]);
return (
<Modal
maxWidth="420px"
minHeight="200px"
open={isOpen}
headerText={
<>
{swapInfo && "DEX Swap"}
</>
}
onClose={() => close()}
>
<Box display="flex" flexDirection="column" gap="15px">
<Box width="100%" display="flex" flexDirection="column" justifyContent="space-between" alignItems="flex-start">
<Box display="flex" justifyContent="space-between" width="100%">
<Typography>{tokenIn ? tokenIn.symbol : "Unknown"} Sender</Typography>
<Link
href={`${chainInfo.url}/address/${swapInfo ? swapInfo.from : ""}`}
target="_blank"
rel="noopener noreferrer"
>
<Typography>{swapInfo ? shorten(swapInfo.from, 12, -10) : ""}</Typography>
</Link>
</Box>
<Box display="flex" justifyContent="space-between" width="100%">
<Typography>{tokenOut ? tokenOut.symbol : "Unknown"} Receiver</Typography>
<Link
href={`${chainInfo.url}/address/${swapInfo ? swapInfo.to : ""}`}
target="_blank"
rel="noopener noreferrer"
>
<Typography>{swapInfo ? shorten(swapInfo.to, 12, -10) : ""}</Typography>
</Link>
</Box>
</Box>
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-around" alignItems="center">
<TokenBox
tokenAmount={swapInfo ? swapInfo.amountIn : 0n}
tokenSymbol={tokenIn ? tokenIn.symbol : "Unknown"}
decimals={tokenIn ? tokenIn.decimals : 18}
chainTokenSymbol={tokenIn?.symbol.toUpperCase() === "WETH" ? undefined : chainInfo.symbol}
/>
<GhostStyledIcon sx={{ fontSize: '24px' }} component={ArrowRightIcon} />
<TokenBox
tokenAmount={swapInfo ? swapInfo.amountOut : 0n}
tokenSymbol={tokenOut ? tokenOut.symbol : "Unknown"}
decimals={tokenOut ? tokenOut.decimals : 18}
chainTokenSymbol={tokenOut?.symbol.toUpperCase() === "WETH" ? undefined : chainInfo.symbol}
/>
</Box>
<PrimaryButton
fullWidth
component="a"
href={`${chainInfo.url}/tx/${historyRecord.txHash}`}
target="_blank"
rel="noopener noreferrer"
>
View Transaction Details
</PrimaryButton>
</Box>
</Modal>
)
}
export default DexViewModal;

View File

@ -0,0 +1,23 @@
import { Box, Typography } from "@mui/material";
import Modal from "../../components/Modal/Modal";
import { PrimaryButton } from "../../components/Button";
const EraseModal = ({ isOpen, close, submit, isAll }) => {
return (
<Modal
maxWidth="350px"
minHeight="200px"
open={isOpen}
headerText={`Erase ${isAll ? "All Records" : "Record"}`}
onClose={() => close()}
>
<Box display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
<Typography>{`Erasing ${isAll ? "all records" : "this record"} is permanent, and cannot be recovered.`}</Typography>
<PrimaryButton fullWidth onClick={submit}>Erase {isAll ? "All Records" : ""}</PrimaryButton>
</Box>
</Modal>
)
}
export default EraseModal;

View File

@ -0,0 +1,401 @@
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
Container,
FormControlLabel,
FormControl,
Checkbox,
Link,
useMediaQuery,
useTheme,
} from "@mui/material";
import { useLocation } from "react-router-dom";
import { useEffect, useState, useMemo, useCallback } from "react";
import ReactGA from "react-ga4";
import SettingsIcon from '@mui/icons-material/Settings';
import Modal from "../../components/Modal/Modal";
import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import TokenStack from "../../components/TokenStack/TokenStack";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import Chip from "../../components/Chip/Chip";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { Tab, Tabs } from "../../components/Tabs/Tabs";
import { useTokenSymbol } from "../../hooks/tokens";
import { useLocalStorage } from "../../hooks/localstorage";
import { shorten } from "../../helpers";
import EraseModal from "./EraseModal";
import SettingsModal from "./SettingsModal";
import DexViewModal from "./DexViewModal";
import StakingViewModal from "./StakingViewModal";
const HistoryItem = ({ item, chainId, config, currentTime, eraseRecord, setViewModalOpen, isSmallScreen }) => {
const theme = useTheme();
const isVerySmallScreen = useMediaQuery("(max-width: 450px)");
const localString = new Date(item.submitted).toLocaleString();
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO")
const chainInfo = useMemo(() => {
const client = config?.getClient();
return {
url: client?.chain?.blockExplorers?.default?.url,
symbol: client?.chain?.nativeCurrency?.symbol,
};
}, [config]);
const chipStatus = useMemo(() => {
const delay = 3600000; // useEvmNetwork({ evmChainId: chainId })
const width = isVerySmallScreen ? "21px" : "120px"
if (item.submitted + delay > currentTime) {
return <Chip sx={{ width: width }} label={isVerySmallScreen ? "" : "Pending"} template={"gray"} />
}
// darkGray for final
return <Chip sx={{ width: width }} label={isVerySmallScreen ? "" : "Executed"} template={"success"} />
}, [item, currentTime, isVerySmallScreen])
// TODO: put correct values into TokenStack based on type
if (isSmallScreen || isVerySmallScreen) {
return (
<>
<Box display="flex" flexDirection="column" gap="10px">
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
flexDirection={isVerySmallScreen ? "row" : "row"}
>
<Box display="flex" gap="20px" p="10px 0">
<TokenStack chainTokenNames={[chainInfo.symbol]} tokens={["FTSO"]} />
<Box display="flex" flexDirection="column">
<Typography>{localString}</Typography>
<Link
href={`${chainInfo.url}/tx/${item.txHash}`}
target="_blank"
rel="noopener noreferrer"
>
<Typography>{shorten(item.txHash)}</Typography>
</Link>
</Box>
</Box>
{chipStatus}
</Box>
<Box display="flex" flexDirection="row" gap="20px">
<TertiaryButton fullWidth onClick={eraseRecord}>Erase</TertiaryButton>
<TertiaryButton fullWidth onClick={setViewModalOpen}>View</TertiaryButton>
</Box>
</Box>
</>
)
}
return (
<TableRow>
<TableCell sx={{ padding: "10px 0" }}>
<Box display="flex" alignItems="center">
<TokenStack chainTokenNames={["ETH"]} tokens={["FTSO"]} />
<Link
ml="10px"
href={`${chainInfo.url}/tx/${item.txHash}`}
target="_blank"
rel="noopener noreferrer"
>
<Typography>{shorten(item.txHash, 9, -7)}</Typography>
</Link>
</Box>
</TableCell>
<TableCell align="center" sx={{ padding: "10px 0" }}>
{chipStatus}
</TableCell>
<TableCell sx={{ padding: "10px 0" }}>
<Typography>{localString}</Typography>
</TableCell>
<TableCell sx={{ padding: "10px 0" }}>
<Box display="flex" flexDirection="row" gap="20px">
<TertiaryButton fullWidth onClick={eraseRecord}>Erase</TertiaryButton>
<TertiaryButton fullWidth onClick={setViewModalOpen}>View</TertiaryButton>
</Box>
</TableCell>
</TableRow>
)
}
const HistoryList = ({ chainId, storedTransactions, historyType, config, eraseRecord }) => {
const isSmallScreen = useMediaQuery("(max-width: 900px)");
const [currentTime, setCurrentTime] = useState(Date.now());
const [viewModalOpen, setViewModalOpen] = useState(null);
const {
BRIDGE_IN,
BRIDGE_OUT,
DEX_ACTION,
STAKE_ACTION,
BOND_ACTION
} = useLocalStorage();
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentTime(Date.now());
}, 5000);
return () => clearInterval(intervalId);
}, [])
const historyContainer = useMemo(() => {
if (isSmallScreen) {
return (
<Box display="flex" flexDirection="column" gap="50px">
{storedTransactions?.map((item, i) => {
return (
<HistoryItem
key={i}
item={item}
config={config}
chainId={chainId}
currentTime={currentTime}
eraseRecord={() => eraseRecord(i)}
setViewModalOpen={() => setViewModalOpen(i)}
isSmallScreen={isSmallScreen}
/>
)
})}
</Box>
)
} else {
return (
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell style={{ padding: "8px 0" }}>Transaction</TableCell>
<TableCell style={{ padding: "8px 0" }} align="center">Status</TableCell>
<TableCell style={{ padding: "8px 0" }}>Submitted</TableCell>
<TableCell style={{ padding: "8px 0" }}></TableCell>
</TableRow>
</TableHead>
<TableBody>
{storedTransactions?.map((item, i) => {
return (
<HistoryItem
key={i}
item={item}
config={config}
currentTime={currentTime}
eraseRecord={() => eraseRecord(i)}
setViewModalOpen={() => setViewModalOpen(i)}
isSmallScreen={isSmallScreen}
/>
)
})}
</TableBody>
</Table>
</TableContainer>
)
}
}, [isSmallScreen, storedTransactions, config]);
return (
<>
{viewModalOpen !== null && historyType === DEX_ACTION && (
<DexViewModal
chainId={chainId}
config={config}
close={() => setViewModalOpen(null)}
isOpen={true}
historyRecord={storedTransactions.at(viewModalOpen)}
/>
)}
{viewModalOpen !== null && historyType === STAKE_ACTION && (
<StakingViewModal
chainId={chainId}
config={config}
close={() => setViewModalOpen(null)}
isOpen={true}
historyRecord={storedTransactions.at(viewModalOpen)}
/>
)}
{historyContainer}
</>
)
}
const History = ({ chainId, config, tryConnectInjected, address }) => {
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const [historyTypeIndex, setHistoryTypeIndex] = useState(0);
const [erasingIndex, setErasingIndex] = useState(null);
const [settingsOpen, setSettingsOpen] = useState(false);
const [erasingOpen, setErasingOpen] = useState(false);
const location = useLocation();
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname });
}, [location]);
const {
POSSIBLE_HISTORY_TYPES,
getHistoryTypes,
setHistoryTypes,
getStorageValue,
setStorageValue
} = useLocalStorage();
const [storedHistoryTypes, setStoredHistoryTypes] = useState(() =>
getHistoryTypes(chainId, address)
)
const [storedTransactions, setStoredTransactions] = useState(() =>
getStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], [])
);
useEffect(() => {
setStoredTransactions(getStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], []));
}, [chainId, address, historyTypeIndex]);
const handleHistoryTypesChange = useCallback((values) => {
setHistoryTypes(chainId, address, values);
setStoredHistoryTypes(getHistoryTypes(chainId, address));
}, [chainId, address]);
const handleConfirmErase = useCallback(() => {
if (erasingIndex !== null) {
const updated = storedTransactions.filter((_, index) => index !== erasingIndex);
setStoredTransactions(updated);
setStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], updated);
} else {
setStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], []);
setStoredTransactions([])
}
setErasingOpen(false);
setErasingIndex(null);
}, [storedTransactions, erasingIndex, chainId, address, historyTypeIndex])
return (
<Box minHeight="calc(100vh - 43px)">
<PageTitle name="History" subtitle="A complete record of your activity on the ghostDAO protocol" />
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
}}
>
<EraseModal
key="EraseModal"
isOpen={erasingOpen}
close={() => {
setErasingOpen(false);
setErasingIndex(null);
}}
submit={handleConfirmErase}
isAll={erasingIndex === null}
/>
<SettingsModal
key="SettingsModal"
isOpen={settingsOpen}
close={() => setSettingsOpen(false)}
onChange={handleHistoryTypesChange}
selectedValues={storedHistoryTypes}
possibleValues={POSSIBLE_HISTORY_TYPES}
/>
<Box mt="15px">
<Paper
fullWidth
enableBackground
topRight={
<Box style={{ position: "absolute", top: "28px", right: "20px", zIndex: 10 }}>
<GhostStyledIcon
component={SettingsIcon}
viewBox="0 0 23 23"
style={{ display: "flex", cursor: "pointer" }}
onClick={() => setSettingsOpen(true)}
/>
</Box>
}
headerContent={
<Box
width="100%"
display="flex"
flexDirection="column"
justifyContent="space-between"
alignItems="center"
gap="15px"
>
<Tabs
centered
textColor="primary"
indicatorColor="primary"
value={storedHistoryTypes[historyTypeIndex]}
aria-label="History menu"
onChange={(_, selectedValue) => {
const index = storedHistoryTypes.indexOf(selectedValue);
if (index !== -1) setHistoryTypeIndex(index);
}}
TabIndicatorProps={{ style: { display: "none" } }}
>
{storedHistoryTypes.map((type, i) => {
return (
<Tab
key={i}
value={type}
aria-label={`history-${i}-button`}
label={type}
style={{ fontSize: "1.2rem" }}
/>
)
})}
</Tabs>
<PrimaryButton
fullWidth
onClick={() => setErasingOpen(true)}
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
>
Erase All Records
</PrimaryButton>
</Box>
}
>
<HistoryList
chainId={chainId}
config={config}
eraseRecord={(index) => {
setErasingIndex(index);
setErasingOpen(true);
}}
historyType={storedHistoryTypes[historyTypeIndex]}
storedTransactions={storedTransactions}
/>
</Paper>
</Box>
</Container>
</Box>
);
}
export default History;

View File

@ -0,0 +1,49 @@
import { Box, FormControlLabel, Checkbox, Typography } from "@mui/material";
import Modal from "../../components/Modal/Modal";
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
const SettingsModal = ({ isOpen, close, submit, selectedValues, possibleValues, onChange }) => {
const handleCheckboxChange = (type, isChecked) => {
const updatedTypes = possibleValues.filter(v => {
if (v === type) return isChecked
return selectedValues.includes(v)
})
onChange(updatedTypes);
};
return (
<Modal
key="SettingsModal"
maxWidth="350px"
minHeight="200px"
open={isOpen}
headerText="History Settings"
onClose={() => close()}
>
<Box display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
{possibleValues.map((type, i) => {
const isChecked = selectedValues.includes(type);
return (
<FormControlLabel
key={i}
control={
<Checkbox
inputProps={{ "data-testid": `history-type-${type}` }}
id={`history-type-${type}`}
checked={isChecked}
onChange={event => handleCheckboxChange(type, event.target.checked)}
icon={<CheckBoxOutlineBlank viewBox="0 0 24 24" />}
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
/>
}
label={<Typography variant="body1">{isChecked ? "Disable" : "Enable"} {type} History Storage</Typography>}
/>
)
})}
</Box>
</Modal>
)
}
export default SettingsModal;

View File

@ -0,0 +1,159 @@
import { useTransactionReceipt } from 'wagmi';
import { decodeEventLog } from 'viem';
import { useMemo } from "react";
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import { Box, Typography, Link } from "@mui/material";
import Token from "../../components/Token/Token";
import Modal from "../../components/Modal/Modal";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import { PrimaryButton } from "../../components/Button";
import { abi as StakingAbi } from "../../abi/GhostStaking.json";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, shorten } from "../../helpers";
import { useTokenSymbol } from "../../hooks/tokens";
import { getTokenDecimals } from "../../hooks/helpers";
const TokenBox = ({ tokenAmount, tokenSymbol, decimals, chainTokenSymbol }) => {
return (
<Box display="flex" flexDirection="column" width="70px">
<Token
parentSx={{
height: "80px",
"& svg:nth-of-type(2)": {
transform: "translate(10%, -50%)",
zIndex: 100,
}
}}
sx={{ transform: "scale(1.65)", }}
chainTokenName={chainTokenSymbol}
name={tokenSymbol}
/>
<Box display="flex" flexDirection="column" alignItems="center">
<Typography>{tokenSymbol}</Typography>
<Typography>{formatNumber(
new DecimalBigNumber(tokenAmount ? tokenAmount : 0n, decimals),
5
)}</Typography>
</Box>
</Box>
)
}
const StakingViewModal = ({ chainId, isOpen, close, config, historyRecord }) => {
if (historyRecord === null) return null;
const { data: receipt, isLoading, error } = useTransactionReceipt({
hash: historyRecord?.txHash,
query: {
enabled: !!historyRecord?.txHash && isOpen,
}
});
const allDecodedLogs = useMemo(() => {
if (!receipt?.logs) return [];
return receipt.logs.map((log) => {
try {
const decoded = decodeEventLog({
abi: StakingAbi,
data: log.data,
topics: log.topics,
});
return {
eventName: decoded.eventName,
args: decoded.args,
address: log.address
};
} catch (e) {
return null;
}
}).filter(Boolean);
}, [receipt]);
const stakeInfo = useMemo(() => {
return allDecodedLogs.find(l => l.eventName === 'Staked');
}, [allDecodedLogs]);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const ftsoDecimals = getTokenDecimals("FTSO");
const chainInfo = useMemo(() => {
const client = config?.getClient();
return {
url: client?.chain?.blockExplorers?.default?.url,
symbol: client?.chain?.nativeCurrency?.symbol,
};
}, [config]);
return (
<Modal
maxWidth="420px"
minHeight="200px"
open={isOpen}
headerText={
<>
{stakeInfo && "Staked"}
</>
}
onClose={() => close()}
>
<Box display="flex" flexDirection="column" gap="15px">
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<Token
parentSx={{
height: "80px",
paddingLeft: "10px",
"& svg:nth-of-type(2)": {
transform: "translate(50%, -50%)",
zIndex: 100,
}
}}
sx={{ transform: "scale(1.65)", }}
chainTokenName={chainInfo.symbol}
name={ftsoSymbol ? ftsoSymbol : ""}
/>
<Box flexBasis="75%" display="flex" flexDirection="column">
<Box display="flex" justifyContent="space-between">
<Typography>Receiver</Typography>
<Link
href={`${chainInfo.url}/address/${stakeInfo.args.receiver}`}
target="_blank"
rel="noopener noreferrer"
>
<Typography>{shorten(stakeInfo.args.receiver, 9, -7)}</Typography>
</Link>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography>Amount</Typography>
<Typography>{formatNumber(
new DecimalBigNumber(stakeInfo ? stakeInfo.args.amount : 0n, ftsoDecimals),
5
)} {ftsoSymbol}</Typography>
</Box>
</Box>
</Box>
<PrimaryButton
fullWidth
component="a"
href={`${chainInfo.url}/tx/${historyRecord.txHash}`}
target="_blank"
rel="noopener noreferrer"
>
View Transaction Details
</PrimaryButton>
</Box>
</Modal>
)
}
export default StakingViewModal;

View File

@ -78,7 +78,6 @@ export const StakeContainer = ({ chainId, address, config, connect }) => {
Rebase&nbsp;
<Countdown otherwise="Ready" targetTime={epoch.end * 1000} />
</Typography>
</Box>
}
>

View File

@ -12,6 +12,7 @@ import Token from "../../../components/Token/Token";
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { useLocalStorage } from "../../../hooks/localstorage";
import { useWarmupLength, stake, unstake, wrap, unwrap } from "../../../hooks/staking";
import { formatNumber } from "../../../helpers";
@ -22,6 +23,8 @@ const StakeConfirmationModal = (props) => {
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
const [isPending, setIsPending] = useState(false);
const { tryStoreStakeInHistory } = useLocalStorage();
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup])
const AcknowledgeWarmupCheckbox = () => {
@ -73,11 +76,12 @@ const StakeConfirmationModal = (props) => {
setIsPending(true);
const actionAmount = new DecimalBigNumber(props.amount, props.spendDecimals);
let isRebase = false;
let txHash = null;
switch (props.action.toUpperCase()) {
case "STAKE":
isRebase = props.bottomToken === props.stnkSymbol;
await stake(
txHash = await stake(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@ -90,7 +94,7 @@ const StakeConfirmationModal = (props) => {
break;
case "UNSTAKE":
isRebase = props.upperToken === props.stnkSymbol;
await unstake(
txHash = await unstake(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@ -102,7 +106,7 @@ const StakeConfirmationModal = (props) => {
);
break;
case "WRAP":
await wrap(
txHash = await wrap(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@ -110,7 +114,7 @@ const StakeConfirmationModal = (props) => {
);
break;
case "UNWRAP":
await unwrap(
txHash = await unwrap(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@ -121,6 +125,8 @@ const StakeConfirmationModal = (props) => {
console.log("Wrong action to be executed!");
}
if (txHash) tryStoreStakeInHistory(props.chainId, props.address, txHash)
setIsPending(false);
props.onClose();
}
@ -156,7 +162,7 @@ const StakeConfirmationModal = (props) => {
chainTokenName={props.chainSymbol}
name={props.upperToken}
/>
<Typography>{props.upperToken}</Typography>
<Typography>{props.upperToken}</Typography>
</Box>
</Box>
<GhostStyledIcon component={ArrowRightIcon} />

View File

@ -273,24 +273,26 @@ export const useNotes = (chainId, address) => {
return { notes, refetch };
}
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => {
const args = [bondId, amount, maxPrice, user, referral];
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, isNative }) => {
const args = [bondId, amount, maxPrice, user];
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({
const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
abi: BondAbi,
address: BOND_DEPOSITORY_ADDRESSES[chainId],
functionName: "deposit",
account: user,
account: sender,
value: isNative ? amount : undefined
});
return txHash;
}
export const redeem = async ({ chainId, user, isGhst, indexes }) => {

View File

@ -1,5 +1,4 @@
import { createContext, useContext, useState, useMemo } from "react";
import { claim } from "../../hooks/staking";
const emptyFunction = () => {};
const BreakoutModalContext = createContext();

View File

@ -5,7 +5,7 @@ import { getObservableClient } from "@polkadot-api/observable-client"
import useSWR from "swr"
const MAX_BLOCK_TIMEOUT = 15000
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
const DEFAULT_CHAIN_ID = "0xcaf9c5ddccc7ba6f96149a6b8a81c26a3641742795cd5e7e0aee79bf3f4e77b7"
const UnstableProvider = createContext(null)
export const useUnstableProvider = () => useContext(UnstableProvider)
@ -39,11 +39,11 @@ export const UnstableProviderProvider = ({ children }) => {
return undefined
}, [accounts])
const connectionState = useMemo(() => {
const connectionState = useMemo(async () => {
if (!providerDetail) return 'no-extension';
if (!provider) return 'loading';
const chains = provider.getChains();
const chains = await provider.getChains();
if (chains[chainId]) return 'connected';
return 'wrong-network';

View File

@ -6,7 +6,7 @@ import {
FTSO_ADDRESSES,
STNK_ADDRESSES,
GHST_ADDRESSES,
FTSO_DAI_LP_ADDRESSES,
FTSO_RESERVE_LP_ADDRESSES,
WETH_ADDRESSES,
} from "../constants/addresses";
import { config } from "../config";
@ -134,10 +134,10 @@ export const getTokenAddress = (chainId, name) => {
address = GHST_ADDRESSES[chainId];
break;
case "GDAI_FTSO":
address = FTSO_DAI_LP_ADDRESSES[chainId];
address = FTSO_RESERVE_LP_ADDRESSES[chainId];
break;
case "RESERVE_FTSO":
address = FTSO_DAI_LP_ADDRESSES[chainId];
address = FTSO_RESERVE_LP_ADDRESSES[chainId];
break;
case "WETH":
address = WETH_ADDRESSES[chainId];
@ -173,7 +173,7 @@ export const getTokenIcons = (chainId, address) => {
case GHST_ADDRESSES[chainId]:
icons = ["GHST"];
break;
case FTSO_DAI_LP_ADDRESSES[chainId]:
case FTSO_RESERVE_LP_ADDRESSES[chainId]:
icons = ["WETH", "FTSO"];
break;
default:
@ -185,7 +185,7 @@ export const getTokenIcons = (chainId, address) => {
export const getChainTokenNames = (chainId, address) => {
let icons = [undefined];
switch (address) {
case FTSO_DAI_LP_ADDRESSES[chainId]:
case FTSO_RESERVE_LP_ADDRESSES[chainId]:
icons = [undefined, "WETH"];
break;
case RESERVE_ADDRESSES[chainId]:
@ -199,7 +199,7 @@ export const getChainTokenNames = (chainId, address) => {
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => {
let stringValue = tokenNameConverter(chainId, "WETH")
if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
if (tokenAddress.toUpperCase() === FTSO_RESERVE_LP_ADDRESSES[chainId].toUpperCase()) {
stringValue = `${baseTokenSymbol}-${stringValue} LP`;
}
return stringValue;
@ -214,7 +214,7 @@ export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => {
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`;
}
break;
case FTSO_DAI_LP_ADDRESSES[chainId]:
case FTSO_RESERVE_LP_ADDRESSES[chainId]:
purchaseUrl += `?pool=true&from=${RESERVE_ADDRESSES[chainId]}&to=${FTSO_ADDRESSES[chainId]}`;
break;
}
@ -248,6 +248,7 @@ export const executeOnChainTransaction = async ({
onReplaced: () => toast(messages.replacedMsg),
chainId
});
// TODO: onReplaced
toast.success(messages.successMsg);

View File

@ -3,6 +3,17 @@ import { createContext, useContext, useState, useEffect } from "react";
const LocalStorageContext = createContext();
export const useLocalStorage = () => useContext(LocalStorageContext);
const BRIDGE_IN = "Bridge-In";
const BRIDGE_OUT = "Bridge-Out";
const DEX_ACTION = "DEX";
const STAKE_ACTION = "Stake";
const BOND_ACTION = "Bond";
const POSSIBLE_HISTORY_TYPES = [BRIDGE_IN, BRIDGE_OUT, DEX_ACTION, STAKE_ACTION, BOND_ACTION];
const DEFAULT_HISTORY_TYPES = [BRIDGE_IN, BRIDGE_OUT];
const HISTORY_PREFIX = "history"
export const LocalStorageProvider = ({ children }) => {
const getStorageKey = (chainId, address, target) => {
return `${chainId}:${address}:${target}`;
@ -19,8 +30,58 @@ export const LocalStorageProvider = ({ children }) => {
localStorage.setItem(key, JSON.stringify(value));
}
const getHistoryTypes = (chainId, address) => {
const key = getStorageKey(chainId, address, HISTORY_PREFIX);
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : DEFAULT_HISTORY_TYPES;
}
const setHistoryTypes = (chainId, address, value) => {
const key = getStorageKey(chainId, address, HISTORY_PREFIX);
localStorage.setItem(key, JSON.stringify(value));
}
const tryStoreInHistory = (chainId, address, target, txHash) => {
const historyTypes = getHistoryTypes(chainId, address);
if (!historyTypes.includes(target)) return;
const key = getStorageKey(chainId, address, target);
const stored = localStorage.getItem(key);
const values = stored ? JSON.parse(stored) : [];
const value = { txHash, submitted: Date.now() };
const newValues = [value, ...values];
localStorage.setItem(key, JSON.stringify(newValues));
}
const tryStoreBondInHistory = (chainId, address, txHash) => {
tryStoreInHistory(chainId, address, BOND_ACTION, txHash);
}
const tryStoreStakeInHistory = (chainId, address, txHash) => {
tryStoreInHistory(chainId, address, STAKE_ACTION, txHash);
}
const tryStoreDexInHistory = (chainId, address, txHash) => {
tryStoreInHistory(chainId, address, DEX_ACTION, txHash);
}
return (
<LocalStorageContext.Provider value={{ getStorageValue, setStorageValue }}>
<LocalStorageContext.Provider value={{
BRIDGE_IN,
BRIDGE_OUT,
DEX_ACTION,
STAKE_ACTION,
BOND_ACTION,
POSSIBLE_HISTORY_TYPES,
tryStoreBondInHistory,
tryStoreStakeInHistory,
tryStoreDexInHistory,
getHistoryTypes,
setHistoryTypes,
getStorageValue,
setStorageValue
}}>
{children}
</LocalStorageContext.Provider>
)

View File

@ -5,7 +5,7 @@ import { useCurrentIndex, useGhostedSupply } from "../staking";
import { useUniswapV2PairReserves } from "../uniswapv2";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import {
FTSO_DAI_LP_ADDRESSES,
FTSO_RESERVE_LP_ADDRESSES,
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
CEX_TICKERS,
@ -135,7 +135,7 @@ export const useReservePrice = (chainId) => {
};
export const useFtsoPrice = (chainId) => {
const { reserves, tokens, refetch } = useUniswapV2PairReserves(chainId, FTSO_DAI_LP_ADDRESSES[chainId]);
const { reserves, tokens, refetch } = useUniswapV2PairReserves(chainId, FTSO_RESERVE_LP_ADDRESSES[chainId]);
const reservePrice = useReservePrice(chainId);
const reserveAddress = RESERVE_ADDRESSES[chainId];

View File

@ -181,7 +181,7 @@ export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSym
successMsg: `${ftsoSymbol} tokens staked successfully! Wait for the warm-up period to claim your ${isRebase ? stnkSymbol : ghstSymbol}.`,
errorMsg: "Staking transaction failed. Check logs for error detalization.",
};
await executeOnChainTransaction({
const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
@ -190,6 +190,7 @@ export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSym
address: STAKING_ADDRESSES[chainId],
functionName: "stake",
});
return txHash;
}
export const unstake = async (chainId, account, amount, isTrigger, isRebase, ftsoSymbol, stnkSymbol, ghstSymbol) => {
@ -199,7 +200,7 @@ export const unstake = async (chainId, account, amount, isTrigger, isRebase, fts
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({
const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
@ -208,6 +209,7 @@ export const unstake = async (chainId, account, amount, isTrigger, isRebase, fts
address: STAKING_ADDRESSES[chainId],
functionName: "unstake",
});
return txHash;
}
export const forfeit = async (chainId, account, ftsoSymbol) => {
@ -216,7 +218,7 @@ 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({
const txHash = await executeOnChainTransaction({
chainId,
messages,
account,
@ -225,6 +227,7 @@ export const forfeit = async (chainId, account, ftsoSymbol) => {
address: STAKING_ADDRESSES[chainId],
functionName: "forfeit",
});
return txHash;
}
export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) => {
@ -234,7 +237,7 @@ 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({
const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
@ -243,6 +246,7 @@ export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) =>
address: STAKING_ADDRESSES[chainId],
functionName: "claim",
});
return txHash;
}
export const breakout = async (chainId, account, receiver, amount) => {
@ -261,7 +265,6 @@ export const breakout = async (chainId, account, receiver, amount) => {
address: STAKING_ADDRESSES[chainId],
functionName: "breakout",
});
return txHash;
}
@ -281,6 +284,7 @@ export const unwrap = async (chainId, account, amount, stnkSymbol) => {
address: STAKING_ADDRESSES[chainId],
functionName: "unwrap",
});
return txHash;
}
export const wrap = async (chainId, account, amount, ghstSymbol) => {
@ -299,6 +303,7 @@ export const wrap = async (chainId, account, amount, ghstSymbol) => {
address: STAKING_ADDRESSES[chainId],
functionName: "wrap",
});
return txHash;
}
export const ghost = async (chainId, account, receiver, amount) => {
@ -317,6 +322,5 @@ export const ghost = async (chainId, account, receiver, amount) => {
address: STAKING_ADDRESSES[chainId],
functionName: "ghost",
});
return txHash;
}

View File

@ -33,7 +33,7 @@ export const swapExactETHForTokens = async ({
deadline
];
await executeOnChainTransaction({
const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@ -43,6 +43,8 @@ export const swapExactETHForTokens = async ({
messages: swapMessages,
value: amountADesired,
});
return txHash;
}
export const swapExactTokensForETH = async ({
@ -63,7 +65,7 @@ export const swapExactTokensForETH = async ({
deadline
];
await executeOnChainTransaction({
const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@ -72,6 +74,8 @@ export const swapExactTokensForETH = async ({
account: address,
messages: swapMessages,
});
return txHash;
}
export const swapExactTokensForTokens = async ({
@ -92,7 +96,7 @@ export const swapExactTokensForTokens = async ({
deadline
];
await executeOnChainTransaction({
const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@ -101,6 +105,8 @@ export const swapExactTokensForTokens = async ({
account: address,
messages: swapMessages,
});
return txHash;
}
export const addLiquidity = async ({
@ -126,7 +132,7 @@ export const addLiquidity = async ({
deadline
];
await executeOnChainTransaction({
const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@ -135,6 +141,8 @@ export const addLiquidity = async ({
account: address,
messages: addMessages,
});
return txHash;
}
export const addLiquidityETH = async ({
@ -172,7 +180,7 @@ export const addLiquidityETH = async ({
deadline
];
await executeOnChainTransaction({
const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@ -182,4 +190,6 @@ export const addLiquidityETH = async ({
messages: addMessages,
value: amountETHDesired,
});
return txHash;
}