diff --git a/package.json b/package.json index 2f6197c..23175c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.1.6", + "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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 892912f..d27005f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/App.jsx b/src/App.jsx index 1986763..7a17edf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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() { } /> } /> } /> + } /> } /> } diff --git a/src/components/NavItem/NavItem.jsx b/src/components/NavItem/NavItem.jsx index 0d98aca..b711209 100644 --- a/src/components/NavItem/NavItem.jsx +++ b/src/components/NavItem/NavItem.jsx @@ -132,6 +132,7 @@ const NavItem = ({ const LinkItem = () => ( { 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"); @@ -91,11 +92,14 @@ const NavContent = ({ chainId, addressChainId }) => { {ghstSymbol} Price: {formatCurrency(ghstPrice, 2)} + + Ghosted Supply: {formatCurrency(ghostedSupplyPrice, 2)} + - +
@@ -141,6 +145,7 @@ const NavContent = ({ chainId, addressChainId }) => { /> + { } - { +const SwapCollection = ({ UpperSwapCard, LowerSwapCard, arrowOnClick, iconNotNeeded }) => { const theme = useTheme(); return ( {UpperSwapCard} - { }} /> - + )} + {iconNotNeeded && } {LowerSwapCard} diff --git a/src/components/TopBar/Wallet/InitialWalletView.tsx b/src/components/TopBar/Wallet/InitialWalletView.tsx index ff3b40a..f15ad4f 100644 --- a/src/components/TopBar/Wallet/InitialWalletView.tsx +++ b/src/components/TopBar/Wallet/InitialWalletView.tsx @@ -152,7 +152,7 @@ function InitialWalletView({ address, chainId, onClose }) { fullWidth onClick={() => onBtnClick("uniswap", DAI_ADDRESSES[chainId], FTSO_ADDRESSES[chainId])} > - FTSO-gDAI on Uniswap + {`${tokens?.ftso?.symbol}-${tokens?.dai?.symbol} on Uniswap`} diff --git a/src/config.js b/src/config.js index bf33239..e340f2c 100644 --- a/src/config.js +++ b/src/config.js @@ -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'), diff --git a/src/constants/addresses.js b/src/constants/addresses.js index fa6582b..c22cc5c 100644 --- a/src/constants/addresses.js +++ b/src/constants/addresses.js @@ -1,32 +1,32 @@ import { NetworkId } from "../constants"; export const STAKING_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x47b662aC17937938ff8938Fe9513beF38e60E40C", + [NetworkId.TESTNET_SEPOLIA]: "0xd90E63E88282596E1ea33765b41Ba3d650f4aD52", [NetworkId.TESTNET_HOODI]: "0x25F62eDc6C89FF84E957C22336A35d2dfc861a86", }; export const BOND_DEPOSITORY_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xcBdad2E86a60fcfF4ecD88D7067D403710D82340", + [NetworkId.TESTNET_SEPOLIA]: "0xdcE486113280e49ca2fB200258E5Ee1B2D21D495", [NetworkId.TESTNET_HOODI]: "0x6Ad50B1E293E68B2fC230c576220a93A9D311571", }; export const DAO_TREASURY_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xed487AF8a6d1d1334e4b899FEb3f39402637Bc85", + [NetworkId.TESTNET_SEPOLIA]: "0x93dd30f819403710de7933B79A74C4A42438458D", [NetworkId.TESTNET_HOODI]: "0x1a1b29b18f714fac9dDabEf530dFc4f85b56A6e8", }; export const FTSO_DAI_LP_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x0000000000000000000000000000000000000000", // 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]: "0x1E392913CB9CeFAd0466D1525a9Ee144E74a233A", + [NetworkId.TESTNET_SEPOLIA]: "0x5f63a27a9214a0352F2EF8dAF1eD4974d713192B", [NetworkId.TESTNET_HOODI]: "0x80c6676c334BCcE60b3CC852085B72143379CE58", }; @@ -36,35 +36,39 @@ export const WETH_ADDRESSES = { }; export const GHST_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xfd210d7ac18Bc7dcE7d72ffd99a20a9F3b44d4F4", + [NetworkId.TESTNET_SEPOLIA]: "0xdf2e5306A3dCcfA4e21bbF4226C17Ff5B008dDC4", [NetworkId.TESTNET_HOODI]: "0xE98f7426457E6533B206e91B7EcA97aa8A258B46", }; export const STNK_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x0cC9868D981852C804C610176b519c48808C26a9", + [NetworkId.TESTNET_SEPOLIA]: "0x02C296A27eA779d5a16F934337c12062C5E3c0D9", [NetworkId.TESTNET_HOODI]: "0xF07e9303A9f16Afd82f4f57Fd6fca68Aa0AB6D7F", }; export const FTSO_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x730EEf44f2a676d4081a2F3B53D10d5e4c15C2Bc", + [NetworkId.TESTNET_SEPOLIA]: "0xcFedFFEB3FdeCd2196820Ba3b71f3F84A1255f93", [NetworkId.TESTNET_HOODI]: "0xb184e423811b644A1924334E63985c259F5D0033", }; export const DISTRIBUTOR_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0x1A848562b86DB7Be5558C1fa8D85326b163c2fFA", + [NetworkId.TESTNET_SEPOLIA]: "0x8fbF8eB4Fcd451EF62Aee33508D46FE120963194", [NetworkId.TESTNET_HOODI]: "0xdF49dC81c457c6f92e26cf6d686C7a8715255842", }; export const GHOST_GOVERNANCE_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xc19f2680B1d64A507B7f3498E9B83B0A069C68Cc", + [NetworkId.TESTNET_SEPOLIA]: "0xDab0c51918E6990d8763FAC8a04AE159e44e0c4f", [NetworkId.TESTNET_HOODI]: "0x1B96B792840d4d19d5097ee007392Ed4d851e64F", }; export const BONDING_CALCULATOR_ADDRESSES = { - [NetworkId.TESTNET_SEPOLIA]: "0xAbE29450bAC493c6D25C467E0f809301084B6a27", + [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", diff --git a/src/containers/Bridge/Bridge.jsx b/src/containers/Bridge/Bridge.jsx new file mode 100644 index 0000000..2917aff --- /dev/null +++ b/src/containers/Bridge/Bridge.jsx @@ -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 ( + + + + + + TX Hash  + + {currentRecord?.transactionHash.slice(0, 9)}...{currentRecord?.transactionHash.slice(-9)} + + + + } + open={activeTxIndex > -1} + onClose={() => setActiveTxIndex(-1)} + minHeight={"100px"} + > + + + handleMouseEnter(0)} + > + + Finalization + {blockNumber?.toString()} blocks left + + + + + handleMouseEnter(1)} + > + + + + + + Slow claps + {clappedValidators} / {validators.length} + + + + + + handleMouseEnter(2)} + > + + Applaused + Check Receiver + + + + + + Session Index: + {currentRecord?.sessionIndex} + + + Receiver Address: + {currentRecord?.receiver} + + + Sent Amount: + {formatCurrency( + new DecimalBigNumber( + BigInt(currentRecord ? currentRecord.amount : "0"), + 18 + ).toString(), 9, ghstSymbol) + } + + + Executed at: + { + new Date(currentRecord ? currentRecord.timestamp : 0).toLocaleString('en-US') + } + + + + + + removeStoredRecord()} + > + Erase Record + + + + This will remove the transaction record from the session storage, but it will not cancel the bridge transaction. + + + + + + + { + bridgeAction + ? `Bridge $${ghstSymbol}` + : "Transaction history" + } + + } + topRight={ + setBridgeAction(!bridgeAction)} + /> + } + + enableBackground + fullWidth + > + + {bridgeAction && ( + <> + setReceiver(event.currentTarget.value)} + inputProps={{ "data-testid": "fromInput" }} + placeholder="Ghost address (sf prefixed)" + type="text" + />} + LowerSwapCard={ setAmount(event.currentTarget.value)} + inputProps={{ "data-testid": "fromInput" }} + endString={"Max"} + endStringOnClick={() => setAmount(ghstBalance.toString())} + />} + /> + + + {gatekeeperAddressEmpty && ( + + + + There is no connected gatekeeper on {chainName} network. Propose gatekeeper on this network to make validators listen to it. + + + + )} + {!gatekeeperAddressEmpty && ( + <> + {!isVerySmallScreen && Gatekeeper:} + + {gatekeeperAddress.slice(0, 10) + "..." + gatekeeperAddress.slice(-8)} + + + )} + + {incomingCommission && validators?.length ? ( + + + + GHOST Wallet is not detected on your browser. Download  + + GHOST Wallet +   to see full detalization for bridge transaction. + + + + ) + : ( + + + {!isVerySmallScreen && Est. Commission:} + unknown + + + {!isVerySmallScreen && Number of validators:} + unknown + + + )} + + + ghostOrConnect()} + > + {address === "" ? + "Connect" + : + "Bridge" + } + + + )} + {!bridgeAction && ( + + + Amount + Datetime + Status + + + {filteredStoredTransactions + .map((obj, idx) => ( + setActiveTxIndex(idx)} + > + + + {formatCurrency( + new DecimalBigNumber(BigInt(obj.amount), 18).toString(), + 3, + ghstSymbol + )} + + + + + + {new Date(obj.timestamp).toLocaleDateString('en-US')} + + + {new Date(obj.timestamp).toLocaleTimeString('en-US')} + + + + + + {idx % 2 === 0 ? + + : + + } + + + + ))} + + + )} + + + + + + ) +} + +export default Bridge; diff --git a/src/containers/Dex/PoolContainer.jsx b/src/containers/Dex/PoolContainer.jsx index e69bf7d..559140d 100644 --- a/src/containers/Dex/PoolContainer.jsx +++ b/src/containers/Dex/PoolContainer.jsx @@ -68,9 +68,6 @@ const PoolContainer = ({ ); const onSwap = () => { - // const oldAmountTop = amountTop; - // const oldAmountBottom = amountBottom; - // setAmountBottom(oldAmountTop); setAmountTop(amountBottom); onCardsSwap(); } diff --git a/src/containers/Faucet/Faucet.jsx b/src/containers/Faucet/Faucet.jsx index 64688b1..0d0eba0 100644 --- a/src/containers/Faucet/Faucet.jsx +++ b/src/containers/Faucet/Faucet.jsx @@ -30,14 +30,6 @@ const Faucet = ({ chainId, address, config, connect }) => { const isSemiSmallScreen = useMediaQuery("(max-width: 480px)"); const isVerySmallScreen = useMediaQuery("(max-width: 379px)"); - 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 }); - const { totalSupply: reserveTotalSupply, refetch: refetchReserveTotalSupply } = useTotalSupply(chainId, "GDAI"); - const { symbol: faucetSymbol } = useTokenSymbol(chainId, "GDAI"); - const [isMint, setIsMint] = useState(true); const [isPending, setIsPending] = useState(false); const [balance, setBalance] = useState(new DecimalBigNumber(0, 0)); @@ -52,6 +44,15 @@ const Faucet = ({ chainId, address, config, connect }) => { 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" }); }, []) @@ -98,11 +99,11 @@ const Faucet = ({ chainId, address, config, connect }) => { }, [amount, daiConversionRate, nativeInfo]); const contractBalanceFree = useMemo(() => { - const realContractBalance = contractBalance ? contractBalance : 0n; + const realContractBalance = contractBalance ? contractBalance.value : 0n; const preparedContractBalance = new DecimalBigNumber(realContractBalance, nativeInfo.decimals); const preparedAccumulatedDonation = new DecimalBigNumber( accumulatedDonation._value, - accumulatedDonation._decimals + nativeInfo.decimals - 18 + nativeInfo.decimals && 18 ); return preparedContractBalance.sub(preparedAccumulatedDonation); }, [contractBalance, accumulatedDonation]); @@ -212,10 +213,12 @@ const Faucet = ({ chainId, address, config, connect }) => { inputWidth={isVerySmallScreen ? "100px" : isSemiSmallScreen ? "180px" : "250px"} tokenName={faucetSymbol} token={} - info={`${isSemiSmallScreen ? "" : "Balance: "}${formatCurrency(daiBalance.toString(), 4, faucetSymbol)}`} + info={`${formatCurrency(daiBalance.toString(), 4, faucetSymbol)}`} value={amount} onChange={event => setAmount(event.currentTarget.value)} inputProps={{ "data-testid": "fromInput" }} + endString={"Max"} + endStringOnClick={() => setAmount(daiBalance.toString())} />} { <> {!isVerySmallScreen && Accumulated {nativeInfo.symbol}:} - {formatNumber(contractBalanceFree, 5)} + {formatCurrency(contractBalanceFree, 5, nativeInfo.symbol)} {!isVerySmallScreen && You will get:} diff --git a/src/hooks/prices/index.js b/src/hooks/prices/index.js index f3a9a21..ee53e2b 100644 --- a/src/hooks/prices/index.js +++ b/src/hooks/prices/index.js @@ -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); +} diff --git a/src/hooks/staking/index.js b/src/hooks/staking/index.js index b540a97..c5d7d9e 100644 --- a/src/hooks/staking/index.js +++ b/src/hooks/staking/index.js @@ -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; } } diff --git a/src/main.jsx b/src/main.jsx index 4624117..dbbd427 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -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( <> diff --git a/src/themes/darkPalette.js b/src/themes/darkPalette.js index 1dcdabb..ebe1a59 100644 --- a/src/themes/darkPalette.js +++ b/src/themes/darkPalette.js @@ -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: {