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",
|
"name": "ghost-dao-interface",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.58",
|
"version": "0.7.59-beta-2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@ -55,5 +55,6 @@
|
|||||||
"sass-embedded": "^1.85.1",
|
"sass-embedded": "^1.85.1",
|
||||||
"vite": "^6.2.0",
|
"vite": "^6.2.0",
|
||||||
"vite-plugin-svgr": "^4.3.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 Governance = lazy(() => import("./containers/Governance/Governance"));
|
||||||
const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails"));
|
const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails"));
|
||||||
const NewProposal = lazy(() => import("./containers/Governance/NewProposal"));
|
const NewProposal = lazy(() => import("./containers/Governance/NewProposal"));
|
||||||
|
const History = lazy(() => import("./containers/History/History"));
|
||||||
|
|
||||||
const PREFIX = "App";
|
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" 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/: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="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>
|
||||||
}
|
}
|
||||||
<Route path="/empty" element={<NotFound
|
<Route path="/empty" element={<NotFound
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import HubIcon from '@mui/icons-material/Hub';
|
|||||||
import PublicIcon from '@mui/icons-material/Public';
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||||
import GavelIcon from '@mui/icons-material/Gavel';
|
import GavelIcon from '@mui/icons-material/Gavel';
|
||||||
|
import HistoryIcon from '@mui/icons-material/History';
|
||||||
import CasinoIcon from '@mui/icons-material/Casino';
|
import CasinoIcon from '@mui/icons-material/Casino';
|
||||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
||||||
import BookIcon from '@mui/icons-material/Book';
|
import BookIcon from '@mui/icons-material/Book';
|
||||||
@ -145,9 +146,6 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
to={`/${chainName}/bonds`}
|
to={`/${chainName}/bonds`}
|
||||||
children={
|
children={
|
||||||
<AccordionDetails style={{ margin: "0 0 -20px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
<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) => {
|
{sortedGhostBonds.map((bond, index) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@ -194,7 +192,7 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<NavItem href="https://ghostchain.io/game-theory-3-3" icon={CasinoIcon} label={`${bridgeNumbers} Game Theory`} />
|
<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`} />
|
<NavItem href="https://docs.ghostchain.io/" icon={BookIcon} label={`Documentation`} />
|
||||||
<StyledBox display="flex" justifyContent="space-around" paddingY="24px">
|
<StyledBox display="flex" justifyContent="space-around" paddingY="24px">
|
||||||
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">
|
<Link href="https://git.ghostchain.io/ghostchain/ghost-dao-contracts" target="_blank" rel="noopener noreferrer">
|
||||||
|
|||||||
@ -1,45 +1,46 @@
|
|||||||
import { defineChain } from 'viem'
|
import { defineChain } from 'viem'
|
||||||
import { http, fallback, createConfig } from 'wagmi'
|
import { http, fallback, createConfig } from 'wagmi'
|
||||||
import { sepolia, hoodi } from 'wagmi/chains'
|
import { hoodi } from 'wagmi/chains'
|
||||||
|
|
||||||
const mordor = defineChain({
|
// const mordor = defineChain({
|
||||||
id: 63,
|
// id: 63,
|
||||||
name: 'Mordor',
|
// name: 'Mordor',
|
||||||
nativeCurrency: {
|
// nativeCurrency: {
|
||||||
decimals: 18,
|
// decimals: 18,
|
||||||
name: 'METC',
|
// name: 'METC',
|
||||||
symbol: 'mETC',
|
// symbol: 'mETC',
|
||||||
},
|
// },
|
||||||
rpcUrls: {
|
// rpcUrls: {
|
||||||
default: { http: ['https://rpc.mordor.etccooperative.org'] },
|
// default: { http: ['https://rpc.mordor.etccooperative.org'] },
|
||||||
},
|
// },
|
||||||
blockExplorers: {
|
// blockExplorers: {
|
||||||
default: {
|
// default: {
|
||||||
name: 'Blockscout',
|
// name: 'Blockscout',
|
||||||
url: 'https://etc-mordor.blockscout.com/'
|
// url: 'https://etc-mordor.blockscout.com/'
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
export const config = createConfig({
|
export const config = createConfig({
|
||||||
chains: [sepolia, hoodi, mordor],
|
chains: [hoodi],
|
||||||
transports: {
|
transports: {
|
||||||
[sepolia.id]: fallback([
|
// [sepolia.id]: fallback([
|
||||||
http('https://ethereum-sepolia-rpc.publicnode.com'),
|
// http('https://ethereum-sepolia-rpc.publicnode.com'),
|
||||||
http('https://1rpc.io/sepolia'),
|
// http('https://1rpc.io/sepolia'),
|
||||||
http('https://eth-sepolia.public.blastapi.io'),
|
// http('https://eth-sepolia.public.blastapi.io'),
|
||||||
http('https://0xrpc.io/sep'),
|
// http('https://0xrpc.io/sep'),
|
||||||
http('https://api.zan.top/eth-sepolia'),
|
// http('https://api.zan.top/eth-sepolia'),
|
||||||
http('https://eth-sepolia.api.onfinality.io/public'),
|
// http('https://eth-sepolia.api.onfinality.io/public'),
|
||||||
http('https://sepolia.drpc.org')
|
// http('https://sepolia.drpc.org')
|
||||||
]),
|
// ]),
|
||||||
[hoodi.id]: fallback([
|
[hoodi.id]: fallback([
|
||||||
http('https://rpc.hoodi.ethpandaops.io'),
|
http('https://rpc.hoodi.ethpandaops.io'),
|
||||||
|
http('https://rpc.sentio.xyz/hoodi'),
|
||||||
http('https://0xrpc.io/hoodi'),
|
http('https://0xrpc.io/hoodi'),
|
||||||
]),
|
]),
|
||||||
[mordor.id]: fallback([
|
// [mordor.id]: fallback([
|
||||||
http('https://rpc.mordor.etccooperative.org'),
|
// http('https://rpc.mordor.etccooperative.org'),
|
||||||
http('https://geth-mordor.etc-network.info'),
|
// http('https://geth-mordor.etc-network.info'),
|
||||||
])
|
// ])
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,25 +4,25 @@ export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|||||||
|
|
||||||
export const STAKING_ADDRESSES = {
|
export const STAKING_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
[NetworkId.TESTNET_HOODI]: "0x5d659FDEFc9c1552A3a53f661Cf4F52b0d7dDb40",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BOND_DEPOSITORY_ADDRESSES = {
|
export const BOND_DEPOSITORY_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
[NetworkId.TESTNET_HOODI]: "0x4105D6b48f0fEfA8033b8B01d89972c2CBd787bD",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DAO_TREASURY_ADDRESSES = {
|
export const DAO_TREASURY_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
[NetworkId.TESTNET_HOODI]: "0xeA5113bF7e0af48B6D0Cf1a4B1e4dD0184708a09",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FTSO_DAI_LP_ADDRESSES = {
|
export const FTSO_RESERVE_LP_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
|
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D",
|
[NetworkId.TESTNET_HOODI]: "0xf3F2cc4DaB20F09eee0d07d8456924927ceA1847",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
|
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,43 +46,43 @@ export const WETH_ADDRESSES = {
|
|||||||
|
|
||||||
export const GHST_ADDRESSES = {
|
export const GHST_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
[NetworkId.TESTNET_HOODI]: "0x594a5CD093859B3FD48aA901d86F71a7D537834a",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const STNK_ADDRESSES = {
|
export const STNK_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
[NetworkId.TESTNET_HOODI]: "0x7a7d0F214b8C03bD962B88AC0a7F543085aB817B",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FTSO_ADDRESSES = {
|
export const FTSO_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
[NetworkId.TESTNET_HOODI]: "0x07427B220934B3421223969eA61a247bFA37cF03",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DISTRIBUTOR_ADDRESSES = {
|
export const DISTRIBUTOR_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
[NetworkId.TESTNET_HOODI]: "0xe503d65C9574bD3C18c8d725cD346eCBFAE24b4d",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GHOST_GOVERNANCE_ADDRESSES = {
|
export const GHOST_GOVERNANCE_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
|
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
|
[NetworkId.TESTNET_HOODI]: "0x0000000000000000000000000000000000000000",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
|
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BONDING_CALCULATOR_ADDRESSES = {
|
export const BONDING_CALCULATOR_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
||||||
[NetworkId.TESTNET_HOODI]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
[NetworkId.TESTNET_HOODI]: "0xf13A5319aD8071e4dcE7776728Ef9Cc221A5c098",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GATEKEEPER_ADDRESSES = {
|
export const GATEKEEPER_ADDRESSES = {
|
||||||
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B",
|
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B",
|
||||||
[NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
|
[NetworkId.TESTNET_HOODI]: "0xc9D5f50e17eeEAF39E0D0cDbbcF4B04FA4A4Bbd6",
|
||||||
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
|
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { ArrowBack } from "@mui/icons-material";
|
import { Box, Container, Skeleton, Typography, useMediaQuery } from "@mui/material";
|
||||||
import { Box, Link, Skeleton, Typography } from "@mui/material";
|
|
||||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
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 { useAccount, useSwitchChain } from "wagmi";
|
||||||
import { isAddress } from "viem";
|
import { isAddress } from "viem";
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
import Countdown from "../../components/Countdown/Countdown";
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
import PageTitle from "../../components/PageTitle/PageTitle";
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
import Metric from "../../components/Metric/Metric";
|
import Metric from "../../components/Metric/Metric";
|
||||||
|
import MetricCollection from "../../components/Metric/MetricCollection";
|
||||||
import TokenStack from "../../components/TokenStack/TokenStack";
|
import TokenStack from "../../components/TokenStack/TokenStack";
|
||||||
|
|
||||||
import BondPrice from "./components/BondPrice";
|
import BondPrice from "./components/BondPrice";
|
||||||
@ -40,6 +42,9 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
|
|||||||
const { network } = useParams();
|
const { network } = useParams();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
const [isSettingsOpen, setSettingsOpen] = useState(false);
|
const [isSettingsOpen, setSettingsOpen] = useState(false);
|
||||||
const { getStorageValue, setStorageValue } = useLocalStorage();
|
const { getStorageValue, setStorageValue } = useLocalStorage();
|
||||||
|
|
||||||
@ -102,17 +107,9 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<PageTitle
|
<PageTitle
|
||||||
|
subtitle="We need this one just for alignment"
|
||||||
name={
|
name={
|
||||||
<Box display="flex" flexDirection="row" alignItems="center">
|
<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
|
<TokenStack
|
||||||
chainTokenNames={preparedQuoteToken.chainNames}
|
chainTokenNames={preparedQuoteToken.chainNames}
|
||||||
tokens={preparedQuoteToken.icons}
|
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">
|
<Box display="flex" flexDirection="column" ml={1} justifyContent="center" alignItems="center">
|
||||||
<Typography variant="h4" fontWeight={500}>
|
<Typography variant="h4" fontWeight={500}>
|
||||||
{preparedQuoteToken.name}
|
{preparedQuoteToken.name} Bond
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -139,30 +136,45 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
|
|||||||
onDecimalsChange={event => setFormatDecimalsInner(event.currentTarget.value)}
|
onDecimalsChange={event => setFormatDecimalsInner(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box display="flex" flexDirection="row" justifyContent="space-between" width={["100%", "70%"]} mt="24px">
|
<Container
|
||||||
<Metric
|
style={{
|
||||||
label={`Bond Price`}
|
marginTop: "15px",
|
||||||
metric={
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
bond.isSoldOut ? (
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
"--"
|
}}
|
||||||
) : (
|
>
|
||||||
<BondPrice
|
<Paper
|
||||||
price={bond.price.inUsd}
|
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
|
<MetricCollection>
|
||||||
label="Market Price"
|
<Metric
|
||||||
metric={<TokenPrice priceInUsd={bond.price.marketPriceInUsd} />}
|
label={`Bond Price`}
|
||||||
/>
|
tooltip="Some description about what the bond price is"
|
||||||
<Metric
|
metric={bond.isSoldOut ? ( "--" ) : ( <BondPrice price={bond.price.inUsd} /> )}
|
||||||
label="Discount"
|
/>
|
||||||
metric={<BondDiscount discount={bond.discount} textOnly />}
|
<Metric
|
||||||
/>
|
label="Market Price"
|
||||||
</Box>
|
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
|
<BondInputArea
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
bond={bond}
|
bond={bond}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import BondDiscount from "./BondDiscount";
|
|||||||
import BondVesting from "./BondVesting";
|
import BondVesting from "./BondVesting";
|
||||||
import BondSlippage from "./BondSlippage";
|
import BondSlippage from "./BondSlippage";
|
||||||
|
|
||||||
|
import { useLocalStorage } from "../../../hooks/localstorage";
|
||||||
import { purchaseBond } from "../../../hooks/bonds";
|
import { purchaseBond } from "../../../hooks/bonds";
|
||||||
import { useWarmupLength } from "../../../hooks/staking";
|
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,
|
chainId,
|
||||||
bond,
|
bond,
|
||||||
slippage,
|
slippage,
|
||||||
@ -51,6 +77,8 @@ const BondConfirmModal = ({
|
|||||||
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
|
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
|
const { tryStoreBondInHistory } = useLocalStorage();
|
||||||
|
|
||||||
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup]);
|
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup]);
|
||||||
|
|
||||||
const AcknowledgeWarmupCheckbox = () => {
|
const AcknowledgeWarmupCheckbox = () => {
|
||||||
@ -104,21 +132,19 @@ const BondConfirmModal = ({
|
|||||||
const one = BigInt(shares * 100);
|
const one = BigInt(shares * 100);
|
||||||
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
|
||||||
const bigIntSlippage = one + BigInt(Math.round(floatSlippage * shares));
|
const bigIntSlippage = one + BigInt(Math.round(floatSlippage * shares));
|
||||||
|
|
||||||
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
|
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
|
||||||
const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
|
|
||||||
|
|
||||||
await purchaseBond({
|
const txHash = await purchaseBond({
|
||||||
chainId,
|
chainId,
|
||||||
bondId: bond.id,
|
bondId: bond.id,
|
||||||
amount: spendAmountValue._value.toBigInt(),
|
amount: spendAmountValue._value.toBigInt(),
|
||||||
maxPrice,
|
maxPrice,
|
||||||
user: recipientAddress,
|
user: recipientAddress,
|
||||||
sender,
|
sender,
|
||||||
referral,
|
|
||||||
isNative
|
isNative
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tryStoreBondInHistory(chainId, sender, txHash);
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
handleConfirmCloseMaster();
|
handleConfirmCloseMaster();
|
||||||
}
|
}
|
||||||
@ -212,5 +238,3 @@ const BondConfirmModal = ({
|
|||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BondConfirmModal;
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
|
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 { useState, useMemo, useCallback } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
|
||||||
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
|
||||||
import { shorten, formatNumber, formatCurrency } from "../../../helpers";
|
import { shorten, formatNumber, formatCurrency } from "../../../helpers";
|
||||||
@ -9,10 +10,11 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
|||||||
|
|
||||||
import BondDiscount from "./BondDiscount";
|
import BondDiscount from "./BondDiscount";
|
||||||
import BondVesting from "./BondVesting";
|
import BondVesting from "./BondVesting";
|
||||||
import BondConfirmModal from "./BondConfirmModal";
|
import { BondConfirmModal, DiscountConfirmModal } from "./BondConfirmModal";
|
||||||
|
|
||||||
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||||
import { PrimaryButton } from "../../../components/Button";
|
import { PrimaryButton } from "../../../components/Button";
|
||||||
|
import GhostStyledIcon from "../../../components/Icon/GhostIcon";
|
||||||
import SwapCard from "../../../components/Swap/SwapCard";
|
import SwapCard from "../../../components/Swap/SwapCard";
|
||||||
import SwapCollection from "../../../components/Swap/SwapCollection";
|
import SwapCollection from "../../../components/Swap/SwapCollection";
|
||||||
import TokenStack from "../../../components/TokenStack/TokenStack";
|
import TokenStack from "../../../components/TokenStack/TokenStack";
|
||||||
@ -33,6 +35,7 @@ const BondInputArea = ({
|
|||||||
preparedQuoteToken,
|
preparedQuoteToken,
|
||||||
connect
|
connect
|
||||||
}) => {
|
}) => {
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
|
||||||
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
@ -45,7 +48,9 @@ const BondInputArea = ({
|
|||||||
|
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
|
const [discountConfirmed, setDiscountConfirmed] = useState(false);
|
||||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
|
const [discountConfirmOpen, setDiscountConfirmOpen] = useState(false);
|
||||||
|
|
||||||
const parsedAmount = useMemo(() => {
|
const parsedAmount = useMemo(() => {
|
||||||
return new DecimalBigNumber(amount, bond.quoteToken.decimals);
|
return new DecimalBigNumber(amount, bond.quoteToken.decimals);
|
||||||
@ -60,12 +65,24 @@ const BondInputArea = ({
|
|||||||
return new DecimalBigNumber("0").gt(bond.discount);
|
return new DecimalBigNumber("0").gt(bond.discount);
|
||||||
}, [bond]);
|
}, [bond]);
|
||||||
|
|
||||||
|
const setModalOpen = () => {
|
||||||
|
if (showDisclaimer && !discountConfirmOpen && !discountConfirmed) setDiscountConfirmOpen(true);
|
||||||
|
else setConfirmOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
const handleConfirmClose = () => {
|
const handleConfirmClose = () => {
|
||||||
setAmount("");
|
setAmount("");
|
||||||
setConfirmOpen(false);
|
setConfirmOpen(false);
|
||||||
|
setDiscountConfirmOpen(false);
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmDiscount = () => {
|
||||||
|
setConfirmOpen(false);
|
||||||
|
setDiscountConfirmOpen(false);
|
||||||
|
setDiscountConfirmed(true);
|
||||||
|
}
|
||||||
|
|
||||||
const setMax = useCallback(() => {
|
const setMax = useCallback(() => {
|
||||||
if (!balance) return;
|
if (!balance) return;
|
||||||
|
|
||||||
@ -95,143 +112,32 @@ const BondInputArea = ({
|
|||||||
}, [amountInBaseToken, baseTokenString, balance, parsedAmount]);
|
}, [amountInBaseToken, baseTokenString, balance, parsedAmount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minHeight="calc(100vh - 210px)" display="flex" flexDirection="column" justifyContent="center">
|
<Box sx={{ width: "100%", maxWidth: "420px", mt: "15px" }}>
|
||||||
<Box display="flex" flexDirection="row" width="100%" justifyContent="center" mt="10px">
|
<Paper
|
||||||
<Box display="flex" flexDirection="column" width="100%" maxWidth="476px">
|
fullWidth
|
||||||
<Box mb="21px">
|
enableBackground
|
||||||
<SwapCollection
|
topRight={
|
||||||
iconNotNeeded
|
<GhostStyledIcon
|
||||||
UpperSwapCard={
|
component={SettingsIcon}
|
||||||
<SwapCard
|
viewBox="0 0 23 23"
|
||||||
maxWidth="476px"
|
style={{ display: "flex", alignItems: "center", cursor: "pointer" }}
|
||||||
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
|
onClick={handleSettingsOpen}
|
||||||
id="from"
|
/>
|
||||||
token={
|
}
|
||||||
<TokenStack
|
headerContent={
|
||||||
chainTokenNames={bond.quoteToken.chainNames}
|
<Typography variant="h6">Bond</Typography>
|
||||||
tokens={bond.quoteToken.icons}
|
}
|
||||||
sx={{ fontSize: "25px", height: "30px" }}
|
>
|
||||||
/>
|
<DiscountConfirmModal
|
||||||
}
|
isOpen={discountConfirmOpen}
|
||||||
tokenName={preparedQuoteToken.name}
|
onSubmit={() => confirmDiscount()}
|
||||||
info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
|
handleConfirmClose={() => handleConfirmClose()}
|
||||||
endString="Max"
|
discount={bond
|
||||||
endStringOnClick={setMax}
|
? formatNumber(Number(bond.discount.mul(new DecimalBigNumber("100"))).toString(), 2)
|
||||||
value={amount}
|
: "Unknown"
|
||||||
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>
|
|
||||||
|
|
||||||
<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
|
<BondConfirmModal
|
||||||
chainId={chainId}
|
chainId={chainId}
|
||||||
bond={bond}
|
bond={bond}
|
||||||
@ -249,8 +155,139 @@ const BondInputArea = ({
|
|||||||
sender={address}
|
sender={address}
|
||||||
handleConfirmClose={() => handleConfirmClose()}
|
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;
|
export default BondInputArea;
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
addLiquidity,
|
addLiquidity,
|
||||||
addLiquidityETH,
|
addLiquidityETH,
|
||||||
} from "../../hooks/uniswapv2";
|
} from "../../hooks/uniswapv2";
|
||||||
|
import { useLocalStorage } from "../../hooks/localstorage";
|
||||||
|
|
||||||
const PoolContainer = ({
|
const PoolContainer = ({
|
||||||
chainSymbol,
|
chainSymbol,
|
||||||
@ -37,6 +38,7 @@ const PoolContainer = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
||||||
|
const { tryStoreDexInHistory } = useLocalStorage();
|
||||||
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [amountTop, setAmountTop] = useState("");
|
const [amountTop, setAmountTop] = useState("");
|
||||||
@ -191,12 +193,16 @@ const PoolContainer = ({
|
|||||||
deadline,
|
deadline,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let txHash;
|
||||||
|
|
||||||
if (topIsNative || bottomIsNative) {
|
if (topIsNative || bottomIsNative) {
|
||||||
await addLiquidityETH(params)
|
txHash = await addLiquidityETH(params)
|
||||||
} else {
|
} else {
|
||||||
await addLiquidity(params);
|
txHash = await addLiquidity(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (txHash) tryStoreDexInHistory(chainId, address, txHash);
|
||||||
|
|
||||||
await balanceRefetchTop();
|
await balanceRefetchTop();
|
||||||
await balanceRefetchBottom();
|
await balanceRefetchBottom();
|
||||||
await pairAddressRefetch();
|
await pairAddressRefetch();
|
||||||
@ -215,7 +221,7 @@ const PoolContainer = ({
|
|||||||
}, [pairAddress])
|
}, [pairAddress])
|
||||||
|
|
||||||
return (
|
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
|
<SwapCollection
|
||||||
UpperSwapCard={
|
UpperSwapCard={
|
||||||
<SwapCard
|
<SwapCard
|
||||||
@ -256,7 +262,7 @@ const PoolContainer = ({
|
|||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap="0px"
|
gap="0px"
|
||||||
maxWidth="356px"
|
maxWidth="390px"
|
||||||
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
||||||
>
|
>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import {
|
|||||||
swapExactETHForTokens,
|
swapExactETHForTokens,
|
||||||
swapExactTokensForETH,
|
swapExactTokensForETH,
|
||||||
} from "../../hooks/uniswapv2";
|
} from "../../hooks/uniswapv2";
|
||||||
|
import { useLocalStorage } from "../../hooks/localstorage";
|
||||||
|
|
||||||
import { EMPTY_ADDRESS } from "../../constants/addresses";
|
import { EMPTY_ADDRESS } from "../../constants/addresses";
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ const SwapContainer = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
const isSmallScreen = useMediaQuery("(max-width: 456px)");
|
||||||
|
const { tryStoreDexInHistory } = useLocalStorage();
|
||||||
|
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [amountBottom, setAmountBottom] = useState("");
|
const [amountBottom, setAmountBottom] = useState("");
|
||||||
@ -173,10 +175,12 @@ const SwapContainer = ({
|
|||||||
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
|
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
|
||||||
const amountBMin = amountBDesired * bigIntSlippage / one;
|
const amountBMin = amountBDesired * bigIntSlippage / one;
|
||||||
|
|
||||||
|
let txHash;
|
||||||
|
|
||||||
if (isWrapping) {
|
if (isWrapping) {
|
||||||
await depositNative(chainId, address, amountADesired);
|
txHash = await depositNative(chainId, address, amountADesired);
|
||||||
} else if (isUnwrapping) {
|
} else if (isUnwrapping) {
|
||||||
await withdrawWeth(chainId, address, amountADesired);
|
txHash = await withdrawWeth(chainId, address, amountADesired);
|
||||||
} else {
|
} else {
|
||||||
const params = {
|
const params = {
|
||||||
chainId,
|
chainId,
|
||||||
@ -190,14 +194,16 @@ const SwapContainer = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (topIsNative) {
|
if (topIsNative) {
|
||||||
await swapExactETHForTokens(params)
|
txHash = await swapExactETHForTokens(params)
|
||||||
} else if (bottomIsNative) {
|
} else if (bottomIsNative) {
|
||||||
await swapExactTokensForETH(params)
|
txHash = await swapExactTokensForETH(params)
|
||||||
} else {
|
} else {
|
||||||
await swapExactTokensForTokens(params);
|
txHash = await swapExactTokensForTokens(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (txHash) tryStoreDexInHistory(chainId, address, txHash);
|
||||||
|
|
||||||
await balanceRefetchTop();
|
await balanceRefetchTop();
|
||||||
await balanceRefetchBottom();
|
await balanceRefetchBottom();
|
||||||
await pairReservesRefetch();
|
await pairReservesRefetch();
|
||||||
@ -207,11 +213,11 @@ const SwapContainer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<SwapCollection
|
||||||
UpperSwapCard={
|
UpperSwapCard={
|
||||||
<SwapCard
|
<SwapCard
|
||||||
maxWidth="356px"
|
maxWidth="390px"
|
||||||
id="from"
|
id="from"
|
||||||
token={tokenNameTop}
|
token={tokenNameTop}
|
||||||
chainTokenName={tokenNameTop !== chainSymbol && chainSymbol}
|
chainTokenName={tokenNameTop !== chainSymbol && chainSymbol}
|
||||||
@ -230,7 +236,7 @@ const SwapContainer = ({
|
|||||||
}
|
}
|
||||||
LowerSwapCard={
|
LowerSwapCard={
|
||||||
<SwapCard
|
<SwapCard
|
||||||
maxWidth="356px"
|
maxWidth="390px"
|
||||||
id="to"
|
id="to"
|
||||||
token={tokenNameBottom}
|
token={tokenNameBottom}
|
||||||
chainTokenName={tokenNameBottom !== chainSymbol && chainSymbol}
|
chainTokenName={tokenNameBottom !== chainSymbol && chainSymbol}
|
||||||
@ -252,7 +258,7 @@ const SwapContainer = ({
|
|||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap="0px"
|
gap="0px"
|
||||||
maxWidth="356px"
|
maxWidth="390px"
|
||||||
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
style={{ fontSize: "12px", color: theme.colors.gray[40] }}
|
||||||
>
|
>
|
||||||
<Box width="100%" display="flex" justifyContent="space-between">
|
<Box width="100%" display="flex" justifyContent="space-between">
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./i
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
RESERVE_ADDRESSES,
|
RESERVE_ADDRESSES,
|
||||||
FTSO_DAI_LP_ADDRESSES,
|
FTSO_RESERVE_LP_ADDRESSES,
|
||||||
BOND_DEPOSITORY_ADDRESSES,
|
BOND_DEPOSITORY_ADDRESSES,
|
||||||
} from "../../../../constants/addresses";
|
} from "../../../../constants/addresses";
|
||||||
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
|
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]);
|
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
|
||||||
|
|
||||||
const possibleTokens = [
|
const possibleTokens = [
|
||||||
{ value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
|
{ value: FTSO_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])}` },
|
{ 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
|
Rebase
|
||||||
<Countdown otherwise="Ready" targetTime={epoch.end * 1000} />
|
<Countdown otherwise="Ready" targetTime={epoch.end * 1000} />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import Token from "../../../components/Token/Token";
|
|||||||
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
|
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
|
||||||
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
|
||||||
|
|
||||||
|
import { useLocalStorage } from "../../../hooks/localstorage";
|
||||||
import { useWarmupLength, stake, unstake, wrap, unwrap } from "../../../hooks/staking";
|
import { useWarmupLength, stake, unstake, wrap, unwrap } from "../../../hooks/staking";
|
||||||
import { formatNumber } from "../../../helpers";
|
import { formatNumber } from "../../../helpers";
|
||||||
|
|
||||||
@ -22,6 +23,8 @@ const StakeConfirmationModal = (props) => {
|
|||||||
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
|
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
|
const { tryStoreStakeInHistory } = useLocalStorage();
|
||||||
|
|
||||||
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup])
|
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup])
|
||||||
|
|
||||||
const AcknowledgeWarmupCheckbox = () => {
|
const AcknowledgeWarmupCheckbox = () => {
|
||||||
@ -73,11 +76,12 @@ const StakeConfirmationModal = (props) => {
|
|||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
const actionAmount = new DecimalBigNumber(props.amount, props.spendDecimals);
|
const actionAmount = new DecimalBigNumber(props.amount, props.spendDecimals);
|
||||||
let isRebase = false;
|
let isRebase = false;
|
||||||
|
let txHash = null;
|
||||||
|
|
||||||
switch (props.action.toUpperCase()) {
|
switch (props.action.toUpperCase()) {
|
||||||
case "STAKE":
|
case "STAKE":
|
||||||
isRebase = props.bottomToken === props.stnkSymbol;
|
isRebase = props.bottomToken === props.stnkSymbol;
|
||||||
await stake(
|
txHash = await stake(
|
||||||
props.chainId,
|
props.chainId,
|
||||||
props.address,
|
props.address,
|
||||||
actionAmount._value.toBigInt(),
|
actionAmount._value.toBigInt(),
|
||||||
@ -90,7 +94,7 @@ const StakeConfirmationModal = (props) => {
|
|||||||
break;
|
break;
|
||||||
case "UNSTAKE":
|
case "UNSTAKE":
|
||||||
isRebase = props.upperToken === props.stnkSymbol;
|
isRebase = props.upperToken === props.stnkSymbol;
|
||||||
await unstake(
|
txHash = await unstake(
|
||||||
props.chainId,
|
props.chainId,
|
||||||
props.address,
|
props.address,
|
||||||
actionAmount._value.toBigInt(),
|
actionAmount._value.toBigInt(),
|
||||||
@ -102,7 +106,7 @@ const StakeConfirmationModal = (props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "WRAP":
|
case "WRAP":
|
||||||
await wrap(
|
txHash = await wrap(
|
||||||
props.chainId,
|
props.chainId,
|
||||||
props.address,
|
props.address,
|
||||||
actionAmount._value.toBigInt(),
|
actionAmount._value.toBigInt(),
|
||||||
@ -110,7 +114,7 @@ const StakeConfirmationModal = (props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "UNWRAP":
|
case "UNWRAP":
|
||||||
await unwrap(
|
txHash = await unwrap(
|
||||||
props.chainId,
|
props.chainId,
|
||||||
props.address,
|
props.address,
|
||||||
actionAmount._value.toBigInt(),
|
actionAmount._value.toBigInt(),
|
||||||
@ -121,6 +125,8 @@ const StakeConfirmationModal = (props) => {
|
|||||||
console.log("Wrong action to be executed!");
|
console.log("Wrong action to be executed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (txHash) tryStoreStakeInHistory(props.chainId, props.address, txHash)
|
||||||
|
|
||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}
|
}
|
||||||
@ -156,7 +162,7 @@ const StakeConfirmationModal = (props) => {
|
|||||||
chainTokenName={props.chainSymbol}
|
chainTokenName={props.chainSymbol}
|
||||||
name={props.upperToken}
|
name={props.upperToken}
|
||||||
/>
|
/>
|
||||||
<Typography>{props.upperToken}</Typography>
|
<Typography>{props.upperToken}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<GhostStyledIcon component={ArrowRightIcon} />
|
<GhostStyledIcon component={ArrowRightIcon} />
|
||||||
|
|||||||
@ -273,24 +273,26 @@ export const useNotes = (chainId, address) => {
|
|||||||
return { notes, refetch };
|
return { notes, refetch };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => {
|
export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, isNative }) => {
|
||||||
const args = [bondId, amount, maxPrice, user, referral];
|
const args = [bondId, amount, maxPrice, user];
|
||||||
const messages = {
|
const messages = {
|
||||||
replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
|
replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
|
||||||
successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
|
successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
|
||||||
errorMsg: "Bond transaction failed. Check logs for error detalization.",
|
errorMsg: "Bond transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
messages,
|
messages,
|
||||||
abi: BondAbi,
|
abi: BondAbi,
|
||||||
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
address: BOND_DEPOSITORY_ADDRESSES[chainId],
|
||||||
functionName: "deposit",
|
functionName: "deposit",
|
||||||
account: user,
|
account: sender,
|
||||||
value: isNative ? amount : undefined
|
value: isNative ? amount : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const redeem = async ({ chainId, user, isGhst, indexes }) => {
|
export const redeem = async ({ chainId, user, isGhst, indexes }) => {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { createContext, useContext, useState, useMemo } from "react";
|
import { createContext, useContext, useState, useMemo } from "react";
|
||||||
import { claim } from "../../hooks/staking";
|
|
||||||
|
|
||||||
const emptyFunction = () => {};
|
const emptyFunction = () => {};
|
||||||
const BreakoutModalContext = createContext();
|
const BreakoutModalContext = createContext();
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { getObservableClient } from "@polkadot-api/observable-client"
|
|||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
|
|
||||||
const MAX_BLOCK_TIMEOUT = 15000
|
const MAX_BLOCK_TIMEOUT = 15000
|
||||||
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
|
const DEFAULT_CHAIN_ID = "0xcaf9c5ddccc7ba6f96149a6b8a81c26a3641742795cd5e7e0aee79bf3f4e77b7"
|
||||||
const UnstableProvider = createContext(null)
|
const UnstableProvider = createContext(null)
|
||||||
export const useUnstableProvider = () => useContext(UnstableProvider)
|
export const useUnstableProvider = () => useContext(UnstableProvider)
|
||||||
|
|
||||||
@ -39,11 +39,11 @@ export const UnstableProviderProvider = ({ children }) => {
|
|||||||
return undefined
|
return undefined
|
||||||
}, [accounts])
|
}, [accounts])
|
||||||
|
|
||||||
const connectionState = useMemo(() => {
|
const connectionState = useMemo(async () => {
|
||||||
if (!providerDetail) return 'no-extension';
|
if (!providerDetail) return 'no-extension';
|
||||||
if (!provider) return 'loading';
|
if (!provider) return 'loading';
|
||||||
|
|
||||||
const chains = provider.getChains();
|
const chains = await provider.getChains();
|
||||||
if (chains[chainId]) return 'connected';
|
if (chains[chainId]) return 'connected';
|
||||||
|
|
||||||
return 'wrong-network';
|
return 'wrong-network';
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
FTSO_ADDRESSES,
|
FTSO_ADDRESSES,
|
||||||
STNK_ADDRESSES,
|
STNK_ADDRESSES,
|
||||||
GHST_ADDRESSES,
|
GHST_ADDRESSES,
|
||||||
FTSO_DAI_LP_ADDRESSES,
|
FTSO_RESERVE_LP_ADDRESSES,
|
||||||
WETH_ADDRESSES,
|
WETH_ADDRESSES,
|
||||||
} from "../constants/addresses";
|
} from "../constants/addresses";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
@ -134,10 +134,10 @@ export const getTokenAddress = (chainId, name) => {
|
|||||||
address = GHST_ADDRESSES[chainId];
|
address = GHST_ADDRESSES[chainId];
|
||||||
break;
|
break;
|
||||||
case "GDAI_FTSO":
|
case "GDAI_FTSO":
|
||||||
address = FTSO_DAI_LP_ADDRESSES[chainId];
|
address = FTSO_RESERVE_LP_ADDRESSES[chainId];
|
||||||
break;
|
break;
|
||||||
case "RESERVE_FTSO":
|
case "RESERVE_FTSO":
|
||||||
address = FTSO_DAI_LP_ADDRESSES[chainId];
|
address = FTSO_RESERVE_LP_ADDRESSES[chainId];
|
||||||
break;
|
break;
|
||||||
case "WETH":
|
case "WETH":
|
||||||
address = WETH_ADDRESSES[chainId];
|
address = WETH_ADDRESSES[chainId];
|
||||||
@ -173,7 +173,7 @@ export const getTokenIcons = (chainId, address) => {
|
|||||||
case GHST_ADDRESSES[chainId]:
|
case GHST_ADDRESSES[chainId]:
|
||||||
icons = ["GHST"];
|
icons = ["GHST"];
|
||||||
break;
|
break;
|
||||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
case FTSO_RESERVE_LP_ADDRESSES[chainId]:
|
||||||
icons = ["WETH", "FTSO"];
|
icons = ["WETH", "FTSO"];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -185,7 +185,7 @@ export const getTokenIcons = (chainId, address) => {
|
|||||||
export const getChainTokenNames = (chainId, address) => {
|
export const getChainTokenNames = (chainId, address) => {
|
||||||
let icons = [undefined];
|
let icons = [undefined];
|
||||||
switch (address) {
|
switch (address) {
|
||||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
case FTSO_RESERVE_LP_ADDRESSES[chainId]:
|
||||||
icons = [undefined, "WETH"];
|
icons = [undefined, "WETH"];
|
||||||
break;
|
break;
|
||||||
case RESERVE_ADDRESSES[chainId]:
|
case RESERVE_ADDRESSES[chainId]:
|
||||||
@ -199,7 +199,7 @@ export const getChainTokenNames = (chainId, address) => {
|
|||||||
|
|
||||||
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => {
|
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => {
|
||||||
let stringValue = tokenNameConverter(chainId, "WETH")
|
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`;
|
stringValue = `${baseTokenSymbol}-${stringValue} LP`;
|
||||||
}
|
}
|
||||||
return stringValue;
|
return stringValue;
|
||||||
@ -214,7 +214,7 @@ export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => {
|
|||||||
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`;
|
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FTSO_DAI_LP_ADDRESSES[chainId]:
|
case FTSO_RESERVE_LP_ADDRESSES[chainId]:
|
||||||
purchaseUrl += `?pool=true&from=${RESERVE_ADDRESSES[chainId]}&to=${FTSO_ADDRESSES[chainId]}`;
|
purchaseUrl += `?pool=true&from=${RESERVE_ADDRESSES[chainId]}&to=${FTSO_ADDRESSES[chainId]}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -248,6 +248,7 @@ export const executeOnChainTransaction = async ({
|
|||||||
onReplaced: () => toast(messages.replacedMsg),
|
onReplaced: () => toast(messages.replacedMsg),
|
||||||
chainId
|
chainId
|
||||||
});
|
});
|
||||||
|
// TODO: onReplaced
|
||||||
|
|
||||||
toast.success(messages.successMsg);
|
toast.success(messages.successMsg);
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,17 @@ import { createContext, useContext, useState, useEffect } from "react";
|
|||||||
const LocalStorageContext = createContext();
|
const LocalStorageContext = createContext();
|
||||||
export const useLocalStorage = () => useContext(LocalStorageContext);
|
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 }) => {
|
export const LocalStorageProvider = ({ children }) => {
|
||||||
const getStorageKey = (chainId, address, target) => {
|
const getStorageKey = (chainId, address, target) => {
|
||||||
return `${chainId}:${address}:${target}`;
|
return `${chainId}:${address}:${target}`;
|
||||||
@ -19,8 +30,58 @@ export const LocalStorageProvider = ({ children }) => {
|
|||||||
localStorage.setItem(key, JSON.stringify(value));
|
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 (
|
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}
|
{children}
|
||||||
</LocalStorageContext.Provider>
|
</LocalStorageContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useCurrentIndex, useGhostedSupply } from "../staking";
|
|||||||
import { useUniswapV2PairReserves } from "../uniswapv2";
|
import { useUniswapV2PairReserves } from "../uniswapv2";
|
||||||
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
import {
|
import {
|
||||||
FTSO_DAI_LP_ADDRESSES,
|
FTSO_RESERVE_LP_ADDRESSES,
|
||||||
RESERVE_ADDRESSES,
|
RESERVE_ADDRESSES,
|
||||||
FTSO_ADDRESSES,
|
FTSO_ADDRESSES,
|
||||||
CEX_TICKERS,
|
CEX_TICKERS,
|
||||||
@ -135,7 +135,7 @@ export const useReservePrice = (chainId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useFtsoPrice = (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 reservePrice = useReservePrice(chainId);
|
||||||
const reserveAddress = RESERVE_ADDRESSES[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}.`,
|
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.",
|
errorMsg: "Staking transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
messages,
|
messages,
|
||||||
@ -190,6 +190,7 @@ export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSym
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "stake",
|
functionName: "stake",
|
||||||
});
|
});
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unstake = async (chainId, account, amount, isTrigger, isRebase, ftsoSymbol, stnkSymbol, ghstSymbol) => {
|
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)}.`,
|
successMsg: `${isRebase ? stnkSymbol : ghstSymbol} tokens unstaked successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
||||||
errorMsg: "Unstake transaction failed. Check logs for error detalization.",
|
errorMsg: "Unstake transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
messages,
|
messages,
|
||||||
@ -208,6 +209,7 @@ export const unstake = async (chainId, account, amount, isTrigger, isRebase, fts
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "unstake",
|
functionName: "unstake",
|
||||||
});
|
});
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const forfeit = async (chainId, account, ftsoSymbol) => {
|
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)}.`,
|
successMsg: `Tokens forfeited successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
|
||||||
errorMsg: "Forfeit transaction failed. Check logs for error detalization.",
|
errorMsg: "Forfeit transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
messages,
|
messages,
|
||||||
account,
|
account,
|
||||||
@ -225,6 +227,7 @@ export const forfeit = async (chainId, account, ftsoSymbol) => {
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "forfeit",
|
functionName: "forfeit",
|
||||||
});
|
});
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) => {
|
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)}.`,
|
successMsg: `${isStnk ? stnkSymbol : ghstSymbol} tokens claimed successfully to ${shorten(account)}.`,
|
||||||
errorMsg: "Claim transaction failed. Check logs for error detalization.",
|
errorMsg: "Claim transaction failed. Check logs for error detalization.",
|
||||||
};
|
};
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
messages,
|
messages,
|
||||||
@ -243,6 +246,7 @@ export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) =>
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "claim",
|
functionName: "claim",
|
||||||
});
|
});
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const breakout = async (chainId, account, receiver, amount) => {
|
export const breakout = async (chainId, account, receiver, amount) => {
|
||||||
@ -261,7 +265,6 @@ export const breakout = async (chainId, account, receiver, amount) => {
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "breakout",
|
functionName: "breakout",
|
||||||
});
|
});
|
||||||
|
|
||||||
return txHash;
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,6 +284,7 @@ export const unwrap = async (chainId, account, amount, stnkSymbol) => {
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "unwrap",
|
functionName: "unwrap",
|
||||||
});
|
});
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wrap = async (chainId, account, amount, ghstSymbol) => {
|
export const wrap = async (chainId, account, amount, ghstSymbol) => {
|
||||||
@ -299,6 +303,7 @@ export const wrap = async (chainId, account, amount, ghstSymbol) => {
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "wrap",
|
functionName: "wrap",
|
||||||
});
|
});
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ghost = async (chainId, account, receiver, amount) => {
|
export const ghost = async (chainId, account, receiver, amount) => {
|
||||||
@ -317,6 +322,5 @@ export const ghost = async (chainId, account, receiver, amount) => {
|
|||||||
address: STAKING_ADDRESSES[chainId],
|
address: STAKING_ADDRESSES[chainId],
|
||||||
functionName: "ghost",
|
functionName: "ghost",
|
||||||
});
|
});
|
||||||
|
|
||||||
return txHash;
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const swapExactETHForTokens = async ({
|
|||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
abi: RouterAbi,
|
abi: RouterAbi,
|
||||||
@ -43,6 +43,8 @@ export const swapExactETHForTokens = async ({
|
|||||||
messages: swapMessages,
|
messages: swapMessages,
|
||||||
value: amountADesired,
|
value: amountADesired,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const swapExactTokensForETH = async ({
|
export const swapExactTokensForETH = async ({
|
||||||
@ -63,7 +65,7 @@ export const swapExactTokensForETH = async ({
|
|||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
abi: RouterAbi,
|
abi: RouterAbi,
|
||||||
@ -72,6 +74,8 @@ export const swapExactTokensForETH = async ({
|
|||||||
account: address,
|
account: address,
|
||||||
messages: swapMessages,
|
messages: swapMessages,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const swapExactTokensForTokens = async ({
|
export const swapExactTokensForTokens = async ({
|
||||||
@ -92,7 +96,7 @@ export const swapExactTokensForTokens = async ({
|
|||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
abi: RouterAbi,
|
abi: RouterAbi,
|
||||||
@ -101,6 +105,8 @@ export const swapExactTokensForTokens = async ({
|
|||||||
account: address,
|
account: address,
|
||||||
messages: swapMessages,
|
messages: swapMessages,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addLiquidity = async ({
|
export const addLiquidity = async ({
|
||||||
@ -126,7 +132,7 @@ export const addLiquidity = async ({
|
|||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
abi: RouterAbi,
|
abi: RouterAbi,
|
||||||
@ -135,6 +141,8 @@ export const addLiquidity = async ({
|
|||||||
account: address,
|
account: address,
|
||||||
messages: addMessages,
|
messages: addMessages,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addLiquidityETH = async ({
|
export const addLiquidityETH = async ({
|
||||||
@ -172,7 +180,7 @@ export const addLiquidityETH = async ({
|
|||||||
deadline
|
deadline
|
||||||
];
|
];
|
||||||
|
|
||||||
await executeOnChainTransaction({
|
const txHash = await executeOnChainTransaction({
|
||||||
chainId,
|
chainId,
|
||||||
args,
|
args,
|
||||||
abi: RouterAbi,
|
abi: RouterAbi,
|
||||||
@ -182,4 +190,6 @@ export const addLiquidityETH = async ({
|
|||||||
messages: addMessages,
|
messages: addMessages,
|
||||||
value: amountETHDesired,
|
value: amountETHDesired,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user