Compare commits

...

3 Commits

Author SHA1 Message Date
6f4d38efb3
fix reserve icons for list of all bonds
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-01-05 17:07:51 +03:00
92819639d0
apply another corrections from @neptune and @ghost_7
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-12-16 17:35:46 +03:00
d1d4313851
apply corrections from @neptune and @ghost_7
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-12-15 18:27:32 +03:00
11 changed files with 276 additions and 123 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "ghost-dao-interface", "name": "ghost-dao-interface",
"private": true, "private": true,
"version": "0.4.0", "version": "0.4.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -30,7 +30,6 @@ const StyledArrow = styled(Box)(
const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded, maxWidth}) => { const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded, maxWidth}) => {
const theme = useTheme(); const theme = useTheme();
return ( return (
<Box display="flex" flexDirection="column" maxWidth={maxWidth ? maxWidth : "476px"}> <Box display="flex" flexDirection="column" maxWidth={maxWidth ? maxWidth : "476px"}>
{UpperSwapCard} {UpperSwapCard}

View File

@ -38,8 +38,8 @@ const Tooltip = ({ message, children }) => {
onResizeCapture={undefined} onResizeCapture={undefined}
slotProps={undefined} slotProps={undefined}
slots={undefined} slots={undefined}
> >
<Paper className="info-tooltip"> <Paper style={{ boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px" }} className="info-tooltip">
{typeof message === "string" ? ( {typeof message === "string" ? (
<Typography variant="body1" className="info-tooltip-text"> <Typography variant="body1" className="info-tooltip-text">
{message} {message}

View File

@ -171,9 +171,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
const applaused = transactionApplaused?.finalized ?? false; const applaused = transactionApplaused?.finalized ?? false;
const clappedAmount = transactionApplaused?.clapped_amount ?? 0n; const clappedAmount = transactionApplaused?.clapped_amount ?? 0n;
const clappedPercentage = clappedAmount * 100n / (totalStakedAmount ?? 1n); const clappedPercentage = clappedAmount * 100n / (totalStakedAmount ?? 1n);
const step = finalization > 0 const clapsPercentage = (numberOfClaps ?? 0) * 100 / (authorities?.length ?? 1);
? 0
: applaused ? 2 : 1;
return { return {
...watchTransaction, ...watchTransaction,
@ -182,14 +180,15 @@ const Bridge = ({ chainId, address, config, connect }) => {
numberOfClaps, numberOfClaps,
clappedAmount, clappedAmount,
clappedPercentage, clappedPercentage,
step, clapsPercentage,
} }
}, [ }, [
transactionApplaused, transactionApplaused,
finalityDelay, finalityDelay,
watchTransaction, watchTransaction,
blockNumber, blockNumber,
totalStakedAmount totalStakedAmount,
authorities
]); ]);
const filteredStoredTransactions = useMemo(() => { const filteredStoredTransactions = useMemo(() => {
@ -242,12 +241,12 @@ const Bridge = ({ chainId, address, config, connect }) => {
let certainty = 0n; let certainty = 0n;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const commit = latestCommits.at(i); const commit = latestCommits.at(i);
if (commit.disabled) { if (commit.disabled || blockNumber < blocksInFourHours) {
continue; continue;
} }
certainty += (commit?.lastStoredBlock ?? 0n) - (blockNumber - blocksInFourHours); certainty += (commit?.lastStoredBlock ?? 0n) - (blockNumber - blocksInFourHours);
} }
return Number(certainty * 100n / (blocksInFourHours * BigInt(length))); return Math.max(Number(certainty * 100n / (blocksInFourHours * BigInt(length))), 0);
}, [latestCommits, blockNumber]); }, [latestCommits, blockNumber]);
const timeToNextEpoch = useMemo(() => { const timeToNextEpoch = useMemo(() => {
@ -284,10 +283,10 @@ const Bridge = ({ chainId, address, config, connect }) => {
timestamp: Date.now() timestamp: Date.now()
} }
const newStoredTransactions = [...storedTransactions, transaction]; const newStoredTransactions = [transaction, ...storedTransactions];
setStoredTransactions(newStoredTransactions); setStoredTransactions(newStoredTransactions);
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions)); localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(newStoredTransactions.length - 1) setActiveTxIndex(0)
} }
return ( return (
@ -322,7 +321,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} height="100%"> <Grid item xs={12} height="100%">
<Paper <Paper
sx={{ paddingBottom: "0 !important", marginBottom: "0 !important" }} sx={{ height: isBigScreen ? "100%" : "142px", paddingBottom: "0 !important", marginBottom: "0 !important" }}
fullWidth fullWidth
enableBackground enableBackground
> >
@ -362,6 +361,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
/> />
</Box> </Box>
)} )}
style={{ height: "434px" }}
fullWidth fullWidth
enableBackground enableBackground
> >
@ -383,6 +383,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
/> />
: <BridgeCardHistory : <BridgeCardHistory
isSemiSmallScreen={isSemiSmallScreen} isSemiSmallScreen={isSemiSmallScreen}
isBigScreen={isBigScreen}
filteredStoredTransactions={filteredStoredTransactions} filteredStoredTransactions={filteredStoredTransactions}
ghstSymbol={ghstSymbol} ghstSymbol={ghstSymbol}
blockNumber={blockNumber} blockNumber={blockNumber}
@ -407,6 +408,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
}</Typography> }</Typography>
</Box> </Box>
} }
style={{ height: "434px" }}
enableBackground enableBackground
fullWidth fullWidth
> >

View File

@ -13,6 +13,7 @@ import {
TableCell, TableCell,
Modal, Modal,
useTheme, useTheme,
useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { useConfig } from "wagmi"; import { useConfig } from "wagmi";
@ -23,13 +24,13 @@ import { PrimaryButton } from "../../components/Button";
import GhostStyledIcon from "../../components/Icon/GhostIcon"; import GhostStyledIcon from "../../components/Icon/GhostIcon";
import SwapCard from "../../components/Swap/SwapCard"; import SwapCard from "../../components/Swap/SwapCard";
import SwapCollection from "../../components/Swap/SwapCollection"; import SwapCollection from "../../components/Swap/SwapCollection";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import { ghost } from "../../hooks/staking"; import { ghost } from "../../hooks/staking";
import { useBalance } from "../../hooks/tokens"; import { useBalance } from "../../hooks/tokens";
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { formatNumber, formatCurrency, timeConverter } from "../../helpers"; import { formatNumber, formatCurrency, timeConverter } from "../../helpers";
@ -158,16 +159,21 @@ export const BridgeCardAction = ({
}, [receiver]) }, [receiver])
return ( return (
<Box width="100%" height="320px" display="flex" flexDirection="column" justifyContent="space-between"> <Box width="100%" height="340px" display="flex" flexDirection="column" justifyContent="space-between">
<SwapCollection <SwapCollection
iconNotNeeded iconNotNeeded
maxWidth="100%"
UpperSwapCard={<SwapCard UpperSwapCard={<SwapCard
id={`bridge-token-receiver`} id={`bridge-token-receiver`}
inputWidth={"100%"} inputWidth={"100%"}
value={receiver} value={convertedReceiver ? sliceString(receiver, 15, -10) : receiver}
onChange={event => setReceiver(event.currentTarget.value)} onChange={event => setReceiver(convertedReceiver ? "" : event.currentTarget.value)}
inputProps={{ "data-testid": "fromInput" }} inputProps={{ "data-testid": "fromInput" }}
placeholder="GHOST address (sf prefixed)" placeholder="GHOST address (sf prefixed)"
endString={convertedReceiver
? <GhostStyledIcon color="success" viewBox="0 0 25 25" component={CheckCircleIcon} />
: undefined
}
type="text" type="text"
maxWidth="100%" maxWidth="100%"
/>} />}
@ -189,18 +195,18 @@ export const BridgeCardAction = ({
gap="10px" gap="10px"
justifyContent="space-between" justifyContent="space-between"
> >
<Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}> <Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{gatekeeperAddressEmpty && ( {gatekeeperAddressEmpty && (
<Box maxWidth="416px" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}> <Box display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
<Typography mr="10px" variant="body2" color="textSecondary"> <Typography align="justify" mr="10px" variant="body2" color="textSecondary">
<em> <em>
There is no connected gatekeeper on {chainName} network. Propose gatekeeper on this network to make authorities listen to it. There is no connected gatekeeper on {chainName} network yet.
</em> </em>
</Typography> </Typography>
</Box> </Box>
)} )}
{!gatekeeperAddressEmpty && ( {!gatekeeperAddressEmpty && (
<> <Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
{!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Gatekeeper:</Typography>} {!isVerySmallScreen && <Typography fontSize="12px" lineHeight="15px">Gatekeeper:</Typography>}
<Link <Link
fontSize="12px" fontSize="12px"
@ -211,7 +217,7 @@ export const BridgeCardAction = ({
> >
{sliceString(gatekeeperAddress, 10, -8)} {sliceString(gatekeeperAddress, 10, -8)}
</Link> </Link>
</> </Box>
)} )}
</Box> </Box>
<Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}> <Box width="100%" display="flex" justifyContent={isVerySmallScreen ? "end" : "space-between"}>
@ -248,12 +254,15 @@ export const BridgeCardAction = ({
export const BridgeCardHistory = ({ export const BridgeCardHistory = ({
isSemiSmallScreen, isSemiSmallScreen,
isBigScreen,
filteredStoredTransactions, filteredStoredTransactions,
ghstSymbol, ghstSymbol,
blockNumber, blockNumber,
finalityDelay, finalityDelay,
setActiveTxIndex setActiveTxIndex
}) => { }) => {
const isVeryBigScreen = useMediaQuery("(max-width: 1360px)");
const theme = useTheme(); const theme = useTheme();
const background = (index) => { const background = (index) => {
return index % 2 === 1 ? "" : theme.colors.gray[750]; return index % 2 === 1 ? "" : theme.colors.gray[750];
@ -274,9 +283,9 @@ export const BridgeCardHistory = ({
<Table sx={{ marginTop: "0px" }} stickyHeader aria-label="sticky available transactions"> <Table sx={{ marginTop: "0px" }} stickyHeader aria-label="sticky available transactions">
<TableHead> <TableHead>
<TableRow sx={{ height: "40px" }}> <TableRow sx={{ height: "40px" }}>
<TableCell align="left" style={{ padding: "0px", paddingLeft: "16px", fontSize: "12px", borderTopLeftRadius: "3px", background: theme.colors.paper.cardHover }}> {!(isBigScreen ^ isVeryBigScreen) && <TableCell align="left" style={{ padding: "0px", paddingLeft: "16px", fontSize: "12px", borderTopLeftRadius: "3px", background: theme.colors.paper.cardHover }}>
Transaction Transaction
</TableCell> </TableCell>}
<TableCell align="center" style={{ padding: "0px", fontSize: "12px", background: theme.colors.paper.cardHover }}> <TableCell align="center" style={{ padding: "0px", fontSize: "12px", background: theme.colors.paper.cardHover }}>
Datetime Datetime
</TableCell> </TableCell>
@ -294,7 +303,7 @@ export const BridgeCardHistory = ({
data-testid={idx + `--tx-history`} data-testid={idx + `--tx-history`}
onClick={() => setActiveTxIndex(idx)} onClick={() => setActiveTxIndex(idx)}
> >
<TableCell style={{ background: background(idx) }}> {!(isBigScreen ^ isVeryBigScreen) && <TableCell style={{ background: background(idx) }}>
<Box display="flex" flexDirection="column" justifyContent="center"> <Box display="flex" flexDirection="column" justifyContent="center">
<Typography variant="caption"> <Typography variant="caption">
{formatCurrency( {formatCurrency(
@ -311,7 +320,7 @@ export const BridgeCardHistory = ({
)} )}
</Typography> </Typography>
</Box> </Box>
</TableCell> </TableCell>}
<TableCell style={{ background: background(idx) }}> <TableCell style={{ background: background(idx) }}>
<Box display="flex" flexDirection="column" alignItems="center" paddingLeft="5px"> <Box display="flex" flexDirection="column" alignItems="center" paddingLeft="5px">

View File

@ -3,6 +3,7 @@ import { Box, Paper, Grid, Typography, LinearProgress, useTheme } from "@mui/mat
import Metric from "../../components/Metric/Metric"; import Metric from "../../components/Metric/Metric";
import Countdown from "../../components/Countdown/Countdown"; import Countdown from "../../components/Countdown/Countdown";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
import { formatNumber } from "../../helpers"; import { formatNumber } from "../../helpers";
export const BridgeHeader = ({ export const BridgeHeader = ({
@ -83,7 +84,7 @@ export const BridgeHeader = ({
<Grid item xs={isSmallScreen ? 12 : 4}> <Grid item xs={isSmallScreen ? 12 : 4}>
<Metric <Metric
isLoading={timeToNextEpoch === undefined} isLoading={timeToNextEpoch === undefined}
metric={formatTime(timeToNextEpoch)} metric={timeToNextEpoch < 30 ? "Rotating..." : formatTime(timeToNextEpoch)}
label="Rotation in" label="Rotation in"
tooltip="Bridge Stability Index refreshes every 10 minutes with new validator blocks; resets each Era when the validator set is updated." tooltip="Bridge Stability Index refreshes every 10 minutes with new validator blocks; resets each Era when the validator set is updated."
/> />
@ -91,12 +92,12 @@ export const BridgeHeader = ({
<Grid item xs={isSmallScreen ? 12 : 4}> <Grid item xs={isSmallScreen ? 12 : 4}>
<Metric <Metric
isLoading={transactionEta === undefined} isLoading={transactionEta === undefined}
metric={formatTime(transactionEta)} metric={transactionEta > 14400 ? "∞" : formatTime(transactionEta)}
label="Max Bridge ETA" label="Max Bridge ETA"
tooltip="Maximum estimated time for finalizing bridge transactions based on the latest update." tooltip="Maximum estimated time for finalizing bridge transactions based on the latest update."
/> />
</Grid> </Grid>
<Grid item gap={2} xs={12} sx={{ marginTop: "20px" }}> <Grid item gap={2} xs={12} style={{ paddingTop: "0" }} >
<Box position="relative" margin="4.5px 0"> <Box position="relative" margin="4.5px 0">
<LinearProgress <LinearProgress
variant="determinate" variant="determinate"
@ -122,9 +123,18 @@ export const BridgeHeader = ({
<Typography variant="body1" sx={{ fontWeight: 'bold' }}> <Typography variant="body1" sx={{ fontWeight: 'bold' }}>
Bridge Stability {bridgeStability Bridge Stability {bridgeStability
? `${formatNumber(bridgeStability, 0)}% ${progressBarPostfix}` ? `${formatNumber(bridgeStability, 0)}% ${progressBarPostfix}`
: "Unknown" : "N/A"
} }
</Typography> </Typography>
<InfoTooltip message={
<Box width="220px">
<Typography>0% - 50%: Do NOT Bridge</Typography>
<Typography>50% - 70%: Critical Risk</Typography>
<Typography>70% - 80%: High Risk</Typography>
<Typography>80% - 90%: Moderate Risk</Typography>
<Typography>90% - 100%: Safe</Typography>
</Box>
} />
</Box> </Box>
</Box> </Box>
</Grid> </Grid>

View File

@ -8,6 +8,7 @@ import ThumbDownAltIcon from '@mui/icons-material/ThumbDownAlt';
import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import AssuredWorkloadIcon from '@mui/icons-material/AssuredWorkload'; import AssuredWorkloadIcon from '@mui/icons-material/AssuredWorkload';
import AccountBalanceIcon from '@mui/icons-material/AccountBalance';
import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom';
import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import ArrowRightIcon from '@mui/icons-material/ArrowRight';
@ -88,11 +89,57 @@ export const BridgeModal = ({
> >
<Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem"> <Box display="flex" gap="1.5rem" flexDirection="column" marginTop=".8rem">
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center"> <Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
{currentRecord?.finalization > 0 && (
<>
<Box
sx={{
transition: "all 0.8s ease",
transform: currentRecord?.finalization > 0 && "scale(1.2)",
color: currentRecord?.finalization === 0 && theme.colors.primary[300]
}}
width="120px"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="center"
>
<GhostStyledIcon
sx={{
width: "35px",
height: "35px",
animation: currentRecord?.finalization > 0 && 'rotateHourGlass 2s ease-in-out infinite',
'@keyframes rotateHourGlass': {
'0%': { transform: 'rotate(0deg)' },
'15%': { transform: 'rotate(0deg)' },
'85%': { transform: 'rotate(180deg)' },
'100%': { transform: 'rotate(180deg)' },
},
}}
viewBox="0 0 25 25"
component={HourglassBottomIcon}
/>
<Typography variant="caption">Finalization</Typography>
<Typography variant="caption">
{(currentRecord?.finalization ?? 0).toString()} blocks left
</Typography>
</Box>
<GhostStyledIcon
sx={{
transition: "all 0.3s ease",
opacity: currentRecord?.finalization > 0 && "0.2"
}}
component={ArrowRightIcon}
/>
</>
)}
<Box <Box
sx={{ sx={{
transition: "all 0.8s ease", transition: "all 0.3s ease",
transform: currentRecord?.step === 0 && "scale(1.2)", opacity: currentRecord?.finalization > 0 && "0.2",
color: currentRecord?.step > 0 && theme.colors.primary[300] transform: currentRecord?.finalization === 0 && currentRecord?.clappedPercentage < 50n && "scale(1.2)",
color: currentRecord?.clappedPercentage > 50n && theme.colors.primary[300]
}} }}
width="120px" width="120px"
display="flex" display="flex"
@ -100,38 +147,42 @@ export const BridgeModal = ({
justifyContent="start" justifyContent="start"
alignItems="center" alignItems="center"
> >
<GhostStyledIcon <Box display="flex" flexDirection="column" justifyContent="center" alignItems="center">
sx={{ {currentRecord?.clappedPercentage < 50n
width: "35px", ? <GhostStyledIcon
height: "35px", sx={{
animation: currentRecord?.step === 0 && 'rotateHourGlass 2s ease-in-out infinite', width: "35px",
'@keyframes rotateHourGlass': { height: "35px",
'0%': { transform: 'rotate(0deg)' }, animation: currentRecord?.finalization === 0 && 'bounce 2s ease-in-out infinite',
'15%': { transform: 'rotate(0deg)' }, '@keyframes bounce': {
'85%': { transform: 'rotate(180deg)' }, '0%, 20%, 50%, 80%, 75%, 100%': { transform: 'translateY(0)' },
'100%': { transform: 'rotate(180deg)' }, '40%': { transform: 'translateY(-4px)' },
}, '60%': { transform: 'translateY(-2px)' },
}} },
viewBox="0 0 25 25" }}
component={HourglassBottomIcon} viewBox="0 0 25 25"
/> component={AccountBalanceIcon}
<Typography variant="caption">Finalization</Typography> />
<Typography variant="caption"> : <GhostStyledIcon sx={{ width: "35px", height: "35px" }} viewBox="0 0 25 25" component={AssuredWorkloadIcon} />
{(currentRecord?.finalization ?? 0).toString()} blocks left }
</Typography> <Typography variant="caption">Capital Backed</Typography>
<Typography variant="caption">
{(currentRecord?.clappedAmount ?? 0n) / 10n**18n} {ghstSymbol} ({currentRecord?.clappedPercentage ?? 0}%)
</Typography>
</Box>
</Box> </Box>
<GhostStyledIcon <GhostStyledIcon
sx={{ transition: "all 0.3s ease", opacity: currentRecord?.step < 1 && "0.2" }} sx={{ transition: "all 0.3s ease", opacity: currentRecord?.clappedPercentage < 50n && "0.2" }}
component={ArrowRightIcon} component={ArrowRightIcon}
/> />
<Box <Box
sx={{ sx={{
transition: "all 0.3s ease", transition: "all 0.3s ease",
opacity: currentRecord?.step < 1 && "0.2", opacity: currentRecord?.finalization > 0 && "0.2",
transform: currentRecord?.step === 1 && "scale(1.2)", transform: currentRecord?.finalization === 0 && currentRecord?.clapsPercentage < 50 && "scale(1.2)",
color: currentRecord?.step > 1 && theme.colors.primary[300] color: currentRecord?.clapsPercentage > 50 && theme.colors.primary[300]
}} }}
width="120px" width="120px"
display="flex" display="flex"
@ -140,14 +191,14 @@ export const BridgeModal = ({
alignItems="center" alignItems="center"
> >
<Box display="flex" flexDirection="row" justifyContent="center" alignItems="center"> <Box display="flex" flexDirection="row" justifyContent="center" alignItems="center">
{currentRecord?.step <= 1 {currentRecord?.clapsPercentage < 50
? ( ? (
<> <>
<GhostStyledIcon <GhostStyledIcon
sx={{ sx={{
width: "35px", width: "35px",
height: "35px", height: "35px",
animation: currentRecord?.step === 1 && 'rotateRightHand 2s ease-in-out infinite', animation: currentRecord?.finalization === 0 && 'rotateRightHand 2s ease-in-out infinite',
'@keyframes rotateRightHand': { '@keyframes rotateRightHand': {
'0%': { transform: 'rotateX(360deg)' }, '0%': { transform: 'rotateX(360deg)' },
'15%': { transform: 'rotateX(360deg)' }, '15%': { transform: 'rotateX(360deg)' },
@ -163,7 +214,7 @@ export const BridgeModal = ({
sx={{ sx={{
width: "35px", width: "35px",
height: "35px", height: "35px",
animation: currentRecord?.step === 1 && 'rotateRightHand 2s ease-in-out infinite', animation: currentRecord?.finalization === 0 && 'rotateRightHand 2s ease-in-out infinite',
'@keyframes rotateRightHand': { '@keyframes rotateRightHand': {
'0%': { transform: 'rotateX(0deg)' }, '0%': { transform: 'rotateX(0deg)' },
'15%': { transform: 'rotateX(0deg)' }, '15%': { transform: 'rotateX(0deg)' },
@ -195,50 +246,36 @@ export const BridgeModal = ({
</Box> </Box>
</Box> </Box>
<GhostStyledIcon {currentRecord?.finalization === 0 && (
sx={{ <>
transition: "all 0.3s ease", <GhostStyledIcon
opacity: currentRecord?.step < 2 && "0.2" sx={{ transition: "all 0.3s ease", opacity: !currentRecord?.applaused && "0.2" }}
}} component={ArrowRightIcon}
component={ArrowRightIcon} />
/>
<Box <Box
sx={{ sx={{
transition: "all 0.3s ease", transition: "all 0.3s ease",
opacity: currentRecord?.step < 2 && "0.2", opacity: !currentRecord?.applaused && "0.2",
transform: currentRecord?.step === 2 && "scale(1.2)", transform: currentRecord?.applaused && "scale(1.2)",
color: currentRecord?.step === 2 && theme.colors.primary[300] color: currentRecord?.applaused && theme.colors.primary[300]
}} }}
width="120px" width="120px"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
justifyContent="start" justifyContent="start"
alignItems="center" alignItems="center"
> >
{currentRecord?.applaused <GhostStyledIcon
? <> sx={{ width: "35px", height: "35px" }}
<GhostStyledIcon viewBox="0 0 25 25"
sx={{ width: "35px", height: "35px" }} component={CheckCircleIcon}
viewBox="0 0 25 25" />
component={CheckCircleIcon} <Typography variant="caption">Applaused</Typography>
/> <Typography variant="caption">Check Receiver</Typography>
<Typography variant="caption">Applaused</Typography> </Box>
<Typography variant="caption">Check Receiver</Typography> </>
</> )}
: <>
<GhostStyledIcon
sx={{ width: "35px", height: "35px" }}
viewBox="0 0 25 25"
component={AssuredWorkloadIcon}
/>
<Typography variant="caption">Capital Backed</Typography>
<Typography variant="caption">
{(currentRecord?.clappedAmount ?? 0n) / 10n**18n} {ghstSymbol} ({currentRecord?.clappedPercentage ?? 0}%)
</Typography>
</>
}
</Box>
</Box> </Box>
<Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0"> <Box display="flex" flexDirection="column" gap="5px" padding="0.6rem 0">
@ -300,7 +337,10 @@ export const BridgeModal = ({
}</Typography> }</Typography>
</Box> </Box>
<Box display="flex" flexDirection="row" justifyContent="space-between"> <Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">Executed at:</Typography> <Box display="flex" flexDirection="row">
<Typography variant="body2">Executed at:</Typography>
<InfoTooltip message="The timestamp when the bridge transaction was submitted." />
</Box>
<Typography variant="body2">{ <Typography variant="body2">{
new Date(currentRecord ? currentRecord.timestamp : 0).toLocaleString('en-US') new Date(currentRecord ? currentRecord.timestamp : 0).toLocaleString('en-US')
}</Typography> }</Typography>
@ -378,7 +418,7 @@ export const BridgeConfirmModal = ({
}} }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://google.com" href="https://ghostchain.io/bridge-disclaimer"
> >
Learn more. Learn more.
</Link> </Link>

View File

@ -10,10 +10,13 @@ import {
TableHead, TableHead,
TableRow, TableRow,
Typography, Typography,
Link,
LinearProgress, LinearProgress,
} from "@mui/material"; } from "@mui/material";
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@mui/material/styles";
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import CheckCircleIcon from '@mui/icons-material/CheckCircle';
@ -31,6 +34,7 @@ export const ValidatorTable = ({
providerDetail, providerDetail,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const [sortedBy, setsortedBy] = useState(undefined);
const stabilityColor = useMemo(() => { const stabilityColor = useMemo(() => {
const red = Math.round(255 * (1 - bridgeStability / 100)); const red = Math.round(255 * (1 - bridgeStability / 100));
@ -38,12 +42,72 @@ export const ValidatorTable = ({
return `rgb(${red}, ${green}, 0)`; return `rgb(${red}, ${green}, 0)`;
}, [bridgeStability]); }, [bridgeStability]);
const sortedCommits = useMemo(() => {
return latestCommits?.sort((a, b) => {
let one = 0;
let two = 0;
switch (sortedBy) {
case -1:
one = Number(b.lastUpdated ?? 0n);
two = Number(a.lastUpdated ?? 0n);
break;
case 1:
one = Number(a.lastUpdated ?? 0n);
two = Number(b.lastUpdated ?? 0n);
break;
case -2:
one = Number(b.lastStoredBlock ?? 0n);
two = Number(a.lastStoredBlock ?? 0n);
break;
case 2:
one = Number(a.lastStoredBlock ?? 0n);
two = Number(b.lastStoredBlock ?? 0n);
break;
case -3:
one = Number(b.lastStoredBlock ?? 0n);
two = Number(a.lastStoredBlock ?? 0n);
break;
case 3:
one = Number(a.lastStoredBlock ?? 0n);
two = Number(b.lastStoredBlock ?? 0n);
break;
case -4:
one = b.disabled ? 1 : 0;
two = a.disabled ? 1 : 0;
break;
case 4:
one = a.disabled ? 1 : 0;
two = b.disabled ? 1 : 0;
break;
default:
break;
}
return one - two;
}) ?? [];
}, [latestCommits, sortedBy]);
const changeSort = (prevValue, newValue) => {
if (prevValue === undefined) {
return newValue;
}
const prevValueAbs = Math.abs(prevValue);
const newValueAbs = Math.abs(newValue);
if (prevValueAbs === newValueAbs) {
return prevValue * -1;
} else {
return newValue;
}
}
return ( return (
<Box width="100%" height="320px" display="flex" flexDirection="column" justifyContent="space-between"> <Box width="100%" height="340px" display="flex" flexDirection="column" justifyContent="space-between" gap="10px">
{!providerDetail && <Box sx={{ borderRadius: "15px", background: theme.colors.paper.background, paddingTop: "40px" }} width="100%" height="100%" display="flex" justifyContent="center"> {!providerDetail && <Box sx={{ borderRadius: "15px", background: theme.colors.paper.background }} width="100%" height="100%" display="flex" justifyContent="center">
<Box padding="20px 30px" display="flex" flexDirection="column" justifyContent="space-around" alignItems="center"> <Box padding="20px 30px" display="flex" flexDirection="column" justifyContent="space-around" alignItems="center">
<Typography sx={{ textAlign: "center" }} variant="h6">GHOST Wallet is not detected on your browser!</Typography> <Typography sx={{ textAlign: "center" }} variant="h6">GHOST Wallet is not detected on your browser!</Typography>
<Typography sx={{ textAlign: "center" }}>Download GHOST Wallet Extension to see real-time validator stats for bridging transaction.</Typography> <Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Wallet Extension for real-time visibility into validator status and related transaction risks.</Typography>
<Typography sx={{ textAlign: "center" }} variant="body2"><b>Important:</b> The GHOST Wallet Extension is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators.</Typography>
<PrimaryButton onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}> <PrimaryButton onClick={() => window.open('https://git.ghostchain.io/ghostchain/ghost-extension-wallet/releases', '_blank', 'noopener,noreferrer')}>
Get GHOST Extension Get GHOST Extension
</PrimaryButton> </PrimaryButton>
@ -53,7 +117,7 @@ export const ValidatorTable = ({
component={Paper} component={Paper}
className="custom-scrollbar" className="custom-scrollbar"
sx={{ sx={{
height: "320px", height: "310px",
overflowY: 'scroll', overflowY: 'scroll',
msOverflowStyle: "thin !important", msOverflowStyle: "thin !important",
scrollbarWidth: "thin !important", scrollbarWidth: "thin !important",
@ -62,11 +126,11 @@ export const ValidatorTable = ({
<Table sx={{ marginTop: "0px" }} stickyHeader aria-label="sticky available validators"> <Table sx={{ marginTop: "0px" }} stickyHeader aria-label="sticky available validators">
<TableHead> <TableHead>
<TableRow sx={{ height: "40px" }}> <TableRow sx={{ height: "40px" }}>
<BridgeHeaderTableCell value="Validator" background={theme.colors.paper.cardHover} borderTopLeftRadius="3px" /> <BridgeHeaderTableCell index={0} changeSort={() => setsortedBy(undefined)} value="Validator" background={theme.colors.paper.cardHover} borderTopLeftRadius="3px" />
<BridgeHeaderTableCell value="Last Acitve" background={theme.colors.paper.cardHover} tooltip="GHOST Validators must submit block commitments every 10 minutes to remain active." /> <BridgeHeaderTableCell index={1} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 1))} value="Last Acitve" background={theme.colors.paper.cardHover} tooltip="GHOST Validators must submit block commitments every 10 minutes to remain active." />
<BridgeHeaderTableCell value="Block Height" background={theme.colors.paper.cardHover} tooltip="The latest EVM block height reported by the Validator." /> <BridgeHeaderTableCell index={2} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 2))} value="Block Height" background={theme.colors.paper.cardHover} tooltip="The latest EVM block height reported by the Validator." />
<BridgeHeaderTableCell value="Block Delayed" background={theme.colors.paper.cardHover} tooltip="Block delays under 4 hours are safe. Block delays over 4 hours risk bridge transaction failure." /> <BridgeHeaderTableCell index={3} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 3))} value="Block Delayed" background={theme.colors.paper.cardHover} tooltip="Block delays under 4 hours are safe. Block delays over 4 hours risk bridge transaction failure." />
<BridgeHeaderTableCell value="Status" background={theme.colors.paper.cardHover} borderTopRightRadius="3px" tooltip="Active and disabled validators fore the current GHOST Epoch." /> <BridgeHeaderTableCell index={4} sortedBy={sortedBy} changeSort={() => setsortedBy(prev => changeSort(prev, 4))} value="Status" background={theme.colors.paper.cardHover} borderTopRightRadius="3px" tooltip="Active and disabled validators fore the current GHOST Epoch." />
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -86,11 +150,26 @@ export const ValidatorTable = ({
</TableBody> </TableBody>
</Table> </Table>
</TableContainer>} </TableContainer>}
<Box marginTop="5px" display="flex" flexDirection="row" justifyContent="center" alignItems="center">
<Link
underline="always"
fontSize="12px"
lineHeight="15px"
target="_blank"
rel="noopener noreferrer"
href="https://telemetry.ghostchain.io"
>
See Validator Telemetry
</Link>
</Box>
</Box> </Box>
) )
} }
const BridgeHeaderTableCell = ({ const BridgeHeaderTableCell = ({
index=0,
sortedBy=undefined,
changeSort,
align="center", align="center",
borderTopRightRadius="0px", borderTopRightRadius="0px",
borderTopLeftRadius="0px", borderTopLeftRadius="0px",
@ -113,11 +192,21 @@ const BridgeHeaderTableCell = ({
background, background,
fontSize, fontSize,
padding, padding,
cursor: "pointer",
}} }}
> >
<Box display="flex" justifyContent="center"> <Box onClick={changeSort} display="flex" justifyContent="center" alignItems="center">
{value} {value}
{tooltip && <InfoTooltip message={tooltip} />} {tooltip
? Math.abs(sortedBy) !== index
? <InfoTooltip message={tooltip} />
: <GhostStyledIcon
sx={{ width: "12px", height: "12px" }}
viewBox="0 0 25 25"
component={sortedBy > 0 ? KeyboardArrowDownIcon : KeyboardArrowUpIcon}
/>
: <></>
}
</Box> </Box>
</TableCell> </TableCell>
) )

View File

@ -7,6 +7,7 @@ import Paper from "../../components/Paper/Paper";
import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react"; import GhostIcon from "../../assets/icons/ghost-nav-header.svg?react";
import EthIcon from "../../assets/tokens/ETH.svg?react"; import EthIcon from "../../assets/tokens/ETH.svg?react";
import { parseKnownToken } from "../../components/Token/Token"
import { useSwitchChain } from 'wagmi'; import { useSwitchChain } from 'wagmi';
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -92,7 +93,7 @@ export default function NotFound({
alignItems="center" alignItems="center"
gap="15px" gap="15px"
> >
<SvgIcon component={EthIcon} viewBox="0 0 32 32" /> <SvgIcon component={parseKnownToken(chain.nativeCurrency.symbol)} inheritViewBox style={{ height: "25px" }} />
<Typography fontSize="1.2em">{chain.name}</Typography> <Typography fontSize="1.2em">{chain.name}</Typography>
</Box> </Box>
</Button> </Button>

View File

@ -42,9 +42,13 @@ export const sortBondsByDiscount = (bonds) => {
return Array.from(bonds).filter((bond) => !bond.isSoldOut).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1)); return Array.from(bonds).filter((bond) => !bond.isSoldOut).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1));
}; };
export const timeConverter = (time) => { export const timeConverter = (time, max = 7200, maxText = "long ago") => {
const seconds = Number(time); const seconds = Number(time);
const mins = Math.floor(seconds / 60); const mins = Math.floor(seconds / 60);
const secs = seconds % 60; const secs = seconds % 60;
return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`; if (mins > max) {
return maxText;
} else {
return `${mins}m ${secs < 10 ? '0' : ''}${secs}s`;
}
} }

View File

@ -137,12 +137,11 @@ export const getTokenAddress = (chainId, name) => {
return address; return address;
} }
// TBD: should be extended on new tokens
export const getTokenIcons = (chainId, address) => { export const getTokenIcons = (chainId, address) => {
let icons = [""]; let icons = [""];
switch (address) { switch (address) {
case RESERVE_ADDRESSES[chainId]: case RESERVE_ADDRESSES[chainId]:
icons = ["RESERVE"]; icons = [chainId === 11155111 ? "GDAI" : "WETH"];
break; break;
case FTSO_ADDRESSES[chainId]: case FTSO_ADDRESSES[chainId]:
icons = ["FTSO"]; icons = ["FTSO"];
@ -154,7 +153,7 @@ export const getTokenIcons = (chainId, address) => {
icons = ["GHST"]; icons = ["GHST"];
break; break;
case FTSO_DAI_LP_ADDRESSES[chainId]: case FTSO_DAI_LP_ADDRESSES[chainId]:
icons = ["FTSO", "RESERVE"]; icons = ["FTSO", chainId === 11155111 ? "GDAI" : "WETH"];
break; break;
} }
return icons; return icons;