diff --git a/package.json b/package.json
index 46385b6..0f6e77a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
- "version": "0.7.54",
+ "version": "0.7.55",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/components/TopBar/Wallet/InitialWalletView.tsx b/src/components/TopBar/Wallet/InitialWalletView.tsx
index 68bbf8a..7f0ce4a 100644
--- a/src/components/TopBar/Wallet/InitialWalletView.tsx
+++ b/src/components/TopBar/Wallet/InitialWalletView.tsx
@@ -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)}
>
-
+
- MY WALLET
+ {chainName?.toUpperCase()}
{shorten(address)}
-
+ {tokens?.ghst &&
{
transform: topHovered ? "rotateZ(-180deg)" : "rotateZ(0deg)",
}}
/>
-
+ }
-
- {formatCurrency(walletValue[currency], 2, currency === "USD" ? "USD" : "👻" )}
+ {formatCurrency(walletValue[currency], currency === "USD" ? 2 : 5, currency === "USD" ? "USD" : "👻" )}
);
@@ -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 }) {
-
+
@@ -158,7 +187,7 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
-
+
@@ -166,6 +195,46 @@ function InitialWalletView({ isWalletOpen, address, chainId, onClose }) {
+ {!isExtensionMissing
+ ? <>
+
+
+
+
+
+
+
+ >
+ :
+ GHOST Connect is not detected!
+ Download GHOST Connect for bounties, validator stats, and (10, 10) {"Stake\u00B2"} rewards.
+ window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}
+ >
+ Get GHOST Connect
+
+
+ }
+
+
+
diff --git a/src/components/TopBar/Wallet/Token.tsx b/src/components/TopBar/Wallet/Token.tsx
index dfcb609..ac03c71 100644
--- a/src/components/TopBar/Wallet/Token.tsx
+++ b/src/components/TopBar/Wallet/Token.tsx
@@ -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 = ({
);
+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 (
+
+ } >
+
+
+
+ {props.symbol}
+
+
+
+
+
+
+
+ { navigate({ pathname: `${chainName}/bridge` }); onClose(); }}
+ fullWidth
+ >
+ Get (10, 10) Stake
+
+
+
+
+
+ );
+}
+
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) => {
@@ -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) => (
-
+
))}
>
);
diff --git a/src/hooks/ghost/UnstableProvider.jsx b/src/hooks/ghost/UnstableProvider.jsx
index 9857b37..157339d 100644
--- a/src/hooks/ghost/UnstableProvider.jsx
+++ b/src/hooks/ghost/UnstableProvider.jsx
@@ -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 (
diff --git a/src/hooks/ghost/index.js b/src/hooks/ghost/index.js
index 02d4cea..eafb10e 100644
--- a/src/hooks/ghost/index.js
+++ b/src/hooks/ghost/index.js
@@ -14,3 +14,5 @@ export * from "./useBabeSlots";
export * from "./useErasTotalStaked";
export * from "./useLatestBlockNumber";
export * from "./useEraIndex";
+export * from "./useSystemAccount";
+export * from "./useChainSpec";
diff --git a/src/hooks/ghost/useChainSpec.js b/src/hooks/ghost/useChainSpec.js
new file mode 100644
index 0000000..814289b
--- /dev/null
+++ b/src/hooks/ghost/useChainSpec.js
@@ -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
+}
diff --git a/src/hooks/ghost/useSystemAccount.js b/src/hooks/ghost/useSystemAccount.js
new file mode 100644
index 0000000..624427e
--- /dev/null
+++ b/src/hooks/ghost/useSystemAccount.js
@@ -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
+}