add ghost connect into right side panel
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
0879af509e
commit
85055ff4c5
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ghost-dao-interface",
|
||||
"private": true,
|
||||
"version": "0.7.54",
|
||||
"version": "0.7.55",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -18,13 +18,14 @@ import { ReactElement, useState, useEffect } from "react";
|
||||
import { useNavigate, useLocation, createSearchParams } from "react-router-dom";
|
||||
|
||||
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||
import { Tokens, useWallet } from "./Token";
|
||||
import { Tokens, NativeToken, useWallet } from "./Token";
|
||||
import { PrimaryButton, SecondaryButton } from "../../Button";
|
||||
|
||||
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
||||
import { formatCurrency, shorten } from "../../../helpers";
|
||||
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
|
||||
import { RESERVE_ADDRESSES, FTSO_ADDRESSES } from "../../../constants/addresses";
|
||||
import { useUnstableProvider, useSystemAccount, useChainSpec } from "../../../hooks/ghost"
|
||||
|
||||
import { useAccount, useDisconnect, useSwitchChain } from "wagmi";
|
||||
|
||||
@ -52,12 +53,12 @@ const CloseButton = styled(IconButton)(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const WalletTotalValue = ({ address, tokens }) => {
|
||||
const ghstPrice = tokens.ghst.price;
|
||||
const [currency, setCurrency] = useState("USD");
|
||||
const WalletTotalValue = ({ chainName, address, tokens, currency, setCurrency }) => {
|
||||
const ghstPrice = tokens?.ghst ? tokens.ghst.price : tokens?.price;
|
||||
const tokensObject = tokens?.ghst ? tokens : { tokens };
|
||||
const [topHovered, setTopHovered] = useState(false);
|
||||
|
||||
const walletTotalValueUSD = Object.values(tokens).reduce(
|
||||
const walletTotalValueUSD = Object.values(tokensObject).reduce(
|
||||
(totalValue, token) => totalValue.add(token.balance.mul(token.price)),
|
||||
new DecimalBigNumber(0n, 0),
|
||||
);
|
||||
@ -77,16 +78,16 @@ const WalletTotalValue = ({ address, tokens }) => {
|
||||
onMouseEnter={() => setTopHovered(true)}
|
||||
onMouseLeave={() => setTopHovered(false)}
|
||||
>
|
||||
<Box width="120px" display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography style={{ lineHeight: 1.1, fontWeight: 600, fontSize: "0.975rem" }} color="textSecondary">
|
||||
MY WALLET
|
||||
{chainName?.toUpperCase()}
|
||||
</Typography>
|
||||
<Typography style={{ fontSize: ".85rem", lineHeight: 1.1 }} variant="h3">
|
||||
{shorten(address)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box pt="1px">
|
||||
{tokens?.ghst && <Box pt="1px">
|
||||
<GhostStyledIcon
|
||||
viewBox="0 0 24 24"
|
||||
component={LoopIcon}
|
||||
@ -96,12 +97,11 @@ const WalletTotalValue = ({ address, tokens }) => {
|
||||
transform: topHovered ? "rotateZ(-180deg)" : "rotateZ(0deg)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>}
|
||||
</Box>
|
||||
|
||||
|
||||
<Typography style={{ fontWeight: 700 }} variant="h3">
|
||||
{formatCurrency(walletValue[currency], 2, currency === "USD" ? "USD" : "👻" )}
|
||||
{formatCurrency(walletValue[currency], currency === "USD" ? 2 : 5, currency === "USD" ? "USD" : "👻" )}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
@ -112,6 +112,9 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const tokens = useWallet(chainId, address);
|
||||
|
||||
const [currency, setCurrency] = useState("USD");
|
||||
const { account, isExtensionMissing } = useUnstableProvider();
|
||||
const { chains } = useSwitchChain();
|
||||
|
||||
const chainName = useMemo(() => {
|
||||
@ -120,6 +123,26 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||
return chain.name.toLowerCase()
|
||||
}, [chains, chainId])
|
||||
|
||||
const chainSpec = useChainSpec();
|
||||
const accountBalanceRaw = useSystemAccount({ account });
|
||||
|
||||
const accountBalance = useMemo(() => {
|
||||
const decimals = chainSpec ? chainSpec.properties.tokenDecimals : 18;
|
||||
if (!accountBalanceRaw) return new DecimalBigNumber(0n, decimals);
|
||||
const balance = accountBalanceRaw.data.free
|
||||
+ accountBalanceRaw.data.frozen
|
||||
+ accountBalanceRaw.data.reserved;
|
||||
|
||||
return new DecimalBigNumber(balance, decimals);
|
||||
}, [accountBalanceRaw, chainSpec])
|
||||
|
||||
const nativeGhst = useMemo(() => {
|
||||
return {
|
||||
...tokens.ghst,
|
||||
balance: accountBalance,
|
||||
}
|
||||
}, [tokens, accountBalance])
|
||||
|
||||
const onBtnClick = (dexName, from, to) => {
|
||||
navigate({
|
||||
pathname: `${chains.find(chain => chain.id === chainId).name.toLowerCase()}/dex/` + dexName,
|
||||
@ -148,7 +171,13 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||
<Paper>
|
||||
<Box sx={{ padding: theme.spacing(0, 3), display: "flex", flexDirection: "column", minHeight: "100vh" }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", padding: theme.spacing(3, 0, 1) }}>
|
||||
<WalletTotalValue address={address} tokens={tokens} />
|
||||
<WalletTotalValue
|
||||
currency={currency}
|
||||
setCurrency={setCurrency}
|
||||
chainName={chainName}
|
||||
address={address}
|
||||
tokens={tokens}
|
||||
/>
|
||||
<CloseButton size="small" onClick={onClose} aria-label="close wallet">
|
||||
<GhostStyledIcon component={CloseIcon} />
|
||||
</CloseButton>
|
||||
@ -158,7 +187,7 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||
<Divider />
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }} style={{ gap: theme.spacing(1) }}>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }} >
|
||||
<Tokens address={address} tokens={tokens} onClose={onClose} />
|
||||
</Box>
|
||||
|
||||
@ -166,6 +195,46 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
|
||||
<Divider />
|
||||
</Box>
|
||||
|
||||
{!isExtensionMissing
|
||||
? <>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }} style={{ gap: theme.spacing(2, 1) }}>
|
||||
<WalletTotalValue
|
||||
currency={currency}
|
||||
setCurrency={setCurrency}
|
||||
chainName={chainSpec?.chainName ?? ""}
|
||||
address={account ?? "...wait"}
|
||||
tokens={nativeGhst}
|
||||
/>
|
||||
<NativeToken {...nativeGhst} />
|
||||
</Box>
|
||||
<Box sx={{ margin: theme.spacing(2, -3, 3) }}>
|
||||
<Divider />
|
||||
</Box>
|
||||
</>
|
||||
: <Box
|
||||
sx={{ borderRadius: "15px", background: theme.colors.paper.background }}
|
||||
width="100%"
|
||||
height="100%"
|
||||
padding="20px 30px"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="space-around"
|
||||
alignItems="center"
|
||||
gap="10px"
|
||||
>
|
||||
<Typography sx={{ textAlign: "center" }} variant="subtitle1">GHOST Connect is not detected!</Typography>
|
||||
<Typography sx={{ textAlign: "center" }} variant="body2">Download GHOST Connect for bounties, validator stats, and (10, 10) {"Stake\u00B2"} rewards.</Typography>
|
||||
<PrimaryButton
|
||||
fullWidth
|
||||
onClick={() => window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}
|
||||
>
|
||||
Get GHOST Connect
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
}
|
||||
|
||||
|
||||
|
||||
<Box sx={{ width: "100%", marginTop: "auto", marginX: "auto", padding: theme.spacing(2, 0) }}>
|
||||
<DisconnectButton onClose={onClose} />
|
||||
</Box>
|
||||
|
||||
@ -19,6 +19,7 @@ import { EMPTY_ADDRESS } from "../../../constants/addresses";
|
||||
|
||||
import GhostStyledIcon from "../../Icon/GhostIcon";
|
||||
import TokenStack from "../../TokenStack/TokenStack";
|
||||
import SingleToken from "../../Token/Token";
|
||||
import { PrimaryButton, SecondaryButton } from "../../Button";
|
||||
|
||||
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
|
||||
@ -68,6 +69,57 @@ const BalanceValue = ({
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const NativeToken = (props) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const chainId = useChainId();
|
||||
|
||||
const { chains } = useSwitchChain();
|
||||
|
||||
const chainName = useMemo(() => {
|
||||
return chains.find(chain => chain.id === chainId).name.toLowerCase();
|
||||
}, [chains, chainId])
|
||||
|
||||
return (
|
||||
<Accordion expanded={props.expanded} onChange={props.onChangeExpanded}>
|
||||
<AccordionSummary expandIcon={<GhostStyledIcon component={ExpandMoreIcon} color="disabled" />} >
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", width: "100%", marginRight: "10px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||
<SingleToken
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
}}
|
||||
name="GHST"
|
||||
isNative
|
||||
/>
|
||||
<Typography>{props.symbol}</Typography>
|
||||
</Box>
|
||||
<BalanceValue
|
||||
balance={props.balance}
|
||||
balanceValueUSD={props.balance.mul(props.price)}
|
||||
/>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}>
|
||||
<Box
|
||||
sx={{ display: "flex", flexDirection: "column", flex: 1, mx: "32px", justifyContent: "center" }}
|
||||
style={{ gap: theme.spacing(1) }}
|
||||
>
|
||||
<Box display="flex" flexDirection="column" className="ghst-pairs" style={{ width: "100%" }}>
|
||||
<PrimaryButton
|
||||
onClick={() =>{ navigate({ pathname: `${chainName}/bridge` }); onClose(); }}
|
||||
fullWidth
|
||||
>
|
||||
<Typography>Get (10, 10) Stake</Typography>
|
||||
</PrimaryButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export const Token = (props) => {
|
||||
const {
|
||||
isNative,
|
||||
@ -75,7 +127,7 @@ export const Token = (props) => {
|
||||
faucetPath,
|
||||
icons,
|
||||
address,
|
||||
price = 0,
|
||||
price,
|
||||
balance,
|
||||
onAddTokenToWallet,
|
||||
expanded,
|
||||
@ -136,7 +188,7 @@ export const Token = (props) => {
|
||||
</Box>
|
||||
<BalanceValue
|
||||
balance={balance}
|
||||
balanceValueUSD={balance.mul(price)}
|
||||
balanceValueUSD={balance?.mul(price)}
|
||||
/>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
@ -294,7 +346,7 @@ export const useWallet = (chainId, userAddress) => {
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const Tokens = ({ address, tokens, onClose }) => {
|
||||
export const Tokens = ({ address, tokens, isNative, onClose }) => {
|
||||
const [expanded, setExpanded] = useState(null);
|
||||
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.ghst, tokens.reserveFtso];
|
||||
|
||||
@ -310,7 +362,7 @@ export const Tokens = ({ address, tokens, onClose }) => {
|
||||
return (
|
||||
<>
|
||||
{alwaysShowTokens.map((token, i) => (
|
||||
<Token key={i} isNative={i === 0} {...tokenProps(token)} />
|
||||
<Token key={i} isNative={i == 0} {...tokenProps(token)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -24,6 +24,21 @@ export const UnstableProviderProvider = ({ children }) => {
|
||||
() => providerDetail ? providerDetail.provider : null
|
||||
);
|
||||
|
||||
const { data: accounts } = useSWR(
|
||||
() => providerDetail ? `providerDetail.${providerDetail.info.uuid}.provider.getAccounts(${chainId})` : null,
|
||||
async () => {
|
||||
if (!providerDetail) return []
|
||||
const activeProvider = await providerDetail.provider;
|
||||
return activeProvider.getAccounts(chainId);
|
||||
},
|
||||
)
|
||||
|
||||
const account = useMemo(() => {
|
||||
if (Array.isArray(accounts)) return accounts.at(0).address;
|
||||
if (typeof accounts === 'string') return accounts.address;
|
||||
return undefined
|
||||
}, [accounts])
|
||||
|
||||
const connectionState = useMemo(() => {
|
||||
if (!providerDetail) return 'no-extension';
|
||||
if (!provider) return 'loading';
|
||||
@ -52,10 +67,11 @@ export const UnstableProviderProvider = ({ children }) => {
|
||||
providerDetail,
|
||||
connectProviderByIndex: setProviderIndex,
|
||||
chainId,
|
||||
account,
|
||||
client,
|
||||
setChainId,
|
||||
chainHead$
|
||||
}), [providerDetails, providerDetail, chainId, client, chainHead$]);
|
||||
}), [providerDetails, providerDetail, chainId, client, account, chainHead$]);
|
||||
|
||||
return (
|
||||
<UnstableProvider.Provider value={value}>
|
||||
|
||||
@ -14,3 +14,5 @@ export * from "./useBabeSlots";
|
||||
export * from "./useErasTotalStaked";
|
||||
export * from "./useLatestBlockNumber";
|
||||
export * from "./useEraIndex";
|
||||
export * from "./useSystemAccount";
|
||||
export * from "./useChainSpec";
|
||||
|
||||
31
src/hooks/ghost/useChainSpec.js
Normal file
31
src/hooks/ghost/useChainSpec.js
Normal file
@ -0,0 +1,31 @@
|
||||
import useSWR from "swr"
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
|
||||
export const useChainSpec = () => {
|
||||
const { client } = useUnstableProvider()
|
||||
const { data: chainSpecV1 } = useSWR(
|
||||
client ? ["chainSpec_v1_", client] : null,
|
||||
async ([prefix, client]) => {
|
||||
const methods = ["chainName", "properties", "genesisHash"]
|
||||
const responses = await Promise.all(methods.map((method) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
client._request(`${prefix}${method}`, [], {
|
||||
onSuccess: resolve,
|
||||
onError: reject,
|
||||
}),
|
||||
)
|
||||
.catch((_) => undefined)
|
||||
}))
|
||||
return {
|
||||
chainName: responses?.at(0),
|
||||
properties: responses?.at(1),
|
||||
genesisHash: responses?.at(2)
|
||||
}
|
||||
},
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
refreshInterval: 0
|
||||
}
|
||||
)
|
||||
return chainSpecV1
|
||||
}
|
||||
41
src/hooks/ghost/useSystemAccount.js
Normal file
41
src/hooks/ghost/useSystemAccount.js
Normal file
@ -0,0 +1,41 @@
|
||||
import useSWRSubscription from "swr/subscription"
|
||||
import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders"
|
||||
import { distinct, filter, map, mergeMap } from "rxjs"
|
||||
|
||||
import { useUnstableProvider } from "./UnstableProvider"
|
||||
import { useMetadata } from "./MetadataProvider"
|
||||
|
||||
export const useSystemAccount = ({ account }) => {
|
||||
const { chainHead$, chainId } = useUnstableProvider()
|
||||
const metadata = useMetadata()
|
||||
const { data: systemAccount } = useSWRSubscription(
|
||||
chainHead$ && chainId && account && metadata
|
||||
? ["systemAccount", chainHead$, chainId, account, metadata]
|
||||
: null,
|
||||
([_, chainHead$, chainId, account, metadata], { next }) => {
|
||||
const { finalized$, storage$ } = chainHead$
|
||||
const subscription = finalized$.pipe(
|
||||
filter(Boolean),
|
||||
mergeMap((blockInfo) => {
|
||||
const builder = getDynamicBuilder(getLookupFn(metadata))
|
||||
const storageAccount = builder.buildStorage("System", "Account")
|
||||
return storage$(blockInfo?.hash, "value", () =>
|
||||
storageAccount?.keys.enc(account)
|
||||
).pipe(
|
||||
filter(Boolean),
|
||||
distinct(),
|
||||
map((value) => storageAccount?.value.dec(value))
|
||||
)
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next(systemAccount) {
|
||||
next(null, systemAccount)
|
||||
},
|
||||
error: next,
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}
|
||||
)
|
||||
return systemAccount
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user