Compare commits

..

5 Commits

Author SHA1 Message Date
bc5f88d572
bump version and apply changes in order to match latest ghost-node and ghost-dao functionality
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-08-03 17:33:35 +03:00
1143a3d491
update dapp with new token names and extend faucet with burn functionality
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-07-25 00:34:58 +03:00
ad4b539961
add DAI symbol to token icon parser
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-30 20:17:24 +03:00
f64570eea5
make select network element fit into the screen on mobile devices
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-30 20:01:07 +03:00
5dffd62c5a
replace hardcoded token symbols with on-chain data hook
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-30 19:49:07 +03:00
53 changed files with 1305 additions and 238 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.1.2",
"version": "0.2.0",
"type": "module",
"scripts": {
"dev": "vite",
@ -18,6 +18,8 @@
"@mui/icons-material": "^6.4.7",
"@mui/material": "^6.4.7",
"@mui/utils": "^6.4.6",
"@polkadot-api/utils": "~0.1.2",
"@polkadot-labs/hdkd-helpers": "^0.0.20",
"@tanstack/react-query": "^5.67.2",
"@tanstack/react-query-devtools": "^5.67.2",
"@wagmi/core": "^2.17.3",

View File

@ -32,6 +32,12 @@ importers:
'@mui/utils':
specifier: ^6.4.6
version: 6.4.6(@types/react@19.0.10)(react@19.0.0)
'@polkadot-api/utils':
specifier: 0.1.2
version: 0.1.2
'@polkadot-labs/hdkd-helpers':
specifier: ^0.0.20
version: 0.0.20
'@tanstack/react-query':
specifier: ^5.67.2
version: 5.67.2(react@19.0.0)
@ -723,6 +729,10 @@ packages:
resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==}
engines: {node: ^14.21.3 || >=16}
'@noble/curves@1.9.6':
resolution: {integrity: sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==}
engines: {node: ^14.21.3 || >=16}
'@noble/hashes@1.4.0':
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: '>= 16'}
@ -743,6 +753,12 @@ packages:
resolution: {integrity: sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==}
deprecated: 'The package is now available as "qr": npm install qr'
'@polkadot-api/utils@0.1.2':
resolution: {integrity: sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg==}
'@polkadot-labs/hdkd-helpers@0.0.20':
resolution: {integrity: sha512-P3o1FpPqLACaHhDT/J6O3xYQIBdOs0FDJtZQI8/LGotgIGp85mKDnH/cSSK3QC2i67ZY/d/POs8K0jEspLMiGg==}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@ -913,6 +929,9 @@ packages:
'@scure/bip39@1.6.0':
resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==}
'@scure/sr25519@0.2.0':
resolution: {integrity: sha512-uUuLP7Z126XdSizKtrCGqYyR3b3hYtJ6Fg/XFUXmc2//k2aXHDLqZwFeXxL97gg4XydPROPVnuaHGF2+xriSKg==}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@ -2375,6 +2394,9 @@ packages:
engines: {node: '>=16.0.0'}
hasBin: true
scale-ts@1.6.1:
resolution: {integrity: sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==}
scheduler@0.25.0:
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
@ -3556,6 +3578,10 @@ snapshots:
dependencies:
'@noble/hashes': 1.8.0
'@noble/curves@1.9.6':
dependencies:
'@noble/hashes': 1.8.0
'@noble/hashes@1.4.0': {}
'@noble/hashes@1.7.0': {}
@ -3566,6 +3592,16 @@ snapshots:
'@paulmillr/qr@0.2.1': {}
'@polkadot-api/utils@0.1.2': {}
'@polkadot-labs/hdkd-helpers@0.0.20':
dependencies:
'@noble/curves': 1.9.6
'@noble/hashes': 1.8.0
'@scure/base': 1.2.6
'@scure/sr25519': 0.2.0
scale-ts: 1.6.1
'@popperjs/core@2.11.8': {}
'@reown/appkit-common@1.7.8(bufferutil@4.0.9)(utf-8-validate@5.0.10)(zod@3.22.4)':
@ -3936,6 +3972,11 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/base': 1.2.6
'@scure/sr25519@0.2.0':
dependencies:
'@noble/curves': 1.9.6
'@noble/hashes': 1.8.0
'@socket.io/component-emitter@3.1.2': {}
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.9)':
@ -5471,8 +5512,8 @@ snapshots:
ox@0.6.7(zod@3.22.4):
dependencies:
'@adraffy/ens-normalize': 1.11.0
'@noble/curves': 1.8.1
'@noble/hashes': 1.7.1
'@noble/curves': 1.9.2
'@noble/hashes': 1.8.0
'@scure/bip32': 1.6.2
'@scure/bip39': 1.5.4
abitype: 1.0.8(zod@3.22.4)
@ -5842,6 +5883,8 @@ snapshots:
sass-embedded-win32-ia32: 1.85.1
sass-embedded-win32-x64: 1.85.1
scale-ts@1.6.1: {}
scheduler@0.25.0: {}
scss@0.2.4:

View File

@ -27,6 +27,7 @@ const StakeContainer = lazy(() => import("./containers/Stake/StakeContainer"));
const TreasuryDashboard = lazy(() => import("./containers/TreasuryDashboard/TreasuryDashboard"));
const Faucet = lazy(() => import("./containers/Faucet/Faucet"));
const Dex = lazy(() => import("./containers/Dex/Dex"));
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
const PREFIX = "App";
@ -187,6 +188,7 @@ function App() {
<Route path="/bonds/:id" element={<BondModalContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/stake" element={<StakeContainer connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId}/>} />
<Route path="/faucet" element={<Faucet config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
</>
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -132,6 +132,7 @@ const NavItem = ({
const LinkItem = () => (
<Link {...linkProps} {...props} underline="hover">
<Box
sx={{ fontFamily: "Ubuntu" }}
display="flex"
flexDirection="row"
alignItems="center"

View File

@ -2,7 +2,7 @@ import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
const PageTitle = ({ name, subtitle, noMargin }) => {
const theme = useTheme();
const mobile = useMediaQuery(theme.breakpoints.down("sm"));
const mobile = useMediaQuery(theme.breakpoints.down("700"));
return (
<Box

View File

@ -47,7 +47,8 @@ import BondDiscount from "../../containers/Bond/components/BondDiscount";
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShowerIcon from '@mui/icons-material/Shower';
import { useFtsoPrice, useGhstPrice } from "../../hooks/prices";
import { useTokenSymbol } from "../../hooks/tokens";
import { useFtsoPrice, useGhstPrice, useGhostedSupplyPrice } from "../../hooks/prices";
import { useLiveBonds } from "../../hooks/bonds/index";
const PREFIX = "NavContent";
@ -66,6 +67,10 @@ const NavContent = ({ chainId, addressChainId }) => {
const { liveBonds: ghostBonds } = useLiveBonds(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const ghostedSupplyPrice = useGhostedSupplyPrice(chainId);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
return (
<Paper className="dapp-sidebar">
@ -82,16 +87,19 @@ const NavContent = ({ chainId, addressChainId }) => {
</Link>
<Box display="flex" flexDirection="column" mt="10px">
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
FTSO Price: {formatCurrency(ftsoPrice, 2)}
{ftsoSymbol} Price: {formatCurrency(ftsoPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight="15px">
GHST Price: {formatCurrency(ghstPrice, 2)}
{ghstSymbol} Price: {formatCurrency(ghstPrice, 2)}
</Box>
<Box fontSize="12px" fontWeight="500" lineHeight={"15px"}>
Ghosted Supply: {formatCurrency(ghostedSupplyPrice, 2)}
</Box>
</Box>
</Box>
<Box className="menu-divider">
<Divider />
<Divider />
</Box>
<div className="dapp-menu-links">
@ -137,6 +145,7 @@ const NavContent = ({ chainId, addressChainId }) => {
/>
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
<NavItem icon={ShowerIcon} label={`Faucet`} to="/faucet" />
<NavItem icon={PublicIcon} label={`Bridge`} to="/bridge" />
<NavItem
icon={CurrencyExchangeIcon}
label={`Dex`}
@ -167,7 +176,6 @@ const NavContent = ({ chainId, addressChainId }) => {
</>
}
<NavItem icon={PublicIcon} label={`Bridge`} href="https://bridge.ghostchain.io/" />
<NavItem
to=''
icon={PublicIcon}

View File

@ -28,14 +28,14 @@ const StyledArrow = styled(Box)(
},
);
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick }) => {
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded }) => {
const theme = useTheme();
return (
<Box display="flex" flexDirection="column" maxWidth="476px">
{UpperSwapCard}
<Box display="flex" flexDirection="row" justifyContent="center">
<StyledArrow
{!iconNotNeeded && (<StyledArrow
width="21px"
height="21px"
borderRadius="6px"
@ -55,7 +55,8 @@ const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick }) => {
}}
/>
</Box>
</StyledArrow>
</StyledArrow>)}
{iconNotNeeded && <Box height="21px" />}
</Box>
<Box marginTop="-7px">{LowerSwapCard}</Box>
</Box>

View File

@ -1,8 +1,8 @@
import { SvgIcon } from "@mui/material";
import { styled } from "@mui/material/styles";
import FtsoIcon from "../../assets/tokens/eGHST.svg?react";
import StnkIcon from "../../assets/tokens/sGHST.svg?react";
import FtsoIcon from "../../assets/tokens/FTSO.svg?react";
import StnkIcon from "../../assets/tokens/STNK.svg?react";
import GhstIcon from "../../assets/tokens/GHST.svg?react";
import DaiIcon from "../../assets/tokens/DAI.svg?react";
import WethIcon from "../../assets/tokens/wETH.svg?react";
@ -30,15 +30,27 @@ const Token = ({ name, viewBox = "0 0 260 260", fontSize = "large", ...props })
case "FTSO":
icon = FtsoIcon;
break;
case "ECSPR":
icon = FtsoIcon;
break;
case "STNK":
icon = StnkIcon;
break;
case "SCSPR":
icon = StnkIcon;
break;
case "GHST":
icon = GhstIcon;
break;
case "CSPR":
icon = GhstIcon;
break;
case "GDAI":
icon = DaiIcon;
break;
case "DAI":
icon = DaiIcon;
break;
case "ETH":
icon = WethIcon;
break;

View File

@ -14,7 +14,7 @@ import EthIcon from "../../assets/tokens/ETH.svg?react";
import { useSwitchChain } from 'wagmi';
import toast from "react-hot-toast";
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId }) {
function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, small }) {
const theme = useTheme();
const { chains, switchChain } = useSwitchChain();
@ -37,7 +37,7 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId })
}
return(
<FormControl sx={{ width: "155px" }}>
<FormControl sx={{ width: small ? "100px" : "155px" }}>
<Select
labelId="network-select-helper-label"
id="network-select-helper"
@ -58,7 +58,7 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId })
<MenuItem key={chain.name} value={chain.id}>
<Box gap="10px" display="flex" flexDirection="row" alignItems="center">
<SvgIcon component={EthIcon} viewBox="0 0 32 32" />
<Typography>{chain.name}</Typography>
{!small && <Typography>{chain.name}</Typography>}
</Box>
</MenuItem>
)

View File

@ -23,6 +23,7 @@ function TopBar({
}) {
const themeColor = useTheme();
const desktop = useMediaQuery(themeColor.breakpoints.up(1048));
const small = useMediaQuery(themeColor.breakpoints.down(400));
return (
<Box
display="flex"
@ -32,11 +33,12 @@ function TopBar({
marginRight={desktop ? "33px" : "0px"}
>
<Box display="flex" alignItems="center">
<Box display="flex" justifyContent="space-between" alignItems="center" width="320px">
<Box display="flex" justifyContent="space-between" alignItems="center" width={small ? "calc(100vw - 78px)" : "320px"}>
<SelectNetwork
wrongNetworkToastId={wrongNetworkToastId}
setWrongNetworkToastId={setWrongNetworkToastId}
chainId={chainId}
small={small}
/>
<Wallet address={address} connect={connect} chainId={chainId} />
</Box>

View File

@ -152,7 +152,7 @@ function InitialWalletView({ address, chainId, onClose }) {
fullWidth
onClick={() => onBtnClick("uniswap", DAI_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])}
>
<Typography>FTSO-gDAI on Uniswap</Typography>
<Typography>{`${tokens?.ftso?.symbol}-${tokens?.dai?.symbol} on Uniswap`}</Typography>
</SecondaryButton>
</Box>

View File

@ -18,7 +18,7 @@ import GhostStyledIcon from "../../Icon/GhostIcon";
import TokenStack from "../../TokenStack/TokenStack";
import { PrimaryButton, SecondaryButton } from "../../Button";
import { useBalance } from "../../../hooks/tokens";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { useDaiPrice, useFtsoPrice, useStnkPrice, useGhstPrice } from "../../../hooks/prices";
import { useLpValuation } from "../../../hooks/treasury";
import { useAccount } from "wagmi";
@ -76,7 +76,7 @@ export const Token = (props) => {
const navigate = useNavigate();
const useLink = (symbol, fromAddress, toAddress, isPool) => {
if (symbol === "GDAI") {
if (symbol.toUpperCase() === "GDAI") {
navigate({ pathname: "/faucet" })
} else {
navigate({
@ -133,7 +133,7 @@ export const Token = (props) => {
onClick={() => useLink(symbol, daiAddress, address, isPool)}
fullWidth
>
<Typography>Get on {symbol === "GDAI" ? "Faucet" : "Uniswap"}</Typography>
<Typography>Get on {symbol.toUpperCase() === "GDAI" ? "Faucet" : "Uniswap"}</Typography>
</SecondaryButton>
</Box>
</Box>
@ -178,9 +178,15 @@ export const useWallet = (chainId, userAddress) => {
const ghstPrice = useGhstPrice(chainId);
const lpDaiFtsoPrice = useLpValuation(chainId, "GDAI_FTSO", 1000000000000000000n);
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { symbol: lpDaiFtsoSymbol } = useTokenSymbol(chainId, "GDAI_FTSO");
const tokens = {
dai: {
symbol: "GDAI",
symbol: daiSymbol,
address: daiAddress,
balance: daiBalance,
price: daiPrice,
@ -188,7 +194,7 @@ export const useWallet = (chainId, userAddress) => {
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/gDAI.svg",
},
ftso: {
symbol: "FTSO",
symbol: ftsoSymbol,
address: ftsoAddress,
balance: ftsoBalance,
price: ftsoPrice,
@ -196,7 +202,7 @@ export const useWallet = (chainId, userAddress) => {
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/eGHST.svg",
},
stnk: {
symbol: "STNK",
symbol: stnkSymbol,
address: stnkAddress,
balance: stnkBalance,
price: stnkPrice,
@ -204,7 +210,7 @@ export const useWallet = (chainId, userAddress) => {
externalUrl: "https://ghostchain.io/wp-content/uploads/2025/03/sGHST.svg",
},
ghst: {
symbol: "GHST",
symbol: ghstSymbol,
address: ghstAddress,
balance: ghstBalance,
price: ghstPrice,
@ -213,7 +219,7 @@ export const useWallet = (chainId, userAddress) => {
},
daiFtso: {
isPool: true,
symbol: "UNI-V2",
symbol: lpDaiFtsoSymbol,
address: lpDaiFtsoBalanceAddress,
balance: lpDaiFtsoBalance,
price: lpDaiFtsoPrice,
@ -248,8 +254,8 @@ export const Tokens = ({ address, tokens, onClose }) => {
return (
<>
{alwaysShowTokens.map(token => (
<Token key={token.symbol} {...tokenProps(token)} />
{alwaysShowTokens.map((token, i) => (
<Token key={i} {...tokenProps(token)} />
))}
</>
);

View File

@ -6,7 +6,6 @@ export const config = createConfig({
transports: {
[sepolia.id]: fallback([
http('https://ethereum-sepolia-rpc.publicnode.com'),
http('https://rpc-sepolia.rockx.com/'),
http('https://1rpc.io/sepolia'),
http('https://eth-sepolia.public.blastapi.io'),
http('https://0xrpc.io/sep'),

View File

@ -1,32 +1,32 @@
import { NetworkId } from "../constants";
export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xb22Ad3b4a23EaEA8c06CD151D7C0e3758d0FB580",
[NetworkId.TESTNET_SEPOLIA]: "0xd90E63E88282596E1ea33765b41Ba3d650f4aD52",
[NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86",
};
export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x8773AC3258b31D3ACfc99Ffd13768ccB170fcF9f",
[NetworkId.TESTNET_SEPOLIA]: "0xdcE486113280e49ca2fB200258E5Ee1B2D21D495",
[NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571",
};
export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x2AAd1EA51044e69756880f580C13a92D910af238",
[NetworkId.TESTNET_SEPOLIA]: "0x93dd30f819403710de7933B79A74C4A42438458D",
[NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8",
};
export const FTSO_DAI_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x64B19626bd074cf7B1019798846c363bbA8A0d53",
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0x1394dC3f7bABaa2F0CA80353648087DAB1BF3fd6",
[NetworkId.TESTNET_HOODI]: "0xf7B2d44209E70782d93A70F7D8eC50010dF7ae50",
};
export const FTSO_STNK_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x29965676fc00C3eA9717B2A02739d294399a382e",
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // TBD
[NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000",
}
export const DAI_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xc7Afd3bC4c74f6E07880447b1759d5d639F2525F",
[NetworkId.TESTNET_SEPOLIA]: "0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B",
[NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58",
};
@ -36,35 +36,39 @@ export const WETH_ADDRESSES = {
};
export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x4643076087234d9B81974beF1eC9c25F3A0202B9",
[NetworkId.TESTNET_SEPOLIA]: "0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4",
[NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46",
};
export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x84060da636f5a83f2668ad238f09f8c667a1ec8b",
[NetworkId.TESTNET_SEPOLIA]: "0x02C296A27eA779d5a16F934337c12062C5E3c0D9",
[NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F",
};
export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x0eF2E888710E9f1d5E734f9ce30FAD40c832D5F3",
[NetworkId.TESTNET_SEPOLIA]: "0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93",
[NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033",
};
export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xE433D078a555163dC6B53968E72418B6a1618f04",
[NetworkId.TESTNET_SEPOLIA]: "0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194",
[NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842",
};
export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xD40E6442Ee01c234CD8AaF335122CfbB2aec8548",
[NetworkId.TESTNET_SEPOLIA]: "0xDab0c51918E6990d8763FAC8a04AE159e44e0c4f",
[NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F",
};
export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x29a6bb5De7a1049632E107544CaEF05e518451e7",
[NetworkId.TESTNET_SEPOLIA]: "0x4896bFc6256A57Df826d7144E48c9633d51d6319",
[NetworkId.TESTNET_HOODI]: "0x2635d526Ad24b98082563937f7b996075052c6Fd",
}
export const GATEKEEPER_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xc85129A097773B7F8970a7364c928C05f265E6A1",
}
export const UNISWAP_V2_ROUTER = {
[NetworkId.TESTNET_SEPOLIA]: "0xee567fe1712faf6149d80da1e6934e354124cfe3",
[NetworkId.TESTNET_HOODI]: "0xD41daF947c6FFEf344754B99ad09466FBCBb7583",

View File

@ -17,6 +17,7 @@ import { ClaimBonds } from "./components/ClaimBonds";
import { useLiveBonds } from "../../hooks/bonds";
import { useTotalReserves } from "../../hooks/treasury";
import { useFtsoPrice } from "../../hooks/prices";
import { useTokenSymbol } from "../../hooks/tokens";
const Bonds = ({ chainId, address, connect }) => {
const [isZoomed] = useState(false);
@ -34,6 +35,8 @@ const Bonds = ({ chainId, address, connect }) => {
const totalReserves = useTotalReserves(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
useEffect(() => {
const interval = setInterval(() => {
const date = Math.round(Date.now() / 1000);
@ -44,7 +47,7 @@ const Bonds = ({ chainId, address, connect }) => {
return (
<Box>
<PageTitle name={"Protocol Bonding"} subtitle="Buy FTSO from the protocol at a discount" />
<PageTitle name={"Protocol Bonding"} subtitle={`Buy ${ftsoSymbol} from the protocol at a discount`} />
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
@ -64,7 +67,7 @@ const Bonds = ({ chainId, address, connect }) => {
isLoading={false}
/>
<Metric
label={`FTSO price`}
label={`${ftsoSymbol} price`}
metric={formatCurrency(ftsoPrice, 2)}
isLoading={false}
/>

View File

@ -9,7 +9,7 @@ const BondInfoText = () => {
fontSize="0.875em"
lineHeight="15px"
>
Important: Bonding is the act of selling naked assets such as gDAI (reserve bonds) or liquidity tokens such as gDAI-FTSO SLP (liquidity bonds) for FTSO at a discount.
Important: Bonding is the act of selling naked assets or liquidity tokens for ghostDAO native token at a discount.
&nbsp;<Link
color={theme.colors.primary[300]}
href="https://ghostchain.io/ghostdao_litepaper"

View File

@ -37,6 +37,9 @@ const BondInputArea = ({
const { currentIndex } = useCurrentIndex(chainId);
const { balance } = useBalance(chainId, bond.quoteToken.quoteTokenAddress, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const [amount, setAmount] = useState("");
const [checked, setChecked] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
@ -164,11 +167,11 @@ const BondInputArea = ({
title={"You Will Get"}
balance={
<span>
{formatCurrency(amountInBaseToken, formatDecimals, "FTSO")}
{formatCurrency(amountInBaseToken, formatDecimals, ftsoSymbol)}
{" "}
{!!currentIndex && (
<span>
({formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, "GHST")})
({formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, ghstSymbol)})
</span>
)}
</span>
@ -182,8 +185,8 @@ const BondInputArea = ({
balance={
<span>
{bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
? `${formatCurrency(baseTokenString, formatDecimals, "FTSO")}`
: `${formatCurrency(baseTokenString, formatDecimals, "FTSO")} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, "GHST")})`}
? `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)}`
: `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, ghstSymbol)})`}
</span>
}
/>
@ -191,7 +194,7 @@ const BondInputArea = ({
<DataRow
title="Discount"
balance={<BondDiscount discount={bond.discount} textOnly />}
tooltip="The bond discount is the percentage difference between FTSO market value and the bond's price"
tooltip={`The bond discount is the percentage difference between ${ftsoSymbol} market value and the bond's price`}
/>
<DataRow

View File

@ -170,7 +170,7 @@ const payoutTokenCapacity = (bond) => {
const payoutTokenCapacity = bond.maxPayout.inBaseToken.lt(bond.capacity.inBaseToken)
? bond.maxPayout.inBaseToken
: bond.capacity.inBaseToken;
return `${formatNumber(payoutTokenCapacity, 4)} FTSO`;
return `${formatNumber(payoutTokenCapacity, 4)} ${bond.baseToken.name}`;
};
const BondRow = ({ bond, secondsTo }) => {

View File

@ -19,6 +19,7 @@ import { formatCurrency } from "../../../helpers";
import { useCurrentIndex, useEpoch, useWarmupLength, useWarmupInfo } from "../../../hooks/staking";
import { useNotes, redeem } from "../../../hooks/bonds";
import { useTokenSymbol } from "../../../hooks/tokens";
export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const isSmallScreen = useScreenSize("md");
@ -37,6 +38,10 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
const { notes, refetch: notesRefetch } = useNotes(chainId, address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
if (!notes || notes.length === 0) return null;
const totalClaimableBalance = new DecimalBigNumber(
@ -91,8 +96,8 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="payout-stnk-button" label="STNK" style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label="GHST" style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
</Tabs>
</Box>
@ -105,8 +110,8 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
<Box mt="4px" mb="8px">
<Typography variant="h4" align="center">
{isPayoutGhst
? formatCurrency(totalClaimableBalance, 5, "GHST")
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, "STNK")
? formatCurrency(totalClaimableBalance, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(totalClaimableBalance), 5, stnkSymbol)
}
</Typography>
</Box>
@ -152,8 +157,8 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
<Typography>Payout</Typography>
<Typography>
{isPayoutGhst
? formatCurrency(note.payout, 5, "GHST")
: formatCurrency(currentIndex.mul(note.payout), 5, "STNK")
? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
}
</Typography>
</Box>
@ -208,8 +213,8 @@ export const ClaimBonds = ({ chainId, address, secondsTo }) => {
<TableCell style={{ padding: "8px 0" }}>
<Typography>
{isPayoutGhst
? formatCurrency(note.payout, 5, "GHST")
: formatCurrency(currentIndex.mul(note.payout), 5, "STNK")
? formatCurrency(note.payout, 5, ghstSymbol)
: formatCurrency(currentIndex.mul(note.payout), 5, stnkSymbol)
}
</Typography>
</TableCell>

View File

@ -0,0 +1,662 @@
import { useEffect, useState, useMemo, useCallback } from "react";
import {
Box,
Container,
Typography,
Link,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TableContainer,
useMediaQuery,
useTheme
} from "@mui/material";
import { ss58Decode } from "@polkadot-labs/hdkd-helpers";
import { toHex } from "@polkadot-api/utils";
import { useBlockNumber, useTransactionConfirmations } from "wagmi";
import PendingActionsIcon from '@mui/icons-material/PendingActions';
import PublicIcon from '@mui/icons-material/Public';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
import ThumbDownAltIcon from '@mui/icons-material/ThumbDownAlt';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CheckIcon from '@mui/icons-material/Check';
import PageTitle from "../../components/PageTitle/PageTitle";
import Paper from "../../components/Paper/Paper";
import SwapCard from "../../components/Swap/SwapCard";
import SwapCollection from "../../components/Swap/SwapCollection";
import TokenStack from "../../components/TokenStack/TokenStack";
import GhostStyledIcon from "../../components/Icon/GhostIcon";
import Modal from "../../components/Modal/Modal";
import { PrimaryButton } from "../../components/Button";
import { GATEKEEPER_ADDRESSES } from "../../constants/addresses";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatCurrency } from "../../helpers";
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useGatekeeperAddress, ghost } from "../../hooks/staking";
const STORAGE_PREFIX = "storedTransactions"
const Bridge = ({ chainId, address, config, connect }) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const [isPending, setIsPending] = useState(false);
const [bridgeAction, setBridgeAction] = useState(true);
const [activeTxIndex, setActiveTxIndex] = useState(-1);
const [txStep, setTxStep] = useState(0);
const [receiver, setReceiver] = useState("");
const [convertedReceiver, setConvertedReceiver] = useState(undefined);
const [amount, setAmount] = useState("");
// ReceivedClaps && ApplausesForTransaction
// session_index
// transaction_hash
// keccak256(receiver, amount, chain_id)
// const initialStoredTransactions = sessionStorage.getItem(STORAGE_PREFIX);
const initialStoredTransactions = JSON.stringify([
{
sessionIndex: 23,
transactionHash: "0x11111111111111111",
receiver: "sfAasdadasasads",
amount: "2312323232223232",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x2222222222222222222",
receiver: "sfAasdadasasads",
amount: "1122232232",
chainId: 1,
timestamp: Date.now()
},
{
sessionIndex: 24,
transactionHash: "0x333333333333333333",
receiver: "sfAasdadasasads",
amount: "99999999999999992",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x4444444444444444444",
receiver: "sfAasdadasasads",
amount: "2312323232223232",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x555555555555555555555",
receiver: "sfAasdadasasads",
amount: "1122232232",
chainId: 1,
timestamp: Date.now()
},
{
sessionIndex: 24,
transactionHash: "0x66666666666666666666666666",
receiver: "sfAasdadasasads",
amount: "99999999999999992",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x77777777777777777777777777",
receiver: "sfAasdadasasads",
amount: "2312323232223232",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x888888888888888888888888888",
receiver: "sfAasdadasasads",
amount: "1122232232",
chainId: 1,
timestamp: Date.now()
},
{
sessionIndex: 24,
transactionHash: "0x999999999999999999999",
receiver: "sfAasdadasasads",
amount: "99999999999999992",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x10101010101010101010",
receiver: "sfAasdadasasads",
amount: "2312323232223232",
chainId: 11155111,
timestamp: Date.now()
},
{
sessionIndex: 23,
transactionHash: "0x12121212121212212",
receiver: "sfAasdadasasads",
amount: "1122232232",
chainId: 1,
timestamp: Date.now()
},
{
sessionIndex: 24,
transactionHash: "0x1313131313131313131",
receiver: "sfAasdadasasads",
amount: "99999999999999992",
chainId: 11155111,
timestamp: Date.now()
}
]);
const [storedTransactions, setStoredTransactions] = useState(
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
);
const incomingCommission = new DecimalBigNumber(69n, 100);
const validators = ["first", "second", "third"];
const clappedValidators = 1;
const { data: blockNumber } = useBlockNumber({ watch: true });
// const { data: txtx } = useTransactionConfirmations({
// hash: "0xdb30adfa3bfc58539bc3a9a92f0dcace8f251af90f8a4f525b57d95d28103afc",
// refetchInterval: 5000
// });
// console.log(txtx)
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const {
balance: ghstBalance,
refetch: ghstBalanceRefetch
} = useBalance(chainId, "GHST", address);
useEffect(() => {
try {
const [publicKey, prefix] = ss58Decode(receiver);
if (prefix !== 1995 && prefix !== 1996) {
throw new Error("bad prefix");
}
setConvertedReceiver(toHex(publicKey));
} catch {
setConvertedReceiver(undefined);
}
}, [receiver])
const chainExplorerUrl = useMemo(() => {
const client = config?.getClient();
return client?.chain?.blockExplorers?.default?.url;
}, [config]);
const chainName = useMemo(() => {
const client = config?.getClient();
return client?.chain?.name;
}, [config]);
const currentRecord = useMemo(() => {
if (activeTxIndex === -1) return undefined
return storedTransactions.at(activeTxIndex)
}, [activeTxIndex, storedTransactions]);
const gatekeeperAddressEmpty = useMemo(() => {
if (gatekeeperAddress === "0x0000000000000000000000000000000000000000") {
return true;
}
return false;
}, [gatekeeperAddress]);
const preparedAmount = useMemo(() => {
try {
return BigInt(parseFloat(amount) * Math.pow(10, 18));
} catch {
return 0n;
}
}, [amount])
const filteredStoredTransactions = useMemo(() => {
return storedTransactions.filter(obj => obj.chainId === chainId);
}, [storedTransactions, chainId]);
const removeStoredRecord = useCallback(() => {
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
setStoredTransactions(newStoredTransactions);
sessionStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
const handleMouseEnter = (index) => {
setTxStep(index);
}
const ghostOrConnect = async () => {
if (address === "") {
connect();
} else {
setIsPending(true);
const txHash = await ghost(chainId, address, convertedReceiver, preparedAmount);
await ghstBalanceRefetch();
setReceiver("");
setAmount("");
setIsPending(false);
}
}
return (
<Box height="calc(100vh - 43px)">
<PageTitle name="GHOST Bridge" subtitle="The only pure Web3 decentralized bridge." />
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "calc(100vh - 153px)"
}}
>
<Modal
data-testid="transaction-details-modal"
maxWidth="476px"
headerContent={
<Box display="flex" flexDirection="row">
<Typography variant="h5">
TX Hash&nbsp;
<Link
sx={{
margin: "0px",
font: "inherit",
letterSpacing: "inherit",
textDecoration: "underline",
color: theme.colors.gray[10],
textUnderlineOffset: "0.23rem",
cursor: "pointer",
textDecorationThickness: "3px",
"&:hover": {
textDecoration: "underline",
}
}}
target="_blank"
rel="noopener noreferrer"
href={currentRecord
? `${chainExplorerUrl}/tx/${currentRecord.transactionHash}`
: ""
}
>
{currentRecord?.transactionHash.slice(0, 9)}...{currentRecord?.transactionHash.slice(-9)}
</Link>
</Typography>
</Box>
}
open={activeTxIndex > -1}
onClose={() => setActiveTxIndex(-1)}
minHeight={"100px"}
>
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<Box
sx={{
transition: "all 0.2s ease",
transform: txStep === 0 && "scale(1.1)",
color: txStep === 0 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
onMouseEnter={() => handleMouseEnter(0)}
>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
viewBox="0 0 25 25"
component={HourglassBottomIcon}
/>
<Typography variant="caption">Finalization</Typography>
<Typography variant="caption">{blockNumber?.toString()} blocks left</Typography>
</Box>
<GhostStyledIcon
sx={{ transition: "all 0.2s ease", opacity: txStep < 1 && "0.2" }}
component={ArrowRightIcon}
/>
<Box
sx={{
transition: "all 0.2s ease",
opacity: txStep < 1 && "0.2",
transform: txStep === 1 && "scale(1.1)",
color: txStep === 1 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
onMouseEnter={() => handleMouseEnter(1)}
>
<Box display="flex" flexDirection="row" justifyContent="center" alignItems="center">
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
viewBox="0 0 25 25"
component={ThumbUpIcon}
/>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
viewBox="0 0 25 25"
component={ThumbDownAltIcon}
/>
</Box>
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center">
<Typography variant="caption">Slow claps</Typography>
<Typography variant="caption">{clappedValidators} / {validators.length}</Typography>
</Box>
</Box>
<GhostStyledIcon
sx={{ transition: "all 0.2s ease", opacity: txStep < 2 && "0.2" }}
component={ArrowRightIcon}
/>
<Box
sx={{
transition: "all 0.2s ease",
opacity: txStep < 2 && "0.2",
transform: txStep === 2 && "scale(1.1)",
color: txStep === 2 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
onMouseEnter={() => handleMouseEnter(2)}
>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
viewBox="0 0 25 25"
component={CheckCircleIcon}
/>
<Typography variant="caption">Applaused</Typography>
<Typography variant="caption">Check Receiver</Typography>
</Box>
</Box>
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">Session Index:</Typography>
<Typography variant="body2">{currentRecord?.sessionIndex}</Typography>
</Box>
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">Receiver Address:</Typography>
<Typography variant="body2">{currentRecord?.receiver}</Typography>
</Box>
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">Sent Amount:</Typography>
<Typography variant="body2">{formatCurrency(
new DecimalBigNumber(
BigInt(currentRecord ? currentRecord.amount : "0"),
18
).toString(), 9, ghstSymbol)
}</Typography>
</Box>
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">Executed at:</Typography>
<Typography variant="body2">{
new Date(currentRecord ? currentRecord.timestamp : 0).toLocaleString('en-US')
}</Typography>
</Box>
</Box>
<Box display="flex" flexDirection="column" gap="5px">
<PrimaryButton
fullWidth
disabled={address === ""}
loading={false}
onClick={() => removeStoredRecord()}
>
Erase Record
</PrimaryButton>
<Typography variant="body2" sx={{ fontStyle: "italic" }}>
This will remove the transaction record from the session storage, but it will not cancel the bridge transaction.
</Typography>
</Box>
</Box>
</Modal>
<Box width="100%" maxWidth="476px" display="flex" alignItems="center" justifyContent="center" flexDirection="column">
<Paper
headerContent={
<Box alignItems="center" justifyContent="space-between" display="flex" width="100%">
<Typography variant="h4">{
bridgeAction
? `Bridge $${ghstSymbol}`
: "Transaction history"
}</Typography>
</Box>
}
topRight={
<GhostStyledIcon
component={bridgeAction ? PendingActionsIcon : PublicIcon}
viewBox="0 0 23 23"
style={{ display: "flex", alignItems: "center", cursor: "pointer" }}
onClick={() => setBridgeAction(!bridgeAction)}
/>
}
enableBackground
fullWidth
>
<Box minHeight="300px" display="flex" flexDirection="column" gap="5px">
{bridgeAction && (
<>
<SwapCollection
iconNotNeeded
UpperSwapCard={<SwapCard
id={`bridge-token-receiver`}
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
value={receiver}
onChange={event => setReceiver(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
placeholder="Ghost address (sf prefixed)"
type="text"
/>}
LowerSwapCard={<SwapCard
id={`bridge-token-amount`}
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
info={`${formatCurrency(ghstBalance.toString(), 4, ghstSymbol)}`}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
endString={"Max"}
endStringOnClick={() => setAmount(ghstBalance.toString())}
/>}
/>
<Box
mb="20px"
mt="20px"
flexDirection="column"
display="flex"
gap="10px"
justifyContent="space-between"
>
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{gatekeeperAddressEmpty && (
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
<Typography mr="10px" variant="body2" color="textSecondary">
<em>
There is no connected gatekeeper on {chainName} network. Propose gatekeeper on this network to make validators listen to it.
</em>
</Typography>
</Box>
)}
{!gatekeeperAddressEmpty && (
<>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Gatekeeper:</Typography>}
<Link
fontSize="12px"
lineHeight="15px"
target="_blank"
rel="noopener noreferrer"
href={`${chainExplorerUrl}/token/${gatekeeperAddress}`}
>
{gatekeeperAddress.slice(0, 10) + "..." + gatekeeperAddress.slice(-8)}
</Link>
</>
)}
</Box>
{incomingCommission && validators?.length ? (
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
<Typography mr="10px" variant="body2" color="textSecondary">
<em>
GHOST Wallet is not detected on your browser. Download&nbsp;
<Link
target="_blank"
rel="noopener noreferrer"
href="https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases"
>
GHOST Wallet
</Link>&nbsp; to see full detalization for bridge transaction.
</em>
</Typography>
</Box>
)
: (
<Box display="flex" flexDirection="column" gap="0px">
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Est. Commission:</Typography>}
<Typography fontSize="12px" lineHeight="15px">unknown</Typography>
</Box>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Number of validators:</Typography>}
<Typography fontSize="12px" lineHeight="15px">unknown</Typography>
</Box>
</Box>
)}
</Box>
<PrimaryButton
fullWidth
disabled={
address === "" || gatekeeperAddressEmpty || !convertedReceiver ||
preparedAmount === 0n || ghstBalance._value < preparedAmount
}
loading={isPending}
onClick={() => ghostOrConnect()}
>
{address === "" ?
"Connect"
:
"Bridge"
}
</PrimaryButton>
</>
)}
{!bridgeAction && (
<Box>
<Box display="grid" gridTemplateColumns="1fr 1fr 60px" sx={{ padding: "0.6rem", borderBottom: "1px solid" }}>
<Typography variant="subtitle1" sx={{ fontWeight: "bold" }}>Amount</Typography>
<Typography variant="subtitle1" sx={{ fontWeight: "bold" }}>Datetime</Typography>
<Typography variant="subtitle1" sx={{ justifySelf: "center", fontWeight: "bold" }}>Status</Typography>
</Box>
<Box display="flex" flexDirection="column" sx={{
height: "250px",
overflowY: "scroll",
"-ms-overflow-style": "thin !important",
"scrollbar-width": "thin !important",
}}>
{filteredStoredTransactions
.map((obj, idx) => (
<Box
display="grid"
gridTemplateColumns="1fr 1fr 60px"
sx={{
borderBottom: "1px solid",
padding: "0.6rem",
paddingTop: "1.2rem",
cursor: 'pointer',
'&:hover': {
background: theme.colors.paper.cardHover
}
}}
key={obj.transactionHash}
onClick={() => setActiveTxIndex(idx)}
>
<Box display="flex" flexDirection="column" justifyContent="center">
<Typography variant="subtitle2">
{formatCurrency(
new DecimalBigNumber(BigInt(obj.amount), 18).toString(),
3,
ghstSymbol
)}
</Typography>
</Box>
<Box display="flex" flexDirection="column">
<Typography variant="subtitle2">
{new Date(obj.timestamp).toLocaleDateString('en-US')}
</Typography>
<Typography variant="subtitle2">
{new Date(obj.timestamp).toLocaleTimeString('en-US')}
</Typography>
</Box>
<Box display="flex" justifyContent="center" alignItems="center">
<Box
display="flex"
justifyContent="center"
alignItems="center"
sx={{
width: "25px",
height: "25px",
background: idx % 2 === 0
? theme.colors.feedback.warning
: theme.colors.feedback.success,
borderRadius: "100%",
boxShadow: "0px 0px 1px black"
}}
>
{idx % 2 === 0 ?
<GhostStyledIcon
sx={{ width: "20px", height: "20px" }}
viewBox="0 0 25 25"
component={HourglassBottomIcon}
/>
:
<GhostStyledIcon
sx={{ width: "20px", height: "20px" }}
viewBox="0 0 25 25"
component={CheckIcon}
/>
}
</Box>
</Box>
</Box>
))}
</Box>
</Box>
)}
</Box>
</Paper>
</Box>
</Container>
</Box>
)
}
export default Bridge;

View File

@ -68,9 +68,6 @@ const PoolContainer = ({
);
const onSwap = () => {
// const oldAmountTop = amountTop;
// const oldAmountBottom = amountBottom;
// setAmountBottom(oldAmountTop);
setAmountTop(amountBottom);
onCardsSwap();
}

View File

@ -42,6 +42,11 @@ const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }
const { balance: stnkBalance } = useBalance(chainId, "STNK", account);
const { balance: ghstBalance } = useBalance(chainId, "GHST", account);
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const searchToken = useMemo(() => {
return [{
name: searchSymbol,
@ -54,31 +59,31 @@ const TokenModal = ({ chainId, account, listOpen, setListOpen, setTokenAddress }
const knownTokens = useMemo(() => {
return [
{
name: "gDAI",
name: daiSymbol,
icons: ["GDAI"],
balance: daiBalance,
address: DAI_ADDRESSES[chainId]
},
{
name: "FTSO",
name: ftsoSymbol,
icons: ["FTSO"],
balance: ftsoBalance,
address: FTSO_ADDRESSES[chainId]
},
{
name: "STNK",
name: stnkSymbol,
icons: ["STNK"],
balance: stnkBalance,
address: STNK_ADDRESSES[chainId]
},
{
name: "GHST",
name: ghstSymbol,
icons: ["GHST"],
balance: ghstBalance,
address: GHST_ADDRESSES[chainId]
}
]
}, [daiBalance, ghstBalance, stnkBalance, ghstBalance]);
}, [daiSymbol, ftsoSymbol, stnkSymbol, ghstSymbol, daiBalance, ftsoBalance, stnkBalance, ghstBalance]);
useEffect(() => {
if (isAddress(userInput)) {

View File

@ -9,33 +9,49 @@ import Paper from "../../components/Paper/Paper";
import SwapCard from "../../components/Swap/SwapCard";
import TokenStack from "../../components/TokenStack/TokenStack";
import { PrimaryButton } from "../../components/Button";
import { Tab, Tabs } from "../../components/Tabs/Tabs";
import { DAI_ADDRESSES } from "../../constants/addresses";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatCurrency, formatNumber } from "../../helpers";
import { useBalance as useTokenBalance, useConversionRate, mintDai } from "../../hooks/tokens";
import {
useBalance as useTokenBalance,
useTokenSymbol,
useTotalSupply,
useConversionRate,
useAccumulatedDonation,
mintDai,
burnDai
} from "../../hooks/tokens";
const Faucet = ({ chainId, address, config, connect }) => {
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const daiConversionRate = useConversionRate(chainId, "GDAI");
const { balance: daiBalance, refetch: daiBalanceRefetch } = useTokenBalance(chainId, "GDAI", address);
const { data: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
const [isMint, setIsMint] = useState(true);
const [isPending, setIsPending] = useState(false);
const [balance, setBalance] = useState(new DecimalBigNumber(0, 0));
const [amount, setAmount] = useState("");
const [faucetToken, setFaucetToken] = useState({
name: "",
address: "",
});
const [scanInfo, setScanInfo] = useState({
name: "",
url: "",
});
const [nativeInfo, setNativeInfo] = useState({
decimals: 18,
name: "",
symbol: "",
})
const daiConversionRate = useConversionRate(chainId, "GDAI");
const accumulatedDonation = useAccumulatedDonation(chainId, "GDAI");
const { balance: daiBalance, refetch: daiBalanceRefetch } = useTokenBalance(chainId, "GDAI", address);
const { data: nativeBalance, refetch: balanceRefetch } = useBalance({ address });
const { data: contractBalance, refetch: contractBalanceRefetch } = useBalance({ address: DAI_ADDRESSES[chainId] });
const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "GDAI");
const { symbol: faucetSymbol } = useTokenSymbol(chainId, "GDAI");
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/faucet" });
@ -51,44 +67,71 @@ const Faucet = ({ chainId, address, config, connect }) => {
let scanName = "";
let scanUrl = "";
const tokenName = "gDAI";
const tokenAddress = DAI_ADDRESSES[chainId];
const client = config?.getClient();
scanName = client?.chain?.blockExplorers?.default?.name;
scanUrl = client?.chain?.blockExplorers?.default?.url;
setFaucetToken({
name: tokenName,
address: tokenAddress,
});
setScanInfo({
name: scanName,
url: scanUrl,
})
setNativeInfo(client?.chain?.nativeCurrency)
}, [chainId]);
const changeIsMinted = (value) => {
if (accumulatedDonation) {
setAmount("");
setIsMint(value);
}
}
const preparedAmount = useMemo(() => {
if (address === "") new DecimalBigNumber("0", 0);
return new DecimalBigNumber(amount, 18);
}, [amount, balance])
const decimals = isMint ? nativeInfo.decimals : 18;
return new DecimalBigNumber(amount, decimals);
}, [amount, balance, nativeInfo])
const estimatedAmount = useMemo(() => {
const estimatedAmountIn = useMemo(() => {
const rate = new DecimalBigNumber(daiConversionRate.toString(), 0);
const value = new DecimalBigNumber(amount, 18);
const value = new DecimalBigNumber(amount, nativeInfo.decimals);
return value.mul(rate);
}, [amount, daiConversionRate])
}, [amount, daiConversionRate, nativeInfo]);
const mintOrConnect = async () => {
const contractBalanceFree = useMemo(() => {
const realContractBalance = contractBalance ? contractBalance.value : 0n;
const preparedContractBalance = new DecimalBigNumber(realContractBalance, nativeInfo.decimals);
const preparedAccumulatedDonation = new DecimalBigNumber(
accumulatedDonation._value,
nativeInfo.decimals && 18
);
return preparedContractBalance.sub(preparedAccumulatedDonation);
}, [contractBalance, accumulatedDonation]);
const estimatedAmountOut = useMemo(() => {
const value = new DecimalBigNumber(amount, nativeInfo.decimals);
if (reserveTotalSupply._value > 0n) {
return value.mul(contractBalanceFree).div(reserveTotalSupply);
}
return new DecimalBigNumber(0n, nativeInfo.decimals);
}, [amount, contractBalanceFree, reserveTotalSupply, nativeInfo]);
const actionOrConnect = async () => {
if (address === "") {
connect();
} else {
setIsPending(true);
await mintDai(chainId, address, preparedAmount._value.toString());
if (isMint) {
await mintDai(chainId, address, preparedAmount._value.toString());
} else {
await burnDai(chainId, address, preparedAmount._value.toString());
}
await balanceRefetch();
await daiBalanceRefetch();
await contractBalanceRefetch();
await refetchReserveTotalSupply();
setAmount("");
setIsPending(false);
}
@ -116,7 +159,7 @@ const Faucet = ({ chainId, address, config, connect }) => {
<meta name="twitter:image" content="https://ghostchain.io/wp-content/uploads/2025/03/ghostFaucet-Featured_Image.png" />
</Helmet>
<PageTitle name={"gDAI Faucet"} subtitle="Swap Sepolia ETH for gDAI." />
<PageTitle name={`${faucetSymbol} Faucet`} subtitle={`Swap Sepolia ${nativeInfo.symbol} for ${faucetSymbol}.`} />
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
@ -131,10 +174,21 @@ const Faucet = ({ chainId, address, config, connect }) => {
<Paper
headerContent={
<Box alignItems="center" justifyContent="space-between" display="flex" width="100%">
<Typography variant="h4">Get {faucetToken.name}</Typography>
<Tabs
centered
textColor="primary"
indicatorColor="primary"
value={isMint ? 0 : 1}
aria-label="Faucet menu"
onChange={(_, view) => changeIsMinted(view === 0)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="faucet-mint-button" label="Mint" style={{ fontSize: "1.5rem" }} />
{accumulatedDonation && <Tab aria-label="faucet-burn-button" label="Burn" style={{ fontSize: "1.5rem" }} />}
</Tabs>
{!isSemiSmallScreen && <PrimaryButton
variant="text"
href={`${scanInfo.url}/token/${faucetToken.address}`}
href={`${scanInfo.url}/token/${DAI_ADDRESSES[chainId]}`}
>
Check on {scanInfo.name}
</PrimaryButton>}
@ -144,16 +198,28 @@ const Faucet = ({ chainId, address, config, connect }) => {
fullWidth
>
<Box>
<SwapCard
{isMint && <SwapCard
id={`faucet-sepolia-eth`}
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
tokenName={"ETH"}
token={<TokenStack tokens={["ETH"]} sx={{ fontSize: "21px" }} />}
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(balance.toString(), 4, "ETH")}`}
tokenName={nativeInfo.symbol}
token={<TokenStack tokens={[nativeInfo.symbol]} sx={{ fontSize: "21px" }} />}
info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(balance.toString(), 4, nativeInfo.symbol)}`}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
/>
/>}
{!isMint && <SwapCard
id={`faucet-sepolia-eth`}
inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"}
tokenName={faucetSymbol}
token={<TokenStack tokens={[faucetSymbol]} sx={{ fontSize: "21px" }} />}
info={`${formatCurrency(daiBalance.toString(), 4, faucetSymbol)}`}
value={amount}
onChange={event => setAmount(event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }}
endString={"Max"}
endStringOnClick={() => setAmount(daiBalance.toString())}
/>}
<Box
mb="20px"
mt="20px"
@ -161,35 +227,56 @@ const Faucet = ({ chainId, address, config, connect }) => {
display="flex"
justifyContent="space-between"
>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">ETH multiplier:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatNumber(daiConversionRate, 2)}</Typography>
</Box>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmount, 5, faucetToken.name)}</Typography>
</Box>
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {faucetToken.name} balance:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(daiBalance, 5, faucetToken.name)}</Typography>
</Box>
{isMint && (
<>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">{nativeInfo.symbol} multiplier:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatNumber(daiConversionRate, 2)}</Typography>
</Box>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountIn, 5, faucetSymbol)}</Typography>
</Box>
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {faucetSymbol} balance:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(daiBalance, 5, faucetSymbol)}</Typography>
</Box>
</>
)}
{!isMint && (
<>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Accumulated {nativeInfo.symbol}:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(contractBalanceFree, 5, nativeInfo.symbol)}</Typography>
</Box>
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">You will get:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(estimatedAmountOut, 5, nativeInfo.symbol)}</Typography>
</Box>
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Your {nativeInfo.symbol} balance:</Typography>}
<Typography fontSize="12px" lineHeight="15px">{formatCurrency(balance, 5, nativeInfo.symbol)}</Typography>
</Box>
</>
)}
</Box>
<PrimaryButton
fullWidth
disabled={
address !== "" && (
preparedAmount?.eq(new DecimalBigNumber(0, 18)) ||
balance?.lt(preparedAmount) ||
isPending
preparedAmount?._value === 0n ||
isPending ||
(isMint && balance?.lt(preparedAmount)) ||
(!isMint && daiBalance?.lt(preparedAmount))
)
}
loading={isPending}
onClick={() => mintOrConnect()}
onClick={() => actionOrConnect()}
>
{address === "" ?
"Connect"
:
"Mint"
isMint ? "Mint" : "Burn"
}
</PrimaryButton>
</Box>

View File

@ -28,21 +28,39 @@ const Stake = ({ chainId, address, isOpened, closeModal, connect }) => {
case (upperToken === "FTSO" && bottomToken === "STNK"):
setAction("STAKE")
break;
case (upperToken === "eCSPR" && bottomToken === "sCSPR"):
setAction("STAKE")
break;
case (upperToken === "FTSO" && bottomToken === "GHST"):
setAction("STAKE")
break;
case (upperToken === "eCSPR" && bottomToken === "CSPR"):
setAction("STAKE")
break;
case (upperToken === "STNK" && bottomToken === "FTSO"):
setAction("UNSTAKE")
break;
case (upperToken === "sCSPR" && bottomToken === "eCSPR"):
setAction("UNSTAKE")
break;
case (upperToken === "GHST" && bottomToken === "FTSO"):
setAction("UNSTAKE")
break;
case (upperToken === "CSPR" && bottomToken === "eCSPR"):
setAction("UNSTAKE")
break;
case (upperToken === "STNK" && bottomToken === "GHST"):
setAction("WRAP")
break;
case (upperToken === "sCSPR" && bottomToken === "CSPR"):
setAction("WRAP")
break;
case (upperToken === "GHST" && bottomToken === "STNK"):
setAction("UNWRAP")
break;
case (upperToken === "CSPR" && bottomToken === "sCSPR"):
setAction("UNWRAP")
break;
default:
setAction("STAKE")
}

View File

@ -16,6 +16,7 @@ import { ClaimsArea } from "./components/ClaimsArea";
import { Apy, CurrentIndex, TotalDeposit } from "./components/Metric";
import { useEpoch } from "../../hooks/staking";
import { useTokenSymbol } from "../../hooks/tokens";
export const StakeContainer = ({ chainId, address, connect }) => {
const [isModalOpened, handleModal] = useState(false);
@ -24,6 +25,9 @@ export const StakeContainer = ({ chainId, address, connect }) => {
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { epoch, refetch: refetchEpoch } = useEpoch(chainId);
useEffect(() => {
@ -44,7 +48,7 @@ export const StakeContainer = ({ chainId, address, connect }) => {
return (
<Box >
<PageTitle name="Protocol Staking" subtitle="Stake your FTSO to earn rebase yields" />
<PageTitle name="Protocol Staking" subtitle={`Stake your ${ftsoSymbol} to earn rebase yields`} />
<Container
style={{
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
@ -71,13 +75,13 @@ export const StakeContainer = ({ chainId, address, connect }) => {
>
<Grid container spacing={1}>
<Grid item xs={isSmallScreen ? 12 : 4}>
<Apy distribute={epoch.distribute} chainId={chainId} />
<Apy stnkSymbol={stnkSymbol} distribute={epoch.distribute} chainId={chainId} />
</Grid>
<Grid item xs={isSmallScreen ? 12 : 4}>
<TotalDeposit chainId={chainId} />
<TotalDeposit stnkSymbol={stnkSymbol} chainId={chainId} />
</Grid>
<Grid item xs={isSmallScreen ? 12 : 4}>
<CurrentIndex chainId={chainId} />
<CurrentIndex ftsoSymbol={ftsoSymbol} chainId={chainId} />
</Grid>
</Grid>

View File

@ -22,7 +22,7 @@ const ClaimConfirmationModal = (props) => {
await forfeit(props.chainId, props.receiver);
break;
case "CLAIM":
await claim(props.chainId, props.receiver, props.outputToken === "STNK");
await claim(props.chainId, props.receiver, props.outputToken === "STNK" || props.outputToken === "sCSPR");
break;
default:
console.log("Unknown action")
@ -55,7 +55,7 @@ const ClaimConfirmationModal = (props) => {
/>
<Box display="flex" flexDirection="row" justifyContent="center">
<Typography>
{props.outputToken}
{props.outputTokenName}
</Typography>
</Box>
</Box>

View File

@ -28,7 +28,7 @@ import { prettifySecondsInDays } from "../../../helpers/timeUtil";
import { formatNumber } from "../../../helpers";
import { STAKING_ADDRESSES } from "../../../constants/addresses";
import { useCurrentIndex, useWarmupInfo } from "../../../hooks/staking";
import { useBalanceForShares } from "../../../hooks/tokens";
import { useBalanceForShares, useTokenSymbol } from "../../../hooks/tokens";
import ClaimConfirmationModal from "./ClaimConfirmationModal";
@ -61,6 +61,10 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
const { currentIndex, refetch: currentIndexRefetch } = useCurrentIndex(chainId);
const { balanceForShares } = useBalanceForShares(chainId, "STNK", claim.shares);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const closeConfirmationModal = () => {
setConfirmationModalOpen(false);
claimRefetch();
@ -85,6 +89,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
receiver={address}
receiveAmount={claim.expiry > epoch.number ? claim.deposit : isPayoutGhst ? balanceForShares.div(currentIndex) : balanceForShares}
outputToken={claim.expiry > epoch.number ? "FTSO" : isPayoutGhst ? "GHST" : "STNK"}
outputTokenName={claim.expiry > epoch.number ? ftsoSymbol : isPayoutGhst ? ghstSymbol : stnkSymbol}
action={claim.expiry > epoch.number ? "forfeit" : "claim"}
chainId={chainId}
/>
@ -98,7 +103,7 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
justifyContent="space-between"
flexDirection={isSmallScreen ? "column" : "row"}
>
<Typography variant="h6">Your active {isPayoutGhst ? "GHST" : "STNK"} claim</Typography>
<Typography variant="h6">Your active {isPayoutGhst ? ghstSymbol : stnkSymbol} claim</Typography>
<Tabs
centered
textColor="primary"
@ -108,8 +113,8 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
onChange={(_, view) => setIsPayoutGhstInner(view === 1)}
TabIndicatorProps={{ style: { display: "none" } }}
>
<Tab aria-label="payout-stnk-button" label="STNK" style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label="GHST" style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-stnk-button" label={stnkSymbol} style={{ fontSize: "1rem" }} />
<Tab aria-label="payout-ghst-button" label={ghstSymbol} style={{ fontSize: "1rem" }} />
</Tabs>
</Box>
}
@ -124,6 +129,8 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
claim={claim}
epoch={epoch}
isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
) : (
<Table>
@ -142,6 +149,8 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
claim={claim}
epoch={epoch}
isClaimable={claim.expiry > epoch.number}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
</Table>
@ -151,21 +160,21 @@ export const ClaimsArea = ({ chainId, address, epoch }) => {
);
};
const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst }) => {
const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isClaimable, isPayoutGhst, stnkSymbol, ghstSymbol }) => {
return (
<TableBody>
<TableRow>
<TableCell style={{ padding: "8px 8px 8px 0" }}>
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
<Token key={isPayoutGhst ? "GHST" : "STNK"} name={isPayoutGhst ? "GHST" : "STNK"} />
<Token key={isPayoutGhst ? ghstSymbol : stnkSymbol} name={isPayoutGhst ? ghstSymbol : stnkSymbol} />
<Box marginLeft="14px" marginRight="10px">
<Typography>{isPayoutGhst ? "GHST" : "STNK"}</Typography>
<Typography>{isPayoutGhst ? ghstSymbol : stnkSymbol}</Typography>
</Box>
</Box>
</TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}>
<Typography gutterBottom={false} style={{ lineHeight: 1.4 }}>
{`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? "GHST" : "STNK"}`}
{`${formatNumber(prepareBalance, 5)} ${isPayoutGhst ? ghstSymbol : stnkSymbol}`}
</Typography>
</TableCell>
<TableCell style={{ padding: "8px 8px 8px 0" }}>
@ -182,13 +191,13 @@ const ClaimInfo = ({ setConfirmationModalOpen, prepareBalance, claim, epoch, isC
);
};
const MobileClaimInfo = ({ setConfirmationModalOpen, prepareBalance, epoch, claim, isPayoutGhst, isClaimable }) => {
const MobileClaimInfo = ({ setConfirmationModalOpen, prepareBalance, epoch, claim, isPayoutGhst, isClaimable, ghstSymbol, stnkSymbol }) => {
return (
<Box mt="10px">
<Box display="flex" flexDirection="row" alignItems="center" style={{ whiteSpace: "nowrap" }}>
<Token key={isPayoutGhst ? "GHST" : "STNK"} name={isPayoutGhst ? "GHST" : "STNK"} />
<Token key={isPayoutGhst ? ghstSymbol : stnkSymbol} name={isPayoutGhst ? ghstSymbol : stnkSymbol} />
<Box marginLeft="14px" marginRight="10px">
<Typography>{isPayoutGhst ? "GHST" : "STNK"}</Typography>
<Typography>{isPayoutGhst ? ghstSymbol : stnkSymbol}</Typography>
</Box>
</Box>

View File

@ -18,7 +18,7 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { formatCurrency } from "../../../helpers";
import { useLpValuation } from "../../../hooks/treasury";
import { useTotalSupply } from "../../../hooks/tokens";
import { useTotalSupply, useTokenSymbol } from "../../../hooks/tokens";
import {
DAI_ADDRESSES,
@ -31,10 +31,13 @@ const FarmPools = ({ chainId }) => {
const { totalSupply: daiFtsoUniTotalSupply } = useTotalSupply(chainId, "GDAI_FTSO");
const daiFtsoUniValuation = useLpValuation(chainId, "GDAI_FTSO", daiFtsoUniTotalSupply._value);
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const pools = [
{
icons: ["FTSO", "GDAI"],
name: "FTSO-gDAI",
name: `${ftsoSymbol}-${daiSymbol}`,
dex: "Uniswap V2",
url: "/dex/uniswap",
tvl: daiFtsoUniValuation,
@ -61,7 +64,7 @@ const FarmPools = ({ chainId }) => {
return (
<Paper headerText="Farm Pools" fullWidth enableBackground>
<TableContainer>
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
<Table aria-label="Farm pools" style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow>
<TableCell style={{ padding: "8px 0", width: "30%" }}>Asset</TableCell>

View File

@ -13,7 +13,7 @@ export const CurrentIndex = props => {
const _props = {
...props,
label: `Current Index`,
tooltip: `The current index indicates the amount of FTSO a holder would possess if they had staked and maintained 1 FTSO since its launch.`,
tooltip: `The current index indicates the amount of ${props.ftsoSymbol} a holder would possess if they had staked and maintained 1 ${props.ftsoSymbol} since its launch.`,
};
if (currentIndex) _props.metric = `${formatNumber(currentIndex, 2)}`;
@ -35,7 +35,7 @@ export const Apy = props => {
const _props = {
...props,
label: "APY",
tooltip: "The annualized rate of return, accounting for compounding from STNKs exponential rebasing.",
tooltip: `The annualized rate of return, accounting for compounding from ${props.stnkSymbol}s exponential rebasing.`,
};
if (apy) _props.metric = `${formatNumber(apy, 2)}${apy === Infinity ? "" : "%"}`;
@ -52,7 +52,7 @@ export const TotalDeposit = props => {
const _props = {
...props,
label: "Total Deposit",
tooltip: "The total stablecoin reserves in the ghostDAO treasury backing the entire circulating supply of STNK.",
tooltip: `The total stablecoin reserves in the ghostDAO treasury backing the entire circulating supply of ${props.stnkSymbol}.`,
};
if (deposit) _props.metric = `${formatCurrency(deposit, 2)}`;

View File

@ -39,7 +39,7 @@ const StakeConfirmationModal = (props) => {
checkedIcon={<CheckBoxOutlined viewBox="0 0 24 24" />}
/>
}
label={`I understand the FTSO (eGHST) Im staking will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed`}
label={`I understand the ${props.ftsoSymbol} Im staking will only be available to claim ${warmupLength.toString()} epochs after my transaction is confirmed`}
/>
</Box>
</>
@ -115,7 +115,7 @@ const StakeConfirmationModal = (props) => {
/>
<Box display="flex" flexDirection="row" justifyContent="center">
<Typography>
{props.upperToken}
{props.upperToken === "FTSO" ? props.ftsoSymbol : props.upperToken === "STNK" ? props.stnkSymbol : props.ghstSymbol}
</Typography>
</Box>
</Box>
@ -127,7 +127,7 @@ const StakeConfirmationModal = (props) => {
/>
<Box display="flex" flexDirection="row" justifyContent="center">
<Typography>
{props.bottomToken}
{props.bottomToken === "FTSO" ? props.ftsoSymbol : props.bottomToken === "STNK" ? props.stnkSymbol : props.ghstSymbol}
</Typography>
</Box>
</Box>
@ -141,7 +141,7 @@ const StakeConfirmationModal = (props) => {
owner={props.address}
spender={STAKING_ADDRESSES[props.chainId]}
decimals={props.spendDecimals}
approvalText={"Approve " + props.upperToken}
approvalText={"Approve " + props.upperToken === "FTSO" ? props.ftsoSymbol : props.upperToken === "STNK" ? props.stnkSymbol : props.ghstSymbol}
approvalPendingText={"Approving..."}
connect={props.connect}
isVertical

View File

@ -23,7 +23,7 @@ import {
STAKING_ADDRESSES,
} from "../../../constants/addresses";
import { useCurrentIndex } from "../../../hooks/staking";
import { useBalance } from "../../../hooks/tokens";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { formatNumber } from "../../../helpers";
const PREFIX = "StakeInputArea";
@ -106,6 +106,10 @@ export const StakeInputArea = ({
const { balance: stnkBalance, refetch: stnkRefetch } = useBalance(chainId, "STNK", address);
const { balance: ghstBalance, refetch: ghstRefetch } = useBalance(chainId, "GHST", address);
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const setIsClaimInner = (value) => {
setIsClaim(value);
localStorage.setItem("stake-isClaim", value);
@ -181,15 +185,37 @@ export const StakeInputArea = ({
}
const SwapCardTemplate = (tokenName, tokenAmount, isUpper, handleModal) => {
const balance = tokenName === "STNK" ?
stnkBalance : tokenName === "FTSO" ?
const balance = tokenName === "STNK" || tokenName === "sCSPR" ?
stnkBalance : tokenName === "FTSO" || tokenName === "eCSPR" ?
ftsoBalance : ghstBalance;
let realTokenName = "";
switch (tokenName.toUpperCase()) {
case "FTSO":
realTokenName = ftsoSymbol;
break;
case "ECSPR":
realTokenName = ftsoSymbol;
break;
case "STNK":
realTokenName = stnkSymbol;
break;
case "SCSPR":
realTokenName = stnkSymbol;
break;
case "GHST":
realTokenName = ghstSymbol;
break;
case "CSPR":
realTokenName = ghstSymbol;
break;
}
return (
<SwapCard
id={`${tokenName.toLowerCase()}-input`}
token={tokenName}
tokenName={tokenName}
tokenName={realTokenName}
tokenOnClick={() => handleModal(true)}
inputProps={{ "data-testid": `${tokenName.toLowerCase()}-input`, min: "0" }}
value={tokenAmount}
@ -228,24 +254,30 @@ export const StakeInputArea = ({
{upperTokenModalOpen && (
<TokenModal
open={upperTokenModalOpen}
handleSelect={data => handleTokenModalInput(data.name, data.isUpper)}
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
handleClose={() => setUpperTokenModalOpen(false)}
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
isUpper={true}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
)}
{bottomTokenModalOpen && (
<TokenModal
open={bottomTokenModalOpen}
handleSelect={data => handleTokenModalInput(data.name, data.isUpper)}
handleSelect={data => handleTokenModalInput(data.token, data.isUpper)}
handleClose={() => setBottomTokenModalOpen(false)}
ftsoBalance={formatNumber(ftsoBalance, formatDecimals)}
stnkBalance={formatNumber(stnkBalance, formatDecimals)}
ghstBalance={formatNumber(ghstBalance, formatDecimals)}
tokenToExclude={upperToken}
isUpper={false}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
)}
@ -292,6 +324,9 @@ export const StakeInputArea = ({
receiveDecimals={bottomToken === "GHST" ? 18 : 9}
isClaim={isClaim}
isTrigger={isTrigger}
ftsoSymbol={ftsoSymbol}
stnkSymbol={stnkSymbol}
ghstSymbol={ghstSymbol}
/>
</>
);

View File

@ -13,20 +13,23 @@ const TokenModal = ({
ghstBalance = "0.00",
tokenToExclude,
isUpper,
ftsoSymbol,
stnkSymbol,
ghstSymbol
}) => {
const theme = useTheme();
const TokenItem = ({ name, exclude, isUpper, balance = "0", icon, address = "", price, decimals, ...props }) => {
const TokenItem = ({ token, name, exclude, isUpper, balance = "0", icon, address = "", price, decimals, ...props }) => {
if (name === exclude) {
return <></>;
}
return (
<ListItem
key={name}
button
key={token}
button="true"
onClick={() => {
handleSelect({name, isUpper});
handleSelect({token, isUpper});
handleClose();
}}
sx={{ borderBottom: `1px solid ${theme.colors.gray[500]}` }}
@ -35,7 +38,7 @@ const TokenModal = ({
{icon ? (
<Avatar src={icon} sx={{ width: "15px", height: "15px" }} />
) : (
<Token name={name} sx={{ fontSize: "15px" }} />
<Token name={token} sx={{ fontSize: "15px" }} />
)}
<ListItemText
@ -66,9 +69,9 @@ const TokenModal = ({
</Link>
</Box>
<List>
{<TokenItem isUpper={isUpper} exclude={tokenToExclude} name="FTSO" balance={ftsoBalance} />}
{<TokenItem isUpper={isUpper} exclude={tokenToExclude} name="STNK" balance={stnkBalance} />}
{<TokenItem isUpper={isUpper} exclude={tokenToExclude} name="GHST" balance={ghstBalance} />}
{<TokenItem isUpper={isUpper} exclude={tokenToExclude} token="FTSO" name={ftsoSymbol} balance={ftsoBalance} />}
{<TokenItem isUpper={isUpper} exclude={tokenToExclude} token="STNK" name={stnkSymbol} balance={stnkBalance} />}
{<TokenItem isUpper={isUpper} exclude={tokenToExclude} token="GHST" name={ghstSymbol} balance={ghstBalance} />}
</List>
</Box>
</Dialog>

View File

@ -17,11 +17,17 @@ import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import SafariFooter from "../../components/SafariFooter/SafariFooter";
import { useTokenSymbol } from "../../hooks/tokens";
const MetricsDashboard = ({ chainId }) => {
const theme = useTheme();
const isMobileScreen = useMediaQuery("(max-width: 885px)");
const isVeryMobileScreen = useMediaQuery("(max-width: 560px)");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const xsCalculator = useMemo(() => {
return isMobileScreen ? isVeryMobileScreen ? 12 : 6 : 4;
}, [isMobileScreen, isVeryMobileScreen])
@ -33,22 +39,22 @@ const MetricsDashboard = ({ chainId }) => {
<Paper fullWidth enableBackground>
<Grid container spacing={2}>
<Grid item xs={xsCalculator}>
<TreasuryMarketCap chainId={chainId} />
<TreasuryMarketCap ftsoSymbol={ftsoSymbol} chainId={chainId} />
</Grid>
<Grid item xs={xsCalculator}>
<FatsoPrice chainId={chainId} />
<FatsoPrice ftsoSymbol={ftsoSymbol} chainId={chainId} />
</Grid>
<Grid item xs={xsCalculator}>
<GhostPrice chainId={chainId} />
<GhostPrice ghstSymbol={ghstSymbol} ftsoSymbol={ftsoSymbol} chainId={chainId} />
</Grid>
<Grid item xs={xsCalculator}>
<CirculatingSupply chainId={chainId} />
<CirculatingSupply stnkSymbol={stnkSymbol} chainId={chainId} />
</Grid>
<Grid item xs={xsCalculator}>
<FatsoBacking chainId={chainId} />
<FatsoBacking ftsoSymbol={ftsoSymbol} chainId={chainId} />
</Grid>
<Grid item xs={xsCalculator}>
<CurrentIndex chainId={chainId} />
<CurrentIndex ftsoSymbol={ftsoSymbol} chainId={chainId} />
</Grid>
</Grid>
</Paper>

View File

@ -15,10 +15,10 @@ export const CurrentIndex = props => {
const _props = {
...props,
label: `Current Index`,
tooltip: `The current index indicates the amount of FTSO a holder would possess if they had staked and maintained 1 FTSO since its launch.`,
tooltip: `The current index indicates the amount of ${props.ftsoSymbol} a holder would possess if they had staked and maintained 1 ${props.ftsoSymbol} since its launch.`,
};
if (currentIndex) _props.metric = `${formatNumber(currentIndex, 2)} FTSO`;
if (currentIndex) _props.metric = `${formatNumber(currentIndex, 2)} ${props.ftsoSymbol}`;
else _props.isLoading = true;
return <Metric {..._props} />;
@ -31,8 +31,8 @@ export const GhostPrice = props => {
const _props = {
...props,
label: "GHST " + `Price`,
tooltip: "1 GHST = 1 FTSO x Current Index",
label: `${props.ghstSymbol} Price`,
tooltip: `1 ${props.ghstSymbol} = 1 ${props.ftsoSymbol} x Current Index`,
};
if (ghstPrice) _props.metric = formatCurrency(ghstPrice, 2);
@ -46,8 +46,8 @@ export const FatsoPrice = props => {
const _props = {
...props,
label: "FTSO " + `Price`,
tooltip: `Weighted FTSO Price Across V2 DEXs on the chosen EVM Chain`,
label: `${props.ftsoSymbol} Price`,
tooltip: `Weighted ${props.ftsoSymbol} Price Across V2 DEXs on the chosen EVM Chain`,
};
if (ftsoPrice) _props.metric = formatCurrency(ftsoPrice, 2);
@ -63,7 +63,7 @@ export const CirculatingSupply = props => {
const _props = {
...props,
label: `Circulating / Total`,
tooltip: `Circulating supply refers to the amount of STNK in circulation, excluding those held by the protocol in its treasury. However, STNK allocated to Protocol-Owned Liquidity is considered part of the circulating supply.`,
tooltip: `Circulating supply refers to the amount of ${props.stnkSymbol} in circulation, excluding those held by the protocol in its treasury. However, ${props.stnkSymbol} allocated to Protocol-Owned Liquidity is considered part of the circulating supply.`,
};
if (circulatingSupply && totalSupply) _props.metric = `${formatNumber(circulatingSupply, 0)}/${formatNumber(totalSupply, 0)}`;
@ -83,8 +83,8 @@ export const FatsoBacking = props => {
const _props = {
...props,
label: `Backing per FTSO`,
tooltip: `The total amount of stablecoins held by the ghostDAO treasury to support the value of each FTSO in circulation.`
label: `Backing per ${props.ftsoSymbol}`,
tooltip: `The total amount of stablecoins held by the ghostDAO treasury to support the value of each ${props.ftsoSymbol} in circulation.`
};
if (backing) _props.metric = formatCurrency(backing, 2);
@ -101,7 +101,7 @@ export const TreasuryMarketCap = props => {
const _props = {
...props,
label: `Market Cap`,
tooltip: `Market Cap = FTSO Price × FTSO Total Supply`
tooltip: `Market Cap = ${props.ftsoSymbol} Price × ${props.ftsoSymbol} Total Supply`
};
if (marketCap) _props.metric = formatCurrency(marketCap, 2);

View File

@ -7,7 +7,7 @@ import { SecondaryButton } from "../../../components/Button";
import { formatNumber, formatCurrency } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import { useBalance } from "../../../hooks/tokens";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import {
useFtsoPrice,
useStnkPrice,
@ -79,6 +79,11 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
const ghstPrice = useGhstPrice(chainId);
const daiPrice = useDaiPrice(chainId);
const { symbol: daiSymbol } = useTokenSymbol(chainId, "GDAI");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance: ftsoBalance, contractAddress: ftsoAddress } = useBalance(chainId, "FTSO", address);
const { balance: stnkBalance, contractAddress: stnkAddress } = useBalance(chainId, "STNK", address);
const { balance: ghstBalance, contractAddress: ghstAddress } = useBalance(chainId, "GHST", address);
@ -95,10 +100,10 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
to: `${ftsoAddress}`,
})}
theme={theme}
tokenName="FTSO"
tokenName={ftsoSymbol}
balance={ftsoBalance}
price={ftsoPrice}
description="FTSO is the native token of the ghostDAO protocol, fully backed by stablecoin reserves held in the ghostDAO treasury."
description={`${ftsoSymbol} is the native token of the ghostDAO protocol, fully backed by stablecoin reserves held in the ghostDAO treasury.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
@ -108,10 +113,10 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
to: `${stnkAddress}`,
})}
theme={theme}
tokenName="STNK"
tokenName={stnkSymbol}
balance={stnkBalance}
price={stnkPrice}
description="STNK is a receipt for staked FTSO, growing with staking rewards. When unstaked, its burned for FTSO at a 1:1 ratio."
description={`${stnkSymbol} is a receipt for staked ${ftsoSymbol}, growing with staking rewards. When unstaked, its burned for ${ftsoSymbol} at a 1:1 ratio.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
@ -121,20 +126,20 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
to: `${ghstAddress}`,
})}
theme={theme}
tokenName="GHST"
tokenName={ghstSymbol}
balance={ghstBalance}
price={ghstPrice}
description="GHST enables ghostDAO to have on-chain governance and to be truly cross-chain. GHST Price = FTSO Price x Current Index."
description={`${ghstSymbol} enables ghostDAO to have on-chain governance and to be truly cross-chain. ${ghstSymbol} Price = ${ftsoSymbol} Price x Current Index.`}
/>
<TokenTab
isMobileScreen={isMobileScreen}
tokenUrl="/faucet"
tokenUrlParams=""
theme={theme}
tokenName="gDAI"
tokenName={daiSymbol}
balance={daiBalance}
price={daiPrice}
description="FTSO is backed by a treasury reserve of crypto assets, with gDAI being the primary and most liquid asset."
description={`${ftsoSymbol} is backed by a treasury reserve of crypto assets, with ${daiSymbol} being the primary and most liquid asset.`}
/>
</Box>
</Grid>

View File

@ -14,7 +14,8 @@ import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { abi as BondingCalculatorAbi } from "../../abi/GhostBondingCalculator.json";
import { useFtsoPrice } from "../prices";
import { getTokenAddress, getTokenIcons, getTokenName, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
import { useTokenSymbol, useTokenSymbols } from "../tokens";
import { getTokenAddress, getTokenIcons, getBondNameDisplayName, getTokenPurchaseLink } from "../helpers";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { shorten } from "../../helpers";
@ -104,6 +105,9 @@ export const useLiveBonds = (chainId) => {
})
});
const { symbols: quoteTokenSymbols } = useTokenSymbols(chainId, markets?.map(m => m.result?.at(1)));
const { symbol: baseTokenSymbol } = useTokenSymbol(chainId, "FTSO");
const liveBonds = liveIndexesRaw ? liveIndexesRaw.map((bondIndex, index) => {
const id = Number(bondIndex);
@ -119,6 +123,7 @@ export const useLiveBonds = (chainId) => {
const markdown = markdowns?.at(index).result
? new DecimalBigNumber(markdowns.at(index).result, quoteTokenDecimals)
: new DecimalBigNumber(1n, 0);
const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";
const quoteTokenPerBaseToken = new DecimalBigNumber(marketPrice, 9);
const priceInUsd = quoteTokenPerUsd.mul(quoteTokenPerBaseToken).mul(markdown);
@ -140,19 +145,20 @@ export const useLiveBonds = (chainId) => {
);
const zero = new DecimalBigNumber(0n, 0);
const bondName = `${baseTokenSymbol}/${quoteTokenSymbol}`;
return {
id,
discount,
displayName: getBondNameDisplayName(chainId, markets?.at(index).result?.at(1)),
displayName: getBondNameDisplayName(chainId, bondName, quoteTokenAddress),
baseToken: {
name: "FTSO",
name: quoteTokenSymbol,
purchaseUrl: getTokenPurchaseLink(chainId, ""),
icons: ["FTSO"],
tokenAddress: getTokenAddress(chainId, "FTSO")
},
quoteToken: {
name: getTokenName(chainId, quoteTokenAddress),
name: quoteTokenName,
purchaseUrl: getTokenPurchaseLink(chainId, quoteTokenAddress),
icons: getTokenIcons(chainId, quoteTokenAddress),
decimals: quoteTokenDecimals,
@ -229,14 +235,17 @@ export const useNotes = (chainId, address) => {
}),
});
const { symbols: quoteTokenSymbols } = useTokenSymbols(chainId, markets?.map(m => m.result?.at(1)));
const notes = indexesFor ? indexesFor.map((noteIndex, index) => {
const id = Number(noteIndex);
const quoteTokenAddress = markets?.at(index).result?.at(1) ? markets.at(index).result.at(1) : "";
const quoteTokenSymbol = quoteTokenSymbols?.at(index).result ? quoteTokenSymbols.at(index).result : "";
return {
id,
quoteToken: {
name: getTokenName(chainId, quoteTokenAddress),
name: quoteTokenSymbol,
icons: getTokenIcons(chainId, quoteTokenAddress),
},
vesting: terms?.at(index).result?.at(2) ? terms.at(index).result.at(2) : 0,

View File

@ -12,59 +12,98 @@ import { abi as StinkyAbi } from "../abi/Stinky.json";
import { abi as GhostAbi } from "../abi/Ghost.json";
import { abi as Erc20Abi } from "../abi/ERC20.json";
// TBD: should be extended on new tokens
export const getTokenAbi = (name) => {
let abi = Erc20Abi;
switch (name?.toUpperCase()) {
case "DAI":
abi = DaiAbi;
break;
case "GDAI":
abi = DaiAbi;
break;
case "FTSO":
abi = FatsoAbi;
break;
case "ECSPR":
abi = FatsoAbi;
break;
case "STNK":
abi = StinkyAbi;
break;
case "SCSPR":
abi = StinkyAbi;
break;
case "GHST":
abi = GhostAbi;
break;
case "CSPR":
abi = GhostAbi;
break;
}
return abi;
}
// TBD: should be extended on new tokens
export const getTokenDecimals = (name) => {
let decimals = 18;
switch (name?.toUpperCase()) {
case "DAI":
decimals = 18;
break;
case "GDAI":
decimals = 18;
break;
case "FTSO":
decimals = 9;
break;
case "ECSPR":
decimals = 9;
break;
case "STNK":
decimals = 9;
break;
case "SCSPR":
decimals = 9;
break;
case "GHST":
decimals = 18;
break;
case "CSPR":
decimals = 18;
break;
}
return decimals;
}
// TBD: should be extended on new tokens
export const getTokenAddress = (chainId, name) => {
let address = name;
switch (name?.toUpperCase()) {
case "DAI":
address = DAI_ADDRESSES[chainId];
break;
case "GDAI":
address = DAI_ADDRESSES[chainId];
break;
case "FTSO":
address = FTSO_ADDRESSES[chainId];
break;
case "ECSPR":
address = FTSO_ADDRESSES[chainId];
break;
case "STNK":
address = STNK_ADDRESSES[chainId];
break;
case "SCSPR":
address = STNK_ADDRESSES[chainId];
break;
case "GHST":
address = GHST_ADDRESSES[chainId];
break;
case "CSPR":
address = GHST_ADDRESSES[chainId];
break;
case "GDAI_FTSO":
address = FTSO_DAI_LP_ADDRESSES[chainId];
break;
@ -72,6 +111,7 @@ export const getTokenAddress = (chainId, name) => {
return address;
}
// TBD: should be extended on new tokens
export const getTokenIcons = (chainId, address) => {
let icons = [""];
switch (address) {
@ -94,39 +134,11 @@ export const getTokenIcons = (chainId, address) => {
return icons;
}
export const getTokenName = (chainId, address) => {
let name = "";
switch (address) {
case DAI_ADDRESSES[chainId]:
name = "gDAI";
break;
case FTSO_ADDRESSES[chainId]:
name = "FTSO";
break;
case STNK_ADDRESSES[chainId]:
name = "STNK";
break;
case GHST_ADDRESSES[chainId]:
name = "GHST";
break;
case FTSO_DAI_LP_ADDRESSES[chainId]:
name = "UNI-V2";
break;
export const getBondNameDisplayName = (chainId, stringValue, tokenAddress) => {
if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
stringValue = `LP ${stringValue}`;
}
return name;
}
export const getBondNameDisplayName = (chainId, tokenAddress) => {
let bondName = "";
switch (tokenAddress) {
case DAI_ADDRESSES[chainId]:
bondName = "FTSO/gDAI";
break;
case FTSO_DAI_LP_ADDRESSES[chainId]:
bondName = "LP FTSO/gDAI"
break;
}
return bondName;
return stringValue;
}
export const getTokenPurchaseLink = (chainId, tokenAddress) => {

View File

@ -1,6 +1,6 @@
import { useReadContract } from "wagmi";
import { useCurrentIndex } from "../staking";
import { useCurrentIndex, useGhostedSupply } from "../staking";
import { useUniswapV2PairReserves } from "../uniswapv2";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { FTSO_DAI_LP_ADDRESSES, DAI_ADDRESSES, FTSO_ADDRESSES } from "../../constants/addresses";
@ -20,13 +20,19 @@ export const useFtsoPrice = (chainId) => {
"GDAI",
);
const stableReserves = DAI_ADDRESSES[chainId].toUpperCase() === tokens.token0.toUpperCase()
? reserves.reserve0._value
: DAI_ADDRESSES[chainId].toUpperCase() === tokens.token1.toUpperCase() ? reserves.reserve1._value : 0n;
const reserveAddress = DAI_ADDRESSES[chainId];
const ftsoAddress = FTSO_ADDRESSES[chainId];
if (!reserveAddress || !ftsoAddress) {
return new DecimalBigNumber(0n, 9);
}
const ftsoReserves = FTSO_ADDRESSES[chainId].toUpperCase() === tokens.token0.toUpperCase()
const stableReserves = reserveAddress.toUpperCase() === tokens.token0.toUpperCase()
? reserves.reserve0._value
: FTSO_ADDRESSES[chainId].toUpperCase() === tokens.token1.toUpperCase() ? reserves.reserve1._value : 0n;
: reserveAddress.toUpperCase() === tokens.token1.toUpperCase() ? reserves.reserve1._value : 0n;
const ftsoReserves = ftsoAddress.toUpperCase() === tokens.token0.toUpperCase()
? reserves.reserve0._value
: ftsoAddress.toUpperCase() === tokens.token1.toUpperCase() ? reserves.reserve1._value : 0n;
let price = 0n
if (ftsoReserves > 0n)
@ -46,3 +52,10 @@ export const useGhstPrice = (chainId) => {
return ftsoPrice.mul(currentIndex);
};
export const useGhostedSupplyPrice = (chainId) => {
const ghstPrice = useGhstPrice(chainId);
const ghostedSupply = useGhostedSupply(chainId);
return ghstPrice.mul(ghostedSupply);
}

View File

@ -83,6 +83,36 @@ export const useWarmupInfo = (chainId, address) => {
return { warmupInfo, refetch }
}
export const useGatekeeperAddress = (chainId) => {
const { data: info, refetch } = useReadContract({
abi: StakingAbi,
address: STAKING_ADDRESSES[chainId],
functionName: "gatekeeper",
scopeKey: `gatekeeper-${chainId}`,
chainId: chainId,
});
const gatekeeperAddress = info ? info : "0x0000000000000000000000000000000000000000";
return { gatekeeperAddress, refetch };
}
export const useGhostedSupply = (chainId) => {
const { data: info, refetch } = useReadContract({
abi: StakingAbi,
address: STAKING_ADDRESSES[chainId],
functionName: "ghostedSupply",
scopeKey: `ghostedSupply-${chainId}`,
chainId: chainId,
});
const ghostedSupply = new DecimalBigNumber(
info ? info : 0n,
18
);
return ghostedSupply;
}
export const stake = async (chainId, account, amount, isRebase, isClaim) => {
const args = [
amount,
@ -186,6 +216,24 @@ export const wrap = async (chainId, account, amount) => {
);
}
export const ghost = async (chainId, account, receiver, amount) => {
const messages = {
replacedMsg: "Bridge transaction was replaced. Wait for inclusion please.",
successMsg: `Amount successfully bridged! Check tx hash status or wait for slow clap finalization.`,
errorMsg: "Bridge transaction failed. Check logs for error detalization.",
};
const txHash = await executeOnChainTransaction(
chainId,
"ghost",
[receiver, amount],
account,
messages
);
return txHash;
}
const executeOnChainTransaction = async (
chainId,
functionName,
@ -211,8 +259,11 @@ const executeOnChainTransaction = async (
});
toast.success(messages.successMsg);
return txHash;
} catch (err) {
console.error(err);
toast.error(messages.errorMsg)
return undefined;
}
}

View File

@ -1,4 +1,4 @@
import { useReadContract, useToken, useBalance as useInnerBalance } from "wagmi";
import { useReadContract, useReadContracts, useToken, useBalance as useInnerBalance } from "wagmi";
import { simulateContract, writeContract, waitForTransactionReceipt } from "@wagmi/core";
import toast from "react-hot-toast";
@ -72,6 +72,23 @@ export const useTokenSymbol = (chainId, name) => {
return { symbol, refetch };
}
export const useTokenSymbols = (chainId, names) => {
const { data: symbols } = useReadContracts({
contracts: names?.map((name) => {
const contractAddress = getTokenAddress(chainId, name);
return {
abi: getTokenAbi(name),
address: contractAddress,
functionName: "symbol",
scopeKey: `symbol-${contractAddress}-${chainId}`,
chainId: chainId,
}
})
});
return { symbols };
}
export const useConversionRate = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name);
const { data: rateRaw } = useReadContract({
@ -88,6 +105,22 @@ export const useConversionRate = (chainId, name) => {
return rate;
}
export const useAccumulatedDonation = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name);
const { data: donationRaw } = useReadContract({
abi: getTokenAbi(name),
address: contractAddress,
functionName: "accumulatedDonation",
scopeKey: `accumulatedDonation-${contractAddress}-${chainId}`,
chainId: chainId,
});
const preparedDonationRaw = donationRaw ? donationRaw : 0n;
const accumulatedDonation = new DecimalBigNumber(preparedDonationRaw, 0);
return accumulatedDonation;
}
export const useCirculatingSupply = (chainId, name) => {
const contractAddress = getTokenAddress(chainId, name);
const { data: circulatingSupplyRaw } = useReadContract({
@ -171,3 +204,28 @@ export const mintDai = async (chainId, account, value) => {
toast.error("Minting gDAI from the faucet failed. Check logs for error detalization.")
}
}
export const burnDai = async (chainId, account, value) => {
try {
const { request } = await simulateContract(config, {
abi: getTokenAbi("GDAI"),
address: getTokenAddress(chainId, "GDAI"),
functionName: 'burn',
args: [value],
account: account,
chainId: chainId
});
const txHash = await writeContract(config, request);
await waitForTransactionReceipt(config, {
hash: txHash,
onReplaced: () => toast("Burn transaction was replaced. Wait for inclusion please."),
chainId
});
toast.success("gDAI successfully burned for native coins! Check your wallet balance.");
} catch (err) {
console.error(err);
toast.error("Burning gDAI from the faucet failed. Check logs for error detalization.")
}
}

View File

@ -14,7 +14,6 @@ const queryClient = new QueryClient();
const TRACKING_ID = import.meta.env.VITE_APP_TRACKING_ID;
ReactGA.initialize(TRACKING_ID);
ReactDOM.createRoot(document.getElementById('root')).render(
<>
<BackgroundCanvas />

View File

@ -18,7 +18,7 @@ export const darkPalette = {
success: "#60C45B", // idk where this is - done
userFeedback: "#49A1F2", // idk where this is
error: "#F06F73", // red negative % - done
warning: "#F06F73", // idk where this is - done
warning: "#49A1F2", // idk where this is - done
pnlGain: "#60C45B", // green positive % - done
},
gray: {