diff --git a/package.json b/package.json
index da381aa..1409f5c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
- "version": "0.7.58",
+ "version": "0.7.59-beta-2",
"type": "module",
"scripts": {
"dev": "vite",
@@ -55,5 +55,6 @@
"sass-embedded": "^1.85.1",
"vite": "^6.2.0",
"vite-plugin-svgr": "^4.3.0"
- }
+ },
+ "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
}
diff --git a/src/App.jsx b/src/App.jsx
index bc78fc0..d0924c1 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -41,6 +41,7 @@ const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
const Governance = lazy(() => import("./containers/Governance/Governance"));
const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails"));
const NewProposal = lazy(() => import("./containers/Governance/NewProposal"));
+const History = lazy(() => import("./containers/History/History"));
const PREFIX = "App";
@@ -204,6 +205,7 @@ function App() {
} />
} />
} />
+ } />
}
{
to={`/${chainName}/bonds`}
children={
- {sortedGhostBonds.length > 0 &&
- Bond Discounts
- }
{sortedGhostBonds.map((bond, index) => {
return (
{
-
+
diff --git a/src/config.js b/src/config.js
index cdab829..ee7461c 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,45 +1,46 @@
import { defineChain } from 'viem'
import { http, fallback, createConfig } from 'wagmi'
-import { sepolia, hoodi } from 'wagmi/chains'
+import { hoodi } from 'wagmi/chains'
-const mordor = defineChain({
- id: 63,
- name: 'Mordor',
- nativeCurrency: {
- decimals: 18,
- name: 'METC',
- symbol: 'mETC',
- },
- rpcUrls: {
- default: { http: ['https://rpc.mordor.etccooperative.org'] },
- },
- blockExplorers: {
- default: {
- name: 'Blockscout',
- url: 'https://etc-mordor.blockscout.com/'
- },
- },
-})
+// const mordor = defineChain({
+// id: 63,
+// name: 'Mordor',
+// nativeCurrency: {
+// decimals: 18,
+// name: 'METC',
+// symbol: 'mETC',
+// },
+// rpcUrls: {
+// default: { http: ['https://rpc.mordor.etccooperative.org'] },
+// },
+// blockExplorers: {
+// default: {
+// name: 'Blockscout',
+// url: 'https://etc-mordor.blockscout.com/'
+// },
+// },
+// })
export const config = createConfig({
- chains: [sepolia, hoodi, mordor],
+ chains: [hoodi],
transports: {
- [sepolia.id]: fallback([
- http('https://ethereum-sepolia-rpc.publicnode.com'),
- http('https://1rpc.io/sepolia'),
- http('https://eth-sepolia.public.blastapi.io'),
- http('https://0xrpc.io/sep'),
- http('https://api.zan.top/eth-sepolia'),
- http('https://eth-sepolia.api.onfinality.io/public'),
- http('https://sepolia.drpc.org')
- ]),
+ // [sepolia.id]: fallback([
+ // http('https://ethereum-sepolia-rpc.publicnode.com'),
+ // http('https://1rpc.io/sepolia'),
+ // http('https://eth-sepolia.public.blastapi.io'),
+ // http('https://0xrpc.io/sep'),
+ // http('https://api.zan.top/eth-sepolia'),
+ // http('https://eth-sepolia.api.onfinality.io/public'),
+ // http('https://sepolia.drpc.org')
+ // ]),
[hoodi.id]: fallback([
http('https://rpc.hoodi.ethpandaops.io'),
+ http('https://rpc.sentio.xyz/hoodi'),
http('https://0xrpc.io/hoodi'),
]),
- [mordor.id]: fallback([
- http('https://rpc.mordor.etccooperative.org'),
- http('https://geth-mordor.etc-network.info'),
- ])
+ // [mordor.id]: fallback([
+ // http('https://rpc.mordor.etccooperative.org'),
+ // http('https://geth-mordor.etc-network.info'),
+ // ])
},
})
diff --git a/src/constants/addresses.js b/src/constants/addresses.js
index 26247eb..fbd8333 100644
--- a/src/constants/addresses.js
+++ b/src/constants/addresses.js
@@ -4,25 +4,25 @@ export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
export const STAKING_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
- [NetworkId.TESTNET_HOODI]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
+ [NetworkId.TESTNET_HOODI]: "0x5d659FDEFc9c1552A3a53f661Cf4F52b0d7dDb40",
[NetworkId.TESTNET_MORDOR]: "0xC2C579631Bf6daA93252154080fecfd68c6aa506",
};
export const BOND_DEPOSITORY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
- [NetworkId.TESTNET_HOODI]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
+ [NetworkId.TESTNET_HOODI]: "0x4105D6b48f0fEfA8033b8B01d89972c2CBd787bD",
[NetworkId.TESTNET_MORDOR]: "0x46BF6F7c3e96351eab7542f2B14c9f2ac6d08dF0",
};
export const DAO_TREASURY_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x05D797f9F34844594C956da58f1785997397f02E",
- [NetworkId.TESTNET_HOODI]: "0x05D797f9F34844594C956da58f1785997397f02E",
+ [NetworkId.TESTNET_HOODI]: "0xeA5113bF7e0af48B6D0Cf1a4B1e4dD0184708a09",
[NetworkId.TESTNET_MORDOR]: "0x05D797f9F34844594C956da58f1785997397f02E",
};
-export const FTSO_DAI_LP_ADDRESSES = {
+export const FTSO_RESERVE_LP_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xCd1505E5d169525e0241c177aF5929A92E02276D",
- [NetworkId.TESTNET_HOODI]: "0x32388605b5E83Ea79CDdC479AA9939DBCF98f59D",
+ [NetworkId.TESTNET_HOODI]: "0xf3F2cc4DaB20F09eee0d07d8456924927ceA1847",
[NetworkId.TESTNET_MORDOR]: "0x53B13C4722081c405ce25c7A7629fC326A49a469",
};
@@ -46,43 +46,43 @@ export const WETH_ADDRESSES = {
export const GHST_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
- [NetworkId.TESTNET_HOODI]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
+ [NetworkId.TESTNET_HOODI]: "0x594a5CD093859B3FD48aA901d86F71a7D537834a",
[NetworkId.TESTNET_MORDOR]: "0x1eCee8BfceC44e535B3Ee92Aca70507668781392",
};
export const STNK_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
- [NetworkId.TESTNET_HOODI]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
+ [NetworkId.TESTNET_HOODI]: "0x7a7d0F214b8C03bD962B88AC0a7F543085aB817B",
[NetworkId.TESTNET_MORDOR]: "0xa31cf59baC26Dd8A8b422b999eB1Ba541C941EA7",
};
export const FTSO_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
- [NetworkId.TESTNET_HOODI]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
+ [NetworkId.TESTNET_HOODI]: "0x07427B220934B3421223969eA61a247bFA37cF03",
[NetworkId.TESTNET_MORDOR]: "0x7ebd1224D36d64eA09312073e60f352d1383801A",
};
export const DISTRIBUTOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
- [NetworkId.TESTNET_HOODI]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
+ [NetworkId.TESTNET_HOODI]: "0xe503d65C9574bD3C18c8d725cD346eCBFAE24b4d",
[NetworkId.TESTNET_MORDOR]: "0xfa524772eec78FAeD0db2cF8A831FDDa9F5B0544",
};
export const GHOST_GOVERNANCE_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xaf7Ad1b83C47405BB9aa96868bCFbb6D65e4C2a1",
- [NetworkId.TESTNET_HOODI]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
+ [NetworkId.TESTNET_HOODI]: "0x0000000000000000000000000000000000000000",
[NetworkId.TESTNET_MORDOR]: "0xF950101af53733Ccf9309Ef4CC374B300dd43010",
};
export const BONDING_CALCULATOR_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
- [NetworkId.TESTNET_HOODI]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
+ [NetworkId.TESTNET_HOODI]: "0xf13A5319aD8071e4dcE7776728Ef9Cc221A5c098",
[NetworkId.TESTNET_MORDOR]: "0xfA821181de76D3EAdb404dDe971A6d28289F22b3",
}
export const GATEKEEPER_ADDRESSES = {
[NetworkId.TESTNET_SEPOLIA]: "0xd735cA07984a16911222c08411A80e24EB38869B",
- [NetworkId.TESTNET_HOODI]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
+ [NetworkId.TESTNET_HOODI]: "0xc9D5f50e17eeEAF39E0D0cDbbcF4B04FA4A4Bbd6",
[NetworkId.TESTNET_MORDOR]: "0x4823F1DC785D721eAdD2bD218E1eeD63aF67fBF4",
}
diff --git a/src/containers/Bond/BondModal.jsx b/src/containers/Bond/BondModal.jsx
index 1409d08..2503817 100644
--- a/src/containers/Bond/BondModal.jsx
+++ b/src/containers/Bond/BondModal.jsx
@@ -1,13 +1,15 @@
-import { ArrowBack } from "@mui/icons-material";
-import { Box, Link, Skeleton, Typography } from "@mui/material";
+import { Box, Container, Skeleton, Typography, useMediaQuery } from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react";
-import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom";
+import { useLocation, useParams, useNavigate } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
+import Countdown from "../../components/Countdown/Countdown";
+import Paper from "../../components/Paper/Paper";
import PageTitle from "../../components/PageTitle/PageTitle";
import Metric from "../../components/Metric/Metric";
+import MetricCollection from "../../components/Metric/MetricCollection";
import TokenStack from "../../components/TokenStack/TokenStack";
import BondPrice from "./components/BondPrice";
@@ -40,6 +42,9 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
const { network } = useParams();
const { pathname } = useLocation();
+ const isSmallScreen = useMediaQuery("(max-width: 650px)");
+ const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
+
const [isSettingsOpen, setSettingsOpen] = useState(false);
const { getStorageValue, setStorageValue } = useLocalStorage();
@@ -102,17 +107,9 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
return (
-
-
-
-
- Back
-
-
-
-
{
/>
- {preparedQuoteToken.name}
+ {preparedQuoteToken.name} Bond
@@ -139,30 +136,45 @@ export const BondModal = ({ bond, chainId, address, config, connect }) => {
onDecimalsChange={event => setFormatDecimalsInner(event.currentTarget.value)}
/>
-
-
- )
+
+
+
+ Bond Expires
+
+
+
}
- />
- }
- />
- }
- />
-
+ >
+
+ )}
+ />
+ }
+ />
+ }
+ />
+
+
+
-
+
{
+ const theme = useTheme();
+ return (
+ Confirm Bond Purchase}
+ onClose={!isPending && handleConfirmClose}
+ >
+
+ I confirm the bond purchase with a negative discount
+
+
+ {discount}%
+
+
+
+ Confirm
+
+
+
+ )
+}
+
+export const BondConfirmModal = ({
chainId,
bond,
slippage,
@@ -51,6 +77,8 @@ const BondConfirmModal = ({
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
const [isPending, setIsPending] = useState(false);
+ const { tryStoreBondInHistory } = useLocalStorage();
+
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup]);
const AcknowledgeWarmupCheckbox = () => {
@@ -104,21 +132,19 @@ const BondConfirmModal = ({
const one = BigInt(shares * 100);
const floatSlippage = slippage === "" ? 0 : parseFloat(slippage);
const bigIntSlippage = one + BigInt(Math.round(floatSlippage * shares));
-
const maxPrice = bond.price.inBaseToken._value * bigIntSlippage / one;
- const referral = import.meta.env.VITE_APP_REFERRAL_ADDRESS;
- await purchaseBond({
+ const txHash = await purchaseBond({
chainId,
bondId: bond.id,
amount: spendAmountValue._value.toBigInt(),
maxPrice,
user: recipientAddress,
sender,
- referral,
isNative
});
+ tryStoreBondInHistory(chainId, sender, txHash);
setIsPending(false);
handleConfirmCloseMaster();
}
@@ -212,5 +238,3 @@ const BondConfirmModal = ({
);
};
-
-export default BondConfirmModal;
diff --git a/src/containers/Bond/components/BondInputArea.jsx b/src/containers/Bond/components/BondInputArea.jsx
index eccc359..f6a251b 100644
--- a/src/containers/Bond/components/BondInputArea.jsx
+++ b/src/containers/Bond/components/BondInputArea.jsx
@@ -1,7 +1,8 @@
import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
-import { Box, Checkbox, FormControlLabel, useMediaQuery } from "@mui/material";
+import { Box, Container, Checkbox, FormControlLabel, Typography, useMediaQuery } from "@mui/material";
import { useState, useMemo, useCallback } from "react";
import { useLocation } from "react-router-dom";
+import SettingsIcon from '@mui/icons-material/Settings';
import { BOND_DEPOSITORY_ADDRESSES } from "../../../constants/addresses";
import { shorten, formatNumber, formatCurrency } from "../../../helpers";
@@ -9,10 +10,11 @@ import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
import BondDiscount from "./BondDiscount";
import BondVesting from "./BondVesting";
-import BondConfirmModal from "./BondConfirmModal";
+import { BondConfirmModal, DiscountConfirmModal } from "./BondConfirmModal";
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { PrimaryButton } from "../../../components/Button";
+import GhostStyledIcon from "../../../components/Icon/GhostIcon";
import SwapCard from "../../../components/Swap/SwapCard";
import SwapCollection from "../../../components/Swap/SwapCollection";
import TokenStack from "../../../components/TokenStack/TokenStack";
@@ -33,6 +35,7 @@ const BondInputArea = ({
preparedQuoteToken,
connect
}) => {
+ const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isSemiSmallScreen = useMediaQuery("(max-width: 480px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const { pathname } = useLocation();
@@ -45,7 +48,9 @@ const BondInputArea = ({
const [amount, setAmount] = useState("");
const [checked, setChecked] = useState(false);
+ const [discountConfirmed, setDiscountConfirmed] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
+ const [discountConfirmOpen, setDiscountConfirmOpen] = useState(false);
const parsedAmount = useMemo(() => {
return new DecimalBigNumber(amount, bond.quoteToken.decimals);
@@ -60,12 +65,24 @@ const BondInputArea = ({
return new DecimalBigNumber("0").gt(bond.discount);
}, [bond]);
+ const setModalOpen = () => {
+ if (showDisclaimer && !discountConfirmOpen && !discountConfirmed) setDiscountConfirmOpen(true);
+ else setConfirmOpen(true);
+ }
+
const handleConfirmClose = () => {
setAmount("");
setConfirmOpen(false);
+ setDiscountConfirmOpen(false);
refetch();
}
+ const confirmDiscount = () => {
+ setConfirmOpen(false);
+ setDiscountConfirmOpen(false);
+ setDiscountConfirmed(true);
+ }
+
const setMax = useCallback(() => {
if (!balance) return;
@@ -95,143 +112,32 @@ const BondInputArea = ({
}, [amountInBaseToken, baseTokenString, balance, parsedAmount]);
return (
-
-
-
-
-
- }
- tokenName={preparedQuoteToken.name}
- info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
- endString="Max"
- endStringOnClick={setMax}
- value={amount}
- onChange={event => setAmount(event.currentTarget.value)}
- inputProps={{ "data-testid": "fromInput" }}
- />}
- LowerSwapCard={
-
- }
- tokenName={bond.baseToken.name}
- value={amountInBaseToken.toString({ decimals: 9 })}
- inputProps={{ "data-testid": "toInput" }}
- />
- }
- />
-
+
+
+ }
+ headerContent={
+ Bond
+ }
+ >
+ confirmDiscount()}
+ handleConfirmClose={() => handleConfirmClose()}
+ discount={bond
+ ? formatNumber(Number(bond.discount.mul(new DecimalBigNumber("100"))).toString(), 2)
+ : "Unknown"
+ }
+ />
-
- First time bonding {bond.quoteToken.name}?
Please approve ghostDAO to use your{" "}
- {bond.quoteToken.name} for bonding.
- >
- }
- >
- {showDisclaimer && (
- setChecked(event.target.checked)}
- icon={}
- checkedIcon={}
- />
- }
- label="I understand that I'm buying a negative discount bond"
- />
- )}
- setConfirmOpen(true)}
- >
- Bond
-
-
-
-
-
-
- {formatCurrency(amountInBaseToken, formatDecimals, ftsoSymbol)}
- {" "}
- {!!currentIndex && (
-
- (≈{formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, ghstSymbol)})
-
- )}
-
- }
- tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
- />
-
-
- {bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
- ? `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)}`
- : `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, ghstSymbol)})`}
-
- }
- />
-
- }
- tooltip={`The discount (or premium) between ${ftsoSymbol} market price and bond price. Higher discount = better deal for bond buyers.`}
- />
-
- }
- tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
- />
-
- {recipientAddress !== address && (
-
- )}
-
-
-
-
handleConfirmClose()}
/>
-
- );
- };
+
+
+
+ }
+ tokenName={preparedQuoteToken.name}
+ info={formatCurrency(balance, formatDecimals, preparedQuoteToken.name)}
+ endString="Max"
+ endStringOnClick={setMax}
+ value={amount}
+ onChange={event => setAmount(event.currentTarget.value)}
+ inputProps={{ "data-testid": "fromInput" }}
+ />}
+ LowerSwapCard={
+
+ }
+ tokenName={bond.baseToken.name}
+ value={amountInBaseToken.toString({ decimals: 9 })}
+ inputProps={{ "data-testid": "toInput" }}
+ />
+ }
+ />
+
+
+
+ {formatCurrency(amountInBaseToken, formatDecimals, ftsoSymbol)}
+ {" "}
+ {!!currentIndex && (
+
+ (≈{formatCurrency(amountInBaseToken.div(currentIndex), formatDecimals, ghstSymbol)})
+
+ )}
+
+ }
+ tooltip={`Total amount of ${ftsoSymbol} you will receive from this bond purchase`}
+ />
+
+
+ {bond.baseToken.tokenAddress.toUpperCase() === bond.quoteToken.quoteTokenAddress.toUpperCase()
+ ? `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)}`
+ : `${formatCurrency(baseTokenString, formatDecimals, ftsoSymbol)} (≈${formatCurrency(baseTokenString.div(currentIndex), formatDecimals, ghstSymbol)})`}
+
+ }
+ />
+
+ }
+ tooltip={"Time until bond fully vests and becomes claimable. Vesting period aligns bond buyer with the protocol by encouraging longer-term holding."}
+ />
+
+ {recipientAddress !== address && (
+ {shorten(recipientAddress)}}
+ tooltip="The address that will receive the bond. To change it from your connected account, update the settings via the gear icon."
+ />
+ )}
+
+
+
+ First time bonding {bond.quoteToken.name}?
Please approve ghostDAO to use your{" "}
+ {bond.quoteToken.name} for bonding.
+ >
+ }
+ >
+ setModalOpen(true)}
+ >
+ Bond
+
+
+
+
+
+ )
+};
export default BondInputArea;
diff --git a/src/containers/Dex/PoolContainer.jsx b/src/containers/Dex/PoolContainer.jsx
index 205904b..e2289a6 100644
--- a/src/containers/Dex/PoolContainer.jsx
+++ b/src/containers/Dex/PoolContainer.jsx
@@ -18,6 +18,7 @@ import {
addLiquidity,
addLiquidityETH,
} from "../../hooks/uniswapv2";
+import { useLocalStorage } from "../../hooks/localstorage";
const PoolContainer = ({
chainSymbol,
@@ -37,6 +38,7 @@ const PoolContainer = ({
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)");
+ const { tryStoreDexInHistory } = useLocalStorage();
const [isPending, setIsPending] = useState(false);
const [amountTop, setAmountTop] = useState("");
@@ -191,12 +193,16 @@ const PoolContainer = ({
deadline,
}
+ let txHash;
+
if (topIsNative || bottomIsNative) {
- await addLiquidityETH(params)
+ txHash = await addLiquidityETH(params)
} else {
- await addLiquidity(params);
+ txHash = await addLiquidity(params);
}
+ if (txHash) tryStoreDexInHistory(chainId, address, txHash);
+
await balanceRefetchTop();
await balanceRefetchBottom();
await pairAddressRefetch();
@@ -215,7 +221,7 @@ const PoolContainer = ({
}, [pairAddress])
return (
-
+
diff --git a/src/containers/Dex/SwapContainer.jsx b/src/containers/Dex/SwapContainer.jsx
index 856ded1..4a90579 100644
--- a/src/containers/Dex/SwapContainer.jsx
+++ b/src/containers/Dex/SwapContainer.jsx
@@ -21,6 +21,7 @@ import {
swapExactETHForTokens,
swapExactTokensForETH,
} from "../../hooks/uniswapv2";
+import { useLocalStorage } from "../../hooks/localstorage";
import { EMPTY_ADDRESS } from "../../constants/addresses";
@@ -45,6 +46,7 @@ const SwapContainer = ({
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery("(max-width: 456px)");
+ const { tryStoreDexInHistory } = useLocalStorage();
const [isPending, setIsPending] = useState(false);
const [amountBottom, setAmountBottom] = useState("");
@@ -173,10 +175,12 @@ const SwapContainer = ({
const amountBDesired = BigInt(Math.round(parseFloat(amountBottom) * Math.pow(10, balanceBottom._decimals)));
const amountBMin = amountBDesired * bigIntSlippage / one;
+ let txHash;
+
if (isWrapping) {
- await depositNative(chainId, address, amountADesired);
+ txHash = await depositNative(chainId, address, amountADesired);
} else if (isUnwrapping) {
- await withdrawWeth(chainId, address, amountADesired);
+ txHash = await withdrawWeth(chainId, address, amountADesired);
} else {
const params = {
chainId,
@@ -190,14 +194,16 @@ const SwapContainer = ({
};
if (topIsNative) {
- await swapExactETHForTokens(params)
+ txHash = await swapExactETHForTokens(params)
} else if (bottomIsNative) {
- await swapExactTokensForETH(params)
+ txHash = await swapExactTokensForETH(params)
} else {
- await swapExactTokensForTokens(params);
+ txHash = await swapExactTokensForTokens(params);
}
}
+ if (txHash) tryStoreDexInHistory(chainId, address, txHash);
+
await balanceRefetchTop();
await balanceRefetchBottom();
await pairReservesRefetch();
@@ -207,11 +213,11 @@ const SwapContainer = ({
}
return (
-
+
diff --git a/src/containers/Governance/components/functions/CreateBond.jsx b/src/containers/Governance/components/functions/CreateBond.jsx
index b84a344..4d5a6e2 100644
--- a/src/containers/Governance/components/functions/CreateBond.jsx
+++ b/src/containers/Governance/components/functions/CreateBond.jsx
@@ -12,7 +12,7 @@ import { ArgumentsWrapper, BooleanTrigger, ArgumentInput, ParsedCell } from "./i
import {
RESERVE_ADDRESSES,
- FTSO_DAI_LP_ADDRESSES,
+ FTSO_RESERVE_LP_ADDRESSES,
BOND_DEPOSITORY_ADDRESSES,
} from "../../../../constants/addresses";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
@@ -114,7 +114,7 @@ export const CreateBondSteps = ({ nativeCurrency, ftsoSymbol, chainId, toInitial
}, [step, tokenAddress, capacity, initialPrice, debtBuffer, depositInterval, tuneInterval]);
const possibleTokens = [
- { value: FTSO_DAI_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_DAI_LP_ADDRESSES[chainId])}` },
+ { value: FTSO_RESERVE_LP_ADDRESSES[chainId], symbol: `${ftsoSymbol}-${reserveSymbol} LP`, label: `${ftsoSymbol}-${reserveSymbol} LP: ${shorten(FTSO_RESERVE_LP_ADDRESSES[chainId])}` },
{ value: RESERVE_ADDRESSES[chainId], symbol: reserveSymbol, label: `${reserveSymbol}: ${shorten(RESERVE_ADDRESSES[chainId])}` },
];
diff --git a/src/containers/History/DexViewModal.jsx b/src/containers/History/DexViewModal.jsx
new file mode 100644
index 0000000..8370d5f
--- /dev/null
+++ b/src/containers/History/DexViewModal.jsx
@@ -0,0 +1,191 @@
+import { useTransactionReceipt, useToken } from 'wagmi';
+import { decodeEventLog } from 'viem';
+import { useMemo } from "react";
+
+import ArrowRightIcon from '@mui/icons-material/ArrowRight';
+import { Box, Typography, Link } from "@mui/material";
+
+import Token from "../../components/Token/Token";
+import Modal from "../../components/Modal/Modal";
+import GhostStyledIcon from "../../components/Icon/GhostIcon";
+import { PrimaryButton } from "../../components/Button";
+
+import { abi as UniswapV2PairAbi } from "../../abi/UniswapV2Pair.json";
+import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
+import { formatNumber, shorten } from "../../helpers";
+
+const TokenBox = ({ tokenAmount, tokenSymbol, decimals, chainTokenSymbol }) => {
+ return (
+
+
+
+
+ {formatNumber(
+ new DecimalBigNumber(tokenAmount ? tokenAmount : 0n, decimals),
+ 5
+ )}
+
+
+ )
+}
+
+const DexViewModal = ({ chainId, isOpen, close, config, historyRecord }) => {
+ if (historyRecord === null) return null;
+
+ const { data: receipt, isLoading, error } = useTransactionReceipt({
+ hash: historyRecord?.txHash,
+ query: {
+ enabled: !!historyRecord?.txHash && isOpen,
+ }
+ });
+
+ const allDecodedLogs = useMemo(() => {
+ if (!receipt?.logs) return [];
+
+ return receipt.logs.map((log) => {
+ try {
+ const decoded = decodeEventLog({
+ abi: UniswapV2PairAbi,
+ data: log.data,
+ topics: log.topics,
+ });
+
+ return {
+ eventName: decoded.eventName,
+ args: decoded.args,
+ address: log.address
+ };
+ } catch (e) {
+ return null;
+ }
+ }).filter(Boolean);
+ }, [receipt]);
+
+ const swapInfo = useMemo(() => {
+ const swapEvent = allDecodedLogs.find(l => l.eventName === 'Swap');
+ if (!swapEvent) return null;
+
+ const isZeroForOne = swapEvent.args.amount0In > 0n;
+
+ const transferLogs = allDecodedLogs.filter(l => l.eventName === 'Transfer');
+
+ const tokenInAddress = isZeroForOne
+ ? transferLogs.find(t => t.args.value === swapEvent.args.amount0In)?.address
+ : transferLogs.find(t => t.args.value === swapEvent.args.amount1In)?.address;
+
+ const tokenOutAddress = isZeroForOne
+ ? transferLogs.find(t => t.args.value === swapEvent.args.amount1Out)?.address
+ : transferLogs.find(t => t.args.value === swapEvent.args.amount0Out)?.address;
+
+ return {
+ from: swapEvent.args.sender,
+ to: swapEvent.args.to,
+ tokenInAddress,
+ tokenOutAddress,
+ amountIn: isZeroForOne ? swapEvent.args.amount0In : swapEvent.args.amount1In,
+ amountOut: isZeroForOne ? swapEvent.args.amount1Out : swapEvent.args.amount0Out,
+ };
+ }, [allDecodedLogs]);
+
+ const { data: tokenIn } = useToken({
+ address: swapInfo?.tokenInAddress,
+ scopeKey: `token-${swapInfo ? swapInfo.tokenInAddress : ""}-${chainId}`,
+ chainId
+ });
+
+ const { data: tokenOut } = useToken({
+ address: swapInfo?.tokenOutAddress,
+ scopeKey: `token-${swapInfo ? swapInfo.tokenOutAddress : ""}-${chainId}`,
+ chainId
+ });
+
+ const chainInfo = useMemo(() => {
+ const client = config?.getClient();
+
+ return {
+ url: client?.chain?.blockExplorers?.default?.url,
+ symbol: client?.chain?.nativeCurrency?.symbol,
+ };
+ }, [config]);
+
+ return (
+
+ {swapInfo && "DEX Swap"}
+ >
+ }
+ onClose={() => close()}
+ >
+
+
+
+ {tokenIn ? tokenIn.symbol : "Unknown"} Sender
+
+ {swapInfo ? shorten(swapInfo.from, 12, -10) : ""}
+
+
+
+
+ {tokenOut ? tokenOut.symbol : "Unknown"} Receiver
+
+ {swapInfo ? shorten(swapInfo.to, 12, -10) : ""}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View Transaction Details
+
+
+
+ )
+}
+
+export default DexViewModal;
diff --git a/src/containers/History/EraseModal.jsx b/src/containers/History/EraseModal.jsx
new file mode 100644
index 0000000..d0f5dd0
--- /dev/null
+++ b/src/containers/History/EraseModal.jsx
@@ -0,0 +1,23 @@
+import { Box, Typography } from "@mui/material";
+
+import Modal from "../../components/Modal/Modal";
+import { PrimaryButton } from "../../components/Button";
+
+const EraseModal = ({ isOpen, close, submit, isAll }) => {
+ return (
+ close()}
+ >
+
+ {`Erasing ${isAll ? "all records" : "this record"} is permanent, and cannot be recovered.`}
+ Erase {isAll ? "All Records" : ""}
+
+
+ )
+}
+
+export default EraseModal;
diff --git a/src/containers/History/History.jsx b/src/containers/History/History.jsx
new file mode 100644
index 0000000..701d623
--- /dev/null
+++ b/src/containers/History/History.jsx
@@ -0,0 +1,401 @@
+import {
+ Box,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+ Container,
+ FormControlLabel,
+ FormControl,
+ Checkbox,
+ Link,
+ useMediaQuery,
+ useTheme,
+} from "@mui/material";
+
+import { useLocation } from "react-router-dom";
+import { useEffect, useState, useMemo, useCallback } from "react";
+import ReactGA from "react-ga4";
+import SettingsIcon from '@mui/icons-material/Settings';
+
+import Modal from "../../components/Modal/Modal";
+import Paper from "../../components/Paper/Paper";
+import PageTitle from "../../components/PageTitle/PageTitle";
+import TokenStack from "../../components/TokenStack/TokenStack";
+import GhostStyledIcon from "../../components/Icon/GhostIcon";
+import Chip from "../../components/Chip/Chip";
+
+import { PrimaryButton, TertiaryButton } from "../../components/Button";
+import { Tab, Tabs } from "../../components/Tabs/Tabs";
+
+import { useTokenSymbol } from "../../hooks/tokens";
+import { useLocalStorage } from "../../hooks/localstorage";
+import { shorten } from "../../helpers";
+
+import EraseModal from "./EraseModal";
+import SettingsModal from "./SettingsModal";
+import DexViewModal from "./DexViewModal";
+import StakingViewModal from "./StakingViewModal";
+
+const HistoryItem = ({ item, chainId, config, currentTime, eraseRecord, setViewModalOpen, isSmallScreen }) => {
+ const theme = useTheme();
+ const isVerySmallScreen = useMediaQuery("(max-width: 450px)");
+
+ const localString = new Date(item.submitted).toLocaleString();
+ const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO")
+
+ const chainInfo = useMemo(() => {
+ const client = config?.getClient();
+ return {
+ url: client?.chain?.blockExplorers?.default?.url,
+ symbol: client?.chain?.nativeCurrency?.symbol,
+ };
+ }, [config]);
+
+ const chipStatus = useMemo(() => {
+ const delay = 3600000; // useEvmNetwork({ evmChainId: chainId })
+ const width = isVerySmallScreen ? "21px" : "120px"
+
+ if (item.submitted + delay > currentTime) {
+ return
+ }
+
+ // darkGray for final
+
+ return
+ }, [item, currentTime, isVerySmallScreen])
+
+ // TODO: put correct values into TokenStack based on type
+
+ if (isSmallScreen || isVerySmallScreen) {
+ return (
+ <>
+
+
+
+
+
+ {localString}
+
+ {shorten(item.txHash)}
+
+
+
+
+ {chipStatus}
+
+
+
+ Erase
+ View
+
+
+ >
+ )
+ }
+
+ return (
+
+
+
+
+
+ {shorten(item.txHash, 9, -7)}
+
+
+
+
+
+ {chipStatus}
+
+
+
+ {localString}
+
+
+
+
+ Erase
+ View
+
+
+
+ )
+}
+
+const HistoryList = ({ chainId, storedTransactions, historyType, config, eraseRecord }) => {
+ const isSmallScreen = useMediaQuery("(max-width: 900px)");
+
+ const [currentTime, setCurrentTime] = useState(Date.now());
+ const [viewModalOpen, setViewModalOpen] = useState(null);
+
+ const {
+ BRIDGE_IN,
+ BRIDGE_OUT,
+ DEX_ACTION,
+ STAKE_ACTION,
+ BOND_ACTION
+ } = useLocalStorage();
+
+ useEffect(() => {
+ const intervalId = setInterval(() => {
+ setCurrentTime(Date.now());
+ }, 5000);
+ return () => clearInterval(intervalId);
+ }, [])
+
+ const historyContainer = useMemo(() => {
+ if (isSmallScreen) {
+ return (
+
+ {storedTransactions?.map((item, i) => {
+ return (
+ eraseRecord(i)}
+ setViewModalOpen={() => setViewModalOpen(i)}
+ isSmallScreen={isSmallScreen}
+ />
+ )
+ })}
+
+ )
+ } else {
+ return (
+
+
+
+
+ Transaction
+ Status
+ Submitted
+
+
+
+
+ {storedTransactions?.map((item, i) => {
+ return (
+ eraseRecord(i)}
+ setViewModalOpen={() => setViewModalOpen(i)}
+ isSmallScreen={isSmallScreen}
+ />
+ )
+ })}
+
+
+
+ )
+ }
+ }, [isSmallScreen, storedTransactions, config]);
+
+ return (
+ <>
+ {viewModalOpen !== null && historyType === DEX_ACTION && (
+ setViewModalOpen(null)}
+ isOpen={true}
+ historyRecord={storedTransactions.at(viewModalOpen)}
+ />
+ )}
+
+ {viewModalOpen !== null && historyType === STAKE_ACTION && (
+ setViewModalOpen(null)}
+ isOpen={true}
+ historyRecord={storedTransactions.at(viewModalOpen)}
+ />
+ )}
+
+ {historyContainer}
+ >
+ )
+}
+
+const History = ({ chainId, config, tryConnectInjected, address }) => {
+ const isSmallScreen = useMediaQuery("(max-width: 650px)");
+ const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
+ const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
+
+ const [historyTypeIndex, setHistoryTypeIndex] = useState(0);
+ const [erasingIndex, setErasingIndex] = useState(null);
+ const [settingsOpen, setSettingsOpen] = useState(false);
+ const [erasingOpen, setErasingOpen] = useState(false);
+
+ const location = useLocation();
+
+ useEffect(() => {
+ ReactGA.send({ hitType: "pageview", page: location.pathname });
+ }, [location]);
+
+ const {
+ POSSIBLE_HISTORY_TYPES,
+ getHistoryTypes,
+ setHistoryTypes,
+ getStorageValue,
+ setStorageValue
+ } = useLocalStorage();
+
+ const [storedHistoryTypes, setStoredHistoryTypes] = useState(() =>
+ getHistoryTypes(chainId, address)
+ )
+ const [storedTransactions, setStoredTransactions] = useState(() =>
+ getStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], [])
+ );
+
+ useEffect(() => {
+ setStoredTransactions(getStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], []));
+ }, [chainId, address, historyTypeIndex]);
+
+ const handleHistoryTypesChange = useCallback((values) => {
+ setHistoryTypes(chainId, address, values);
+ setStoredHistoryTypes(getHistoryTypes(chainId, address));
+ }, [chainId, address]);
+
+ const handleConfirmErase = useCallback(() => {
+ if (erasingIndex !== null) {
+ const updated = storedTransactions.filter((_, index) => index !== erasingIndex);
+ setStoredTransactions(updated);
+ setStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], updated);
+ } else {
+ setStorageValue(chainId, address, storedHistoryTypes[historyTypeIndex], []);
+ setStoredTransactions([])
+ }
+ setErasingOpen(false);
+ setErasingIndex(null);
+ }, [storedTransactions, erasingIndex, chainId, address, historyTypeIndex])
+
+ return (
+
+
+
+ {
+ setErasingOpen(false);
+ setErasingIndex(null);
+ }}
+ submit={handleConfirmErase}
+ isAll={erasingIndex === null}
+ />
+
+ setSettingsOpen(false)}
+ onChange={handleHistoryTypesChange}
+ selectedValues={storedHistoryTypes}
+ possibleValues={POSSIBLE_HISTORY_TYPES}
+ />
+
+
+
+ setSettingsOpen(true)}
+ />
+
+ }
+ headerContent={
+
+ {
+ const index = storedHistoryTypes.indexOf(selectedValue);
+ if (index !== -1) setHistoryTypeIndex(index);
+ }}
+ TabIndicatorProps={{ style: { display: "none" } }}
+ >
+ {storedHistoryTypes.map((type, i) => {
+ return (
+
+ )
+ })}
+
+
+ setErasingOpen(true)}
+ sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
+ >
+ Erase All Records
+
+
+ }
+ >
+ {
+ setErasingIndex(index);
+ setErasingOpen(true);
+ }}
+ historyType={storedHistoryTypes[historyTypeIndex]}
+ storedTransactions={storedTransactions}
+ />
+
+
+
+
+ );
+}
+
+export default History;
diff --git a/src/containers/History/SettingsModal.jsx b/src/containers/History/SettingsModal.jsx
new file mode 100644
index 0000000..5c2fd91
--- /dev/null
+++ b/src/containers/History/SettingsModal.jsx
@@ -0,0 +1,49 @@
+import { Box, FormControlLabel, Checkbox, Typography } from "@mui/material";
+
+import Modal from "../../components/Modal/Modal";
+import { CheckBoxOutlineBlank, CheckBoxOutlined } from "@mui/icons-material";
+
+const SettingsModal = ({ isOpen, close, submit, selectedValues, possibleValues, onChange }) => {
+ const handleCheckboxChange = (type, isChecked) => {
+ const updatedTypes = possibleValues.filter(v => {
+ if (v === type) return isChecked
+ return selectedValues.includes(v)
+ })
+ onChange(updatedTypes);
+ };
+
+ return (
+ close()}
+ >
+
+ {possibleValues.map((type, i) => {
+ const isChecked = selectedValues.includes(type);
+ return (
+ handleCheckboxChange(type, event.target.checked)}
+ icon={}
+ checkedIcon={}
+ />
+ }
+ label={{isChecked ? "Disable" : "Enable"} {type} History Storage}
+ />
+ )
+ })}
+
+
+ )
+}
+
+export default SettingsModal;
diff --git a/src/containers/History/StakingViewModal.jsx b/src/containers/History/StakingViewModal.jsx
new file mode 100644
index 0000000..1a32f87
--- /dev/null
+++ b/src/containers/History/StakingViewModal.jsx
@@ -0,0 +1,159 @@
+import { useTransactionReceipt } from 'wagmi';
+import { decodeEventLog } from 'viem';
+import { useMemo } from "react";
+
+import ArrowRightIcon from '@mui/icons-material/ArrowRight';
+import { Box, Typography, Link } from "@mui/material";
+
+import Token from "../../components/Token/Token";
+import Modal from "../../components/Modal/Modal";
+import GhostStyledIcon from "../../components/Icon/GhostIcon";
+import { PrimaryButton } from "../../components/Button";
+
+import { abi as StakingAbi } from "../../abi/GhostStaking.json";
+import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
+import { formatNumber, shorten } from "../../helpers";
+
+import { useTokenSymbol } from "../../hooks/tokens";
+import { getTokenDecimals } from "../../hooks/helpers";
+
+const TokenBox = ({ tokenAmount, tokenSymbol, decimals, chainTokenSymbol }) => {
+ return (
+
+
+
+
+ {tokenSymbol}
+
+ {formatNumber(
+ new DecimalBigNumber(tokenAmount ? tokenAmount : 0n, decimals),
+ 5
+ )}
+
+
+ )
+}
+
+const StakingViewModal = ({ chainId, isOpen, close, config, historyRecord }) => {
+ if (historyRecord === null) return null;
+
+ const { data: receipt, isLoading, error } = useTransactionReceipt({
+ hash: historyRecord?.txHash,
+ query: {
+ enabled: !!historyRecord?.txHash && isOpen,
+ }
+ });
+
+ const allDecodedLogs = useMemo(() => {
+ if (!receipt?.logs) return [];
+
+ return receipt.logs.map((log) => {
+ try {
+ const decoded = decodeEventLog({
+ abi: StakingAbi,
+ data: log.data,
+ topics: log.topics,
+ });
+
+ return {
+ eventName: decoded.eventName,
+ args: decoded.args,
+ address: log.address
+ };
+ } catch (e) {
+ return null;
+ }
+ }).filter(Boolean);
+ }, [receipt]);
+
+ const stakeInfo = useMemo(() => {
+ return allDecodedLogs.find(l => l.eventName === 'Staked');
+ }, [allDecodedLogs]);
+
+ const { symbol: ftsoSymbol } = useTokenSymbol(chainId, "FTSO");
+ const ftsoDecimals = getTokenDecimals("FTSO");
+
+ const chainInfo = useMemo(() => {
+ const client = config?.getClient();
+
+ return {
+ url: client?.chain?.blockExplorers?.default?.url,
+ symbol: client?.chain?.nativeCurrency?.symbol,
+ };
+ }, [config]);
+
+ return (
+
+ {stakeInfo && "Staked"}
+ >
+ }
+ onClose={() => close()}
+ >
+
+
+
+
+
+
+ Receiver
+
+ {shorten(stakeInfo.args.receiver, 9, -7)}
+
+
+
+ Amount
+ {formatNumber(
+ new DecimalBigNumber(stakeInfo ? stakeInfo.args.amount : 0n, ftsoDecimals),
+ 5
+ )} {ftsoSymbol}
+
+
+
+
+
+ View Transaction Details
+
+
+
+ )
+}
+
+export default StakingViewModal;
diff --git a/src/containers/Stake/StakeContainer.jsx b/src/containers/Stake/StakeContainer.jsx
index 00802dc..5a62505 100644
--- a/src/containers/Stake/StakeContainer.jsx
+++ b/src/containers/Stake/StakeContainer.jsx
@@ -78,7 +78,6 @@ export const StakeContainer = ({ chainId, address, config, connect }) => {
Rebase
-
}
>
diff --git a/src/containers/Stake/components/StakeConfirmationModal.jsx b/src/containers/Stake/components/StakeConfirmationModal.jsx
index 0a17e97..dc3052f 100644
--- a/src/containers/Stake/components/StakeConfirmationModal.jsx
+++ b/src/containers/Stake/components/StakeConfirmationModal.jsx
@@ -12,6 +12,7 @@ import Token from "../../../components/Token/Token";
import { PrimaryButton, SecondaryButton } from "../../../components/Button";
import { TokenAllowanceGuard } from "../../../components/TokenAllowanceGuard/TokenAllowanceGuard";
+import { useLocalStorage } from "../../../hooks/localstorage";
import { useWarmupLength, stake, unstake, wrap, unwrap } from "../../../hooks/staking";
import { formatNumber } from "../../../helpers";
@@ -22,6 +23,8 @@ const StakeConfirmationModal = (props) => {
const [acknowledgedWarmup, setAcknowledgedWarmup] = useState(false);
const [isPending, setIsPending] = useState(false);
+ const { tryStoreStakeInHistory } = useLocalStorage();
+
useEffect(() => setAcknowledgedWarmup(acknowledgedWarmup || !needsWarmup), [acknowledgedWarmup, needsWarmup])
const AcknowledgeWarmupCheckbox = () => {
@@ -73,11 +76,12 @@ const StakeConfirmationModal = (props) => {
setIsPending(true);
const actionAmount = new DecimalBigNumber(props.amount, props.spendDecimals);
let isRebase = false;
+ let txHash = null;
switch (props.action.toUpperCase()) {
case "STAKE":
isRebase = props.bottomToken === props.stnkSymbol;
- await stake(
+ txHash = await stake(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@@ -90,7 +94,7 @@ const StakeConfirmationModal = (props) => {
break;
case "UNSTAKE":
isRebase = props.upperToken === props.stnkSymbol;
- await unstake(
+ txHash = await unstake(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@@ -102,7 +106,7 @@ const StakeConfirmationModal = (props) => {
);
break;
case "WRAP":
- await wrap(
+ txHash = await wrap(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@@ -110,7 +114,7 @@ const StakeConfirmationModal = (props) => {
);
break;
case "UNWRAP":
- await unwrap(
+ txHash = await unwrap(
props.chainId,
props.address,
actionAmount._value.toBigInt(),
@@ -121,6 +125,8 @@ const StakeConfirmationModal = (props) => {
console.log("Wrong action to be executed!");
}
+ if (txHash) tryStoreStakeInHistory(props.chainId, props.address, txHash)
+
setIsPending(false);
props.onClose();
}
@@ -156,7 +162,7 @@ const StakeConfirmationModal = (props) => {
chainTokenName={props.chainSymbol}
name={props.upperToken}
/>
- {props.upperToken}
+ {props.upperToken}
diff --git a/src/hooks/bonds/index.js b/src/hooks/bonds/index.js
index 5983218..e924bfe 100644
--- a/src/hooks/bonds/index.js
+++ b/src/hooks/bonds/index.js
@@ -273,24 +273,26 @@ export const useNotes = (chainId, address) => {
return { notes, refetch };
}
-export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, referral, isNative }) => {
- const args = [bondId, amount, maxPrice, user, referral];
+export const purchaseBond = async ({ chainId, bondId, amount, maxPrice, user, sender, isNative }) => {
+ const args = [bondId, amount, maxPrice, user];
const messages = {
replacedMsg: "Bond transaction was replaced. Wait for inclusion please.",
successMsg: `Bond successfully purchased for ${shorten(user)}! Wait until it'll mature before claim.`,
errorMsg: "Bond transaction failed. Check logs for error detalization.",
};
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
abi: BondAbi,
address: BOND_DEPOSITORY_ADDRESSES[chainId],
functionName: "deposit",
- account: user,
+ account: sender,
value: isNative ? amount : undefined
});
+
+ return txHash;
}
export const redeem = async ({ chainId, user, isGhst, indexes }) => {
diff --git a/src/hooks/breakoutModal/index.jsx b/src/hooks/breakoutModal/index.jsx
index 6f7ed1b..c2f8a8c 100644
--- a/src/hooks/breakoutModal/index.jsx
+++ b/src/hooks/breakoutModal/index.jsx
@@ -1,5 +1,4 @@
import { createContext, useContext, useState, useMemo } from "react";
-import { claim } from "../../hooks/staking";
const emptyFunction = () => {};
const BreakoutModalContext = createContext();
diff --git a/src/hooks/ghost/UnstableProvider.jsx b/src/hooks/ghost/UnstableProvider.jsx
index 9f9a9ed..07ca64d 100644
--- a/src/hooks/ghost/UnstableProvider.jsx
+++ b/src/hooks/ghost/UnstableProvider.jsx
@@ -5,7 +5,7 @@ import { getObservableClient } from "@polkadot-api/observable-client"
import useSWR from "swr"
const MAX_BLOCK_TIMEOUT = 15000
-const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
+const DEFAULT_CHAIN_ID = "0xcaf9c5ddccc7ba6f96149a6b8a81c26a3641742795cd5e7e0aee79bf3f4e77b7"
const UnstableProvider = createContext(null)
export const useUnstableProvider = () => useContext(UnstableProvider)
@@ -39,11 +39,11 @@ export const UnstableProviderProvider = ({ children }) => {
return undefined
}, [accounts])
- const connectionState = useMemo(() => {
+ const connectionState = useMemo(async () => {
if (!providerDetail) return 'no-extension';
if (!provider) return 'loading';
- const chains = provider.getChains();
+ const chains = await provider.getChains();
if (chains[chainId]) return 'connected';
return 'wrong-network';
diff --git a/src/hooks/helpers.js b/src/hooks/helpers.js
index 89dac6f..d1cabaa 100644
--- a/src/hooks/helpers.js
+++ b/src/hooks/helpers.js
@@ -6,7 +6,7 @@ import {
FTSO_ADDRESSES,
STNK_ADDRESSES,
GHST_ADDRESSES,
- FTSO_DAI_LP_ADDRESSES,
+ FTSO_RESERVE_LP_ADDRESSES,
WETH_ADDRESSES,
} from "../constants/addresses";
import { config } from "../config";
@@ -134,10 +134,10 @@ export const getTokenAddress = (chainId, name) => {
address = GHST_ADDRESSES[chainId];
break;
case "GDAI_FTSO":
- address = FTSO_DAI_LP_ADDRESSES[chainId];
+ address = FTSO_RESERVE_LP_ADDRESSES[chainId];
break;
case "RESERVE_FTSO":
- address = FTSO_DAI_LP_ADDRESSES[chainId];
+ address = FTSO_RESERVE_LP_ADDRESSES[chainId];
break;
case "WETH":
address = WETH_ADDRESSES[chainId];
@@ -173,7 +173,7 @@ export const getTokenIcons = (chainId, address) => {
case GHST_ADDRESSES[chainId]:
icons = ["GHST"];
break;
- case FTSO_DAI_LP_ADDRESSES[chainId]:
+ case FTSO_RESERVE_LP_ADDRESSES[chainId]:
icons = ["WETH", "FTSO"];
break;
default:
@@ -185,7 +185,7 @@ export const getTokenIcons = (chainId, address) => {
export const getChainTokenNames = (chainId, address) => {
let icons = [undefined];
switch (address) {
- case FTSO_DAI_LP_ADDRESSES[chainId]:
+ case FTSO_RESERVE_LP_ADDRESSES[chainId]:
icons = [undefined, "WETH"];
break;
case RESERVE_ADDRESSES[chainId]:
@@ -199,7 +199,7 @@ export const getChainTokenNames = (chainId, address) => {
export const getBondNameDisplayName = (chainId, tokenAddress, baseTokenSymbol) => {
let stringValue = tokenNameConverter(chainId, "WETH")
- if (tokenAddress.toUpperCase() === FTSO_DAI_LP_ADDRESSES[chainId].toUpperCase()) {
+ if (tokenAddress.toUpperCase() === FTSO_RESERVE_LP_ADDRESSES[chainId].toUpperCase()) {
stringValue = `${baseTokenSymbol}-${stringValue} LP`;
}
return stringValue;
@@ -214,7 +214,7 @@ export const getTokenPurchaseLink = (chainId, tokenAddress, chainName) => {
purchaseUrl = `https://app.dao.ghostchain.io/#/${chainName}/wrapper`;
}
break;
- case FTSO_DAI_LP_ADDRESSES[chainId]:
+ case FTSO_RESERVE_LP_ADDRESSES[chainId]:
purchaseUrl += `?pool=true&from=${RESERVE_ADDRESSES[chainId]}&to=${FTSO_ADDRESSES[chainId]}`;
break;
}
@@ -248,6 +248,7 @@ export const executeOnChainTransaction = async ({
onReplaced: () => toast(messages.replacedMsg),
chainId
});
+ // TODO: onReplaced
toast.success(messages.successMsg);
diff --git a/src/hooks/localstorage/index.jsx b/src/hooks/localstorage/index.jsx
index 1f2dc8e..cb6dec8 100644
--- a/src/hooks/localstorage/index.jsx
+++ b/src/hooks/localstorage/index.jsx
@@ -3,6 +3,17 @@ import { createContext, useContext, useState, useEffect } from "react";
const LocalStorageContext = createContext();
export const useLocalStorage = () => useContext(LocalStorageContext);
+const BRIDGE_IN = "Bridge-In";
+const BRIDGE_OUT = "Bridge-Out";
+const DEX_ACTION = "DEX";
+const STAKE_ACTION = "Stake";
+const BOND_ACTION = "Bond";
+
+const POSSIBLE_HISTORY_TYPES = [BRIDGE_IN, BRIDGE_OUT, DEX_ACTION, STAKE_ACTION, BOND_ACTION];
+const DEFAULT_HISTORY_TYPES = [BRIDGE_IN, BRIDGE_OUT];
+
+const HISTORY_PREFIX = "history"
+
export const LocalStorageProvider = ({ children }) => {
const getStorageKey = (chainId, address, target) => {
return `${chainId}:${address}:${target}`;
@@ -19,8 +30,58 @@ export const LocalStorageProvider = ({ children }) => {
localStorage.setItem(key, JSON.stringify(value));
}
+ const getHistoryTypes = (chainId, address) => {
+ const key = getStorageKey(chainId, address, HISTORY_PREFIX);
+ const stored = localStorage.getItem(key);
+ return stored ? JSON.parse(stored) : DEFAULT_HISTORY_TYPES;
+ }
+
+ const setHistoryTypes = (chainId, address, value) => {
+ const key = getStorageKey(chainId, address, HISTORY_PREFIX);
+ localStorage.setItem(key, JSON.stringify(value));
+ }
+
+ const tryStoreInHistory = (chainId, address, target, txHash) => {
+ const historyTypes = getHistoryTypes(chainId, address);
+ if (!historyTypes.includes(target)) return;
+
+ const key = getStorageKey(chainId, address, target);
+ const stored = localStorage.getItem(key);
+ const values = stored ? JSON.parse(stored) : [];
+
+ const value = { txHash, submitted: Date.now() };
+ const newValues = [value, ...values];
+ localStorage.setItem(key, JSON.stringify(newValues));
+ }
+
+ const tryStoreBondInHistory = (chainId, address, txHash) => {
+ tryStoreInHistory(chainId, address, BOND_ACTION, txHash);
+ }
+
+ const tryStoreStakeInHistory = (chainId, address, txHash) => {
+ tryStoreInHistory(chainId, address, STAKE_ACTION, txHash);
+ }
+
+ const tryStoreDexInHistory = (chainId, address, txHash) => {
+ tryStoreInHistory(chainId, address, DEX_ACTION, txHash);
+ }
+
return (
-
+
{children}
)
diff --git a/src/hooks/prices/index.js b/src/hooks/prices/index.js
index 4579970..6cd4b13 100644
--- a/src/hooks/prices/index.js
+++ b/src/hooks/prices/index.js
@@ -5,7 +5,7 @@ import { useCurrentIndex, useGhostedSupply } from "../staking";
import { useUniswapV2PairReserves } from "../uniswapv2";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import {
- FTSO_DAI_LP_ADDRESSES,
+ FTSO_RESERVE_LP_ADDRESSES,
RESERVE_ADDRESSES,
FTSO_ADDRESSES,
CEX_TICKERS,
@@ -135,7 +135,7 @@ export const useReservePrice = (chainId) => {
};
export const useFtsoPrice = (chainId) => {
- const { reserves, tokens, refetch } = useUniswapV2PairReserves(chainId, FTSO_DAI_LP_ADDRESSES[chainId]);
+ const { reserves, tokens, refetch } = useUniswapV2PairReserves(chainId, FTSO_RESERVE_LP_ADDRESSES[chainId]);
const reservePrice = useReservePrice(chainId);
const reserveAddress = RESERVE_ADDRESSES[chainId];
diff --git a/src/hooks/staking/index.js b/src/hooks/staking/index.js
index abe11df..ecbbc2e 100644
--- a/src/hooks/staking/index.js
+++ b/src/hooks/staking/index.js
@@ -181,7 +181,7 @@ export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSym
successMsg: `${ftsoSymbol} tokens staked successfully! Wait for the warm-up period to claim your ${isRebase ? stnkSymbol : ghstSymbol}.`,
errorMsg: "Staking transaction failed. Check logs for error detalization.",
};
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
@@ -190,6 +190,7 @@ export const stake = async (chainId, account, amount, isRebase, isClaim, ftsoSym
address: STAKING_ADDRESSES[chainId],
functionName: "stake",
});
+ return txHash;
}
export const unstake = async (chainId, account, amount, isTrigger, isRebase, ftsoSymbol, stnkSymbol, ghstSymbol) => {
@@ -199,7 +200,7 @@ export const unstake = async (chainId, account, amount, isTrigger, isRebase, fts
successMsg: `${isRebase ? stnkSymbol : ghstSymbol} tokens unstaked successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
errorMsg: "Unstake transaction failed. Check logs for error detalization.",
};
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
@@ -208,6 +209,7 @@ export const unstake = async (chainId, account, amount, isTrigger, isRebase, fts
address: STAKING_ADDRESSES[chainId],
functionName: "unstake",
});
+ return txHash;
}
export const forfeit = async (chainId, account, ftsoSymbol) => {
@@ -216,7 +218,7 @@ export const forfeit = async (chainId, account, ftsoSymbol) => {
successMsg: `Tokens forfeited successfully! Check your ${ftsoSymbol} balance on ${shorten(account)}.`,
errorMsg: "Forfeit transaction failed. Check logs for error detalization.",
};
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
messages,
account,
@@ -225,6 +227,7 @@ export const forfeit = async (chainId, account, ftsoSymbol) => {
address: STAKING_ADDRESSES[chainId],
functionName: "forfeit",
});
+ return txHash;
}
export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) => {
@@ -234,7 +237,7 @@ export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) =>
successMsg: `${isStnk ? stnkSymbol : ghstSymbol} tokens claimed successfully to ${shorten(account)}.`,
errorMsg: "Claim transaction failed. Check logs for error detalization.",
};
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
messages,
@@ -243,6 +246,7 @@ export const claim = async (chainId, account, isStnk, stnkSymbol, ghstSymbol) =>
address: STAKING_ADDRESSES[chainId],
functionName: "claim",
});
+ return txHash;
}
export const breakout = async (chainId, account, receiver, amount) => {
@@ -261,7 +265,6 @@ export const breakout = async (chainId, account, receiver, amount) => {
address: STAKING_ADDRESSES[chainId],
functionName: "breakout",
});
-
return txHash;
}
@@ -281,6 +284,7 @@ export const unwrap = async (chainId, account, amount, stnkSymbol) => {
address: STAKING_ADDRESSES[chainId],
functionName: "unwrap",
});
+ return txHash;
}
export const wrap = async (chainId, account, amount, ghstSymbol) => {
@@ -299,6 +303,7 @@ export const wrap = async (chainId, account, amount, ghstSymbol) => {
address: STAKING_ADDRESSES[chainId],
functionName: "wrap",
});
+ return txHash;
}
export const ghost = async (chainId, account, receiver, amount) => {
@@ -317,6 +322,5 @@ export const ghost = async (chainId, account, receiver, amount) => {
address: STAKING_ADDRESSES[chainId],
functionName: "ghost",
});
-
return txHash;
}
diff --git a/src/hooks/uniswapv2/UniswapV2Router.js b/src/hooks/uniswapv2/UniswapV2Router.js
index 488674b..05c359f 100644
--- a/src/hooks/uniswapv2/UniswapV2Router.js
+++ b/src/hooks/uniswapv2/UniswapV2Router.js
@@ -33,7 +33,7 @@ export const swapExactETHForTokens = async ({
deadline
];
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@@ -43,6 +43,8 @@ export const swapExactETHForTokens = async ({
messages: swapMessages,
value: amountADesired,
});
+
+ return txHash;
}
export const swapExactTokensForETH = async ({
@@ -63,7 +65,7 @@ export const swapExactTokensForETH = async ({
deadline
];
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@@ -72,6 +74,8 @@ export const swapExactTokensForETH = async ({
account: address,
messages: swapMessages,
});
+
+ return txHash;
}
export const swapExactTokensForTokens = async ({
@@ -92,7 +96,7 @@ export const swapExactTokensForTokens = async ({
deadline
];
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@@ -101,6 +105,8 @@ export const swapExactTokensForTokens = async ({
account: address,
messages: swapMessages,
});
+
+ return txHash;
}
export const addLiquidity = async ({
@@ -126,7 +132,7 @@ export const addLiquidity = async ({
deadline
];
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@@ -135,6 +141,8 @@ export const addLiquidity = async ({
account: address,
messages: addMessages,
});
+
+ return txHash;
}
export const addLiquidityETH = async ({
@@ -172,7 +180,7 @@ export const addLiquidityETH = async ({
deadline
];
- await executeOnChainTransaction({
+ const txHash = await executeOnChainTransaction({
chainId,
args,
abi: RouterAbi,
@@ -182,4 +190,6 @@ export const addLiquidityETH = async ({
messages: addMessages,
value: amountETHDesired,
});
+
+ return txHash;
}