native coin added to wallet tab

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2025-11-04 15:26:03 +03:00
parent e153760532
commit 0925c79ff8
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
6 changed files with 116 additions and 25 deletions

View File

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

View File

@ -13,6 +13,7 @@ import { ChangeEvent, useState, useEffect } from "react";
import { useNavigate, createSearchParams } from "react-router-dom";
import { useQuery } from "react-query";
import { formatCurrency, formatNumber } from "../../../helpers";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber"
import { tokenNameConverter } from "../../../helpers/tokenConverter";
import { isNetworkLegacy } from "../../../constants";
@ -21,9 +22,15 @@ import TokenStack from "../../TokenStack/TokenStack";
import { PrimaryButton, SecondaryButton } from "../../Button";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { useDaiPrice, useFtsoPrice, useStnkPrice, useGhstPrice } from "../../../hooks/prices";
import {
useNativePrice,
useReservePrice,
useFtsoPrice,
useStnkPrice,
useGhstPrice
} from "../../../hooks/prices";
import { useLpValuation } from "../../../hooks/treasury";
import { useAccount } from "wagmi";
import { useAccount, useBalance as useNativeBalance, useConfig } from "wagmi";
const addTokenToWallet = async (token, userAddress) => {
if (!window.ethereum) return;
@ -62,6 +69,7 @@ const BalanceValue = ({
export const Token = (props) => {
const {
isNative,
symbol,
icons,
address,
@ -100,8 +108,11 @@ export const Token = (props) => {
}
return (
<Accordion expanded={expanded} onChange={onChangeExpanded}>
<AccordionSummary expandIcon={<GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}>
<Accordion expanded={isNative ? false : expanded} onChange={onChangeExpanded}>
<AccordionSummary
sx={{ paddingRight: isNative ? "37.43px" : "" }}
expandIcon={isNative ? null : <GhostStyledIcon component={ExpandMoreIcon} color="disabled" />}
>
<Box sx={{ display: "flex", justifyContent: "space-between", width: "100%", marginRight: "10px" }}>
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
<TokenStack
@ -119,7 +130,7 @@ export const Token = (props) => {
/>
</Box>
</AccordionSummary>
<AccordionDetails style={{ margin: "auto", padding: theme.spacing(1, 0) }}>
{!isNative && <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) }}
@ -139,7 +150,7 @@ export const Token = (props) => {
</SecondaryButton>
</Box>
</Box>
</AccordionDetails>
</AccordionDetails>}
</Accordion>
);
};
@ -148,6 +159,11 @@ const sumObjValues = (obj: Record<string, string> = {}) =>
Object.values(obj).reduce((sum, b = "0.0") => sum + (parseFloat(b) || 0), 0);
export const useWallet = (chainId, userAddress) => {
const {
data: nativeBalanceRaw,
refetch: nativeBalanceRefetch
} = useNativeBalance({ address: userAddress });
const nativeBalance = new DecimalBigNumber(nativeBalanceRaw?.value ?? 0n, 18);
const {
balance: reserveBalance,
refetch: reserveRefetch,
@ -174,12 +190,16 @@ export const useWallet = (chainId, userAddress) => {
contractAddress: lpReserveFtsoBalanceAddress,
} = useBalance(chainId, "RESERVE_FTSO", userAddress);
const reservePrice = useDaiPrice(chainId);
const nativePrice = useNativePrice(chainId);
const reservePrice = useReservePrice(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const lpReserveFtsoPrice = useLpValuation(chainId, "RESERVE_FTSO", 1000000000000000000n);
const config = useConfig();
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
@ -187,6 +207,13 @@ export const useWallet = (chainId, userAddress) => {
const { symbol: lpReserveFtsoSymbol } = useTokenSymbol(chainId, "RESERVE_FTSO");
const tokens = {
native: {
symbol: nativeSymbol,
icons: [nativeSymbol],
balance: nativeBalance,
price: nativePrice,
refetch: nativeBalanceRefetch
},
reserve: {
symbol: reserveSymbol,
address: reserveAddress,
@ -250,7 +277,7 @@ export const useWallet = (chainId, userAddress) => {
export const Tokens = ({ address, tokens, onClose }) => {
const [expanded, setExpanded] = useState(null);
const alwaysShowTokens = [tokens.reserve, tokens.ftso, tokens.stnk, tokens.ghst, tokens.reserveFtso];
const alwaysShowTokens = [tokens.native, tokens.reserve, tokens.ftso, tokens.stnk, tokens.ghst, tokens.reserveFtso];
const tokenProps = (token) => ({
...token,
@ -264,7 +291,7 @@ export const Tokens = ({ address, tokens, onClose }) => {
return (
<>
{alwaysShowTokens.map((token, i) => (
<Token key={i} {...tokenProps(token)} />
<Token key={i} isNative={i === 0} {...tokenProps(token)} />
))}
</>
);

View File

@ -95,6 +95,21 @@ export const UNISWAP_V2_FACTORY = {
[NetworkId.TESTNET_MORDOR]: "0x909f96C1a436B3386E9962e30f3Ce753070ff524",
};
export const NATIVE_TICKERS = {
[NetworkId.TESTNET_SEPOLIA]: [
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
],
[NetworkId.TESTNET_HOODI]: [
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT",
"https://api.coinbase.com/v2/prices/ETH-USDT/spot",
],
[NetworkId.TESTNET_MORDOR]: [
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",
"https://api.coinbase.com/v2/prices/ETC-USDT/spot",
],
}
export const CEX_TICKERS = {
[NetworkId.TESTNET_MORDOR]: [
"https://api.binance.com/api/v3/ticker/price?symbol=ETCUSDT",

View File

@ -13,7 +13,8 @@ import {
useFtsoPrice,
useStnkPrice,
useGhstPrice,
useDaiPrice,
useReservePrice,
useNativePrice,
} from "../../../hooks/prices";
import { tokenNameConverter } from "../../../helpers/tokenConverter";
@ -79,10 +80,11 @@ const TokenInfo = ({ chainId, isMobileScreen }) => {
const config = useConfig();
const nativeSymbol = config?.getClient()?.chain?.nativeCurrency?.symbol;
const nativePrice = useNativePrice(chainId);
const ftsoPrice = useFtsoPrice(chainId);
const stnkPrice = useStnkPrice(chainId);
const ghstPrice = useGhstPrice(chainId);
const reservePrice = useDaiPrice(chainId);
const reservePrice = useReservePrice(chainId);
const { symbol: reserveSymbol } = useTokenSymbol(chainId, "RESERVE");
const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");

View File

@ -8,12 +8,13 @@ import {
FTSO_DAI_LP_ADDRESSES,
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
CEX_TICKERS
CEX_TICKERS,
NATIVE_TICKERS,
} from "../../constants/addresses";
const cexPriceGetters = new Map();
function callWithCacheTTL(fn, ttlMs = 5000) {
function callWithCacheTTL(fn, ttlMs = 10000) {
let lastFetchTime = 0;
let cachedValue;
let inFlight = null;
@ -41,13 +42,13 @@ function callWithCacheTTL(fn, ttlMs = 5000) {
}
}
export const useDaiPrice = (chainId) => {
const [daiPrice, setDaiPrice] = useState(new DecimalBigNumber(1000000000000000000n, 18));
const cexApis = CEX_TICKERS[chainId];
export const useNativePrice = (chainId) => {
const [nativePrice, setNativePrice] = useState(new DecimalBigNumber(1000000000000000000n, 18));
const cexApis = NATIVE_TICKERS[chainId];
useEffect(() => {
if (!cexApis) {
setDaiPrice(new DecimalBigNumber(1000000000000000000n, 18));
setNativePrice(new DecimalBigNumber(1000000000000000000n, 18));
return;
}
@ -70,27 +71,73 @@ export const useDaiPrice = (chainId) => {
if ('data' in response) {
const coinPrice = Number(response?.data?.amount ?? 0.0);
const priceInWei = Math.floor(coinPrice * 1e18 / 0.99);
setDaiPrice(new DecimalBigNumber(BigInt(priceInWei), 18));
setNativePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
} else if ('price' in response) {
const coinPrice = Number(response?.price ?? 0.0);
const priceInWei = Math.floor(coinPrice * 1e18)
setDaiPrice(new DecimalBigNumber(BigInt(priceInWei), 18));
setNativePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
} else {
throw Error("Unexpected json in response.");
}
})
.catch(error => {
setDaiPrice(new DecimalBigNumber(0n, 18));
setNativePrice(new DecimalBigNumber(0n, 18));
});
}, [chainId, cexApis])
return daiPrice;
return nativePrice;
}
export const useReservePrice = (chainId) => {
const [reservePrice, setReservePrice] = useState(new DecimalBigNumber(1000000000000000000n, 18));
const cexApis = CEX_TICKERS[chainId];
useEffect(() => {
if (!cexApis) {
setReservePrice(new DecimalBigNumber(1000000000000000000n, 18));
return;
}
let getCexPriceCached = cexPriceGetters.get(chainId);
if (!getCexPriceCached) {
getCexPriceCached = callWithCacheTTL(() => {
const fetchPromises = cexApis.map(url => fetch(url)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
)
return Promise.any(fetchPromises);
}, 5000);
cexPriceGetters.set(chainId, getCexPriceCached);
}
getCexPriceCached()
.then(response => {
if ('data' in response) {
const coinPrice = Number(response?.data?.amount ?? 0.0);
const priceInWei = Math.floor(coinPrice * 1e18 / 0.99);
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
} else if ('price' in response) {
const coinPrice = Number(response?.price ?? 0.0);
const priceInWei = Math.floor(coinPrice * 1e18)
setReservePrice(new DecimalBigNumber(BigInt(priceInWei), 18));
} else {
throw Error("Unexpected json in response.");
}
})
.catch(error => {
setReservePrice(new DecimalBigNumber(0n, 18));
});
}, [chainId, cexApis])
return reservePrice;
};
export const useFtsoPrice = (chainId) => {
const { reserves, tokens, refetch } = useUniswapV2PairReserves(chainId, FTSO_DAI_LP_ADDRESSES[chainId]);
const reservePrice = useDaiPrice(chainId);
const reservePrice = useReservePrice(chainId);
const reserveAddress = RESERVE_ADDRESSES[chainId];
const ftsoAddress = FTSO_ADDRESSES[chainId];
if (!reserveAddress || !ftsoAddress) {

View File

@ -3,7 +3,7 @@ import { useReadContract } from "wagmi";
import { DAO_TREASURY_ADDRESSES } from "../../constants/addresses";
import { abi as TreasuryAbi } from "../../abi/GhostTreasury.json";
import { useDaiPrice } from "../prices/index";
import { useReservePrice } from "../prices/index";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { getTokenAddress } from "../helpers";
@ -30,7 +30,7 @@ export const useTotalReserves = (chainId) => {
});
const original = useOrinalCoefficient(chainId);
const price = useDaiPrice(chainId);
const price = useReservePrice(chainId);
const totalReservesPrepared = totalReservesRaw ? totalReservesRaw : 0n;
const totalReserves = new DecimalBigNumber(totalReservesPrepared, 9);