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:
parent
a911859841
commit
314abe1aba
@ -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"
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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'),
|
||||
// ])
|
||||
},
|
||||
})
|
||||
|
||||
@ -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",
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
<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}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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])}` },
|
||||
];
|
||||
|
||||
|
||||
191
src/containers/History/DexViewModal.jsx
Normal file
191
src/containers/History/DexViewModal.jsx
Normal 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;
|
||||
23
src/containers/History/EraseModal.jsx
Normal file
23
src/containers/History/EraseModal.jsx
Normal 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;
|
||||
401
src/containers/History/History.jsx
Normal file
401
src/containers/History/History.jsx
Normal 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;
|
||||
49
src/containers/History/SettingsModal.jsx
Normal file
49
src/containers/History/SettingsModal.jsx
Normal 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;
|
||||
159
src/containers/History/StakingViewModal.jsx
Normal file
159
src/containers/History/StakingViewModal.jsx
Normal 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;
|
||||
@ -78,7 +78,6 @@ export const StakeContainer = ({ chainId, address, config, connect }) => {
|
||||
Rebase
|
||||
<Countdown otherwise="Ready" targetTime={epoch.end * 1000} />
|
||||
</Typography>
|
||||
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { createContext, useContext, useState, useMemo } from "react";
|
||||
import { claim } from "../../hooks/staking";
|
||||
|
||||
const emptyFunction = () => {};
|
||||
const BreakoutModalContext = createContext();
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user