-
-
{avgPrice}
+
+
{avgPrice}
{dayPriceDelta}
{additionalInfo}
+
+
+
);
}
diff --git a/src/components/Synthetics/TVChart/FavoriteTokensBar.tsx b/src/components/Synthetics/TVChart/FavoriteTokensBar.tsx
new file mode 100644
index 0000000000..6f45597a6e
--- /dev/null
+++ b/src/components/Synthetics/TVChart/FavoriteTokensBar.tsx
@@ -0,0 +1,91 @@
+import cx from "classnames";
+import { useCallback } from "react";
+import type { Address } from "viem";
+import { motion, AnimatePresence } from "framer-motion";
+
+import { useSelector } from "context/SyntheticsStateContext/utils";
+import { selectAvailableChartTokens } from "context/SyntheticsStateContext/selectors/chartSelectors";
+import { selectChainId, selectTokensData } from "context/SyntheticsStateContext/selectors/globalSelectors";
+import { selectTradeboxChooseSuitableMarket } from "context/SyntheticsStateContext/selectors/tradeboxSelectors";
+import { useTokensFavorites } from "context/TokensFavoritesContext/TokensFavoritesContextProvider";
+import { getMidPrice } from "domain/tokens";
+import { formatUsdPrice } from "lib/numbers";
+import { getTokenVisualMultiplier, isChartAvailableForToken } from "sdk/configs/tokens";
+import { use24hPriceDeltaMap } from "domain/synthetics/tokens";
+
+
+export function FavoriteTokensBar() {
+ const chainId = useSelector(selectChainId);
+ const availableTokens = useSelector(selectAvailableChartTokens);
+ const tokensData = useSelector(selectTokensData);
+ const chooseSuitableMarket = useSelector(selectTradeboxChooseSuitableMarket);
+ const { favoriteTokens } = useTokensFavorites("chart-token-selector");
+
+ const availableChartTokens = availableTokens?.filter((token) =>
+ isChartAvailableForToken(chainId, token.symbol)
+ );
+
+ const favoriteChartTokens = availableChartTokens?.filter((token) =>
+ favoriteTokens.includes(token.address)
+ );
+
+ const availableChartTokenAddresses = favoriteChartTokens?.map((token) => token.address as Address);
+ const dayPriceDeltaMap = use24hPriceDeltaMap(chainId, availableChartTokenAddresses);
+
+ const handleTokenClick = useCallback((tokenAddress: string) => {
+ chooseSuitableMarket(tokenAddress, undefined, undefined);
+ }, [chooseSuitableMarket]);
+
+ if (!favoriteChartTokens?.length) {
+ return null;
+ }
+
+ return (
+
+
+ {favoriteChartTokens.map((token) => {
+ const tokenData = tokensData?.[token.address];
+ const dayPriceDelta = dayPriceDeltaMap?.[token.address];
+ const averagePrice = tokenData ? getMidPrice(tokenData.prices) : undefined;
+ const formattedPrice = averagePrice
+ ? formatUsdPrice(averagePrice, {
+ visualMultiplier: tokenData?.visualMultiplier,
+ })
+ : undefined;
+
+ return (
+ handleTokenClick(token.address)}
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ exit={{ opacity: 0 }}
+ transition={{ duration: 0.3 }}
+ >
+
+ {getTokenVisualMultiplier(token)}
+ {token.symbol}-USD
+ {formattedPrice && (
+
+ {formattedPrice}
+
+ )}
+ {dayPriceDelta?.deltaPercentageStr && (
+ 0,
+ "negative": dayPriceDelta.deltaPercentage < 0,
+ })}
+ >
+ {dayPriceDelta.deltaPercentageStr}
+
+ )}
+
+
+ );
+ })}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Synthetics/TVChart/TVChart.scss b/src/components/Synthetics/TVChart/TVChart.scss
index cc39b28f19..62ee36c2da 100644
--- a/src/components/Synthetics/TVChart/TVChart.scss
+++ b/src/components/Synthetics/TVChart/TVChart.scss
@@ -38,12 +38,17 @@
display: grid;
grid-template-columns: minmax(auto, max-content) auto;
background-color: var(--color-slate-800);
+ border: 1px solid var(--color-slate-700);
+}
+
+.Favorite-tokens-bar {
+ background-color: var(--color-slate-800);
}
.Chart-top-scrollable {
display: grid;
grid-template-columns: auto auto auto auto max-content;
- grid-column-gap: 2.4rem;
+ grid-column-gap: 2rem;
align-items: center;
overflow: hidden;
overflow-x: auto;
diff --git a/src/components/Synthetics/TradeBox/Curtain.tsx b/src/components/Synthetics/TradeBox/Curtain.tsx
index 28d02e61e0..d8b54f9ced 100644
--- a/src/components/Synthetics/TradeBox/Curtain.tsx
+++ b/src/components/Synthetics/TradeBox/Curtain.tsx
@@ -8,7 +8,7 @@ import Button from "components/Button/Button";
import LeftArrowIcon from "img/ic_arrowleft16.svg?react";
-const HEADER_HEIGHT = 52;
+const HEADER_HEIGHT = 36;
const DECELERATION = 0.01;
const DIRECTION_THRESHOLD = 2;
const MOVEMENT_THRESHOLD = 10;
@@ -238,12 +238,12 @@ export function Curtain({
-
+
-
-
setIsSettingsVisible(true)}
- />
- {settingsWarningDotVisible && }
-
-
- {getListSection()}
+ {getListSection()}
diff --git a/src/pages/Stake/AffiliateClaimModal.tsx b/src/pages/Stake/AffiliateClaimModal.tsx
deleted file mode 100644
index e5478dd7de..0000000000
--- a/src/pages/Stake/AffiliateClaimModal.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { ethers } from "ethers";
-import { useCallback, useMemo, useState } from "react";
-
-import { getContract } from "config/contracts";
-import { SetPendingTransactions } from "context/PendingTxnsContext/PendingTxnsContext";
-import { callContract } from "lib/contracts";
-import { formatAmount } from "lib/numbers";
-import { UncheckedJsonRpcSigner } from "lib/rpc/UncheckedJsonRpcSigner";
-import { abis } from "sdk/abis";
-
-import Button from "components/Button/Button";
-import Modal from "components/Modal/Modal";
-
-export function AffiliateClaimModal(props: {
- isVisible: boolean;
- setIsVisible: (isVisible: boolean) => void;
- signer: UncheckedJsonRpcSigner | undefined;
- chainId: number;
- setPendingTxns: SetPendingTransactions;
- totalVesterRewards: bigint | undefined;
-}) {
- const { isVisible, setIsVisible, signer, chainId, setPendingTxns, totalVesterRewards } = props;
- const [isClaiming, setIsClaiming] = useState(false);
-
- const affiliateVesterAddress = getContract(chainId, "AffiliateVester");
-
- const isPrimaryEnabled = totalVesterRewards != undefined && totalVesterRewards !== 0n && !isClaiming;
-
- const primaryText = useMemo(() => (isClaiming ? t`Claiming...` : t`Claim`), [isClaiming]);
-
- const formattedRewards = useMemo(() => formatAmount(totalVesterRewards, 18, 4, true), [totalVesterRewards]);
-
- const onClickPrimary = useCallback(() => {
- setIsClaiming(true);
-
- const affiliateVesterContract = new ethers.Contract(affiliateVesterAddress, abis.Vester, signer);
-
- callContract(chainId, affiliateVesterContract, "claim", [], {
- sentMsg: t`Claim submitted.`,
- failMsg: t`Claim failed.`,
- successMsg: t`Claim completed!`,
- setPendingTxns,
- })
- .then(() => {
- setIsVisible(false);
- })
- .finally(() => {
- setIsClaiming(false);
- });
- }, [chainId, affiliateVesterAddress, signer, setPendingTxns, setIsVisible]);
-
- return (
-
-
-
-
- This will claim {formattedRewards} GMX.
-
-
- After claiming, you can stake these GMX tokens by using the "Stake" button in the GMX section of this Earn
- page.
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/AffiliateVesterWithdrawModal.tsx b/src/pages/Stake/AffiliateVesterWithdrawModal.tsx
deleted file mode 100644
index e8f1057051..0000000000
--- a/src/pages/Stake/AffiliateVesterWithdrawModal.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { ethers } from "ethers";
-import { useCallback, useMemo, useState } from "react";
-
-import { getContract } from "config/contracts";
-import { callContract } from "lib/contracts";
-import { abis } from "sdk/abis";
-
-import Button from "components/Button/Button";
-import Modal from "components/Modal/Modal";
-
-export function AffiliateVesterWithdrawModal(props) {
- const { isVisible, setIsVisible, chainId, signer, setPendingTxns } = props;
- const [isWithdrawing, setIsWithdrawing] = useState(false);
-
- const affiliateVesterAddress = getContract(chainId, "AffiliateVester");
-
- const primaryText = useMemo(() => (isWithdrawing ? t`Confirming...` : t`Confirm Withdraw`), [isWithdrawing]);
-
- const onClickPrimary = useCallback(() => {
- setIsWithdrawing(true);
- const contract = new ethers.Contract(affiliateVesterAddress, abis.Vester, signer);
-
- callContract(chainId, contract, "withdraw", [], {
- sentMsg: t`Withdraw submitted.`,
- failMsg: t`Withdraw failed.`,
- successMsg: t`Withdrawn!`,
- setPendingTxns,
- })
- .then(() => {
- setIsVisible(false);
- })
- .finally(() => {
- setIsWithdrawing(false);
- });
- }, [chainId, affiliateVesterAddress, signer, setPendingTxns, setIsVisible]);
-
- return (
-
-
-
-
- This will withdraw all esGMX tokens as well as pause vesting.
-
-
- esGMX tokens that have been converted to GMX will be claimed and remain as GMX tokens.
-
-
- To claim GMX tokens without withdrawing, use the "Claim" button.
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/ClaimModal.tsx b/src/pages/Stake/ClaimModal.tsx
deleted file mode 100644
index 5fa09809d6..0000000000
--- a/src/pages/Stake/ClaimModal.tsx
+++ /dev/null
@@ -1,280 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import cx from "classnames";
-import { ethers } from "ethers";
-import React, { useCallback, useMemo, useState } from "react";
-
-import { ARBITRUM } from "config/chains";
-import { getContract } from "config/contracts";
-import { SetPendingTransactions } from "context/PendingTxnsContext/PendingTxnsContext";
-import { useGovTokenAmount } from "domain/synthetics/governance/useGovTokenAmount";
-import { useGovTokenDelegates } from "domain/synthetics/governance/useGovTokenDelegates";
-import { useTokensAllowanceData } from "domain/synthetics/tokens";
-import { approveTokens } from "domain/tokens";
-import { callContract } from "lib/contracts";
-import { useLocalStorageSerializeKey } from "lib/localStorage";
-import { formatAmount } from "lib/numbers";
-import { UncheckedJsonRpcSigner } from "lib/rpc/UncheckedJsonRpcSigner";
-import { abis } from "sdk/abis";
-import { NATIVE_TOKEN_ADDRESS } from "sdk/configs/tokens";
-
-import { AlertInfo } from "components/AlertInfo/AlertInfo";
-import { ApproveTokenButton } from "components/ApproveTokenButton/ApproveTokenButton";
-import Button from "components/Button/Button";
-import Checkbox from "components/Checkbox/Checkbox";
-import ExternalLink from "components/ExternalLink/ExternalLink";
-import ModalWithPortal from "components/Modal/ModalWithPortal";
-
-import { GMX_DAO_LINKS } from "./constants";
-
-export function ClaimModal(props: {
- isVisible: boolean;
- setIsVisible: (isVisible: boolean) => void;
- rewardRouterAddress: string;
- signer: UncheckedJsonRpcSigner | undefined;
- chainId: number;
- setPendingTxns: SetPendingTransactions;
- totalGmxRewards: bigint | undefined;
- nativeTokenSymbol: string;
- wrappedTokenSymbol: string;
- isNativeTokenToClaim?: boolean;
- gmxUsageOptionsMsg?: React.ReactNode;
- onClaimSuccess?: () => void;
-}) {
- const {
- isVisible,
- setIsVisible,
- rewardRouterAddress,
- signer,
- chainId,
- setPendingTxns,
- totalGmxRewards,
- nativeTokenSymbol,
- wrappedTokenSymbol,
- isNativeTokenToClaim,
- gmxUsageOptionsMsg,
- onClaimSuccess,
- } = props;
- const [isClaiming, setIsClaiming] = useState(false);
- const [shouldClaimGmx, setShouldClaimGmx] = useLocalStorageSerializeKey(
- [chainId, "StakeV2-compound-should-claim-gmx"],
- true
- );
- const [shouldStakeGmx, setShouldStakeGmx] = useLocalStorageSerializeKey(
- [chainId, "StakeV2-compound-should-stake-gmx"],
- true
- );
- const [shouldClaimEsGmx, setShouldClaimEsGmx] = useLocalStorageSerializeKey(
- [chainId, "StakeV2-compound-should-claim-es-gmx"],
- true
- );
- const [shouldStakeEsGmx, setShouldStakeEsGmx] = useLocalStorageSerializeKey(
- [chainId, "StakeV2-compound-should-stake-es-gmx"],
- true
- );
- const [shouldClaimWeth, setShouldClaimWeth] = useLocalStorageSerializeKey(
- [chainId, "StakeV2-compound-should-claim-weth"],
- true
- );
- const [shouldConvertWeth, setShouldConvertWeth] = useLocalStorageSerializeKey(
- [chainId, "StakeV2-compound-should-convert-weth"],
- true
- );
-
- const govTokenAmount = useGovTokenAmount(chainId);
- const govTokenDelegatesAddress = useGovTokenDelegates(chainId);
- const isUndelegatedGovToken =
- chainId === ARBITRUM && govTokenDelegatesAddress === NATIVE_TOKEN_ADDRESS && govTokenAmount && govTokenAmount > 0;
-
- const gmxAddress = getContract(chainId, "GMX");
- const stakedGmxTrackerAddress = getContract(chainId, "StakedGmxTracker");
-
- const [isApproving, setIsApproving] = useState(false);
-
- const { tokensAllowanceData: gmxAllowanceData } = useTokensAllowanceData(chainId, {
- spenderAddress: stakedGmxTrackerAddress,
- tokenAddresses: [gmxAddress],
- });
-
- const gmxTokenAllowance = gmxAllowanceData?.[gmxAddress];
-
- const needApproval =
- shouldStakeGmx &&
- totalGmxRewards !== undefined &&
- ((gmxTokenAllowance !== undefined && totalGmxRewards > gmxTokenAllowance) ||
- (totalGmxRewards > 0n && gmxTokenAllowance === undefined));
-
- const isPrimaryEnabled = !isClaiming && !isApproving && !needApproval && !isUndelegatedGovToken;
-
- const primaryText = useMemo(() => {
- if (needApproval || isApproving) {
- return t`Pending GMX approval`;
- }
- if (isClaiming) {
- return t`Claiming...`;
- }
- return t`Claim`;
- }, [needApproval, isApproving, isClaiming]);
-
- const onClickPrimary = useCallback(() => {
- if (needApproval) {
- approveTokens({
- setIsApproving,
- signer,
- tokenAddress: gmxAddress,
- spender: stakedGmxTrackerAddress,
- chainId,
- permitParams: undefined,
- approveAmount: undefined,
- });
- return;
- }
-
- setIsClaiming(true);
-
- const contract = new ethers.Contract(rewardRouterAddress, abis.RewardRouter, signer);
- callContract(
- chainId,
- contract,
- "handleRewards",
- [
- shouldClaimGmx || shouldStakeGmx,
- shouldStakeGmx,
- shouldClaimEsGmx || shouldStakeEsGmx,
- shouldStakeEsGmx,
- true,
- isNativeTokenToClaim ? shouldClaimWeth || shouldConvertWeth : false,
- isNativeTokenToClaim ? shouldConvertWeth : false,
- ],
- {
- sentMsg: t`Claim submitted!`,
- failMsg: t`Claim failed.`,
- successMsg: t`Claim completed!`,
- successDetailsMsg: !shouldStakeGmx ? gmxUsageOptionsMsg : undefined,
- setPendingTxns,
- }
- )
- .then(() => {
- setIsVisible(false);
- onClaimSuccess?.();
- })
- .finally(() => {
- setIsClaiming(false);
- });
- }, [
- needApproval,
- signer,
- gmxAddress,
- stakedGmxTrackerAddress,
- chainId,
- rewardRouterAddress,
- shouldClaimGmx,
- shouldStakeGmx,
- shouldClaimEsGmx,
- shouldStakeEsGmx,
- isNativeTokenToClaim,
- shouldClaimWeth,
- shouldConvertWeth,
- gmxUsageOptionsMsg,
- setPendingTxns,
- setIsVisible,
- onClaimSuccess,
- ]);
-
- const toggleShouldStakeGmx = useCallback(
- (value: boolean) => {
- if (value) {
- setShouldClaimGmx(true);
- }
- setShouldStakeGmx(value);
- },
- [setShouldClaimGmx, setShouldStakeGmx]
- );
-
- const toggleShouldStakeEsGmx = useCallback(
- (value: boolean) => {
- if (value) {
- setShouldClaimEsGmx(true);
- }
- setShouldStakeEsGmx(value);
- },
- [setShouldClaimEsGmx, setShouldStakeEsGmx]
- );
-
- const toggleConvertWeth = useCallback(
- (value: boolean) => {
- if (value) {
- setShouldClaimWeth(true);
- }
- setShouldConvertWeth(value);
- },
- [setShouldClaimWeth, setShouldConvertWeth]
- );
-
- return (
-
-
-
-
- Claim GMX Rewards
-
-
-
-
- Stake GMX Rewards
-
-
-
-
- Claim esGMX Rewards
-
-
-
-
- Stake esGMX Rewards
-
-
- {isNativeTokenToClaim && (
- <>
-
-
- Claim {wrappedTokenSymbol} Rewards
-
-
-
-
-
- Convert {wrappedTokenSymbol} to {nativeTokenSymbol}
-
-
-
- >
- )}
-
- {(needApproval || isApproving) && (
-
- )}
- {isUndelegatedGovToken ? (
-
-
-
- Delegate your undelegated {formatAmount(govTokenAmount, 18, 2, true)} GMX DAO
-
- voting power before claiming.
-
-
- ) : null}
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/EscrowedGmxCard.tsx b/src/pages/Stake/EscrowedGmxCard.tsx
deleted file mode 100644
index a68be74cc4..0000000000
--- a/src/pages/Stake/EscrowedGmxCard.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import { Trans } from "@lingui/macro";
-import { useConnectModal } from "@rainbow-me/rainbowkit";
-import { useMemo } from "react";
-import useSWR from "swr";
-
-import { ARBITRUM, getConstant } from "config/chains";
-import { getContract } from "config/contracts";
-import { USD_DECIMALS } from "config/factors";
-import { getIcons } from "config/icons";
-import { useGmxPrice } from "domain/legacy";
-import { useChainId } from "lib/chains";
-import { contractFetcher } from "lib/contracts";
-import { ProcessedData } from "lib/legacy";
-import { expandDecimals, formatAmount } from "lib/numbers";
-import useWallet from "lib/wallets/useWallet";
-import { bigMath } from "sdk/utils/bigmath";
-
-import { AmountWithUsdBalance, AmountWithUsdHuman } from "components/AmountWithUsd/AmountWithUsd";
-import Button from "components/Button/Button";
-import GMXAprTooltip from "components/Stake/GMXAprTooltip";
-import Tooltip from "components/Tooltip/Tooltip";
-
-export function EscrowedGmxCard({
- processedData,
- showStakeEsGmxModal,
- showUnstakeEsGmxModal,
-}: {
- processedData: ProcessedData | undefined;
- showStakeEsGmxModal: () => void;
- showUnstakeEsGmxModal: () => void;
-}) {
- const { active, signer } = useWallet();
- const { chainId } = useChainId();
- const { openConnectModal } = useConnectModal();
-
- const icons = getIcons(chainId);
-
- const readerAddress = getContract(chainId, "Reader");
-
- const esGmxAddress = getContract(chainId, "ES_GMX");
-
- const stakedGmxDistributorAddress = getContract(chainId, "StakedGmxDistributor");
- const stakedGlpDistributorAddress = getContract(chainId, "StakedGlpDistributor");
-
- const excludedEsGmxAccounts = [stakedGmxDistributorAddress, stakedGlpDistributorAddress];
-
- const nativeTokenSymbol = getConstant(chainId, "nativeTokenSymbol");
-
- const { data: esGmxSupply } = useSWR
(
- [`StakeV2:esGmxSupply:${active}`, chainId, readerAddress, "getTokenSupply", esGmxAddress],
- {
- fetcher: contractFetcher(signer, "ReaderV2", [excludedEsGmxAccounts]),
- }
- );
-
- const { gmxPrice } = useGmxPrice(chainId, { arbitrum: chainId === ARBITRUM ? signer : undefined }, active);
-
- let esGmxSupplyUsd;
- if (esGmxSupply !== undefined && gmxPrice !== undefined) {
- esGmxSupplyUsd = bigMath.mulDiv(esGmxSupply, gmxPrice, expandDecimals(1, 18));
- }
-
- const gmxAvgAprText = useMemo(() => {
- return `${formatAmount(processedData?.gmxAprTotal, 2, 2, true)}%`;
- }, [processedData?.gmxAprTotal]);
-
- return (
-
-
-
-

-
- Escrowed GMX
-
-
-
-
-
-
-
- Price
-
-
${formatAmount(gmxPrice, USD_DECIMALS, 2, true)}
-
-
-
-
-
-
- APR
-
-
- (
-
- )}
- />
-
-
-
-
-
-
-
-
- {active && (
-
- )}
- {active && (
-
- )}
- {!active && (
-
- )}
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/GlpCard.tsx b/src/pages/Stake/GlpCard.tsx
deleted file mode 100644
index 9e61e9c5b4..0000000000
--- a/src/pages/Stake/GlpCard.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { Trans } from "@lingui/macro";
-
-import { getConstant } from "config/chains";
-import { USD_DECIMALS } from "config/factors";
-import { getIcons } from "config/icons";
-import { GLP_PRICE_DECIMALS } from "config/ui";
-import { useChainId } from "lib/chains";
-import { GLP_DECIMALS, ProcessedData } from "lib/legacy";
-import { formatKeyAmount } from "lib/numbers";
-
-import { AmountWithUsdBalance, AmountWithUsdHuman } from "components/AmountWithUsd/AmountWithUsd";
-import Button from "components/Button/Button";
-import StatsTooltipRow from "components/StatsTooltip/StatsTooltipRow";
-import Tooltip from "components/Tooltip/Tooltip";
-
-export function GlpCard({ processedData }: { processedData: ProcessedData | undefined }) {
- const { chainId } = useChainId();
-
- const icons = getIcons(chainId);
-
- const nativeTokenSymbol = getConstant(chainId, "nativeTokenSymbol");
- const wrappedTokenSymbol = getConstant(chainId, "wrappedTokenSymbol");
-
- return (
-
-
-
-
-

- GLP
-
-
-
-
-
-
- Price
-
-
${formatKeyAmount(processedData, "glpPrice", USD_DECIMALS, GLP_PRICE_DECIMALS, true)}
-
-
-
-
-
-
- Rewards
-
-
-
-
- }
- showDollar={false}
- />
-
- }
- showDollar={false}
- />
- >
- }
- />
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/GmxAndVotingPowerCard.tsx b/src/pages/Stake/GmxAndVotingPowerCard.tsx
deleted file mode 100644
index 6662ba54e1..0000000000
--- a/src/pages/Stake/GmxAndVotingPowerCard.tsx
+++ /dev/null
@@ -1,340 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import cx from "classnames";
-import { useMemo } from "react";
-
-import { ARBITRUM, AVALANCHE, getConstant } from "config/chains";
-import { USD_DECIMALS } from "config/factors";
-import { getIcons } from "config/icons";
-import { useGmxPrice, useTotalGmxStaked, useTotalGmxSupply } from "domain/legacy";
-import { useGovTokenAmount } from "domain/synthetics/governance/useGovTokenAmount";
-import { useGovTokenDelegates } from "domain/synthetics/governance/useGovTokenDelegates";
-import { useChainId } from "lib/chains";
-import { ProcessedData, useENS } from "lib/legacy";
-import { expandDecimals, formatAmount, formatBalanceAmount, formatKeyAmount } from "lib/numbers";
-import { shortenAddressOrEns } from "lib/wallets";
-import useWallet from "lib/wallets/useWallet";
-import { NATIVE_TOKEN_ADDRESS } from "sdk/configs/tokens";
-import { bigMath } from "sdk/utils/bigmath";
-
-import { AlertInfo } from "components/AlertInfo/AlertInfo";
-import { AmountWithUsdBalance, AmountWithUsdHuman } from "components/AmountWithUsd/AmountWithUsd";
-import Button from "components/Button/Button";
-import ExternalLink from "components/ExternalLink/ExternalLink";
-import GMXAprTooltip from "components/Stake/GMXAprTooltip";
-import ChainsStatsTooltipRow from "components/StatsTooltip/ChainsStatsTooltipRow";
-import StatsTooltipRow from "components/StatsTooltip/StatsTooltipRow";
-import Tooltip from "components/Tooltip/Tooltip";
-
-import { GMX_DAO_LINKS, getGmxDAODelegateLink } from "./constants";
-
-export function GmxAndVotingPowerCard({
- processedData,
- sbfGmxBalance,
- setIsUnstakeModalVisible,
- setUnstakeModalTitle,
- setUnstakeModalMaxAmount,
- setUnstakeValue,
- setUnstakingTokenSymbol,
- setUnstakeMethodName,
- showStakeGmxModal,
-}: {
- processedData: ProcessedData | undefined;
- sbfGmxBalance: bigint;
- setIsUnstakeModalVisible: (visible: boolean) => void;
- setUnstakeModalTitle: (title: string) => void;
- setUnstakeModalMaxAmount: (amount: bigint) => void;
- setUnstakeValue: (value: string) => void;
- setUnstakingTokenSymbol: (symbol: string) => void;
- setUnstakeMethodName: (methodName: string) => void;
- showStakeGmxModal: () => void;
-}) {
- const { chainId } = useChainId();
- const { active, signer, account } = useWallet();
- const icons = getIcons(chainId);
-
- const govTokenAmount = useGovTokenAmount(chainId);
- const govTokenDelegatesAddress = useGovTokenDelegates(chainId);
- const { ensName: govTokenDelegatesEns } = useENS(govTokenDelegatesAddress);
- const { gmxPrice, gmxPriceFromArbitrum, gmxPriceFromAvalanche } = useGmxPrice(
- chainId,
- { arbitrum: chainId === ARBITRUM ? signer : undefined },
- active
- );
- const isAnyFeeGmxTrackerRewards = (processedData?.feeGmxTrackerRewardsUsd ?? 0n) > 10n ** BigInt(USD_DECIMALS) / 100n;
-
- const stakedGMXInfo = useTotalGmxStaked();
-
- const { [AVALANCHE]: avaxGmxStaked, [ARBITRUM]: arbitrumGmxStaked, total: totalGmxStaked } = stakedGMXInfo;
-
- let { total: totalGmxSupply } = useTotalGmxSupply();
-
- let stakedGmxSupplyUsd;
- if (totalGmxStaked !== 0n && gmxPrice !== undefined) {
- stakedGmxSupplyUsd = bigMath.mulDiv(totalGmxStaked, gmxPrice, expandDecimals(1, 18));
- }
-
- let totalSupplyUsd;
- if (totalGmxSupply !== undefined && totalGmxSupply !== 0n && gmxPrice !== undefined) {
- totalSupplyUsd = bigMath.mulDiv(totalGmxSupply, gmxPrice, expandDecimals(1, 18));
- }
-
- const showUnstakeGmxModal = () => {
- setIsUnstakeModalVisible(true);
- setUnstakeModalTitle(t`Unstake GMX`);
- let maxAmount = processedData?.gmxInStakedGmx;
-
- if (maxAmount !== undefined) {
- maxAmount = bigMath.min(maxAmount, sbfGmxBalance);
- }
-
- setUnstakeModalMaxAmount(maxAmount!);
- setUnstakeValue("");
- setUnstakingTokenSymbol("GMX");
- setUnstakeMethodName("unstakeGmx");
- };
-
- const stakedEntries = useMemo(
- () => ({
- "Staked on Arbitrum": arbitrumGmxStaked,
- "Staked on Avalanche": avaxGmxStaked,
- }),
- [arbitrumGmxStaked, avaxGmxStaked]
- );
-
- const gmxAvgAprText = useMemo(() => {
- return `${formatAmount(processedData?.gmxAprTotal, 2, 2, true)}%`;
- }, [processedData?.gmxAprTotal]);
-
- const nativeTokenSymbol = getConstant(chainId, "nativeTokenSymbol");
- const wrappedTokenSymbol = getConstant(chainId, "wrappedTokenSymbol");
-
- return (
-
-
-
-

- {t`GMX & Voting Power`}
-
-
-
-
-
-
- Price
-
-
- {gmxPrice === undefined ? (
- "..."
- ) : (
- (
- <>
-
-
- >
- )}
- />
- )}
-
-
-
-
- {chainId === ARBITRUM && (
-
-
- Voting Power
-
-
- {govTokenAmount !== undefined ? (
-
- {govTokenDelegatesAddress === NATIVE_TOKEN_ADDRESS && govTokenAmount > 0 ? (
-
-
-
- Delegate your undelegated {formatAmount(govTokenAmount, 18, 2, true)} GMX DAO
- {" "}
- voting power.
-
-
- ) : null}
- No delegate found
- ) : (
-
- {govTokenDelegatesAddress === account
- ? t`Myself`
- : govTokenDelegatesEns
- ? shortenAddressOrEns(govTokenDelegatesEns, 25)
- : shortenAddressOrEns(govTokenDelegatesAddress, 13)}
-
- )
- }
- showDollar={false}
- />
-
- Explore the list of delegates.
- >
- }
- />
- ) : (
- "..."
- )}
-
-
- )}
-
-
-
- APR
-
-
- (
-
- )}
- />
-
-
-
-
- Rewards
-
-
-
-
- }
- showDollar={false}
- />
-
- }
- showDollar={false}
- />
- {isAnyFeeGmxTrackerRewards && (
-
- }
- showDollar={false}
- />
- )}
- >
- }
- />
-
-
-
-
-
- Total Staked
-
-
- }
- content={}
- />
-
-
-
-
-
-
- {active && (
-
- )}
- {active && (
-
- )}
- {active && chainId === ARBITRUM && (
-
- )}
- {active && (
-
- )}
-
-
-
- );
-}
diff --git a/src/pages/Stake/Stake.css b/src/pages/Stake/Stake.css
deleted file mode 100644
index a7c368e070..0000000000
--- a/src/pages/Stake/Stake.css
+++ /dev/null
@@ -1,128 +0,0 @@
-.StakeModal .Modal-content {
- width: 31rem;
-}
-.StakeModal .Modal-body {
- font-size: var(--font-size-body-medium);
-}
-
-.DelegateGMXAlertInfo {
- text-wrap: wrap;
-
- a {
- color: var(--warning-yellow);
- opacity: 0.7;
-
- &:hover {
- color: var(--warning-yellow);
- opacity: 1;
- }
- }
-}
-
-.StakeV2 .Page-title-section {
- position: relative;
- z-index: 2;
-}
-
-.CompoundModal-menu {
- margin-bottom: 0.8rem;
-}
-
-.CompoundModal-menu .Checkbox {
- margin-bottom: 0.465rem;
-}
-
-.StakeV2-address-input {
- padding: 1.5rem 3.41rem;
- padding-bottom: 0;
-}
-
-.StakeV2-buy-gmx-modal .Modal-content {
- max-width: 46.5rem;
-}
-
-.StakeV2-address-input input {
- box-sizing: border-box;
- width: 100%;
- font-size: 1.7rem;
-}
-
-.StakeV2-boost-bar {
- overflow: hidden;
- vertical-align: middle;
- margin-left: 0.31rem;
- border-radius: 2px;
- width: 1.5rem;
- height: 0.8rem;
- border: 1px solid var(--color-gray-500);
- display: inline-block;
- position: relative;
-}
-
-.StakeV2-boost-icon {
- font-size: 1.085rem;
- z-index: 2;
-}
-
-.StakeV2-boost-bar-fill {
- z-index: 1;
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- background: var(--color-gray-300);
-}
-
-.StakeV2-cards {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-gap: 1.5rem;
-}
-
-.App-card-buttons {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
-}
-
-.App-card-buttons.glp-buttons {
- padding-top: 0.8rem;
-}
-
-.App-card-footer {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- padding: 1.5rem 1.5rem 1.5rem;
-}
-.App-card-footer .App-card-divider {
- margin-bottom: 1.8rem;
-}
-
-.Stake-modal-icons {
- display: inline-flex;
- line-height: 1;
-}
-
-.Stake-modal-icons .icon {
- vertical-align: bottom;
-}
-
-@media (max-width: 900px) {
- .StakeV2-cards {
- grid-template-columns: 1fr;
- }
-
- .StakeV2-content {
- min-height: 100vh;
- }
-
- .StakeV2-total-rewards-card {
- grid-row: 4;
- }
- .App-card-footer {
- position: relative;
- padding: 0;
- }
-}
diff --git a/src/pages/Stake/Stake.tsx b/src/pages/Stake/Stake.tsx
deleted file mode 100644
index fe12949863..0000000000
--- a/src/pages/Stake/Stake.tsx
+++ /dev/null
@@ -1,316 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { useCallback, useMemo, useState } from "react";
-import useSWR from "swr";
-import { zeroAddress } from "viem";
-
-import { AVALANCHE, BOTANIX, getChainName } from "config/chains";
-import { getContract } from "config/contracts";
-import { getIncentivesV2Url } from "config/links";
-import { usePendingTxns } from "context/PendingTxnsContext/PendingTxnsContext";
-import useIncentiveStats from "domain/synthetics/common/useIncentiveStats";
-import { getTotalGmInfo, useMarketTokensData } from "domain/synthetics/markets";
-import { useLpInterviewNotification } from "domain/synthetics/userFeedback/useLpInterviewNotification";
-import { useChainId } from "lib/chains";
-import { contractFetcher } from "lib/contracts";
-import { PLACEHOLDER_ACCOUNT, getPageTitle } from "lib/legacy";
-import { formatAmount } from "lib/numbers";
-import useWallet from "lib/wallets/useWallet";
-import { bigMath } from "sdk/utils/bigmath";
-
-import SEO from "components/Common/SEO";
-import ExternalLink from "components/ExternalLink/ExternalLink";
-import Footer from "components/Footer/Footer";
-import { InterviewModal } from "components/InterviewModal/InterviewModal";
-import PageTitle from "components/PageTitle/PageTitle";
-import { BotanixBanner } from "components/Synthetics/BotanixBanner/BotanixBanner";
-import UserIncentiveDistributionList from "components/Synthetics/UserIncentiveDistributionList/UserIncentiveDistributionList";
-
-import { EscrowedGmxCard } from "./EscrowedGmxCard";
-import { GlpCard } from "./GlpCard";
-import { GmxAndVotingPowerCard } from "./GmxAndVotingPowerCard";
-import { StakeModal } from "./StakeModal";
-import { TotalRewardsCard } from "./TotalRewardsCard";
-import { UnstakeModal } from "./UnstakeModal";
-import { useProcessedData } from "./useProcessedData";
-import { Vesting } from "./Vesting";
-
-import "./Stake.css";
-
-function StakeContent() {
- const { active, signer, account } = useWallet();
- const { chainId } = useChainId();
- const incentiveStats = useIncentiveStats(chainId);
- const { isLpInterviewModalVisible, setIsLpInterviewModalVisible } = useLpInterviewNotification();
-
- const incentivesMessage = useMemo(() => {
- const avalancheLink = (
-
- {getChainName(AVALANCHE)}
-
- );
- if (incentiveStats?.lp?.isActive && incentiveStats?.trading?.isActive) {
- return (
-
- Liquidity and trading incentives programs are live on {avalancheLink}.
-
- );
- } else if (incentiveStats?.lp?.isActive) {
- return (
-
- Liquidity incentives program is live on {avalancheLink}.
-
- );
- } else if (incentiveStats?.trading?.isActive) {
- return (
-
- Trading incentives program is live on {avalancheLink}.
-
- );
- }
- }, [incentiveStats?.lp?.isActive, incentiveStats?.trading?.isActive]);
-
- const { setPendingTxns } = usePendingTxns();
-
- const [isStakeGmxModalVisible, setIsStakeGmxModalVisible] = useState(false);
- const [stakeGmxValue, setStakeGmxValue] = useState("");
- const [isStakeEsGmxModalVisible, setIsStakeEsGmxModalVisible] = useState(false);
- const [stakeEsGmxValue, setStakeEsGmxValue] = useState("");
-
- const [isUnstakeModalVisible, setIsUnstakeModalVisible] = useState(false);
- const [unstakeModalTitle, setUnstakeModalTitle] = useState("");
- const [unstakeModalMaxAmount, setUnstakeModalMaxAmount] = useState(undefined);
- const [unstakeValue, setUnstakeValue] = useState("");
- const [unstakingTokenSymbol, setUnstakingTokenSymbol] = useState("");
- const [unstakeMethodName, setUnstakeMethodName] = useState("");
-
- const rewardRouterAddress = getContract(chainId, "RewardRouter");
-
- const gmxAddress = getContract(chainId, "GMX");
- const esGmxAddress = getContract(chainId, "ES_GMX");
-
- const stakedGmxTrackerAddress = getContract(chainId, "StakedGmxTracker");
- const feeGmxTrackerAddress = getContract(chainId, "FeeGmxTracker");
-
- const { marketTokensData } = useMarketTokensData(chainId, { isDeposit: false });
-
- const { data: sbfGmxBalance } = useSWR(
- [`StakeV2:sbfGmxBalance:${active}`, chainId, feeGmxTrackerAddress, "balanceOf", account ?? PLACEHOLDER_ACCOUNT],
- {
- fetcher: contractFetcher(undefined, "Token"),
- }
- );
-
- const userTotalGmInfo = useMemo(() => {
- if (!active) return;
- return getTotalGmInfo(marketTokensData);
- }, [marketTokensData, active]);
-
- const processedData = useProcessedData();
-
- const reservedAmount =
- (processedData?.gmxInStakedGmx !== undefined &&
- processedData?.esGmxInStakedGmx !== undefined &&
- sbfGmxBalance !== undefined &&
- processedData?.gmxInStakedGmx + processedData?.esGmxInStakedGmx - sbfGmxBalance) ||
- 0n;
-
- let totalRewardTokens;
-
- if (processedData && processedData.bonusGmxInFeeGmx !== undefined) {
- totalRewardTokens = processedData.bonusGmxInFeeGmx;
- }
-
- let totalRewardAndLpTokens = totalRewardTokens ?? 0n;
- if (processedData?.glpBalance !== undefined) {
- totalRewardAndLpTokens = totalRewardAndLpTokens + processedData.glpBalance;
- }
- if ((userTotalGmInfo?.balance ?? 0n) > 0) {
- totalRewardAndLpTokens = totalRewardAndLpTokens + (userTotalGmInfo?.balance ?? 0n);
- }
-
- const showStakeGmxModal = useCallback(() => {
- setIsStakeGmxModalVisible(true);
- setStakeGmxValue("");
- }, []);
-
- const showStakeEsGmxModal = useCallback(() => {
- setIsStakeEsGmxModalVisible(true);
- setStakeEsGmxValue("");
- }, []);
-
- const showUnstakeEsGmxModal = () => {
- setIsUnstakeModalVisible(true);
- setUnstakeModalTitle(t`Unstake esGMX`);
- let maxAmount = processedData?.esGmxInStakedGmx;
-
- if (maxAmount !== undefined) {
- maxAmount = bigMath.min(maxAmount, sbfGmxBalance);
- }
-
- setUnstakeModalMaxAmount(maxAmount);
- setUnstakeValue("");
- setUnstakingTokenSymbol("esGMX");
- setUnstakeMethodName("unstakeEsGmx");
- };
-
- let earnMsg;
- if (totalRewardAndLpTokens && totalRewardAndLpTokens > 0) {
- let gmxAmountStr;
- if (processedData?.gmxInStakedGmx && processedData.gmxInStakedGmx > 0) {
- gmxAmountStr = formatAmount(processedData.gmxInStakedGmx, 18, 2, true) + " GMX";
- }
- let esGmxAmountStr;
- if (processedData?.esGmxInStakedGmx && processedData.esGmxInStakedGmx > 0) {
- esGmxAmountStr = formatAmount(processedData.esGmxInStakedGmx, 18, 2, true) + " esGMX";
- }
- let glpStr;
- if (processedData?.glpBalance && processedData.glpBalance > 0) {
- glpStr = formatAmount(processedData.glpBalance, 18, 2, true) + " GLP";
- }
- let gmStr;
- if (userTotalGmInfo?.balance && userTotalGmInfo.balance > 0) {
- gmStr = formatAmount(userTotalGmInfo.balance, 18, 2, true) + " GM";
- }
- const amountStr = [gmxAmountStr, esGmxAmountStr, gmStr, glpStr].filter((s) => s).join(", ");
- earnMsg = (
-
-
- You are earning rewards with {formatAmount(totalRewardAndLpTokens, 18, 2, true)} tokens.
-
- Tokens: {amountStr}.
-
-
- );
- }
-
- return (
-
-
-
-
-
- Deposit GMX and{" "}
- esGMX tokens to
- earn rewards.
-
- {earnMsg && {earnMsg}
}
- {incentivesMessage}
-
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
Claim and view your incentives, airdrops, and prizes}
- />
-
-
-
-
-
-
-
- );
-}
-
-export default function Stake() {
- const { chainId } = useChainId();
- const isBotanix = chainId === BOTANIX;
-
- return isBotanix ? (
-
- ) : (
-
- );
-}
diff --git a/src/pages/Stake/StakeModal.tsx b/src/pages/Stake/StakeModal.tsx
deleted file mode 100644
index 2296887cce..0000000000
--- a/src/pages/Stake/StakeModal.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import cx from "classnames";
-import { ZeroAddress, ethers } from "ethers";
-import { useCallback, useMemo, useState } from "react";
-
-import { ARBITRUM } from "config/chains";
-import { BASIS_POINTS_DIVISOR_BIGINT } from "config/factors";
-import { getIcons } from "config/icons";
-import { MAX_METAMASK_MOBILE_DECIMALS } from "config/ui";
-import { SetPendingTransactions } from "context/PendingTxnsContext/PendingTxnsContext";
-import { useGovTokenAmount } from "domain/synthetics/governance/useGovTokenAmount";
-import { useGovTokenDelegates } from "domain/synthetics/governance/useGovTokenDelegates";
-import { useTokensAllowanceData } from "domain/synthetics/tokens";
-import { approveTokens } from "domain/tokens";
-import { callContract } from "lib/contracts";
-import { ProcessedData } from "lib/legacy";
-import { formatAmount, formatAmountFree, limitDecimals, parseValue } from "lib/numbers";
-import { UncheckedJsonRpcSigner } from "lib/rpc/UncheckedJsonRpcSigner";
-import useIsMetamaskMobile from "lib/wallets/useIsMetamaskMobile";
-import { abis } from "sdk/abis";
-import { NATIVE_TOKEN_ADDRESS } from "sdk/configs/tokens";
-import { bigMath } from "sdk/utils/bigmath";
-
-import { AlertInfo } from "components/AlertInfo/AlertInfo";
-import { ApproveTokenButton } from "components/ApproveTokenButton/ApproveTokenButton";
-import Button from "components/Button/Button";
-import BuyInputSection from "components/BuyInputSection/BuyInputSection";
-import ExternalLink from "components/ExternalLink/ExternalLink";
-import Modal from "components/Modal/Modal";
-
-import { GMX_DAO_LINKS } from "./constants";
-
-export function StakeModal(props: {
- isVisible: boolean;
- setIsVisible: (isVisible: boolean) => void;
- chainId: number;
- title: string;
- maxAmount: bigint | undefined;
- value: string;
- setValue: (value: string) => void;
- signer: UncheckedJsonRpcSigner | undefined;
- stakingTokenSymbol: string;
- stakingTokenAddress: string;
- farmAddress: string;
- rewardRouterAddress: string;
- stakeMethodName: string;
- setPendingTxns: SetPendingTransactions;
- processedData: ProcessedData | undefined;
-}) {
- const {
- isVisible,
- setIsVisible,
- chainId,
- title,
- maxAmount,
- value,
- setValue,
- signer,
- stakingTokenSymbol,
- stakingTokenAddress,
- farmAddress,
- rewardRouterAddress,
- stakeMethodName,
- setPendingTxns,
- processedData,
- } = props;
-
- const govTokenAmount = useGovTokenAmount(chainId);
- const govTokenDelegatesAddress = useGovTokenDelegates(chainId);
- const isUndelegatedGovToken = useMemo(
- () =>
- chainId === ARBITRUM && govTokenDelegatesAddress === NATIVE_TOKEN_ADDRESS && govTokenAmount && govTokenAmount > 0,
- [chainId, govTokenDelegatesAddress, govTokenAmount]
- );
-
- const [isStaking, setIsStaking] = useState(false);
- const isMetamaskMobile = useIsMetamaskMobile();
- const [isApproving, setIsApproving] = useState(false);
- const icons = getIcons(chainId);
-
- const { tokensAllowanceData } = useTokensAllowanceData(chainId, {
- spenderAddress: farmAddress,
- tokenAddresses: [stakingTokenAddress].filter(Boolean),
- });
- const tokenAllowance = tokensAllowanceData?.[stakingTokenAddress];
-
- const amount = useMemo(() => parseValue(value, 18), [value]);
-
- const needApproval =
- farmAddress !== ZeroAddress && tokenAllowance !== undefined && amount !== undefined && amount > tokenAllowance;
-
- const stakeBonusPercentage = useMemo(() => {
- if (
- processedData &&
- amount !== undefined &&
- amount > 0 &&
- processedData.esGmxInStakedGmx !== undefined &&
- processedData.gmxInStakedGmx !== undefined
- ) {
- const divisor = processedData.esGmxInStakedGmx + processedData.gmxInStakedGmx;
- if (divisor !== 0n) {
- return bigMath.mulDiv(amount, BASIS_POINTS_DIVISOR_BIGINT, divisor);
- }
- }
- return undefined;
- }, [amount, processedData]);
-
- const error = useMemo(() => {
- if (amount === undefined || amount === 0n) {
- return t`Enter an amount`;
- }
- if (maxAmount !== undefined && amount > maxAmount) {
- return t`Max amount exceeded`;
- }
- return undefined;
- }, [amount, maxAmount]);
-
- const isPrimaryEnabled = useMemo(
- () => !error && !isApproving && !needApproval && !isStaking && !isUndelegatedGovToken,
- [error, isApproving, needApproval, isStaking, isUndelegatedGovToken]
- );
-
- const primaryText = useMemo(() => {
- if (error) {
- return error;
- }
- if (isApproving || needApproval) {
- return t`Pending ${stakingTokenSymbol} approval`;
- }
- if (isStaking) {
- return t`Staking...`;
- }
- return t`Stake`;
- }, [error, isApproving, needApproval, isStaking, stakingTokenSymbol]);
-
- const onClickPrimary = useCallback(() => {
- if (needApproval) {
- approveTokens({
- setIsApproving,
- signer,
- tokenAddress: stakingTokenAddress,
- spender: farmAddress,
- chainId,
- permitParams: undefined,
- approveAmount: undefined,
- });
- return;
- }
-
- setIsStaking(true);
- const contract = new ethers.Contract(rewardRouterAddress, abis.RewardRouter, signer);
-
- callContract(chainId, contract, stakeMethodName, [amount], {
- sentMsg: t`Stake submitted!`,
- failMsg: t`Stake failed.`,
- setPendingTxns,
- })
- .then(() => {
- setIsVisible(false);
- })
- .finally(() => {
- setIsStaking(false);
- });
- }, [
- needApproval,
- signer,
- stakingTokenAddress,
- farmAddress,
- chainId,
- rewardRouterAddress,
- stakeMethodName,
- amount,
- setPendingTxns,
- setIsVisible,
- ]);
-
- const onClickMaxButton = useCallback(() => {
- if (maxAmount === undefined) return;
- const formattedMaxAmount = formatAmountFree(maxAmount, 18, 18);
- const finalMaxAmount = isMetamaskMobile
- ? limitDecimals(formattedMaxAmount, MAX_METAMASK_MOBILE_DECIMALS)
- : formattedMaxAmount;
- setValue(finalMaxAmount);
- }, [maxAmount, isMetamaskMobile, setValue]);
-
- return (
-
-
-
-
setValue(e.target.value)}
- >
-
-
]})
- {stakingTokenSymbol}
-
-
-
-
- {(needApproval || isApproving) && (
-
- )}
-
- {stakeBonusPercentage !== undefined &&
- stakeBonusPercentage > 0 &&
- amount !== undefined &&
- maxAmount !== undefined &&
- amount <= maxAmount && (
-
- You will earn {formatAmount(stakeBonusPercentage, 2, 2)}% more rewards with this action.
-
- )}
-
- {isUndelegatedGovToken ? (
-
-
-
- Delegate your undelegated {formatAmount(govTokenAmount, 18, 2, true)} GMX DAO
- {" "}
- voting power before staking.
-
-
- ) : null}
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/TotalRewardsCard.tsx b/src/pages/Stake/TotalRewardsCard.tsx
deleted file mode 100644
index bee47abaad..0000000000
--- a/src/pages/Stake/TotalRewardsCard.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { useConnectModal } from "@rainbow-me/rainbowkit";
-import { useCallback, useMemo, useState } from "react";
-import { Link } from "react-router-dom";
-import { toast } from "react-toastify";
-import useSWR from "swr";
-
-import { ARBITRUM, getConstant } from "config/chains";
-import { getContract } from "config/contracts";
-import { USD_DECIMALS } from "config/factors";
-import { MARKETS } from "config/markets";
-import { usePendingTxns } from "context/PendingTxnsContext/PendingTxnsContext";
-import { useGmMarketsApy } from "domain/synthetics/markets/useGmMarketsApy";
-import { useChainId } from "lib/chains";
-import { contractFetcher } from "lib/contracts/contractFetcher";
-import { PLACEHOLDER_ACCOUNT, ProcessedData } from "lib/legacy";
-import { formatAmount, formatKeyAmount } from "lib/numbers";
-import useWallet from "lib/wallets/useWallet";
-
-import { AmountWithUsdBalance } from "components/AmountWithUsd/AmountWithUsd";
-import Button from "components/Button/Button";
-import StatsTooltipRow from "components/StatsTooltip/StatsTooltipRow";
-import Tooltip from "components/Tooltip/Tooltip";
-
-import { ClaimModal } from "./ClaimModal";
-
-export function TotalRewardsCard({
- processedData,
- showStakeGmxModal,
-}: {
- processedData: ProcessedData | undefined;
- showStakeGmxModal: () => void;
-}) {
- const { active, account, signer } = useWallet();
- const { chainId } = useChainId();
- const { openConnectModal } = useConnectModal();
- const { setPendingTxns } = usePendingTxns();
-
- const [isCompoundModalVisible, setIsCompoundModalVisible] = useState(false);
-
- const nativeTokenSymbol = getConstant(chainId, "nativeTokenSymbol");
- const wrappedTokenSymbol = getConstant(chainId, "wrappedTokenSymbol");
- const rewardRouterAddress = getContract(chainId, "RewardRouter");
- const readerAddress = getContract(chainId, "Reader");
- const gmxAddress = getContract(chainId, "GMX");
- const esGmxAddress = getContract(chainId, "ES_GMX");
- const glpAddress = getContract(chainId, "GLP");
-
- const stakedGmxTrackerAddress = getContract(chainId, "StakedGmxTracker");
-
- const walletTokens = [gmxAddress, esGmxAddress, glpAddress, stakedGmxTrackerAddress];
-
- const isAnyNativeTokenRewards =
- (processedData?.totalNativeTokenRewardsUsd ?? 0n) > 10n ** BigInt(USD_DECIMALS) / 100n;
-
- const { mutate: refetchBalances } = useSWR(
- [
- `StakeV2:walletBalances:${active}`,
- chainId,
- readerAddress,
- "getTokenBalancesWithSupplies",
- account || PLACEHOLDER_ACCOUNT,
- ],
- {
- fetcher: contractFetcher(signer, "ReaderV2", [walletTokens]),
- }
- );
-
- const gmxAvgAprText = useMemo(() => {
- return `${formatAmount(processedData?.gmxAprTotal, 2, 2, true)}%`;
- }, [processedData?.gmxAprTotal]);
-
- const gmxMarketAddress = useMemo(() => {
- if (chainId === ARBITRUM) {
- return Object.values(MARKETS[ARBITRUM]).find((m) => m.indexTokenAddress === gmxAddress)?.marketTokenAddress;
- }
- return undefined;
- }, [chainId, gmxAddress]);
-
- const {
- marketsTokensApyData,
- marketsTokensIncentiveAprData,
- // glvTokensIncentiveAprData,
- // marketsTokensLidoAprData,
- // glvApyInfoData,
- } = useGmMarketsApy(chainId, { period: "90d" });
-
- const gmxMarketApyDataText = useMemo(() => {
- if (!gmxMarketAddress || chainId !== ARBITRUM) return;
-
- const gmxApy =
- (marketsTokensApyData?.[gmxMarketAddress] ?? 0n) + (marketsTokensIncentiveAprData?.[gmxMarketAddress] ?? 0n);
-
- return `${formatAmount(gmxApy, 28, 2, true)}%`;
- }, [marketsTokensApyData, marketsTokensIncentiveAprData, gmxMarketAddress, chainId]);
-
- const hideToasts = useCallback(() => toast.dismiss(), []);
- const handleStakeGmx = useCallback(async () => {
- hideToasts();
- showStakeGmxModal();
- }, [showStakeGmxModal, hideToasts]);
-
- return (
- <>
-
-
-
-
- Stake GMX
- {" "}
- and earn {gmxAvgAprText} APR
-
-
- {chainId === ARBITRUM && (
- <>
-
-
-
- Provide liquidity
- {" "}
- and earn {gmxMarketApyDataText} APY
-
-
-
-
-
- Trade GMX
-
-
-
- >
- )}
-
- }
- />
-
-
- Total Rewards
-
-
-
-
-
GMX
-
- }
- position="bottom-end"
- content={
- <>
-
- {t`GMX Staked Rewards`}:
-
- >
- }
- showDollar={false}
- value={
-
- }
- />
-
- {t`Vested Claimable GMX`}:
-
- >
- }
- showDollar={false}
- value={
-
- }
- />
- >
- }
- />
-
-
- {isAnyNativeTokenRewards ? (
-
-
- {nativeTokenSymbol} ({wrappedTokenSymbol})
-
-
-
- ) : null}
-
-
- Total
-
-
${formatKeyAmount(processedData, "totalRewardsUsd", USD_DECIMALS, 2, true)}
-
-
-
-
- {active && (
-
- )}
- {!active && (
-
- )}
-
-
-
-
- >
- );
-}
diff --git a/src/pages/Stake/UnstakeModal.tsx b/src/pages/Stake/UnstakeModal.tsx
deleted file mode 100644
index 52a0a913b9..0000000000
--- a/src/pages/Stake/UnstakeModal.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { ethers } from "ethers";
-import { useCallback, useMemo, useState } from "react";
-
-import { ARBITRUM } from "config/chains";
-import { BASIS_POINTS_DIVISOR_BIGINT } from "config/factors";
-import { getIcons } from "config/icons";
-import { SetPendingTransactions } from "context/PendingTxnsContext/PendingTxnsContext";
-import { callContract } from "lib/contracts";
-import { ProcessedData } from "lib/legacy";
-import { formatAmount, formatAmountFree, parseValue } from "lib/numbers";
-import { UncheckedJsonRpcSigner } from "lib/rpc/UncheckedJsonRpcSigner";
-import { abis } from "sdk/abis";
-import { bigMath } from "sdk/utils/bigmath";
-
-import { AlertInfo } from "components/AlertInfo/AlertInfo";
-import Button from "components/Button/Button";
-import BuyInputSection from "components/BuyInputSection/BuyInputSection";
-import Modal from "components/Modal/Modal";
-
-export function UnstakeModal(props: {
- isVisible: boolean;
- setIsVisible: (isVisible: boolean) => void;
- chainId: number;
- title: string;
- maxAmount: bigint | undefined;
- value: string;
- setValue: (value: string) => void;
- signer: UncheckedJsonRpcSigner | undefined;
- unstakingTokenSymbol: string;
- rewardRouterAddress: string;
- unstakeMethodName: string;
- reservedAmount: bigint | undefined;
- setPendingTxns: SetPendingTransactions;
- processedData: ProcessedData | undefined;
-}) {
- const {
- isVisible,
- setIsVisible,
- chainId,
- title,
- maxAmount,
- value,
- setValue,
- signer,
- unstakingTokenSymbol,
- rewardRouterAddress,
- unstakeMethodName,
- reservedAmount,
- setPendingTxns,
- processedData,
- } = props;
-
- const [isUnstaking, setIsUnstaking] = useState(false);
- const icons = getIcons(chainId);
-
- const amount = useMemo(() => parseValue(value, 18), [value]);
-
- const unstakeBonusLostPercentage = useMemo(() => {
- if (
- processedData &&
- amount !== undefined &&
- amount > 0 &&
- processedData.esGmxInStakedGmx !== undefined &&
- processedData.gmxInStakedGmx !== undefined
- ) {
- const divisor = processedData.esGmxInStakedGmx + processedData.gmxInStakedGmx;
- if (divisor !== 0n) {
- return bigMath.mulDiv(amount, BASIS_POINTS_DIVISOR_BIGINT, divisor);
- }
- }
- return undefined;
- }, [amount, processedData]);
-
- const votingPowerBurnAmount = amount;
-
- const error = useMemo(() => {
- if (amount === undefined || amount === 0n) {
- return t`Enter an amount`;
- }
- if (maxAmount !== undefined && amount > maxAmount) {
- return t`Max amount exceeded`;
- }
- return undefined;
- }, [amount, maxAmount]);
-
- const isPrimaryEnabled = !error && !isUnstaking;
-
- const primaryText = useMemo(() => {
- if (error) {
- return error;
- }
- if (isUnstaking) {
- return t`Unstaking...`;
- }
- return t`Unstake`;
- }, [error, isUnstaking]);
-
- const onClickPrimary = useCallback(() => {
- setIsUnstaking(true);
- const contract = new ethers.Contract(rewardRouterAddress, abis.RewardRouter, signer);
- callContract(chainId, contract, unstakeMethodName, [amount], {
- sentMsg: t`Unstake submitted!`,
- failMsg: t`Unstake failed.`,
- successMsg: t`Unstake completed!`,
- setPendingTxns,
- })
- .then(() => {
- setIsVisible(false);
- })
- .finally(() => {
- setIsUnstaking(false);
- });
- }, [chainId, rewardRouterAddress, unstakeMethodName, amount, setPendingTxns, setIsVisible, signer]);
-
- const onClickMaxButton = useCallback(() => {
- if (maxAmount === undefined) return;
- setValue(formatAmountFree(maxAmount, 18, 18));
- }, [maxAmount, setValue]);
-
- return (
-
-
-
-
setValue(e.target.value)}
- >
-
-
]})
- {unstakingTokenSymbol}
-
-
-
- {reservedAmount !== undefined && reservedAmount > 0 && (
-
- You have {formatAmount(reservedAmount, 18, 2, true)} tokens reserved for vesting.
-
- )}
- {unstakeBonusLostPercentage !== undefined &&
- unstakeBonusLostPercentage > 0 &&
- amount !== undefined &&
- maxAmount !== undefined &&
- amount <= maxAmount && (
-
-
- {chainId === ARBITRUM ? (
-
- Unstaking will burn {formatAmount(votingPowerBurnAmount, 18, 2, true)} voting power.
-
- ) : null}
-
- You will earn {formatAmount(unstakeBonusLostPercentage, 2, 2)}% less rewards with this action.
-
-
-
- )}
-
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/VesterDepositModal.tsx b/src/pages/Stake/VesterDepositModal.tsx
deleted file mode 100644
index 2842fb1202..0000000000
--- a/src/pages/Stake/VesterDepositModal.tsx
+++ /dev/null
@@ -1,241 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { ethers } from "ethers";
-import { useCallback, useMemo, useState } from "react";
-
-import { getIcons } from "config/icons";
-import { SetPendingTransactions } from "context/PendingTxnsContext/PendingTxnsContext";
-import { callContract } from "lib/contracts";
-import { formatAmount, formatAmountFree, parseValue } from "lib/numbers";
-import { UncheckedJsonRpcSigner } from "lib/rpc/UncheckedJsonRpcSigner";
-import { abis } from "sdk/abis";
-import { bigMath } from "sdk/utils/bigmath";
-
-import Button from "components/Button/Button";
-import BuyInputSection from "components/BuyInputSection/BuyInputSection";
-import Modal from "components/Modal/Modal";
-import StatsTooltipRow from "components/StatsTooltip/StatsTooltipRow";
-import { SyntheticsInfoRow } from "components/Synthetics/SyntheticsInfoRow";
-import TooltipWithPortal from "components/Tooltip/TooltipWithPortal";
-
-export function VesterDepositModal(props: {
- isVisible: boolean;
- setIsVisible: (isVisible: boolean) => void;
- chainId: number;
- title: string;
- maxAmount: bigint | undefined;
- value: string;
- setValue: (value: string) => void;
- balance: bigint | undefined;
- vestedAmount: bigint | undefined;
- averageStakedAmount: bigint | undefined;
- maxVestableAmount: bigint | undefined;
- signer: UncheckedJsonRpcSigner | undefined;
- stakeTokenLabel: string;
- reserveAmount: bigint | undefined;
- maxReserveAmount: bigint | undefined;
- vesterAddress: string;
- setPendingTxns: SetPendingTransactions;
-}) {
- const {
- isVisible,
- setIsVisible,
- chainId,
- title,
- maxAmount,
- value,
- setValue,
- balance,
- vestedAmount,
- averageStakedAmount,
- maxVestableAmount,
- signer,
- stakeTokenLabel,
- reserveAmount,
- maxReserveAmount,
- vesterAddress,
- setPendingTxns,
- } = props;
- const [isDepositing, setIsDepositing] = useState(false);
- const icons = getIcons(chainId);
-
- const amount = useMemo(() => parseValue(value, 18), [value]);
-
- const { nextReserveAmount, nextDepositAmount, additionalReserveAmount } = useMemo(() => {
- let nextReserveAmount = reserveAmount;
- let nextDepositAmount = vestedAmount;
- let additionalReserveAmount = 0n;
-
- if (amount !== undefined && vestedAmount !== undefined) {
- nextDepositAmount = vestedAmount + amount;
- }
-
- if (
- amount !== undefined &&
- nextDepositAmount !== undefined &&
- averageStakedAmount !== undefined &&
- maxVestableAmount !== undefined &&
- maxVestableAmount > 0 &&
- nextReserveAmount !== undefined
- ) {
- nextReserveAmount = bigMath.mulDiv(nextDepositAmount, averageStakedAmount, maxVestableAmount);
- if (reserveAmount !== undefined && nextReserveAmount > reserveAmount) {
- additionalReserveAmount = nextReserveAmount - reserveAmount;
- }
- }
-
- return { nextReserveAmount, nextDepositAmount, additionalReserveAmount };
- }, [amount, vestedAmount, averageStakedAmount, maxVestableAmount, reserveAmount]);
-
- const error = useMemo(() => {
- if (amount === undefined) {
- return t`Enter an amount`;
- }
- if (maxAmount !== undefined && amount > maxAmount) {
- return t`Max amount exceeded`;
- }
- if (maxReserveAmount !== undefined && nextReserveAmount !== undefined && nextReserveAmount > maxReserveAmount) {
- return t`Insufficient staked tokens`;
- }
- return undefined;
- }, [amount, maxAmount, maxReserveAmount, nextReserveAmount]);
-
- const isPrimaryEnabled = !error && !isDepositing;
-
- const primaryText = useMemo(() => {
- if (error) {
- return error;
- }
- if (isDepositing) {
- return t`Depositing...`;
- }
- return t`Deposit`;
- }, [error, isDepositing]);
-
- const onClickPrimary = useCallback(() => {
- setIsDepositing(true);
- const contract = new ethers.Contract(vesterAddress, abis.Vester, signer);
-
- callContract(chainId, contract, "deposit", [amount], {
- sentMsg: t`Deposit submitted!`,
- failMsg: t`Deposit failed!`,
- successMsg: t`Deposited!`,
- setPendingTxns,
- })
- .then(() => {
- setIsVisible(false);
- })
- .finally(() => {
- setIsDepositing(false);
- });
- }, [chainId, vesterAddress, amount, setPendingTxns, setIsVisible, signer]);
-
- const onClickMaxButton = useCallback(() => {
- if (maxAmount === undefined) return;
- setValue(formatAmountFree(maxAmount, 18, 18));
- }, [maxAmount, setValue]);
-
- return (
-
-
-
-
setValue(e.target.value)}
- >
-
-

- esGMX
-
-
-
-
-
-
{formatAmount(balance, 18, 2, true)} esGMX>} />
-
-
- Vault Capacity for your Account:
-
-
-
-
-
- }
- />
- }
- />
- {reserveAmount !== undefined && (
-
-
-
- {amount !== undefined &&
- nextReserveAmount !== undefined &&
- maxReserveAmount !== undefined &&
- nextReserveAmount > maxReserveAmount && (
- <>
-
-
- You need a total of at least {formatAmount(nextReserveAmount, 18, 2, true)}{" "}
- {stakeTokenLabel} to vest {formatAmount(amount, 18, 2, true)} esGMX.
-
- >
- )}
- >
- }
- />
- }
- />
- )}
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/Stake/VesterWithdrawModal.tsx b/src/pages/Stake/VesterWithdrawModal.tsx
deleted file mode 100644
index 8c94514958..0000000000
--- a/src/pages/Stake/VesterWithdrawModal.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { ethers } from "ethers";
-import { useState } from "react";
-
-import { SetPendingTransactions } from "context/PendingTxnsContext/PendingTxnsContext";
-import { callContract } from "lib/contracts";
-import { UncheckedJsonRpcSigner } from "lib/rpc/UncheckedJsonRpcSigner";
-import { abis } from "sdk/abis";
-
-import Button from "components/Button/Button";
-import Modal from "components/Modal/Modal";
-
-export function VesterWithdrawModal(props: {
- isVisible: boolean;
- setIsVisible: (isVisible: boolean) => void;
- chainId: number;
- title: string;
- signer: UncheckedJsonRpcSigner | undefined;
- vesterAddress: string;
- setPendingTxns: SetPendingTransactions;
-}) {
- const { isVisible, setIsVisible, chainId, title, signer, vesterAddress, setPendingTxns } = props;
- const [isWithdrawing, setIsWithdrawing] = useState(false);
-
- const onClickPrimary = () => {
- setIsWithdrawing(true);
- const contract = new ethers.Contract(vesterAddress, abis.Vester, signer);
-
- callContract(chainId, contract, "withdraw", [], {
- sentMsg: t`Withdraw submitted.`,
- failMsg: t`Withdraw failed.`,
- successMsg: t`Withdrawn!`,
- setPendingTxns,
- })
- .then(() => {
- setIsVisible(false);
- })
- .finally(() => {
- setIsWithdrawing(false);
- });
- };
-
- return (
-
- );
-}
diff --git a/src/pages/Stake/Vesting.tsx b/src/pages/Stake/Vesting.tsx
deleted file mode 100644
index a7fa5ac1fd..0000000000
--- a/src/pages/Stake/Vesting.tsx
+++ /dev/null
@@ -1,528 +0,0 @@
-import { Trans, t } from "@lingui/macro";
-import { useConnectModal } from "@rainbow-me/rainbowkit";
-import { useState } from "react";
-import useSWR from "swr";
-
-import { getContract } from "config/contracts";
-import { getIcons } from "config/icons";
-import { usePendingTxns } from "context/PendingTxnsContext/PendingTxnsContext";
-import useVestingData from "domain/vesting/useVestingData";
-import { useChainId } from "lib/chains";
-import { contractFetcher } from "lib/contracts";
-import { helperToast } from "lib/helperToast";
-import { PLACEHOLDER_ACCOUNT, ProcessedData } from "lib/legacy";
-import { formatAmount, formatKeyAmount } from "lib/numbers";
-import useWallet from "lib/wallets/useWallet";
-
-import Button from "components/Button/Button";
-import ExternalLink from "components/ExternalLink/ExternalLink";
-import PageTitle from "components/PageTitle/PageTitle";
-import StatsTooltipRow from "components/StatsTooltip/StatsTooltipRow";
-import Tooltip from "components/Tooltip/Tooltip";
-
-import { AffiliateClaimModal } from "./AffiliateClaimModal";
-import { AffiliateVesterWithdrawModal } from "./AffiliateVesterWithdrawModal";
-import { VesterDepositModal } from "./VesterDepositModal";
-import { VesterWithdrawModal } from "./VesterWithdrawModal";
-
-export function Vesting({ processedData }: { processedData: ProcessedData | undefined }) {
- const { active, signer, account } = useWallet();
- const { chainId } = useChainId();
- const { openConnectModal } = useConnectModal();
- const [isAffiliateVesterWithdrawModalVisible, setIsAffiliateVesterWithdrawModalVisible] = useState(false);
- const vestingData = useVestingData(account);
- const { setPendingTxns } = usePendingTxns();
-
- const [isVesterWithdrawModalVisible, setIsVesterWithdrawModalVisible] = useState(false);
- const [vesterWithdrawTitle, setVesterWithdrawTitle] = useState("");
- const [vesterWithdrawAddress, setVesterWithdrawAddress] = useState("");
- const [vesterDepositMaxReserveAmount, setVesterDepositMaxReserveAmount] = useState