diff --git a/sdk/src/configs/chains.ts b/sdk/src/configs/chains.ts index 0c48e38bd7..ab10ddf901 100644 --- a/sdk/src/configs/chains.ts +++ b/sdk/src/configs/chains.ts @@ -43,13 +43,17 @@ export { export const CONTRACTS_CHAIN_IDS = [ARBITRUM, AVALANCHE, BOTANIX] as const; export const CONTRACTS_CHAIN_IDS_DEV = [...CONTRACTS_CHAIN_IDS, AVALANCHE_FUJI, ARBITRUM_SEPOLIA] as const; export const SETTLEMENT_CHAIN_IDS = [ARBITRUM, AVALANCHE] as const; -export const SETTLEMENT_CHAIN_IDS_DEV = [...SETTLEMENT_CHAIN_IDS, ARBITRUM_SEPOLIA] as const; +export const SETTLEMENT_CHAIN_IDS_DEV = [...SETTLEMENT_CHAIN_IDS, ARBITRUM_SEPOLIA, AVALANCHE_FUJI] as const; export const SOURCE_CHAIN_IDS = [ SOURCE_OPTIMISM_SEPOLIA, SOURCE_SEPOLIA, SOURCE_BASE_MAINNET, SOURCE_BSC_MAINNET, SOURCE_ETHEREUM_MAINNET, + ARBITRUM_SEPOLIA, + ARBITRUM, + AVALANCHE, + AVALANCHE_FUJI, ] as const; export type ContractsChainId = (typeof CONTRACTS_CHAIN_IDS_DEV)[number]; @@ -195,6 +199,30 @@ const SOURCE_CHAIN_CONFIGS = { slug: "ethereum-mainnet", explorerUrl: "https://etherscan.io/", }, + [ARBITRUM]: { + chainId: ARBITRUM, + name: "Arbitrum", + slug: "arbitrum", + explorerUrl: "https://arbiscan.io/", + }, + [AVALANCHE]: { + chainId: AVALANCHE, + name: "Avalanche", + slug: "avalanche", + explorerUrl: "https://snowtrace.io/", + }, + [ARBITRUM_SEPOLIA]: { + chainId: ARBITRUM_SEPOLIA, + name: "Arbitrum Sepolia", + slug: "arbitrum-sepolia", + explorerUrl: "https://sepolia.arbiscan.io/", + }, + [AVALANCHE_FUJI]: { + chainId: AVALANCHE_FUJI, + name: "Avalanche Fuji", + slug: "avalanche-fuji", + explorerUrl: "https://testnet.snowtrace.io/", + }, // Use this notation to correctly infer chain names, etc. from config } as const satisfies Record; diff --git a/sdk/src/configs/multichain.ts b/sdk/src/configs/multichain.ts index d6ed4e50db..065b21c7fe 100644 --- a/sdk/src/configs/multichain.ts +++ b/sdk/src/configs/multichain.ts @@ -1,37 +1,57 @@ import { - ARBITRUM_SEPOLIA, ARBITRUM, AVALANCHE, - SOURCE_OPTIMISM_SEPOLIA, - SOURCE_SEPOLIA, + AVALANCHE_FUJI, + ARBITRUM_SEPOLIA, SOURCE_BASE_MAINNET, - SOURCE_BSC_MAINNET, SOURCE_ETHEREUM_MAINNET, + SOURCE_BSC_MAINNET, + SOURCE_SEPOLIA, + SOURCE_OPTIMISM_SEPOLIA, } from "./chainIds"; -import { SettlementChainId, SourceChainId } from "./chains"; +import { + SETTLEMENT_CHAIN_IDS, + SETTLEMENT_CHAIN_IDS_DEV, + SettlementChainId, + SOURCE_CHAIN_IDS, + SourceChainId, +} from "./chains"; -function ensureExhaustive(value: Record): T[] { - return Object.keys(value).map(Number) as T[]; +export function isSettlementChain(chainId: number): chainId is SettlementChainId { + return SETTLEMENT_CHAIN_IDS.includes(chainId as any) || SETTLEMENT_CHAIN_IDS_DEV.includes(chainId as any); } -export const SETTLEMENT_CHAINS: SettlementChainId[] = ensureExhaustive({ - [ARBITRUM_SEPOLIA]: true, - [ARBITRUM]: true, - [AVALANCHE]: true, -}); +const SOURCE_CHAIN_IDS_MAP: Record = { + [ARBITRUM]: [SOURCE_BASE_MAINNET, SOURCE_ETHEREUM_MAINNET, SOURCE_BSC_MAINNET], + [AVALANCHE]: [SOURCE_BASE_MAINNET, SOURCE_BSC_MAINNET], + [AVALANCHE_FUJI]: [ARBITRUM_SEPOLIA], + [ARBITRUM_SEPOLIA]: [SOURCE_SEPOLIA, SOURCE_OPTIMISM_SEPOLIA], +}; -export const SOURCE_CHAINS: SourceChainId[] = ensureExhaustive({ - [SOURCE_OPTIMISM_SEPOLIA]: true, - [SOURCE_SEPOLIA]: true, - [SOURCE_BASE_MAINNET]: true, - [SOURCE_BSC_MAINNET]: true, - [SOURCE_ETHEREUM_MAINNET]: true, -}); +export function isSourceChain( + chainId: number | undefined, + forSettlementChain: number | undefined +): chainId is SourceChainId { + if (!chainId || !forSettlementChain) { + return false; + } -export function isSettlementChain(chainId: number): chainId is SettlementChainId { - return SETTLEMENT_CHAINS.includes(chainId as SettlementChainId); + const sourceChainIds = SOURCE_CHAIN_IDS_MAP[forSettlementChain as SettlementChainId]; + if (!sourceChainIds) { + return false; + } + + return sourceChainIds.includes(chainId as any); } -export function isSourceChain(chainId: number | undefined): chainId is SourceChainId { - return SOURCE_CHAINS.includes(chainId as SourceChainId); +/** + * Check if a chain is a source chain for any settlement chain + * Useful when settlement chain context is not available + */ +export function isSourceChainForAnySettlementChain(chainId: number | undefined): chainId is SourceChainId { + if (!chainId) { + return false; + } + + return SOURCE_CHAIN_IDS.includes(chainId as any); } diff --git a/src/components/BridgeModal/BridgeInModal.tsx b/src/components/BridgeModal/BridgeInModal.tsx index a8c842e241..498b6426d6 100644 --- a/src/components/BridgeModal/BridgeInModal.tsx +++ b/src/components/BridgeModal/BridgeInModal.tsx @@ -187,7 +187,7 @@ export function BridgeInModal({ const firstChainWithBalance = Object.entries(multichainMarketTokenBalances.balances).find(([chainIdStr, data]) => { const chainIdNum = Number(chainIdStr); if ( - !isSourceChain(chainIdNum) || + !isSourceChain(chainIdNum, chainId) || (chainIdNum as number) === chainId || (chainIdNum as number) === GMX_ACCOUNT_PSEUDO_CHAIN_ID ) { @@ -330,7 +330,7 @@ export function BridgeInModal({ paySource={"sourceChain"} label={t`Deposit`} onSelectTokenAddress={(newBridgeInChain) => { - if (!isSourceChain(newBridgeInChain)) { + if (!isSourceChain(newBridgeInChain, chainId)) { return; } setBridgeInChain(newBridgeInChain); diff --git a/src/components/DropdownSelector/DropdownSelector.tsx b/src/components/DropdownSelector/DropdownSelector.tsx index 3b889c2d6d..dbe61c7f11 100644 --- a/src/components/DropdownSelector/DropdownSelector.tsx +++ b/src/components/DropdownSelector/DropdownSelector.tsx @@ -1,6 +1,9 @@ import { Listbox } from "@headlessui/react"; import cx from "classnames"; +import { NoopWrapper } from "components/NoopWrapper/NoopWrapper"; +import TooltipWithPortal from "components/Tooltip/TooltipWithPortal"; + import ChevronDownIcon from "img/ic_chevron_down.svg?react"; type Primitive = string | number; @@ -18,6 +21,8 @@ export const DropdownSelector = ({ placeholder, slim = false, variant, + itemDisabled, + itemDisabledMessage, }: { value: Id | undefined; onChange: (value: Id) => void; @@ -27,6 +32,8 @@ export const DropdownSelector = ({ placeholder?: string; slim?: boolean; variant?: "ghost"; + itemDisabled?: (option: Option) => boolean; + itemDisabledMessage?: (option: Option) => string; } & WithConditionalItemKey) => { return ( @@ -54,21 +61,34 @@ export const DropdownSelector = ({ "border-1/2 border-slate-600 bg-slate-800" )} > - {options.map((option) => ( - - cx( - "cursor-pointer", - slim ? "text-body-medium p-4" : "text-body-large px-14 py-8", - (active || selected) && (variant === "ghost" ? "bg-fill-surfaceHover" : "bg-fill-surfaceHover") - ) - } - > - - - ))} + {options.map((option) => { + const isDisabled = itemDisabled?.(option); + const disabledMessage = itemDisabledMessage?.(option); + const Wrapper = isDisabled && disabledMessage ? TooltipWithPortal : NoopWrapper; + + return ( + + {({ active, selected, disabled }) => ( + +
+ +
+
+ )} +
+ ); + })}
diff --git a/src/components/GmSwap/GmSwapBox/GmDepositWithdrawalBox/GmDepositWithdrawalBox.tsx b/src/components/GmSwap/GmSwapBox/GmDepositWithdrawalBox/GmDepositWithdrawalBox.tsx index bc419381ec..2b519768c5 100644 --- a/src/components/GmSwap/GmSwapBox/GmDepositWithdrawalBox/GmDepositWithdrawalBox.tsx +++ b/src/components/GmSwap/GmSwapBox/GmDepositWithdrawalBox/GmDepositWithdrawalBox.tsx @@ -487,7 +487,11 @@ export function GmSwapBoxDepositWithdrawal() { } setPaySource( - isSourceChain(newSrcChainId) ? "sourceChain" : isGmxAccount ? "gmxAccount" : "settlementChain" + isSourceChain(newSrcChainId, chainId) + ? "sourceChain" + : isGmxAccount + ? "gmxAccount" + : "settlementChain" ); handleFirstTokenSelect(tokenAddress as ERC20Address | NativeTokenSupportedAddress); }} diff --git a/src/components/GmxAccountModal/DepositView.tsx b/src/components/GmxAccountModal/DepositView.tsx index 386edf77f1..2556af1476 100644 --- a/src/components/GmxAccountModal/DepositView.tsx +++ b/src/components/GmxAccountModal/DepositView.tsx @@ -1,10 +1,11 @@ +import { addressToBytes32 } from "@layerzerolabs/lz-v2-utilities"; import { Trans, t } from "@lingui/macro"; import cx from "classnames"; import noop from "lodash/noop"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Skeleton from "react-loading-skeleton"; import { useLatest } from "react-use"; -import { Hex, decodeErrorResult, zeroAddress } from "viem"; +import { Hex, decodeErrorResult, encodeEventTopics, toHex, zeroAddress } from "viem"; import { useAccount, useChains } from "wagmi"; import { @@ -20,7 +21,6 @@ import { getContract } from "config/contracts"; import { getChainIcon } from "config/icons"; import { CHAIN_ID_PREFERRED_DEPOSIT_TOKEN, - DEBUG_MULTICHAIN_SAME_CHAIN_DEPOSIT, MULTICHAIN_FUNDING_SLIPPAGE_BPS, MULTI_CHAIN_DEPOSIT_TRADE_TOKENS, StargateErrorsAbi, @@ -31,8 +31,8 @@ import { useGmxAccountDepositViewTokenAddress, useGmxAccountDepositViewTokenInputValue, useGmxAccountModalOpen, - useGmxAccountSelector, useGmxAccountSelectedTransferGuid, + useGmxAccountSelector, useGmxAccountSettlementChainId, } from "context/GmxAccountContext/hooks"; import { selectGmxAccountDepositViewTokenInputAmount } from "context/GmxAccountContext/selectors"; @@ -40,8 +40,9 @@ import { useSubaccountContext } from "context/SubaccountContext/SubaccountContex import { useSyntheticsEvents } from "context/SyntheticsEvents"; import { useMultichainApprovalsActiveListener } from "context/SyntheticsEvents/useMultichainEvents"; import { getMultichainTransferSendParams } from "domain/multichain/getSendParams"; +import { isStringEqualInsensitive, matchLogRequest } from "domain/multichain/progress/LongCrossChainTask"; import { sendCrossChainDepositTxn } from "domain/multichain/sendCrossChainDepositTxn"; -import { sendSameChainDepositTxn } from "domain/multichain/sendSameChainDepositTxn"; +import { estimateSameChainDepositGas, sendSameChainDepositTxn } from "domain/multichain/sendSameChainDepositTxn"; import { SendParam } from "domain/multichain/types"; import { useGmxAccountFundingHistory } from "domain/multichain/useGmxAccountFundingHistory"; import { useMultichainDepositNetworkComposeGas } from "domain/multichain/useMultichainDepositNetworkComposeGas"; @@ -50,6 +51,7 @@ import { useNativeTokenBalance } from "domain/multichain/useNativeTokenBalance"; import { useQuoteOft } from "domain/multichain/useQuoteOft"; import { useQuoteOftLimits } from "domain/multichain/useQuoteOftLimits"; import { useQuoteSendNativeFee } from "domain/multichain/useQuoteSend"; +import { useGasPrice } from "domain/synthetics/fees/useGasPrice"; import { getNeedTokenApprove, useTokensAllowanceData, useTokensDataRequest } from "domain/synthetics/tokens"; import { NativeTokenSupportedAddress, approveTokens } from "domain/tokens"; import { useChainId } from "lib/chains"; @@ -69,9 +71,11 @@ import { EMPTY_ARRAY, EMPTY_OBJECT, getByKey } from "lib/objects"; import { useJsonRpcProvider } from "lib/rpc"; import { TxnCallback, TxnEventName, WalletTxnCtx } from "lib/transactions"; import { useHasOutdatedUi } from "lib/useHasOutdatedUi"; +import { useThrottledAsync } from "lib/useThrottledAsync"; +import { getPublicClientWithRpc } from "lib/wallets/rainbowKitConfig"; import { useIsNonEoaAccountOnAnyChain } from "lib/wallets/useAccountType"; -import { useEthersSigner } from "lib/wallets/useEthersSigner"; import { useIsGeminiWallet } from "lib/wallets/useIsGeminiWallet"; +import { abis } from "sdk/abis"; import { convertTokenAddress, getToken } from "sdk/configs/tokens"; import { bigMath } from "sdk/utils/bigmath"; import { convertToTokenAmount, convertToUsd, getMidPrice } from "sdk/utils/tokens"; @@ -90,6 +94,7 @@ import { ValueTransition } from "components/ValueTransition/ValueTransition"; import ChevronRightIcon from "img/ic_chevron_right.svg?react"; import SpinnerIcon from "img/ic_spinner.svg?react"; +import { calculateNetworkFeeDetails } from "./calculateNetworkFeeDetails"; import { useAvailableToTradeAssetMultichain, useMultichainTradeTokensRequest } from "./hooks"; import { wrapChainAction } from "./wrapChainAction"; @@ -123,7 +128,6 @@ export const DepositView = () => { const [, setSettlementChainId] = useGmxAccountSettlementChainId(); const [depositViewChain, setDepositViewChain] = useGmxAccountDepositViewChain(); - const walletSigner = useEthersSigner({ chainId: srcChainId }); const { provider: sourceChainProvider } = useJsonRpcProvider(depositViewChain); const [isVisibleOrView, setIsVisibleOrView] = useGmxAccountModalOpen(); @@ -136,11 +140,12 @@ export const DepositView = () => { isPriceDataLoading, isBalanceDataLoading, } = useMultichainTradeTokensRequest(settlementChainId, account); + const { tokensData: settlementChainTokensData } = useTokensDataRequest(settlementChainId, depositViewChain); const [isApproving, setIsApproving] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [shouldSendCrossChainDepositWhenLoaded, setShouldSendCrossChainDepositWhenLoaded] = useState(false); - const { setMultichainSubmittedDeposit } = useSyntheticsEvents(); + const { setMultichainSubmittedDeposit, setMultichainFundingPendingId } = useSyntheticsEvents(); const selectedToken = depositViewTokenAddress !== undefined ? getToken(settlementChainId, depositViewTokenAddress) : undefined; @@ -159,11 +164,28 @@ export const DepositView = () => { : undefined; const selectedTokenChainData = useMemo(() => { - if (selectedToken === undefined) return undefined; + if (selectedToken === undefined) { + return undefined; + } + + if (depositViewChain === settlementChainId) { + const settlementChainTokenData = getByKey(settlementChainTokensData, selectedToken.address); + if (!settlementChainTokenData) { + return undefined; + } + return { + ...settlementChainTokenData, + sourceChainId: depositViewChain, + sourceChainDecimals: settlementChainTokenData?.decimals, + sourceChainPrices: settlementChainTokenData?.prices, + sourceChainBalance: settlementChainTokenData?.walletBalance, + }; + } + return multichainTokens.find( (token) => token.address === selectedToken.address && token.sourceChainId === depositViewChain ); - }, [selectedToken, multichainTokens, depositViewChain]); + }, [selectedToken, depositViewChain, settlementChainId, multichainTokens, settlementChainTokensData]); const selectedTokenSourceChainBalance = selectedTokenChainData?.sourceChainBalance; const selectedTokenSourceChainDecimals = selectedTokenChainData?.sourceChainDecimals; @@ -260,16 +282,23 @@ export const DepositView = () => { useMultichainApprovalsActiveListener(depositViewChain, "multichain-deposit-view"); + const sourceChainTokenToApproveAddress: string | undefined = useMemo(() => { + if (depositViewChain === settlementChainId) { + return depositViewTokenAddress; + } + return selectedTokenSourceChainTokenId?.address; + }, [depositViewChain, depositViewTokenAddress, selectedTokenSourceChainTokenId?.address, settlementChainId]); + const tokensAllowanceResult = useTokensAllowanceData(depositViewChain, { spenderAddress, - tokenAddresses: selectedTokenSourceChainTokenId ? [selectedTokenSourceChainTokenId.address] : [], + tokenAddresses: sourceChainTokenToApproveAddress ? [sourceChainTokenToApproveAddress] : [], skip: depositViewChain === undefined, }); const tokensAllowanceData = depositViewChain !== undefined ? tokensAllowanceResult.tokensAllowanceData : undefined; const needTokenApprove = getNeedTokenApprove( tokensAllowanceData, - depositViewTokenAddress === zeroAddress ? zeroAddress : selectedTokenSourceChainTokenId?.address, + sourceChainTokenToApproveAddress, amountLD, EMPTY_ARRAY ); @@ -287,7 +316,7 @@ export const DepositView = () => { return; } - if (!selectedTokenSourceChainTokenId) { + if (!sourceChainTokenToApproveAddress) { helperToast.error(t`Approval failed`); return; } @@ -295,7 +324,7 @@ export const DepositView = () => { await wrapChainAction(depositViewChain, setSettlementChainId, async (signer) => { await approveTokens({ chainId: depositViewChain, - tokenAddress: selectedTokenSourceChainTokenId.address, + tokenAddress: sourceChainTokenToApproveAddress, signer: signer, spender: spenderAddress, onApproveSubmitted: () => setIsApproving(true), @@ -309,7 +338,7 @@ export const DepositView = () => { amountLD, spenderAddress, depositViewChain, - selectedTokenSourceChainTokenId, + sourceChainTokenToApproveAddress, setSettlementChainId, ]); @@ -394,6 +423,43 @@ export const DepositView = () => { targetChainId: settlementChainId, }); + const gasPrice = useGasPrice(settlementChainId); + + const sameChainNetworkFeeAsyncResult = useThrottledAsync( + async ({ params }) => { + const client = getPublicClientWithRpc(params.settlementChainId); + + return estimateSameChainDepositGas({ + chainId: params.settlementChainId as SettlementChainId, + client, + tokenAddress: params.depositViewTokenAddress, + amount: params.inputAmount, + account: params.account, + }); + }, + { + params: + account && depositViewTokenAddress && inputAmount !== undefined && settlementChainId + ? { + account, + depositViewTokenAddress, + inputAmount, + settlementChainId, + } + : undefined, + } + ); + + const sameChainNetworkFeeDetails = useMemo( + () => + calculateNetworkFeeDetails({ + gasLimit: sameChainNetworkFeeAsyncResult.data, + gasPrice, + tokensData: settlementChainTokensData, + }), + [sameChainNetworkFeeAsyncResult.data, gasPrice, settlementChainTokensData] + ); + const isFirstDeposit = useIsFirstDeposit(); const latestIsFirstDeposit = useLatest(isFirstDeposit); @@ -406,30 +472,88 @@ export const DepositView = () => { const sameChainCallback: TxnCallback = useCallback( (txnEvent) => { + if (!account) { + return; + } + if (txnEvent.event === TxnEventName.Sent) { helperToast.success("Deposit sent", { toastId: "same-chain-gmx-account-deposit" }); setIsVisibleOrView("main"); + setIsSubmitting(false); + if (txnEvent.data.type === "wallet" && depositViewTokenAddress && inputAmount !== undefined) { + const txnHash = txnEvent.data.transactionHash; + const mockId = setMultichainSubmittedDeposit({ + amount: inputAmount, + settlementChainId, + sourceChainId: 0, + tokenAddress: convertTokenAddress(settlementChainId, depositViewTokenAddress, "wrapped"), + sentTxn: txnHash, + }); + + if (!mockId) { + return; + } + + getPublicClientWithRpc(settlementChainId) + .waitForTransactionReceipt({ + hash: txnHash, + }) + .then((receipt) => { + const bridgeInEvent = receipt.logs.find( + (log) => + isStringEqualInsensitive(log.address, getContract(settlementChainId, "EventEmitter")) && + matchLogRequest( + encodeEventTopics({ + abi: abis.EventEmitter, + eventName: "EventLog1", + args: { eventNameHash: "MultichainBridgeIn", topic1: toHex(addressToBytes32(account)) }, + }), + log.topics + ) + ); + const bridgeInEventIndex = bridgeInEvent?.logIndex; + if (bridgeInEventIndex === undefined) { + return; + } + + const id = `${txnHash.toLowerCase()}:${bridgeInEventIndex}`; + setMultichainFundingPendingId(mockId, id); + }); + } } else if (txnEvent.event === TxnEventName.Error) { helperToast.error(t`Deposit failed`, { toastId: "same-chain-gmx-account-deposit" }); + setIsSubmitting(false); } }, - [setIsVisibleOrView] + [ + account, + depositViewTokenAddress, + inputAmount, + setIsVisibleOrView, + setMultichainFundingPendingId, + setMultichainSubmittedDeposit, + settlementChainId, + ] ); const handleSameChainDeposit = useCallback(async () => { - if (!account || !depositViewTokenAddress || inputAmount === undefined || !walletSigner) { + if (!account || !depositViewTokenAddress || inputAmount === undefined) { return; } - await sendSameChainDepositTxn({ - chainId: settlementChainId as SettlementChainId, - signer: walletSigner, - tokenAddress: depositViewTokenAddress, - amount: inputAmount, - account, - callback: sameChainCallback, + setIsSubmitting(true); + + await wrapChainAction(settlementChainId, setSettlementChainId, async (signer) => { + await sendSameChainDepositTxn({ + chainId: settlementChainId as SettlementChainId, + signer, + tokenAddress: depositViewTokenAddress, + amount: inputAmount, + account, + callback: sameChainCallback, + }); }); - }, [account, depositViewTokenAddress, inputAmount, sameChainCallback, settlementChainId, walletSigner]); + }, [account, depositViewTokenAddress, inputAmount, sameChainCallback, settlementChainId, setSettlementChainId]); const makeCrossChainCallback = useCallback( (params: { @@ -599,13 +723,13 @@ export const DepositView = () => { ]); const handleDeposit = useCallback(async () => { - if (DEBUG_MULTICHAIN_SAME_CHAIN_DEPOSIT && (walletChainId as SettlementChainId) === settlementChainId) { + if (depositViewChain === settlementChainId) { await handleSameChainDeposit(); } else { setIsSubmitting(true); setShouldSendCrossChainDepositWhenLoaded(true); } - }, [walletChainId, settlementChainId, handleSameChainDeposit]); + }, [depositViewChain, settlementChainId, handleSameChainDeposit]); const isCrossChainDepositLoading = useRef(false); useEffect(() => { @@ -803,6 +927,88 @@ export const DepositView = () => { const isTestnet = isTestnetChain(settlementChainId); + const estimatedTimeValue = useMemo(() => { + if (depositViewChain === settlementChainId) { + return Instant; + } + + if (inputAmount === undefined || inputAmount === 0n) { + return "..."; + } + + return isTestnet ? 1m 40s : 30s; + }, [depositViewChain, inputAmount, isTestnet, settlementChainId]); + + const depositFeeValue = useMemo(() => { + if (depositViewChain === settlementChainId) { + return No fee; + } + + if (protocolFeeAmount === undefined || selectedTokenSourceChainDecimals === undefined) { + return "..."; + } + + return ( + + ); + }, [ + depositViewChain, + settlementChainId, + protocolFeeAmount, + selectedTokenSourceChainDecimals, + protocolFeeUsd, + selectedToken?.symbol, + ]); + + const networkFeeValue = useMemo(() => { + if (depositViewViemChain === undefined) { + return "..."; + } + + if (depositViewChain === settlementChainId) { + if (!sameChainNetworkFeeDetails) { + return "..."; + } + + return ( + + ); + } + + if (networkFee === undefined) { + return "..."; + } + + return ( + + ); + }, [ + networkFee, + depositViewViemChain, + depositViewChain, + settlementChainId, + networkFeeUsd, + sameChainNetworkFeeDetails, + ]); + return (
@@ -941,50 +1147,9 @@ export const DepositView = () => { {depositViewTokenAddress && (
- Estimated Time} - value={ - inputAmount === undefined || inputAmount === 0n ? ( - "..." - ) : isTestnet ? ( - 1m 40s - ) : ( - 30s - ) - } - /> - Network Fee} - value={ - networkFee !== undefined && depositViewViemChain ? ( - - ) : ( - "..." - ) - } - /> - Deposit Fee} - value={ - protocolFeeAmount !== undefined && selectedTokenSourceChainDecimals !== undefined ? ( - - ) : ( - "..." - ) - } - /> + Estimated Time} value={estimatedTimeValue} /> + Network Fee} value={networkFeeValue} /> + Deposit Fee} value={depositFeeValue} /> GMX Balance} value={} diff --git a/src/components/GmxAccountModal/SelectAssetToDepositView.tsx b/src/components/GmxAccountModal/SelectAssetToDepositView.tsx index 6986d13643..e863dfe36c 100644 --- a/src/components/GmxAccountModal/SelectAssetToDepositView.tsx +++ b/src/components/GmxAccountModal/SelectAssetToDepositView.tsx @@ -1,9 +1,9 @@ -import { Trans } from "@lingui/macro"; +import { t, Trans } from "@lingui/macro"; import cx from "classnames"; import { useMemo, useState } from "react"; import { useAccount } from "wagmi"; -import { getChainName } from "config/chains"; +import { AnyChainId, getChainName, SourceChainId } from "config/chains"; import { getChainIcon } from "config/icons"; import { MULTI_CHAIN_TOKEN_MAPPING } from "config/multichain"; import { @@ -12,6 +12,8 @@ import { useGmxAccountModalOpen, } from "context/GmxAccountContext/hooks"; import { TokenChainData } from "domain/multichain/types"; +import { useTokensDataRequest } from "domain/synthetics/tokens"; +import { TokenData, TokensData } from "domain/tokens"; import { useChainId } from "lib/chains"; import { formatUsd } from "lib/numbers"; import { EMPTY_OBJECT } from "lib/objects"; @@ -67,8 +69,111 @@ type DisplayTokenChainData = TokenChainData & { sourceChainBalanceUsd: bigint; }; +function sortByBalanceUsd(a: DisplayTokenChainData, b: DisplayTokenChainData): number { + if (a.sourceChainBalanceUsd === b.sourceChainBalanceUsd) { + return 0; + } + return a.sourceChainBalanceUsd > b.sourceChainBalanceUsd ? -1 : 1; +} + +function getMultichainTokens({ + tokenChainDataArray, + searchQuery, + selectedNetwork, +}: { + tokenChainDataArray: TokenChainData[]; + searchQuery: string; + selectedNetwork: number | "all"; +}): DisplayTokenChainData[] { + return tokenChainDataArray + .filter((tokenChainData) => { + const matchesSearch = tokenChainData.symbol.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesNetwork = selectedNetwork === "all" || tokenChainData.sourceChainId === selectedNetwork; + return matchesSearch && matchesNetwork; + }) + .map((tokenChainData): DisplayTokenChainData => { + let balanceUsd = 0n; + + if (tokenChainData.sourceChainPrices) { + balanceUsd = + convertToUsd( + tokenChainData.sourceChainBalance, + tokenChainData.sourceChainDecimals, + getMidPrice(tokenChainData.sourceChainPrices) + ) ?? 0n; + } + + return { + ...tokenChainData, + sourceChainBalanceUsd: balanceUsd, + }; + }) + .sort(sortByBalanceUsd); +} + +function getSettlementChainTokens({ + settlementChainTokensData, + searchQuery, + selectedNetwork, + chainId, +}: { + settlementChainTokensData: TokensData | undefined; + searchQuery: string; + selectedNetwork: number | "all"; + chainId: AnyChainId; +}): DisplayTokenChainData[] { + return Object.values(settlementChainTokensData || (EMPTY_OBJECT as TokensData)) + .filter((token: TokenData) => { + const matchesSearch = token.symbol.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesNetwork = selectedNetwork === "all" || chainId === selectedNetwork; + const hasBalance = token.walletBalance !== undefined && token.walletBalance > 0n; + return matchesSearch && matchesNetwork && hasBalance; + }) + .map((token: TokenData): DisplayTokenChainData => { + const balanceUsd = convertToUsd(token.walletBalance, token.decimals, getMidPrice(token.prices)) ?? 0n; + return { + ...token, + sourceChainId: chainId as SourceChainId, + sourceChainDecimals: token.decimals, + sourceChainPrices: token.prices, + sourceChainBalance: token.walletBalance, + sourceChainBalanceUsd: balanceUsd, + }; + }) + .sort(sortByBalanceUsd); +} + +function getFilteredTokens({ + tokenChainDataArray, + settlementChainTokensData, + searchQuery, + selectedNetwork, + chainId, +}: { + chainId: AnyChainId; + selectedNetwork: number | "all"; + tokenChainDataArray: TokenChainData[]; + settlementChainTokensData: TokensData | undefined; + searchQuery: string; +}): DisplayTokenChainData[] { + const multichainTokens = getMultichainTokens({ + tokenChainDataArray, + searchQuery, + selectedNetwork, + }); + + const settlementChainTokens = getSettlementChainTokens({ + settlementChainTokensData, + searchQuery, + selectedNetwork, + chainId, + }); + + return multichainTokens.concat(settlementChainTokens); +} + export const SelectAssetToDepositView = () => { - const { chainId } = useChainId(); + const { chainId, srcChainId } = useChainId(); const { address: account } = useAccount(); const [, setIsVisibleOrView] = useGmxAccountModalOpen(); @@ -79,50 +184,35 @@ export const SelectAssetToDepositView = () => { const [searchQuery, setSearchQuery] = useState(""); const { tokenChainDataArray } = useMultichainTradeTokensRequest(chainId, account); + const { tokensData: settlementChainTokensData } = useTokensDataRequest(chainId, srcChainId); - const NETWORKS_FILTER = useMemo(() => { - const wildCard = { id: "all" as const, name: "All Networks" }; + const networksFilter = useMemo(() => { + const wildCard = { id: "all" as const, name: t`All Networks` }; - const chainFilters = Object.keys(MULTI_CHAIN_TOKEN_MAPPING[chainId] ?? EMPTY_OBJECT).map((sourceChainId) => ({ - id: parseInt(sourceChainId), - name: getChainName(parseInt(sourceChainId)), - })); + const chainFilters = Object.keys(MULTI_CHAIN_TOKEN_MAPPING[chainId] ?? EMPTY_OBJECT) + .map((sourceChainId) => ({ + id: parseInt(sourceChainId), + name: getChainName(parseInt(sourceChainId)), + })) + .concat({ + id: chainId, + name: getChainName(chainId), + }); return [wildCard, ...chainFilters]; }, [chainId]); - const filteredBalances: DisplayTokenChainData[] = useMemo(() => { - return tokenChainDataArray - .filter((tokenChainData) => { - const matchesSearch = tokenChainData.symbol.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesNetwork = selectedNetwork === "all" || tokenChainData.sourceChainId === selectedNetwork; - return matchesSearch && matchesNetwork; - }) - .map((tokenChainData) => { - let balanceUsd = 0n; - - if (tokenChainData.sourceChainPrices) { - balanceUsd = - convertToUsd( - tokenChainData.sourceChainBalance, - tokenChainData.sourceChainDecimals, - getMidPrice(tokenChainData.sourceChainPrices) - ) ?? 0n; - } - - return { - ...tokenChainData, - sourceChainBalanceUsd: balanceUsd, - }; - }) - .sort((a, b) => { - if (a.sourceChainBalanceUsd === b.sourceChainBalanceUsd) { - return 0; - } - - return a.sourceChainBalanceUsd > b.sourceChainBalanceUsd ? -1 : 1; - }); - }, [tokenChainDataArray, searchQuery, selectedNetwork]); + const filteredTokens: DisplayTokenChainData[] = useMemo( + () => + getFilteredTokens({ + tokenChainDataArray, + settlementChainTokensData, + searchQuery, + selectedNetwork, + chainId, + }), + [tokenChainDataArray, settlementChainTokensData, searchQuery, selectedNetwork, chainId] + ); return (
@@ -133,7 +223,7 @@ export const SelectAssetToDepositView = () => {
- {NETWORKS_FILTER.map((network) => ( + {networksFilter.map((network) => (
- {filteredBalances.map((tokenChainData) => ( + {filteredTokens.map((tokenChainData) => ( { }} /> ))} - {filteredBalances.length === 0 && ( + {filteredTokens.length === 0 && (
{selectedNetwork === "all" ? ( No assets are available for deposit diff --git a/src/components/GmxAccountModal/TransferDetailsView.tsx b/src/components/GmxAccountModal/TransferDetailsView.tsx index 501375d51c..70a70e751d 100644 --- a/src/components/GmxAccountModal/TransferDetailsView.tsx +++ b/src/components/GmxAccountModal/TransferDetailsView.tsx @@ -65,17 +65,33 @@ export const TransferDetailsView = () => { } }, [selectedTransfer]); - const sourceChainName = selectedTransfer ? getChainName(selectedTransfer.sourceChainId) : undefined; - const token = selectedTransfer ? getToken(chainId, selectedTransfer.token) : undefined; + let sourceChainId: number | undefined; + let initialChainId: number | undefined; + if (selectedTransfer) { + if (selectedTransfer.sourceChainId === 0) { + sourceChainId = selectedTransfer.settlementChainId; + initialChainId = selectedTransfer.settlementChainId; + } else { + sourceChainId = selectedTransfer.sourceChainId; + if (selectedTransfer.operation === "deposit") { + initialChainId = selectedTransfer.sourceChainId; + } else { + initialChainId = selectedTransfer.settlementChainId; + } + } + } + + const sourceChainName = sourceChainId ? getChainName(sourceChainId) : undefined; + const handleRepeatTransaction = () => { - if (!selectedTransfer || !token) { + if (!selectedTransfer || !token || !sourceChainId) { return; } if (selectedTransfer.operation === "deposit") { - setGmxAccountDepositViewChain(selectedTransfer.sourceChainId as SourceChainId); + setGmxAccountDepositViewChain(sourceChainId as SourceChainId); setGmxAccountDepositViewTokenAddress(selectedTransfer.token); setGmxAccountDepositViewTokenInputValue(formatAmountFree(selectedTransfer.sentAmount, token.decimals)); setGmxAccountModalOpen("deposit"); @@ -83,7 +99,7 @@ export const TransferDetailsView = () => { } if (selectedTransfer.operation === "withdrawal") { - setGmxAccountWithdrawalViewChain(selectedTransfer.sourceChainId as SourceChainId); + setGmxAccountWithdrawalViewChain(sourceChainId as SourceChainId); setGmxAccountWithdrawalViewTokenAddress(convertTokenAddress(chainId, selectedTransfer.token, "wrapped")); setGmxAccountWithdrawalViewTokenInputValue(formatAmountFree(selectedTransfer.sentAmount, token.decimals)); setGmxAccountModalOpen("withdraw"); @@ -92,15 +108,15 @@ export const TransferDetailsView = () => { const addressLabel = selectedTransfer ? shortenAddressOrEns(selectedTransfer.account, 13) : undefined; - const networkLabel = selectedTransfer && ( + const networkLabel = selectedTransfer && sourceChainId && ( {" "} - {getChainName(selectedTransfer.sourceChainId)} + {getChainName(sourceChainId)} ); @@ -125,23 +141,13 @@ export const TransferDetailsView = () => { } /> - {selectedTransfer?.sentTxn && ( + {selectedTransfer?.sentTxn && initialChainId && (
{shortenAddressOrEns(selectedTransfer.sentTxn, 13)} @@ -151,7 +157,7 @@ export const TransferDetailsView = () => { } /> )} - {selectedTransfer?.sentTxn && ( + {selectedTransfer?.sentTxn && selectedTransfer.sourceChainId !== 0 && ( Testnet LayerZero Scan : LayerZero Scan} value={ diff --git a/src/components/GmxAccountModal/WithdrawalView.tsx b/src/components/GmxAccountModal/WithdrawalView.tsx index b1f7ea0426..e643434562 100644 --- a/src/components/GmxAccountModal/WithdrawalView.tsx +++ b/src/components/GmxAccountModal/WithdrawalView.tsx @@ -1,10 +1,11 @@ +import { addressToBytes32 } from "@layerzerolabs/lz-v2-utilities"; import { t, Trans } from "@lingui/macro"; import cx from "classnames"; import { type Provider } from "ethers"; import { useCallback, useEffect, useMemo, useState } from "react"; import Skeleton from "react-loading-skeleton"; import { useHistory } from "react-router-dom"; -import { Address, encodeAbiParameters, encodeFunctionData, zeroAddress } from "viem"; +import { Address, encodeAbiParameters, encodeEventTopics, toHex, zeroAddress } from "viem"; import { useAccount } from "wagmi"; import { @@ -18,7 +19,6 @@ import { import { CHAIN_ID_TO_NETWORK_ICON } from "config/icons"; import { CHAIN_ID_PREFERRED_DEPOSIT_TOKEN, - FAKE_INPUT_AMOUNT_MAP, getLayerZeroEndpointId, getMappedTokenId, getMultichainTokenId, @@ -47,6 +47,11 @@ import { useSelector } from "context/SyntheticsStateContext/utils"; import { useArbitraryError, useArbitraryRelayParamsAndPayload } from "domain/multichain/arbitraryRelayParams"; import { fallbackCustomError } from "domain/multichain/fallbackCustomError"; import { getMultichainTransferSendParams } from "domain/multichain/getSendParams"; +import { isStringEqualInsensitive, matchLogRequest } from "domain/multichain/progress/LongCrossChainTask"; +import { + estimateSameChainWithdrawalGas, + sendSameChainWithdrawalTxn, +} from "domain/multichain/sendSameChainWithdrawalTxn"; import { toastCustomOrStargateError } from "domain/multichain/toastCustomOrStargateError"; import { BridgeOutParams, SendParam } from "domain/multichain/types"; import { useGmxAccountFundingHistory } from "domain/multichain/useGmxAccountFundingHistory"; @@ -56,9 +61,10 @@ import { useQuoteOftLimits } from "domain/multichain/useQuoteOftLimits"; import { useQuoteSendNativeFee } from "domain/multichain/useQuoteSend"; import { callRelayTransaction } from "domain/synthetics/express/callRelayTransaction"; import { buildAndSignBridgeOutTxn } from "domain/synthetics/express/expressOrderUtils"; -import { ExpressTransactionBuilder, RawRelayParamsPayload } from "domain/synthetics/express/types"; -import { useTokensDataRequest } from "domain/synthetics/tokens"; -import { convertToUsd, TokenData } from "domain/tokens"; +import { ExpressTransactionBuilder, ExpressTxnParams, RawRelayParamsPayload } from "domain/synthetics/express/types"; +import { useGasPrice } from "domain/synthetics/fees/useGasPrice"; +import { TokensData, useTokensDataRequest } from "domain/synthetics/tokens"; +import { convertToUsd, sortTokenDataByBalance, TokenData } from "domain/tokens"; import { useChainId } from "lib/chains"; import { useLeadingDebounce } from "lib/debounce/useLeadingDebounde"; import { helperToast } from "lib/helperToast"; @@ -71,13 +77,15 @@ import { sendTxnSentMetric, sendTxnValidationErrorMetric, } from "lib/metrics"; -import { bigintToNumber, expandDecimals, formatAmountFree, formatUsd, parseValue, USD_DECIMALS } from "lib/numbers"; +import { expandDecimals, formatAmountFree, formatUsd, parseValue, USD_DECIMALS } from "lib/numbers"; import { EMPTY_ARRAY, getByKey } from "lib/objects"; import { useJsonRpcProvider } from "lib/rpc"; -import { sendWalletTransaction } from "lib/transactions"; +import { TxnEventName } from "lib/transactions"; import { ExpressTxnData, sendExpressTransaction } from "lib/transactions/sendExpressTransaction"; import { useHasOutdatedUi } from "lib/useHasOutdatedUi"; +import { AsyncResult, useThrottledAsync } from "lib/useThrottledAsync"; import { WalletSigner } from "lib/wallets"; +import { getPublicClientWithRpc } from "lib/wallets/rainbowKitConfig"; import { abis } from "sdk/abis"; import { getContract } from "sdk/configs/contracts"; import { getGasPaymentTokens } from "sdk/configs/express"; @@ -91,6 +99,7 @@ import { Amount } from "components/Amount/Amount"; import { AmountWithUsdBalance } from "components/AmountWithUsd/AmountWithUsd"; import Button from "components/Button/Button"; import { DropdownSelector } from "components/DropdownSelector/DropdownSelector"; +import { calculateNetworkFeeDetails } from "components/GmxAccountModal/calculateNetworkFeeDetails"; import { useAvailableToTradeAssetMultichain, useGmxAccountWithdrawNetworks } from "components/GmxAccountModal/hooks"; import NumberInput from "components/NumberInput/NumberInput"; import TokenIcon from "components/TokenIcon/TokenIcon"; @@ -151,22 +160,360 @@ const useIsFirstWithdrawal = () => { return isFirstWithdrawal; }; +function getFilteredNetworks({ + networks, + unwrappedSelectedTokenAddress, + chainId, +}: { + networks: { id: number; name: string }[]; + unwrappedSelectedTokenAddress: string | undefined; + chainId: number; +}): { id: number; name: string; disabled?: boolean }[] { + if (!unwrappedSelectedTokenAddress) { + return networks; + } + + return networks + .map((network): { id: number; name: string; disabled?: boolean } => { + if (network.id === chainId) { + return network; + } + + const mappedTokenId = getMappedTokenId( + chainId as SettlementChainId, + unwrappedSelectedTokenAddress, + network.id as SourceChainId + ); + const isTransferable = mappedTokenId !== undefined; + return { + ...network, + disabled: !isTransferable, + }; + }) + .sort((a, b) => { + return a.disabled ? 1 : b.disabled ? -1 : 0; + }); +} + +function getMultichainWithdrawalTokens({ + chainId, + tokensData, +}: { + chainId: ContractsChainId; + tokensData: TokensData; +}): TokenData[] { + return ( + MULTI_CHAIN_WITHDRAWAL_TRADE_TOKENS[chainId as SettlementChainId] + ?.map((tokenAddress) => tokensData[tokenAddress] as TokenData | undefined) + .filter((token): token is TokenData => { + return token !== undefined && token.address !== zeroAddress; + }) + .sort(sortTokenDataByBalance) || EMPTY_ARRAY + ); +} + +function getSameChainWithdrawalTokens({ + chainId, + tokensData, +}: { + chainId: ContractsChainId; + tokensData: TokensData; +}): TokenData[] { + return Object.values(tokensData) + .filter((token): token is TokenData => { + return ( + token !== undefined && + token.address !== zeroAddress && + token.gmxAccountBalance !== undefined && + token.gmxAccountBalance > 0n && + !MULTI_CHAIN_WITHDRAWAL_TRADE_TOKENS[chainId]?.includes(token.address) + ); + }) + .sort(sortTokenDataByBalance); +} + +function getWithdrawalTokenOptions({ + chainId, + tokensData, +}: { + chainId: ContractsChainId; + tokensData: TokensData | undefined; +}): TokenData[] { + if (!isSettlementChain(chainId) || !tokensData) { + return EMPTY_ARRAY; + } + + const multichainTokens = getMultichainWithdrawalTokens({ chainId, tokensData }); + const sameChainTokens = getSameChainWithdrawalTokens({ chainId, tokensData }); + + return multichainTokens.concat(sameChainTokens); +} + +function useWithdrawViewTransactions({ + selectedToken, + inputAmountUsd, + bridgeOutParams, + expressTxnParamsAsyncResult, +}: { + selectedToken: TokenData | undefined; + inputAmountUsd: bigint | undefined; + bridgeOutParams: BridgeOutParams | undefined; + expressTxnParamsAsyncResult: AsyncResult; +}) { + const { chainId } = useChainId(); + const { address: account } = useAccount(); + const [withdrawalViewChain] = useGmxAccountWithdrawalViewChain(); + const [selectedTokenAddress] = useGmxAccountWithdrawalViewTokenAddress(); + const isSameChain = withdrawalViewChain === chainId; + const { provider } = useJsonRpcProvider(chainId); + const isFirstWithdrawal = useIsFirstWithdrawal(); + const [, setSettlementChainId] = useGmxAccountSettlementChainId(); + const [, setIsVisibleOrView] = useGmxAccountModalOpen(); + const { + setMultichainSubmittedWithdrawal, + setMultichainWithdrawalSentTxnHash, + setMultichainWithdrawalSentError, + setMultichainFundingPendingId, + } = useSyntheticsEvents(); + const [isSubmitting, setIsSubmitting] = useState(false); + const gasPaymentParams = expressTxnParamsAsyncResult?.data?.gasPaymentParams; + const unwrappedSelectedTokenAddress = + selectedTokenAddress !== undefined ? convertTokenAddress(chainId, selectedTokenAddress, "native") : undefined; + const unwrappedSelectedTokenSymbol = unwrappedSelectedTokenAddress + ? getToken(chainId, unwrappedSelectedTokenAddress).symbol + : undefined; + + const handleSameChainWithdraw = useCallback(async () => { + if (withdrawalViewChain === undefined || selectedToken === undefined || account === undefined) { + return; + } + + if (!bridgeOutParams) { + helperToast.error(t`Missing required parameters`); + return; + } + + setIsSubmitting(true); + + await wrapChainAction(chainId, setSettlementChainId, async (signer) => { + try { + await sendSameChainWithdrawalTxn({ + chainId: chainId as SettlementChainId, + signer, + bridgeOutParams, + callback: (txnEvent) => { + if (txnEvent.event === TxnEventName.Sent) { + helperToast.success("Withdrawal sent", { toastId: "same-chain-gmx-account-withdrawal" }); + setIsVisibleOrView("main"); + setIsSubmitting(false); + + if (txnEvent.data.type === "wallet") { + const txnHash = txnEvent.data.transactionHash; + const mockId = setMultichainSubmittedWithdrawal({ + amount: bridgeOutParams.amount, + settlementChainId: chainId, + sourceChainId: 0, + tokenAddress: selectedToken.address, + sentTxn: txnHash, + }); + + if (!mockId) { + return; + } + + getPublicClientWithRpc(chainId) + .waitForTransactionReceipt({ + hash: txnHash, + }) + .then((receipt) => { + const bridgeOutEvent = receipt.logs.find( + (log) => + isStringEqualInsensitive(log.address, getContract(chainId, "EventEmitter")) && + matchLogRequest( + encodeEventTopics({ + abi: abis.EventEmitter, + eventName: "EventLog1", + args: { + eventNameHash: "MultichainBridgeOut", + topic1: toHex(addressToBytes32(account)), + }, + }), + log.topics + ) + ); + const bridgeOutEventIndex = bridgeOutEvent?.logIndex; + if (bridgeOutEventIndex === undefined) { + return; + } + + const id = `${txnHash.toLowerCase()}:${bridgeOutEventIndex}`; + setMultichainFundingPendingId(mockId, id); + }); + } + } else if (txnEvent.event === TxnEventName.Error) { + helperToast.error(t`Withdrawal failed`, { toastId: "same-chain-gmx-account-withdrawal" }); + setIsSubmitting(false); + } + }, + }); + } catch (error) { + helperToast.error(t`Withdrawal failed`, { toastId: "same-chain-gmx-account-withdrawal" }); + } finally { + setIsSubmitting(false); + } + }); + }, [ + account, + bridgeOutParams, + chainId, + selectedToken, + setIsVisibleOrView, + setMultichainFundingPendingId, + setMultichainSubmittedWithdrawal, + setSettlementChainId, + withdrawalViewChain, + ]); + + const handleMultichainWithdraw = useCallback(async () => { + if (withdrawalViewChain === undefined || selectedToken === undefined || account === undefined) { + return; + } + + const metricData = initMultichainWithdrawalMetricData({ + settlementChain: chainId, + sourceChain: withdrawalViewChain, + assetSymbol: unwrappedSelectedTokenSymbol ?? selectedToken.symbol, + sizeInUsd: inputAmountUsd!, + isFirstWithdrawal, + }); + + sendOrderSubmittedMetric(metricData.metricId); + + if ( + gasPaymentParams === undefined || + bridgeOutParams === undefined || + expressTxnParamsAsyncResult.promise === undefined || + provider === undefined + ) { + helperToast.error(t`Missing required parameters`); + sendTxnValidationErrorMetric(metricData.metricId); + return; + } + + const expressTxnParams = await expressTxnParamsAsyncResult.promise; + + if (expressTxnParams === undefined || !expressTxnParams.gasPaymentValidations.isValid) { + helperToast.error(t`Missing required parameters`); + sendTxnValidationErrorMetric(metricData.metricId); + return; + } + + setIsSubmitting(true); + try { + const relayParamsPayload = expressTxnParams.relayParamsPayload; + + await wrapChainAction(withdrawalViewChain, setSettlementChainId, async (signer) => { + await simulateWithdraw({ + chainId: chainId as SettlementChainId, + relayerFeeTokenAddress: gasPaymentParams.relayerFeeTokenAddress, + relayerFeeAmount: gasPaymentParams.relayerFeeAmount, + relayParamsPayload: relayParamsPayload, + params: bridgeOutParams, + signer, + provider, + srcChainId: withdrawalViewChain, + }); + + sendOrderSimulatedMetric(metricData.metricId); + + const signedTxnData: ExpressTxnData = await buildAndSignBridgeOutTxn({ + chainId: chainId as SettlementChainId, + signer, + account, + relayParamsPayload: relayParamsPayload as RawRelayParamsPayload, + params: bridgeOutParams, + relayerFeeAmount: gasPaymentParams.relayerFeeAmount, + relayerFeeTokenAddress: gasPaymentParams.relayerFeeTokenAddress, + srcChainId: withdrawalViewChain, + }); + + const mockWithdrawalId = setMultichainSubmittedWithdrawal({ + amount: bridgeOutParams.amount, + settlementChainId: chainId, + sourceChainId: withdrawalViewChain, + tokenAddress: unwrappedSelectedTokenAddress ?? selectedToken.address, + }); + + const receipt = await sendExpressTransaction({ + chainId, + txnData: signedTxnData, + isSponsoredCall: expressTxnParams.isSponsoredCall, + }); + + sendOrderTxnSubmittedMetric(metricData.metricId); + + setIsVisibleOrView("main"); + + const txResult = await receipt.wait(); + + if (txResult.status === "success") { + sendTxnSentMetric(metricData.metricId); + if (txResult.transactionHash && mockWithdrawalId) { + setMultichainWithdrawalSentTxnHash(mockWithdrawalId, txResult.transactionHash); + } + } else if (txResult.status === "failed" && mockWithdrawalId) { + setMultichainWithdrawalSentError(mockWithdrawalId); + } + }); + } catch (error) { + const prettyError = toastCustomOrStargateError(chainId, error); + sendTxnErrorMetric(metricData.metricId, prettyError, "unknown"); + } finally { + setIsSubmitting(false); + } + }, [ + account, + bridgeOutParams, + chainId, + expressTxnParamsAsyncResult.promise, + gasPaymentParams, + inputAmountUsd, + isFirstWithdrawal, + provider, + selectedToken, + setIsVisibleOrView, + setMultichainSubmittedWithdrawal, + setMultichainWithdrawalSentError, + setMultichainWithdrawalSentTxnHash, + setSettlementChainId, + unwrappedSelectedTokenAddress, + unwrappedSelectedTokenSymbol, + withdrawalViewChain, + ]); + + const handleWithdraw = useCallback(async () => { + if (isSameChain) { + await handleSameChainWithdraw(); + } else { + await handleMultichainWithdraw(); + } + }, [isSameChain, handleSameChainWithdraw, handleMultichainWithdraw]); + + return { handleWithdraw, isSubmitting }; +} + export const WithdrawalView = () => { const history = useHistory(); const { chainId } = useChainId(); - const [, setSettlementChainId] = useGmxAccountSettlementChainId(); const [withdrawalViewChain, setWithdrawalViewChain] = useGmxAccountWithdrawalViewChain(); + const isSameChain = withdrawalViewChain === chainId; const { address: account } = useAccount(); const [, setDepositViewTokenAddress] = useGmxAccountDepositViewTokenAddress(); const [, setDepositViewTokenInputValue] = useGmxAccountDepositViewTokenInputValue(); const [isVisibleOrView, setIsVisibleOrView] = useGmxAccountModalOpen(); const [inputValue, setInputValue] = useGmxAccountWithdrawalViewTokenInputValue(); const [selectedTokenAddress, setSelectedTokenAddress] = useGmxAccountWithdrawalViewTokenAddress(); - const [isSubmitting, setIsSubmitting] = useState(false); - const isFirstWithdrawal = useIsFirstWithdrawal(); - const { setIsSettingsVisible } = useSettings(); - const { setMultichainSubmittedWithdrawal, setMultichainWithdrawalSentTxnHash, setMultichainWithdrawalSentError } = - useSyntheticsEvents(); + const hasOutdatedUi = useHasOutdatedUi(); const { tokensData } = useTokensDataRequest(chainId, withdrawalViewChain); @@ -184,9 +531,6 @@ export const WithdrawalView = () => { const unwrappedSelectedTokenAddress = selectedTokenAddress !== undefined ? convertTokenAddress(chainId, selectedTokenAddress, "native") : undefined; - const unwrappedSelectedTokenSymbol = unwrappedSelectedTokenAddress - ? getToken(chainId, unwrappedSelectedTokenAddress).symbol - : undefined; const wrappedNativeTokenAddress = getContract(chainId, "NATIVE_TOKEN"); const wrappedNativeToken = getByKey(tokensData, wrappedNativeTokenAddress); @@ -201,40 +545,17 @@ export const WithdrawalView = () => { ? convertToUsd(inputAmount, selectedToken.decimals, selectedToken.prices.maxPrice) : undefined; - const filteredNetworks = useMemo(() => { - if (!unwrappedSelectedTokenAddress) { - return networks; - } - - return networks.filter((network) => { - const mappedTokenId = getMappedTokenId( - chainId as SettlementChainId, + const filteredNetworks = useMemo( + () => + getFilteredNetworks({ + networks, unwrappedSelectedTokenAddress, - network.id as SourceChainId - ); - return mappedTokenId !== undefined; - }); - }, [unwrappedSelectedTokenAddress, networks, chainId]); + chainId, + }), + [unwrappedSelectedTokenAddress, networks, chainId] + ); - const options = useMemo((): TokenData[] => { - if (!isSettlementChain(chainId) || !tokensData) { - return EMPTY_ARRAY; - } - - return ( - MULTI_CHAIN_WITHDRAWAL_TRADE_TOKENS[chainId as SettlementChainId] - ?.map((tokenAddress) => tokensData[tokenAddress] as TokenData | undefined) - .filter((token): token is TokenData => { - return token !== undefined && token.address !== zeroAddress; - }) - .sort((a, b) => { - const aFloat = bigintToNumber(a.gmxAccountBalance ?? 0n, a.decimals); - const bFloat = bigintToNumber(b.gmxAccountBalance ?? 0n, b.decimals); - - return bFloat - aFloat; - }) ?? EMPTY_ARRAY - ); - }, [chainId, tokensData]); + const tokenOptions = useMemo(() => getWithdrawalTokenOptions({ chainId, tokensData }), [chainId, tokensData]); const { gmxAccountUsd } = useAvailableToTradeAssetMultichain(); @@ -249,7 +570,13 @@ export const WithdrawalView = () => { }, [selectedToken, inputAmount, inputAmountUsd, gmxAccountUsd]); const sendParamsWithoutSlippage: SendParam | undefined = useMemo(() => { - if (!account || inputAmount === undefined || inputAmount <= 0n || withdrawalViewChain === undefined) { + if ( + isSameChain || + !account || + inputAmount === undefined || + inputAmount <= 0n || + withdrawalViewChain === undefined + ) { return; } @@ -259,7 +586,7 @@ export const WithdrawalView = () => { amountLD: inputAmount, isToGmx: false, }); - }, [account, inputAmount, withdrawalViewChain]); + }, [isSameChain, account, inputAmount, withdrawalViewChain]); const quoteOft = useQuoteOft({ sendParams: sendParamsWithoutSlippage, @@ -301,11 +628,18 @@ export const WithdrawalView = () => { }); const baseSendParams = useMemo(() => { - if (!withdrawalViewChain || !account || !unwrappedSelectedTokenSymbol) { + if (isSameChain || !withdrawalViewChain || !account) { + return; + } + + const prices = getByKey(tokensData, unwrappedSelectedTokenAddress)?.prices; + const decimals = selectedTokenSettlementChainTokenId?.decimals; + + if (!prices || decimals === undefined) { return; } - const fakeInputAmount = FAKE_INPUT_AMOUNT_MAP[unwrappedSelectedTokenSymbol]; + const fakeInputAmount = convertToTokenAmount(expandDecimals(1, USD_DECIMALS), decimals, getMidPrice(prices)); if (fakeInputAmount === undefined) { return; @@ -318,15 +652,27 @@ export const WithdrawalView = () => { isToGmx: false, srcChainId: chainId, }); - }, [account, chainId, unwrappedSelectedTokenSymbol, withdrawalViewChain]); + }, [ + account, + chainId, + isSameChain, + selectedTokenSettlementChainTokenId?.decimals, + tokensData, + unwrappedSelectedTokenAddress, + withdrawalViewChain, + ]); const isMaxButtonDisabled = useMemo(() => { + if (isSameChain) { + return false; + } + if (!baseSendParams) { return true; } return false; - }, [baseSendParams]); + }, [baseSendParams, isSameChain]); const baseNativeFee = useQuoteSendNativeFee({ sendParams: baseSendParams, @@ -359,6 +705,16 @@ export const WithdrawalView = () => { return; } + if (isSameChain) { + return { + token: selectedTokenAddress as Address, + amount: inputAmount, + minAmountOut: inputAmount, // Not actually used in smart contracts + data: "0x", + provider: zeroAddress, + }; + } + const dstEid = getLayerZeroEndpointId(withdrawalViewChain); const stargateAddress = getStargatePoolAddress(chainId, unwrappedSelectedTokenAddress); @@ -381,10 +737,45 @@ export const WithdrawalView = () => { ), provider: stargateAddress, }; - }, [withdrawalViewChain, selectedTokenAddress, unwrappedSelectedTokenAddress, inputAmount, chainId]); + }, [withdrawalViewChain, selectedTokenAddress, unwrappedSelectedTokenAddress, inputAmount, isSameChain, chainId]); + + const gasPrice = useGasPrice(chainId); + + const sameChainNetworkFeeAsyncResult = useThrottledAsync( + async ({ params }) => { + const client = getPublicClientWithRpc(params.chainId); + + return estimateSameChainWithdrawalGas({ + chainId: params.chainId as SettlementChainId, + client, + bridgeOutParams: params.bridgeOutParams, + account: params.account, + }); + }, + { + params: + isSameChain && account && bridgeOutParams && chainId + ? { + account, + bridgeOutParams, + chainId, + } + : undefined, + } + ); + + const sameChainNetworkFeeDetails = useMemo( + () => + calculateNetworkFeeDetails({ + gasLimit: sameChainNetworkFeeAsyncResult.data, + gasPrice, + tokensData, + }), + [sameChainNetworkFeeAsyncResult.data, gasPrice, tokensData] + ); const expressTransactionBuilder: ExpressTransactionBuilder | undefined = useMemo(() => { - if (account === undefined || bridgeOutParams === undefined || withdrawalViewChain === undefined) { + if (account === undefined || bridgeOutParams === undefined || withdrawalViewChain === undefined || isSameChain) { return; } @@ -403,7 +794,7 @@ export const WithdrawalView = () => { }); return expressTransactionBuilder; - }, [account, bridgeOutParams, chainId, withdrawalViewChain]); + }, [account, bridgeOutParams, chainId, isSameChain, withdrawalViewChain]); const expressTxnParamsAsyncResult = useArbitraryRelayParamsAndPayload({ expressTransactionBuilder, @@ -421,7 +812,6 @@ export const WithdrawalView = () => { const relayFeeAmount = expressTxnParamsAsyncResult?.data?.gasPaymentParams.relayerFeeAmount; const gasPaymentTokenAmount = expressTxnParamsAsyncResult?.data?.gasPaymentParams.gasPaymentTokenAmount; - const gasPaymentParams = expressTxnParamsAsyncResult?.data?.gasPaymentParams; const { networkFeeUsd, wntFee, wntFeeUsd, networkFeeInGasPaymentToken } = useMemo(() => { if ( @@ -538,7 +928,7 @@ export const WithdrawalView = () => { (tokenAddress: string) => { setSelectedTokenAddress(tokenAddress); - if (withdrawalViewChain !== undefined) { + if (withdrawalViewChain !== undefined && !isSameChain) { const unwrappedTokenAddress = convertTokenAddress(chainId, tokenAddress, "native"); const tokenId = getMappedTokenId(chainId as SettlementChainId, unwrappedTokenAddress, withdrawalViewChain); if (tokenId === undefined) { @@ -563,138 +953,15 @@ export const WithdrawalView = () => { } } }, - [chainId, setSelectedTokenAddress, setWithdrawalViewChain, withdrawalViewChain] + [chainId, isSameChain, setSelectedTokenAddress, setWithdrawalViewChain, withdrawalViewChain] ); - const handleWithdraw = async () => { - if (withdrawalViewChain === undefined || selectedToken === undefined || account === undefined) { - return; - } - - if ((withdrawalViewChain as SourceChainId | ContractsChainId | undefined) === chainId) { - if (!bridgeOutParams) { - helperToast.error(t`Missing required parameters`); - - return; - } - - setIsSubmitting(true); - - await wrapChainAction(chainId, setSettlementChainId, async (signer) => { - try { - await sendWalletTransaction({ - chainId, - to: getContract(chainId, "MultichainTransferRouter"), - signer, - callData: encodeFunctionData({ - abi: abis.MultichainTransferRouter, - functionName: "transferOut", - args: [bridgeOutParams], - }), - }); - } catch (error) { - alert("Failed to send withdrawal. Retry"); - } finally { - setIsSubmitting(false); - } - }); - - return; - } - - const metricData = initMultichainWithdrawalMetricData({ - settlementChain: chainId, - sourceChain: withdrawalViewChain, - assetSymbol: unwrappedSelectedTokenSymbol ?? selectedToken.symbol, - sizeInUsd: inputAmountUsd!, - isFirstWithdrawal, - }); - - sendOrderSubmittedMetric(metricData.metricId); - - if ( - gasPaymentParams === undefined || - bridgeOutParams === undefined || - expressTxnParamsAsyncResult.promise === undefined || - provider === undefined - ) { - helperToast.error(t`Missing required parameters`); - sendTxnValidationErrorMetric(metricData.metricId); - return; - } - - const expressTxnParams = await expressTxnParamsAsyncResult.promise; - - if (expressTxnParams === undefined || !expressTxnParams.gasPaymentValidations.isValid) { - helperToast.error(t`Missing required parameters`); - sendTxnValidationErrorMetric(metricData.metricId); - return; - } - - setIsSubmitting(true); - try { - const relayParamsPayload = expressTxnParams.relayParamsPayload; - - await wrapChainAction(withdrawalViewChain, setSettlementChainId, async (signer) => { - await simulateWithdraw({ - chainId: chainId as SettlementChainId, - relayerFeeTokenAddress: gasPaymentParams.relayerFeeTokenAddress, - relayerFeeAmount: gasPaymentParams.relayerFeeAmount, - relayParamsPayload: relayParamsPayload, - params: bridgeOutParams, - signer, - provider, - srcChainId: withdrawalViewChain, - }); - - sendOrderSimulatedMetric(metricData.metricId); - - const signedTxnData: ExpressTxnData = await buildAndSignBridgeOutTxn({ - chainId: chainId as SettlementChainId, - signer, - account, - relayParamsPayload: relayParamsPayload as RawRelayParamsPayload, - params: bridgeOutParams, - relayerFeeAmount: gasPaymentParams.relayerFeeAmount, - relayerFeeTokenAddress: gasPaymentParams.relayerFeeTokenAddress, - srcChainId: withdrawalViewChain, - }); - - const mockWithdrawalId = setMultichainSubmittedWithdrawal({ - amount: bridgeOutParams.amount, - settlementChainId: chainId, - sourceChainId: withdrawalViewChain, - tokenAddress: unwrappedSelectedTokenAddress ?? selectedToken.address, - }); - - const receipt = await sendExpressTransaction({ - chainId, - txnData: signedTxnData, - isSponsoredCall: expressTxnParams.isSponsoredCall, - }); - - sendOrderTxnSubmittedMetric(metricData.metricId); - - setIsVisibleOrView("main"); - - const txResult = await receipt.wait(); - - if (txResult.status === "success") { - sendTxnSentMetric(metricData.metricId); - if (txResult.transactionHash && mockWithdrawalId) { - setMultichainWithdrawalSentTxnHash(mockWithdrawalId, txResult.transactionHash); - } - } else if (txResult.status === "failed" && mockWithdrawalId) { - setMultichainWithdrawalSentError(mockWithdrawalId); - } - }); - } catch (error) { - const prettyError = toastCustomOrStargateError(chainId, error); - sendTxnErrorMetric(metricData.metricId, prettyError, "unknown"); - } finally { - setIsSubmitting(false); - } - }; + const { handleWithdraw, isSubmitting } = useWithdrawViewTransactions({ + selectedToken, + inputAmountUsd, + bridgeOutParams, + expressTxnParamsAsyncResult, + }); const handleMaxButtonClick = useCallback(async () => { if ( @@ -942,6 +1209,77 @@ export const WithdrawalView = () => { const isTestnet = isTestnetChain(chainId); + const estimatedTimeValue = useMemo(() => { + if (isSameChain) { + return Instant; + } + + if (inputAmount === undefined || inputAmount === 0n) { + return "..."; + } + + return isTestnet ? 1m 40s : 20s; + }, [isSameChain, inputAmount, isTestnet]); + + const networkFeeValue = useMemo(() => { + if (isSameChain) { + if (!sameChainNetworkFeeDetails) { + return "..."; + } + + return ( + + ); + } + + if (networkFeeUsd === undefined || gasPaymentToken === undefined) { + return "..."; + } + + return ( + + ); + }, [gasPaymentToken, isSameChain, networkFeeInGasPaymentToken, networkFeeUsd, sameChainNetworkFeeDetails]); + + const withdrawFeeValue = useMemo(() => { + if (isSameChain) { + return No fee; + } + + if (protocolFeeUsd === undefined || selectedTokenSettlementChainTokenId === undefined) { + return "..."; + } + + return ( + + ); + }, [isSameChain, protocolFeeUsd, selectedTokenSettlementChainTokenId, protocolFeeAmount, selectedToken?.symbol]); + + const networkItemDisabledMessage = useCallback( + (option: { id: number; name: string; disabled?: boolean | string }) => { + return t`Withdrawing ${selectedToken?.symbol} to ${option.name} is not currently supported`; + }, + [selectedToken?.symbol] + ); + return (
@@ -961,7 +1299,7 @@ export const WithdrawalView = () => {
) : undefined } - options={options} + options={tokenOptions} item={WithdrawAssetItem} itemKey={withdrawAssetItemKey} /> @@ -1005,6 +1343,8 @@ export const WithdrawalView = () => { options={filteredNetworks} item={NetworkItem} itemKey={networkItemKey} + itemDisabled={networkItemDisabled} + itemDisabledMessage={networkItemDisabledMessage} />
@@ -1076,24 +1416,23 @@ export const WithdrawalView = () => { {shouldShowMinRecommendedAmount && (
- - You're withdrawing {selectedToken?.symbol}, your gas token. Gas is required for this withdrawal, so - please keep at least{" "} - {formatUsd(gasTokenBuffer, { displayDecimals: 0 })} in{" "} - {selectedToken?.symbol} or switch your gas token in{" "} - { - setIsSettingsVisible(true); - setTimeout(() => { - setIsVisibleOrView(false); - }, 200); - }} - > - settings - - . - + {isSameChain ? ( + + You're withdrawing {selectedToken?.symbol}, your gas token. Gas is required for express trading, so + please keep at least{" "} + {formatUsd(gasTokenBuffer, { displayDecimals: 0 })} in{" "} + {selectedToken?.symbol} or switch your gas token in{" "} + settings. + + ) : ( + + You're withdrawing {selectedToken?.symbol}, your gas token. Gas is required for this withdrawal, so + please keep at least{" "} + {formatUsd(gasTokenBuffer, { displayDecimals: 0 })} in{" "} + {selectedToken?.symbol} or switch your gas token in{" "} + settings. + + )}
)} @@ -1167,48 +1506,10 @@ export const WithdrawalView = () => { Estimated Time} valueClassName="numbers" - value={ - inputAmount === undefined || inputAmount === 0n ? ( - "..." - ) : isTestnet ? ( - 1m 40s - ) : ( - 20s - ) - } - /> - Network Fee} - value={ - networkFeeUsd !== undefined && gasPaymentToken ? ( - - ) : ( - "..." - ) - } - /> - Withdraw Fee} - value={ - protocolFeeUsd !== undefined && selectedTokenSettlementChainTokenId ? ( - - ) : ( - "..." - ) - } + value={estimatedTimeValue} /> + Network Fee} value={networkFeeValue} /> + Withdraw Fee} value={withdrawFeeValue} /> GMX Balance} value={} @@ -1232,7 +1533,11 @@ function networkItemKey(option: { id: number; name: string }) { return option.id; } -function NetworkItem({ option }: { option: { id: number; name: string } }) { +function networkItemDisabled(option: { id: number; name: string; disabled?: boolean | string }): boolean { + return !!option.disabled; +} + +function NetworkItem({ option }: { option: { id: number; name: string; disabled?: boolean } }) { return (
@@ -1317,3 +1622,22 @@ async function simulateWithdraw({ }); }, "simulation"); } + +function WarningSettingsButton({ children }: { children: React.ReactNode }) { + const { setIsSettingsVisible } = useSettings(); + const [, setIsVisibleOrView] = useGmxAccountModalOpen(); + + return ( + { + setIsSettingsVisible(true); + setTimeout(() => { + setIsVisibleOrView(false); + }, 200); + }} + > + {children} + + ); +} diff --git a/src/components/GmxAccountModal/calculateNetworkFeeDetails.ts b/src/components/GmxAccountModal/calculateNetworkFeeDetails.ts new file mode 100644 index 0000000000..b0c3592802 --- /dev/null +++ b/src/components/GmxAccountModal/calculateNetworkFeeDetails.ts @@ -0,0 +1,37 @@ +import { zeroAddress } from "viem"; + +import { TokensData } from "domain/synthetics/tokens"; +import { convertToUsd } from "domain/tokens"; +import { getByKey } from "lib/objects"; +import { getMidPrice } from "sdk/utils/tokens"; + +export function calculateNetworkFeeDetails({ + gasLimit, + gasPrice, + tokensData, +}: { + gasLimit: bigint | undefined; + gasPrice: bigint | undefined; + tokensData: TokensData | undefined; +}): { amount: bigint; usd: bigint; decimals: number; symbol: string } | undefined { + if (gasLimit === undefined || gasPrice === undefined) { + return undefined; + } + + const nativeTokenAmount = gasLimit * gasPrice; + const nativeTokenData = getByKey(tokensData, zeroAddress); + + if (nativeTokenData === undefined) { + return undefined; + } + + const usd: bigint = + convertToUsd(nativeTokenAmount, nativeTokenData.decimals, getMidPrice(nativeTokenData.prices)) ?? 0n; + + return { + amount: nativeTokenAmount, + usd, + decimals: nativeTokenData.decimals, + symbol: nativeTokenData.symbol, + }; +} diff --git a/src/components/GmxAccountModal/hooks.ts b/src/components/GmxAccountModal/hooks.ts index 9a0a5c3ea5..09e68035c3 100644 --- a/src/components/GmxAccountModal/hooks.ts +++ b/src/components/GmxAccountModal/hooks.ts @@ -452,13 +452,18 @@ export function useGmxAccountWithdrawNetworks() { const sourceChains = Object.keys(MULTI_CHAIN_TOKEN_MAPPING[chainId] || {}).map(Number); const networks = useMemo(() => { - return sourceChains.map((sourceChainId) => { - return { - id: sourceChainId, - name: getChainName(sourceChainId), - }; - }); - }, [sourceChains]); + return sourceChains + .map((sourceChainId) => { + return { + id: sourceChainId, + name: getChainName(sourceChainId), + }; + }) + .concat({ + id: chainId, + name: getChainName(chainId), + }); + }, [chainId, sourceChains]); return networks; } diff --git a/src/components/NetworkDropdown/NetworkDropdown.tsx b/src/components/NetworkDropdown/NetworkDropdown.tsx index 2312c6b130..b881aa14d7 100644 --- a/src/components/NetworkDropdown/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown/NetworkDropdown.tsx @@ -5,7 +5,7 @@ import partition from "lodash/partition"; import { useAccount } from "wagmi"; import { getChainIcon } from "config/icons"; -import { isSettlementChain, isSourceChain } from "config/multichain"; +import { isSettlementChain, isSourceChainForAnySettlementChain } from "config/multichain"; import type { NetworkOption } from "config/networkOptions"; import { switchNetwork } from "lib/wallets"; import { useIsNonEoaAccountOnAnyChain } from "lib/wallets/useAccountType"; @@ -78,14 +78,14 @@ function NetworkMenuItems({ networkOptions, chainId }: { networkOptions: Network const [disabledNetworks, enabledNetworks] = partition( networkOptions, - (network) => isSourceChain(network.value) && isNonEoaAccountOnAnyChain + (network) => isSourceChainForAnySettlementChain(network.value) && isNonEoaAccountOnAnyChain ); const walletAndGmxAccountNetworks = enabledNetworks.filter( - (network) => isSourceChain(network.value) || isValidVisualSettlementChain(network.value) + (network) => isSourceChainForAnySettlementChain(network.value) || isValidVisualSettlementChain(network.value) ); const walletOnlyNetworks = enabledNetworks.filter( - (network) => !(isSourceChain(network.value) || isValidVisualSettlementChain(network.value)) + (network) => !(isSourceChainForAnySettlementChain(network.value) || isValidVisualSettlementChain(network.value)) ); return ( diff --git a/src/components/SwitchToSettlementChain/utils.ts b/src/components/SwitchToSettlementChain/utils.ts index 5961330b01..00e6f9ad84 100644 --- a/src/components/SwitchToSettlementChain/utils.ts +++ b/src/components/SwitchToSettlementChain/utils.ts @@ -1,6 +1,8 @@ import { SourceChainId } from "config/chains"; -import { isSourceChain, isSettlementChain } from "config/multichain"; +import { isSourceChainForAnySettlementChain, isSettlementChain } from "config/multichain"; export function needSwitchToSettlementChain(walletChainId: number | undefined): walletChainId is SourceChainId { - return Boolean(walletChainId && isSourceChain(walletChainId) && !isSettlementChain(walletChainId)); + return Boolean( + walletChainId && isSourceChainForAnySettlementChain(walletChainId) && !isSettlementChain(walletChainId) + ); } diff --git a/src/components/TokenSelector/MultichainTokenSelector.tsx b/src/components/TokenSelector/MultichainTokenSelector.tsx index 5ef187a92c..a01d34212e 100644 --- a/src/components/TokenSelector/MultichainTokenSelector.tsx +++ b/src/components/TokenSelector/MultichainTokenSelector.tsx @@ -102,7 +102,7 @@ export function MultichainTokenSelector({ setIsModalVisible(false); const isGmxAccount = tokenChainId === GMX_ACCOUNT_PSEUDO_CHAIN_ID; const tokenSrcChainId = - tokenChainId !== chainId && tokenChainId !== GMX_ACCOUNT_PSEUDO_CHAIN_ID && isSourceChain(tokenChainId) + tokenChainId !== chainId && tokenChainId !== GMX_ACCOUNT_PSEUDO_CHAIN_ID && isSourceChain(tokenChainId, chainId) ? tokenChainId : undefined; propsOnSelectTokenAddress(tokenAddress, isGmxAccount, tokenSrcChainId); diff --git a/src/components/TokenSelector/MultichainTokenSelectorForLp.tsx b/src/components/TokenSelector/MultichainTokenSelectorForLp.tsx index bf110a4de5..7dbf8423fc 100644 --- a/src/components/TokenSelector/MultichainTokenSelectorForLp.tsx +++ b/src/components/TokenSelector/MultichainTokenSelectorForLp.tsx @@ -72,7 +72,7 @@ export function MultichainTokenSelectorForLp({ setIsModalVisible(false); const isGmxAccount = tokenChainId === GMX_ACCOUNT_PSEUDO_CHAIN_ID; const tokenSrcChainId = - tokenChainId !== chainId && !isGmxAccount && isSourceChain(tokenChainId) ? tokenChainId : undefined; + tokenChainId !== chainId && !isGmxAccount && isSourceChain(tokenChainId, chainId) ? tokenChainId : undefined; propsOnSelectTokenAddress(tokenAddress, isGmxAccount, tokenSrcChainId); }; diff --git a/src/config/indexers.ts b/src/config/indexers.ts index 6a0d04ab34..4b1623137d 100644 --- a/src/config/indexers.ts +++ b/src/config/indexers.ts @@ -10,7 +10,7 @@ const INDEXER_URLS = { "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/gmx-arbitrum-referrals/master-240506225935-51167d5/gn", syntheticsStats: "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/synthetics-arbitrum-stats/master-250410222518-4486206/gn", - subsquid: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql", + subsquid: "https://gmx.squids.live/gmx-synthetics-arbitrum@045a75/api/graphql", }, [AVALANCHE]: { @@ -20,7 +20,7 @@ const INDEXER_URLS = { "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/gmx-avalanche-referrals/master-240415215829-f6877d6/gn", syntheticsStats: "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/synthetics-avalanche-stats/master-250410222549-4486206/gn", - subsquid: "https://gmx.squids.live/gmx-synthetics-avalanche:prod/api/graphql", + subsquid: "https://gmx.squids.live/gmx-synthetics-avalanche@045a75/api/graphql", }, [AVALANCHE_FUJI]: { @@ -28,15 +28,15 @@ const INDEXER_URLS = { "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/gmx-fuji-referrals/synts-stats-230726124533-065cd0d/gn", syntheticsStats: "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/synthetics-fuji-stats/master-250708141244-939c871/gn", - subsquid: "https://gmx.squids.live/gmx-synthetics-fuji:prod/api/graphql", + subsquid: "https://gmx.squids.live/gmx-synthetics-fuji@045a75/api/graphql", }, [ARBITRUM_SEPOLIA]: { - subsquid: "https://gmx.squids.live/gmx-synthetics-arb-sepolia:prod/api/graphql", + subsquid: "https://gmx.squids.live/gmx-synthetics-arb-sepolia@045a75/api/graphql", }, [BOTANIX]: { - subsquid: "https://gmx.squids.live/gmx-synthetics-botanix:prod/api/graphql", + subsquid: "https://gmx.squids.live/gmx-synthetics-botanix@045a75/api/graphql", stats: "https://api.goldsky.com/api/public/project_cmgptuc4qhclc01rh9s4q554a/subgraphs/synthetics-botanix-stats/botanix-250617091016-f7b3bb5/gn", syntheticsStats: diff --git a/src/config/multichain.ts b/src/config/multichain.ts index 1f36d4b02f..7e2bd92484 100644 --- a/src/config/multichain.ts +++ b/src/config/multichain.ts @@ -32,7 +32,10 @@ import { ARBITRUM, ARBITRUM_SEPOLIA, AVALANCHE, + AVALANCHE_FUJI, ContractsChainId, + SETTLEMENT_CHAIN_IDS, + SETTLEMENT_CHAIN_IDS_DEV, SettlementChainId, SOURCE_BASE_MAINNET, SOURCE_BSC_MAINNET, @@ -44,7 +47,7 @@ import { import { isDevelopment } from "config/env"; import { LayerZeroEndpointId } from "domain/multichain/types"; import { numberToBigint } from "lib/numbers"; -import { isSettlementChain, isSourceChain, SOURCE_CHAINS } from "sdk/configs/multichain"; +import { isSettlementChain, isSourceChain } from "sdk/configs/multichain"; import { convertTokenAddress, getTokenBySymbol } from "sdk/configs/tokens"; import platformTokensData from "./static/platformTokens.json"; @@ -92,6 +95,10 @@ export type MultichainTokenId = { isPlatformToken?: boolean; }; +export const SETTLEMENT_CHAINS: SettlementChainId[] = isDevelopment() + ? (SETTLEMENT_CHAIN_IDS_DEV as unknown as SettlementChainId[]) + : (SETTLEMENT_CHAIN_IDS as unknown as SettlementChainId[]); + const TOKEN_GROUPS: Partial>>> = { ["USDC"]: { [ARBITRUM]: { @@ -309,12 +316,6 @@ function addMultichainPlatformTokenConfig( } } -export const DEBUG_MULTICHAIN_SAME_CHAIN_DEPOSIT = false; - -if (isDevelopment() && DEBUG_MULTICHAIN_SAME_CHAIN_DEPOSIT) { - SOURCE_CHAINS.push(ARBITRUM_SEPOLIA as SourceChainId, ARBITRUM as SourceChainId, AVALANCHE as SourceChainId); -} - export const MULTI_CHAIN_TOKEN_MAPPING = {} as MultichainTokenMapping; export const MULTI_CHAIN_DEPOSIT_TRADE_TOKENS = {} as Record; export const MULTI_CHAIN_WITHDRAWAL_TRADE_TOKENS = {} as Record; @@ -358,11 +359,11 @@ for (const tokenSymbol in TOKEN_GROUPS) { for (const sourceChainIdString in TOKEN_GROUPS[tokenSymbol]) { const sourceChainId = parseInt(sourceChainIdString) as SettlementChainId | SourceChainId; - if (!isSourceChain(sourceChainId)) { + if (!isSourceChain(sourceChainId, settlementChainId)) { continue; } - if (!isDevelopment() && (settlementChainId as number) === (sourceChainId as number)) { + if ((settlementChainId as number) === (sourceChainId as number)) { continue; } @@ -434,13 +435,12 @@ export const MULTICALLS_MAP: Record = { [SOURCE_SEPOLIA]: "0xca11bde05977b3631167028862be2a173976ca11", [SOURCE_BASE_MAINNET]: "0xca11bde05977b3631167028862be2a173976ca11", [SOURCE_BSC_MAINNET]: "0xca11bde05977b3631167028862be2a173976ca11", + [ARBITRUM]: "0xca11bde05977b3631167028862be2a173976ca11", + [AVALANCHE]: "0xca11bde05977b3631167028862be2a173976ca11", + [ARBITRUM_SEPOLIA]: "0xca11bde05977b3631167028862be2a173976ca11", + [AVALANCHE_FUJI]: "0xca11bde05977b3631167028862be2a173976ca11", }; -if (isDevelopment() && DEBUG_MULTICHAIN_SAME_CHAIN_DEPOSIT) { - MULTICALLS_MAP[ARBITRUM_SEPOLIA as SourceChainId] = "0xca11bde05977b3631167028862be2a173976ca11"; - MULTICALLS_MAP[ARBITRUM as SourceChainId] = "0xca11bde05977b3631167028862be2a173976ca11"; -} - /** * Compiled bytecode for MockUnlimitedToken * @see https://github.com/gmx-io/gmx-synthetics/blob/updates/contracts/mock/MockUnlimitedToken.sol @@ -452,6 +452,7 @@ export const CHAIN_ID_PREFERRED_DEPOSIT_TOKEN: Record [ARBITRUM_SEPOLIA]: "0x3253a335E7bFfB4790Aa4C25C4250d206E9b9773", // USDC.SG [ARBITRUM]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC [AVALANCHE]: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", // USDC + [AVALANCHE_FUJI]: "0x3eBDeaA0DB3FfDe96E7a0DBBAFEC961FC50F725F", // USDC }; export const MULTICHAIN_FUNDING_SLIPPAGE_BPS = 50; @@ -467,6 +468,7 @@ export const CHAIN_ID_TO_ENDPOINT_ID: Record> = diff --git a/src/context/ChainContext/ChainContext.tsx b/src/context/ChainContext/ChainContext.tsx index 5f7dee8ead..b7ba464cb0 100644 --- a/src/context/ChainContext/ChainContext.tsx +++ b/src/context/ChainContext/ChainContext.tsx @@ -27,7 +27,7 @@ const realChainId = window.ethereum?.chainId ? parseInt(window.ethereum?.chainId export const context = createContext({ chainId: initialChainId, - srcChainId: isSourceChain(realChainId) ? realChainId : undefined, + srcChainId: isSourceChain(realChainId, initialChainId) ? realChainId : undefined, isConnectedToChainId: false, }); diff --git a/src/context/SyntheticsEvents/types.ts b/src/context/SyntheticsEvents/types.ts index a7a724f853..42034a1b49 100644 --- a/src/context/SyntheticsEvents/types.ts +++ b/src/context/SyntheticsEvents/types.ts @@ -130,6 +130,7 @@ export type SubmittedMultichainWithdrawal = { settlementChainId: number; sourceChainId: number; tokenAddress: string; + sentTxn?: string; }; export type PendingMultichainFunding = MultichainFundingHistoryItem[]; diff --git a/src/context/SyntheticsEvents/useMultichainEvents.ts b/src/context/SyntheticsEvents/useMultichainEvents.ts index 021e0e4628..eb393cf1a0 100644 --- a/src/context/SyntheticsEvents/useMultichainEvents.ts +++ b/src/context/SyntheticsEvents/useMultichainEvents.ts @@ -69,6 +69,7 @@ export type MultichainEventsState = { >; removeMultichainFundingPendingIds: (id: string | string[]) => void; + setMultichainFundingPendingId: (mockId: string, id: string) => void; }; const DEFAULT_MULTICHAIN_FUNDING_STATE: PendingMultichainFunding = []; @@ -812,13 +813,16 @@ export function useMultichainEvents({ hasPageLostFocus }: { hasPageLostFocus: bo } const stubId = ``; + const isSameChain = submittedEvent.sourceChainId === 0; + const step = isSameChain ? "executed" : "submitted"; + setPendingMultichainFunding((prev) => { const newPendingMultichainFunding = structuredClone(prev); newPendingMultichainFunding.push({ account: currentAccount, id: stubId, operation: "deposit", - step: "submitted", + step, settlementChainId: chainId, sourceChainId: submittedEvent.sourceChainId, token: submittedEvent.tokenAddress, @@ -848,20 +852,23 @@ export function useMultichainEvents({ hasPageLostFocus }: { hasPageLostFocus: bo } const stubId = ``; + const isSameChain = submittedEvent.sourceChainId === 0; + const step = isSameChain ? "executed" : "submitted"; + setPendingMultichainFunding((prev) => { const newPendingMultichainFunding = structuredClone(prev); newPendingMultichainFunding.push({ account: currentAccount, id: stubId, operation: "withdrawal", - step: "submitted", + step, settlementChainId: chainId, sourceChainId: submittedEvent.sourceChainId, token: submittedEvent.tokenAddress, sentAmount: submittedEvent.amount, sentTimestamp: nowInSeconds(), - sentTxn: undefined, + sentTxn: isSameChain ? submittedEvent.sentTxn : undefined, receivedAmount: undefined, receivedTxn: undefined, receivedTimestamp: undefined, @@ -968,6 +975,17 @@ export function useMultichainEvents({ hasPageLostFocus }: { hasPageLostFocus: bo return newIds; }); }, + setMultichainFundingPendingId: (mockId: string, id: string) => { + setMultichainFundingPendingIds((prev) => ({ ...prev, [mockId]: id })); + setPendingMultichainFunding((prev) => { + const newPendingMultichainFunding = structuredClone(prev); + const pendingMultichainFundingIndex = newPendingMultichainFunding.findIndex((item) => item.id === mockId); + if (pendingMultichainFundingIndex !== -1) { + newPendingMultichainFunding[pendingMultichainFundingIndex].id = id; + } + return newPendingMultichainFunding; + }); + }, }), [ pendingMultichainFunding, diff --git a/src/context/WebsocketContext/WebsocketContextProvider.tsx b/src/context/WebsocketContext/WebsocketContextProvider.tsx index 370fcd80dd..2711f1f562 100644 --- a/src/context/WebsocketContext/WebsocketContextProvider.tsx +++ b/src/context/WebsocketContext/WebsocketContextProvider.tsx @@ -165,8 +165,8 @@ export function WebsocketContextProvider({ children }: { children: ReactNode }) } const distinctChains = Object.keys(additionalSourceChains) - .map((chainId) => parseInt(chainId) as SourceChainId) - .filter((chainId) => chainId !== srcChainId && isSourceChain(chainId)); + .map((chainIdStr) => parseInt(chainIdStr) as SourceChainId) + .filter((additionalChainId) => additionalChainId !== srcChainId && isSourceChain(additionalChainId, chainId)); if (distinctChains.length === 0) { return; @@ -217,7 +217,7 @@ export function WebsocketContextProvider({ children }: { children: ReactNode }) } }; }, - [additionalSourceChains, hasPageLostFocus, isConnected, srcChainId] + [additionalSourceChains, chainId, hasPageLostFocus, isConnected, srcChainId] ); useEffect( diff --git a/src/domain/multichain/__tests__/getMultichainTransferableGasPaymentTokenAddresses.spec.ts b/src/domain/multichain/__tests__/getMultichainTransferableGasPaymentTokenAddresses.spec.ts index 4d03c3d810..b2b5d03b31 100644 --- a/src/domain/multichain/__tests__/getMultichainTransferableGasPaymentTokenAddresses.spec.ts +++ b/src/domain/multichain/__tests__/getMultichainTransferableGasPaymentTokenAddresses.spec.ts @@ -46,7 +46,6 @@ describe("getMultichainTransferableGasPaymentTokenSymbols", () => { Ethereum; Arbitrum; USDC, ETH, USDT BNB; Arbitrum; USDC, USDT Base; Arbitrum; USDC, ETH - Ethereum; Avalanche; USDC, USDT BNB; Avalanche; USDC, USDT Base; Avalanche; USDC Sepolia; Arbitrum Sepolia; USDC.SG, ETH diff --git a/src/domain/multichain/__tests__/nativeTokenPriceMap.spec.ts b/src/domain/multichain/__tests__/nativeTokenPriceMap.spec.ts index 7aad051164..3417d17c34 100644 --- a/src/domain/multichain/__tests__/nativeTokenPriceMap.spec.ts +++ b/src/domain/multichain/__tests__/nativeTokenPriceMap.spec.ts @@ -1,10 +1,15 @@ import { describe, expect, it } from "vitest"; -import { SETTLEMENT_CHAINS, SOURCE_CHAINS } from "config/multichain"; +import { SETTLEMENT_CHAINS } from "config/multichain"; import { + ARBITRUM, + ARBITRUM_SEPOLIA, + AVALANCHE, + AVALANCHE_FUJI, getChainName, SOURCE_BASE_MAINNET, SOURCE_BSC_MAINNET, + SOURCE_CHAIN_IDS, SOURCE_ETHEREUM_MAINNET, SOURCE_OPTIMISM_SEPOLIA, SOURCE_SEPOLIA, @@ -21,11 +26,15 @@ const SOURCE_CHAIN_NATIVE_SYMBOL_MAP: Record = { [SOURCE_BASE_MAINNET]: "ETH", [SOURCE_BSC_MAINNET]: "BNB", [SOURCE_ETHEREUM_MAINNET]: "ETH", + [ARBITRUM]: "ETH", + [AVALANCHE]: "AVAX", + [ARBITRUM_SEPOLIA]: "ETH", + [AVALANCHE_FUJI]: "AVAX", }; describe("NATIVE_TOKEN_PRICE_MAP", () => { it("should be defined", () => { - for (const srcChainId of SOURCE_CHAINS) { + for (const srcChainId of SOURCE_CHAIN_IDS) { for (const settlementChainId of SETTLEMENT_CHAINS) { if (!areChainsRelated(settlementChainId, srcChainId)) { continue; diff --git a/src/domain/multichain/getSendParams.ts b/src/domain/multichain/getSendParams.ts index 285218fb11..4969fca62a 100644 --- a/src/domain/multichain/getSendParams.ts +++ b/src/domain/multichain/getSendParams.ts @@ -63,7 +63,7 @@ export function getMultichainTransferSendParams({ let extraOptions = "0x"; if (isToGmx) { - if (srcChainId === undefined || !isSourceChain(srcChainId)) { + if (srcChainId === undefined || !isSourceChain(srcChainId, dstChainId)) { throw new Error("Source chain is not supported"); } diff --git a/src/domain/multichain/sendSameChainDepositTxn.ts b/src/domain/multichain/sendSameChainDepositTxn.ts index 2efc6c076b..7ae34c8e8d 100644 --- a/src/domain/multichain/sendSameChainDepositTxn.ts +++ b/src/domain/multichain/sendSameChainDepositTxn.ts @@ -1,8 +1,10 @@ import { Contract } from "ethers"; import { Address, encodeFunctionData, zeroAddress } from "viem"; +import type { PublicClient } from "viem"; import type { SettlementChainId } from "config/chains"; import { getContract } from "config/contracts"; +import { applyGasLimitBuffer } from "lib/gas/estimateGasLimit"; import { TxnCallback, WalletTxnCtx, sendWalletTransaction } from "lib/transactions"; import type { WalletSigner } from "lib/wallets"; import { abis } from "sdk/abis"; @@ -84,3 +86,84 @@ export async function sendSameChainDepositTxn({ }); } } + +export async function estimateSameChainDepositGas({ + chainId, + client, + tokenAddress, + amount, + account, +}: { + chainId: SettlementChainId; + client: PublicClient; + tokenAddress: string; + amount: bigint; + account: string; +}): Promise { + const multichainVaultAddress = getContract(chainId, "MultichainVault"); + const multichainTransferRouterAddress = getContract(chainId, "MultichainTransferRouter"); + + if (tokenAddress === zeroAddress) { + const token = getToken(chainId, tokenAddress); + const wrappedAddress = token?.wrappedAddress; + + if (!wrappedAddress) { + throw new Error("Wrapped address is not set"); + } + + const callData = encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "multicall", + args: [ + [ + encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "sendWnt", + args: [multichainVaultAddress, amount], + }), + encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "bridgeIn", + args: [account, wrappedAddress], + }), + ], + ], + }); + + const gas = await client.estimateGas({ + account: account, + to: multichainTransferRouterAddress, + data: callData, + value: amount, + }); + + return applyGasLimitBuffer(gas); + } else { + const callData = encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "multicall", + args: [ + [ + encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "sendTokens", + args: [tokenAddress, multichainVaultAddress, amount], + }), + encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "bridgeIn", + args: [account, tokenAddress], + }), + ], + ], + }); + + const gas = await client.estimateGas({ + account: account, + to: multichainTransferRouterAddress, + data: callData, + }); + + return applyGasLimitBuffer(gas); + } +} diff --git a/src/domain/multichain/sendSameChainWithdrawalTxn.ts b/src/domain/multichain/sendSameChainWithdrawalTxn.ts new file mode 100644 index 0000000000..2adde7fc3f --- /dev/null +++ b/src/domain/multichain/sendSameChainWithdrawalTxn.ts @@ -0,0 +1,80 @@ +import { encodeFunctionData, PublicClient } from "viem"; + +import type { SettlementChainId } from "config/chains"; +import { getContract } from "config/contracts"; +import { applyGasLimitBuffer } from "lib/gas/estimateGasLimit"; +import { sendWalletTransaction, TxnCallback, WalletTxnCtx } from "lib/transactions"; +import type { WalletSigner } from "lib/wallets"; +import { abis } from "sdk/abis"; + +import type { BridgeOutParams } from "./types"; + +export async function sendSameChainWithdrawalTxn({ + chainId, + signer, + bridgeOutParams, + callback, +}: { + chainId: SettlementChainId; + signer: WalletSigner; + bridgeOutParams: BridgeOutParams; + callback?: TxnCallback; +}) { + const multichainTransferRouterAddress = getContract(chainId, "MultichainTransferRouter"); + + await sendWalletTransaction({ + chainId: chainId, + signer: signer, + to: multichainTransferRouterAddress, + callData: encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "transferOut", + args: [ + { + token: bridgeOutParams.token, + amount: bridgeOutParams.amount, + minAmountOut: bridgeOutParams.minAmountOut, + provider: bridgeOutParams.provider, + data: bridgeOutParams.data, + }, + ], + }), + callback, + }); +} + +export async function estimateSameChainWithdrawalGas({ + chainId, + client, + bridgeOutParams, + account, +}: { + chainId: SettlementChainId; + client: PublicClient; + bridgeOutParams: BridgeOutParams; + account: string; +}): Promise { + const multichainTransferRouterAddress = getContract(chainId, "MultichainTransferRouter"); + + const callData = encodeFunctionData({ + abi: abis.MultichainTransferRouter, + functionName: "transferOut", + args: [ + { + token: bridgeOutParams.token, + amount: bridgeOutParams.amount, + minAmountOut: bridgeOutParams.minAmountOut, + provider: bridgeOutParams.provider, + data: bridgeOutParams.data, + }, + ], + }); + + const gas = await client.estimateGas({ + account: account, + to: multichainTransferRouterAddress, + data: callData, + }); + + return applyGasLimitBuffer(gas); +} diff --git a/src/domain/multichain/types.ts b/src/domain/multichain/types.ts index 265602f57c..9ef39c79d7 100644 --- a/src/domain/multichain/types.ts +++ b/src/domain/multichain/types.ts @@ -38,7 +38,7 @@ export type BridgeOutParams = { data: string; }; -export type LayerZeroEndpointId = 40161 | 40231 | 40232 | 30184 | 30110 | 30106 | 30102 | 30101; +export type LayerZeroEndpointId = 40161 | 40231 | 40232 | 30184 | 30110 | 30106 | 30102 | 30101 | 40106; export type OFTLimit = { minAmountLD: bigint; diff --git a/src/domain/multichain/useMultichainFundingToast.tsx b/src/domain/multichain/useMultichainFundingToast.tsx index fcb05a50f5..2573ef6838 100644 --- a/src/domain/multichain/useMultichainFundingToast.tsx +++ b/src/domain/multichain/useMultichainFundingToast.tsx @@ -31,7 +31,7 @@ function useGmxAccountPendingFundingHistoryItems( const result: Record = {}; for (const item of fundingHistory) { - if (guids.includes(item.id)) { + if (guids.includes(item.id) && item.sourceChainId !== 0) { result[item.id] = item; } } diff --git a/src/domain/synthetics/express/expressOrderUtils.ts b/src/domain/synthetics/express/expressOrderUtils.ts index c7e5b96790..bbd1c72e76 100644 --- a/src/domain/synthetics/express/expressOrderUtils.ts +++ b/src/domain/synthetics/express/expressOrderUtils.ts @@ -816,7 +816,7 @@ export async function getMultichainInfoFromSigner( ): Promise { const srcChainId = await signer.provider!.getNetwork().then((n) => Number(n.chainId) as AnyChainId); - if (!isSourceChain(srcChainId)) { + if (!isSourceChain(srcChainId, chainId)) { return undefined; } diff --git a/src/domain/synthetics/subaccount/utils.ts b/src/domain/synthetics/subaccount/utils.ts index b3c240959c..8e69d23f3e 100644 --- a/src/domain/synthetics/subaccount/utils.ts +++ b/src/domain/synthetics/subaccount/utils.ts @@ -187,7 +187,7 @@ export function getIsSubaccountNonceExpired({ } else if (signedApproval.subaccountRouterAddress === getContract(chainId, "MultichainSubaccountRouter")) { onChainNonce = onchainData.multichainApprovalNonce; } else if (!signedApproval.subaccountRouterAddress) { - if (isSourceChain(signedApproval.signatureChainId)) { + if (isSourceChain(signedApproval.signatureChainId, chainId)) { onChainNonce = onchainData.multichainApprovalNonce; } else { onChainNonce = onchainData.approvalNonce; diff --git a/src/domain/synthetics/tokens/useTokenAllowanceData.ts b/src/domain/synthetics/tokens/useTokenAllowanceData.ts index 88d440cb8c..aeb33f44fb 100644 --- a/src/domain/synthetics/tokens/useTokenAllowanceData.ts +++ b/src/domain/synthetics/tokens/useTokenAllowanceData.ts @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import { isSourceChain } from "config/multichain"; +import { isSourceChainForAnySettlementChain } from "config/multichain"; import { useSyntheticsEvents } from "context/SyntheticsEvents"; import { MulticallRequestConfig, useMulticall } from "lib/multicall"; import { EMPTY_OBJECT } from "lib/objects"; @@ -77,7 +77,7 @@ export function useTokensAllowanceData( const newData: TokensAllowanceData = {}; let statuses = approvalStatuses; - if (chainId !== undefined && !isSourceChain(chainId)) { + if (isSourceChainForAnySettlementChain(chainId)) { statuses = multichainSourceChainApprovalStatuses; } diff --git a/src/domain/synthetics/trade/useTradeParamsProcessor.ts b/src/domain/synthetics/trade/useTradeParamsProcessor.ts index 18a78f859f..aa3254d1ac 100644 --- a/src/domain/synthetics/trade/useTradeParamsProcessor.ts +++ b/src/domain/synthetics/trade/useTradeParamsProcessor.ts @@ -75,7 +75,8 @@ export function useTradeParamsProcessor() { if ( chainIdFromParams && - (isContractsChain(Number(chainIdFromParams), isDevelopment()) || isSourceChain(Number(chainIdFromParams))) + (isContractsChain(Number(chainIdFromParams), isDevelopment()) || + isSourceChain(Number(chainIdFromParams), chainId)) ) { await switchNetwork(Number(chainIdFromParams), true); } diff --git a/src/domain/tokens/utils.ts b/src/domain/tokens/utils.ts index b8c9717480..8d2809a313 100644 --- a/src/domain/tokens/utils.ts +++ b/src/domain/tokens/utils.ts @@ -2,6 +2,7 @@ import { ethers } from "ethers"; import { BOTANIX, ContractsChainId, getExplorerUrl } from "config/chains"; import { USD_DECIMALS } from "config/factors"; +import type { TokenData } from "domain/synthetics/tokens"; import { convertToTokenAmount } from "domain/synthetics/tokens/utils"; import { adjustForDecimals, @@ -16,6 +17,7 @@ import { import { expandDecimals, PRECISION } from "lib/numbers"; import { getVisibleV1Tokens, getWhitelistedV1Tokens } from "sdk/configs/tokens"; import { InfoTokens, Token, TokenInfo } from "sdk/types/tokens"; +import { convertToUsd, getMidPrice } from "sdk/utils/tokens"; export * from "sdk/utils/tokens"; @@ -282,3 +284,20 @@ const BLACKLISTED_REGEX = /Wrapped|\(Wormhole\)|\(LayerZero\)/gim; export function stripBlacklistedWords(name: string): string { return name.replace(BLACKLISTED_REGEX, "").trim(); } + +export function sortTokenDataByBalance(a: TokenData, b: TokenData): 1 | -1 | 0 { + const aBalanceUsd = + a.prices && a.gmxAccountBalance !== undefined + ? convertToUsd(a.gmxAccountBalance, a.decimals, getMidPrice(a.prices)) ?? 0n + : 0n; + const bBalanceUsd = + b.prices && b.gmxAccountBalance !== undefined + ? convertToUsd(b.gmxAccountBalance, b.decimals, getMidPrice(b.prices)) ?? 0n + : 0n; + + if (aBalanceUsd === bBalanceUsd) { + return 0; + } + + return bBalanceUsd > aBalanceUsd ? 1 : -1; +} diff --git a/src/lib/chains/useChainIdImpl.ts b/src/lib/chains/useChainIdImpl.ts index 3d6cf5ed4d..e59a400037 100644 --- a/src/lib/chains/useChainIdImpl.ts +++ b/src/lib/chains/useChainIdImpl.ts @@ -46,7 +46,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { let srcChainId: SourceChainId | undefined = undefined; if ( possibleSrcChainId && - isSourceChain(possibleSrcChainId) && + isSourceChain(possibleSrcChainId, settlementChainId) && !isSettlementChain(possibleSrcChainId) && areChainsRelated(settlementChainId, possibleSrcChainId) ) { @@ -54,11 +54,12 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { } const isCurrentChainSupported = connectedChainId && isContractsChain(connectedChainId, IS_DEVELOPMENT); - const isCurrentChainSource = connectedChainId && isSourceChain(connectedChainId); + const isCurrentChainSource = connectedChainId && isSourceChain(connectedChainId, settlementChainId); const isLocalStorageChainSupported = chainIdFromLocalStorage && isContractsChain(chainIdFromLocalStorage, IS_DEVELOPMENT); - const isLocalStorageChainSource = chainIdFromLocalStorage && isSourceChain(chainIdFromLocalStorage); + const isLocalStorageChainSource = + chainIdFromLocalStorage && isSourceChain(chainIdFromLocalStorage, settlementChainId); const mustChangeChainId = !connectedChainId || (!isCurrentChainSource && !isCurrentChainSupported); @@ -72,7 +73,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { const connectHandler = (connectInfo: { chainId: string }) => { const rawChainId = parseInt(connectInfo.chainId); - if (isContractsChain(rawChainId, IS_DEVELOPMENT) || isSourceChain(rawChainId)) { + if (isContractsChain(rawChainId, IS_DEVELOPMENT) || isSourceChain(rawChainId, settlementChainId)) { setDisplayedChainId(rawChainId); localStorage.setItem(SELECTED_NETWORK_LOCAL_STORAGE_KEY, rawChainId.toString()); } @@ -82,7 +83,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { return () => { window.ethereum?.removeListener("connect", connectHandler); }; - }, [chainIdFromLocalStorage]); + }, [chainIdFromLocalStorage, settlementChainId]); useEffect(() => { if (isCurrentChainSupported) { @@ -147,7 +148,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { return; } if ( - !isSourceChain(account.chainId) && + !isSourceChain(account.chainId, settlementChainId) && !isContractsChain(account.chainId, IS_DEVELOPMENT) && !isSettlementChain(account.chainId) ) { @@ -160,7 +161,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { }); return unsubscribe; - }, []); + }, [settlementChainId]); useEffect(() => { if (connectedChainId) { @@ -169,7 +170,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { const switchNetworkHandler = (switchNetworkInfo: CustomEvent<{ chainId: number }>) => { const newChainId = switchNetworkInfo.detail.chainId; - if (isContractsChain(newChainId, IS_DEVELOPMENT) || isSourceChain(newChainId)) { + if (isContractsChain(newChainId, IS_DEVELOPMENT) || isSourceChain(newChainId, settlementChainId)) { setDisplayedChainId(newChainId); } }; @@ -177,7 +178,7 @@ export function useChainIdImpl(settlementChainId: SettlementChainId): { return () => { document.removeEventListener("networkChange", switchNetworkHandler); }; - }, [connectedChainId]); + }, [connectedChainId, settlementChainId]); if (mustChangeChainId) { if (isLocalStorageChainSupported) { diff --git a/src/lib/wallets/useAccountType.ts b/src/lib/wallets/useAccountType.ts index 3d7e85b0f2..78794e5193 100644 --- a/src/lib/wallets/useAccountType.ts +++ b/src/lib/wallets/useAccountType.ts @@ -1,10 +1,10 @@ import { getPublicClient } from "@wagmi/core"; +import uniq from "lodash/uniq"; import useSWR from "swr"; import { Hex, PublicClient } from "viem"; import { useAccount, useChainId, usePublicClient } from "wagmi"; -import { AnyChainId, CONTRACTS_CHAIN_IDS, ContractsChainId, getChainSlug } from "config/chains"; -import { SOURCE_CHAINS } from "config/multichain"; +import { AnyChainId, CONTRACTS_CHAIN_IDS, ContractsChainId, getChainSlug, SOURCE_CHAIN_IDS } from "config/chains"; import { getIsNonEoaAccountError, nonEoaAccountError } from "lib/errors/customErrors"; import { getRainbowKitConfig } from "./rainbowKitConfig"; @@ -128,8 +128,10 @@ export function useIsNonEoaAccountOnAnyChain(): boolean { return undefined; } + const chainIds = uniq((CONTRACTS_CHAIN_IDS as AnyChainId[]).concat(SOURCE_CHAIN_IDS)); + return Promise.all( - (CONTRACTS_CHAIN_IDS as unknown as AnyChainId[]).concat(SOURCE_CHAINS).map(async (chainId) => { + chainIds.map(async (chainId) => { const publicClient = getPublicClient(getRainbowKitConfig(), { chainId }); if (!publicClient) { diff --git a/src/locales/de/messages.po b/src/locales/de/messages.po index e61d9619bc..4fca10c312 100644 --- a/src/locales/de/messages.po +++ b/src/locales/de/messages.po @@ -1274,6 +1274,11 @@ msgstr "Limit Swap fehlgeschlagen" msgid "Failed" msgstr "Fehlgeschlagen" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "Keine Gebühr" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "Liq. {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "System" msgid "Earn rewards by sharing your code!" msgstr "Verdiene Belohnungen, indem du deinen Code teilst!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "Sofort" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "Offene Positionen" @@ -2937,6 +2947,11 @@ msgstr "Wie starte ich auf GMX?" msgid "Withdrawing from subaccount..." msgstr "Wird vom Unterkonto abgehoben…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "Abhebung fehlgeschlagen" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "Gesamt gestaket" @@ -3872,6 +3887,10 @@ msgstr "Warum erhalte ich zwei verschiedene GLV-Tokens?" msgid "Set TP/SL" msgstr "TP/SL setzen" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "Alle Netzwerke" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "Token auswählen" msgid "Click here to give us your feedback on GMX" msgstr "Klicken Sie hier, um uns Ihr Feedback zu GMX zu geben" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "Exposition gegenüber PnL der Markthändler" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "Optimal" @@ -4773,6 +4788,10 @@ msgstr "Kollateral ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "Du hast eine bestehende Position mit {0} als Collateral. Diese Order ist für diese Position nicht gültig. <0>Wechsle zu {1}-Collateral" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "Sie heben {0} ab, Ihr Gas-Token. Gas ist für Express-Trading erforderlich, bitte behalten Sie mindestens <0>{1} in {2} oder wechseln Sie Ihr Gas-Token in <1>den Einstellungen." + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "Du wirst {0}% mehr Belohnungen mit dieser Aktion verdienen." @@ -5853,6 +5872,10 @@ msgstr "<0>Wechsle zum {0}-Pool für potenziell niedrigeren Netto-Preis-Impa msgid "market" msgstr "markt" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "Die Auszahlung von {0} an {1} wird derzeit nicht unterstützt" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "Weitere Möglichkeiten" msgid "Vested GMX not withdrawn" msgstr "Vestetes GMX nicht abgezogen" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "Maximale Kapazität:" @@ -6872,10 +6890,6 @@ msgstr "Code wird erstellt" msgid "Swap failed." msgstr "Tausch fehlgeschlagen." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "Erhalte Gebührenrabatte und verdiene Rebates über das GMX-Referral-Pro msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "{gmOrGlvLabel}-Verkaufsvorgang fehlgeschlagen. Deine Mittel sind sicher und befinden sich derzeit auf deinem GMX-Konto. Du kannst zur Settlement-Chain wechseln und zum {indexName}-Pool gehen, um deine {gmOrGlvLabel}-Token manuell zu verkaufen." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "Kaufen oder Übertragen von {nativeTokenSymbol} zu {chainName}" @@ -7694,10 +7704,6 @@ msgstr "Funding eingelöst." msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "Hebel halten ist nicht verfügbar, da Position max. erlaubten Hebel überschreitet. <0>Mehr lesen." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "Stop-Loss fehlgeschlagen" @@ -8255,10 +8261,6 @@ msgstr "Marktorder wird abbrechbar in ..." msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "Das Staken von GMX wird derzeit auf Botanix nicht unterstützt. Für Zugriff auf diese Funktionen besuche bitte die Arbitrum- und Avalanche-Deployments." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "{glvOrGm} einzahlen" diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po index 41c38d92f3..3500541f46 100644 --- a/src/locales/en/messages.po +++ b/src/locales/en/messages.po @@ -1274,6 +1274,11 @@ msgstr "Failed Limit Swap" msgid "Failed" msgstr "Failed" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "No fee" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "Liq. {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "System" msgid "Earn rewards by sharing your code!" msgstr "Earn rewards by sharing your code!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "Instant" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "Open positions" @@ -2937,6 +2947,11 @@ msgstr "How do I get started on GMX?" msgid "Withdrawing from subaccount..." msgstr "Withdrawing from subaccount..." +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "Withdrawal failed" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "Total Staked" @@ -3872,6 +3887,10 @@ msgstr "Why do I receive two different GLV tokens?" msgid "Set TP/SL" msgstr "Set TP/SL" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "All Networks" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "Select a token" msgid "Click here to give us your feedback on GMX" msgstr "Click here to give us your feedback on GMX" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "Exposure to Market Traders’ PnL" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "Optimal" @@ -4773,6 +4788,10 @@ msgstr "Collateral ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "You will earn {0}% more rewards with this action." @@ -5853,6 +5872,10 @@ msgstr "<0>Switch to the {0} pool for potentially lower net price impact" msgid "market" msgstr "market" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "Withdrawing {0} to {1} is not currently supported" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "Additional Opportunities" msgid "Vested GMX not withdrawn" msgstr "Vested GMX not withdrawn" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "Report issue" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "Max Capacity:" @@ -6872,10 +6890,6 @@ msgstr "Creating code" msgid "Swap failed." msgstr "Swap failed." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "Please try reloading or report the issue if the problem persists." - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "Get fee discounts and earn rebates through the GMX referral program.<0/> msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "Something went wrong" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "Buy or Transfer {nativeTokenSymbol} to {chainName}" @@ -7694,10 +7704,6 @@ msgstr "Funding claimed." msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "Please try reloading or report the issue if the <0/> problem persists." - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "Failed Stop Loss" @@ -8255,10 +8261,6 @@ msgstr "Market order will be cancellable in ..." msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "ERROR CODE: {errorCode}" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "Deposit {glvOrGm}" diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po index 193f175279..354c048a15 100644 --- a/src/locales/es/messages.po +++ b/src/locales/es/messages.po @@ -1274,6 +1274,11 @@ msgstr "Intercambio Límite Fallido" msgid "Failed" msgstr "Fallida" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "Sin tarifa" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "Liq. {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "Sistema" msgid "Earn rewards by sharing your code!" msgstr "¡Gana recompensas compartiendo tu código!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "Instantáneo" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "Posiciones abiertas" @@ -2937,6 +2947,11 @@ msgstr "¿Cómo empiezo en GMX?" msgid "Withdrawing from subaccount..." msgstr "Retirando de la subcuenta…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "Retiro fallido" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "Total Stakeado" @@ -3872,6 +3887,10 @@ msgstr "¿Por qué recibo dos tokens GLV diferentes?" msgid "Set TP/SL" msgstr "Establecer TP/SL" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "Todas las Redes" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "Seleccionar un token" msgid "Click here to give us your feedback on GMX" msgstr "Haz clic aquí para darnos tus comentarios sobre GMX" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "Exposición a GyP de Traders de Mercado" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "Óptimo" @@ -4773,6 +4788,10 @@ msgstr "Garantía ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "Tienes una posición existente con {0} como colateral. Esta orden no será válida para esa posición. <0>Cambiar a colateral {1}" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "Estás retirando {0}, tu token de gas. Se requiere gas para el trading express, por favor mantén al menos <0>{1} en {2} o cambia tu token de gas en <1>configuraciones." + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "Ganarás {0}% más recompensas con esta acción." @@ -5853,6 +5872,10 @@ msgstr "<0>Cambia al pool {0} para un posible impacto neto menor en el preci msgid "market" msgstr "mercado" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "La retirada de {0} a {1} no está actualmente soportada" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "Oportunidades adicionales" msgid "Vested GMX not withdrawn" msgstr "GMX en vesting no retirado" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "Capacidad máxima:" @@ -6872,10 +6890,6 @@ msgstr "Creando código" msgid "Swap failed." msgstr "Intercambio fallido." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "Obtenga descuentos en comisiones y gane reembolsos a través del program msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "La operación de venta de {gmOrGlvLabel} falló. Tus fondos están seguros y actualmente almacenados en tu cuenta GMX. Puedes cambiar a la cadena de liquidación e ir al pool {indexName} para vender manualmente tus tokens {gmOrGlvLabel}." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "Compra o Transfiere {nativeTokenSymbol} a {chainName}" @@ -7694,10 +7704,6 @@ msgstr "Funding reclamado." msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "Mantener apalancamiento no disponible ya que la Posición excede el apal. máximo permitido. <0>Leer más." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "Stop Loss Fallido" @@ -8255,10 +8261,6 @@ msgstr "Orden de mercado será cancelable en ..." msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "El staking de GMX no está disponible actualmente en Botanix. Para acceder a estas funciones, visita las implementaciones en Arbitrum y Avalanche." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "Depositar {glvOrGm}" diff --git a/src/locales/fr/messages.po b/src/locales/fr/messages.po index 89bb3fdbe0..28087e12c0 100644 --- a/src/locales/fr/messages.po +++ b/src/locales/fr/messages.po @@ -1274,6 +1274,11 @@ msgstr "Swap limité échoué" msgid "Failed" msgstr "Échec" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "Aucun frais" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "Liq. {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "Système" msgid "Earn rewards by sharing your code!" msgstr "Gagnez des récompenses en partageant votre code !" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "Instantané" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "Positions ouvertes" @@ -2937,6 +2947,11 @@ msgstr "Comment commencer sur GMX ?" msgid "Withdrawing from subaccount..." msgstr "Retrait du sous-compte…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "Retrait échoué" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "Total staké" @@ -3872,6 +3887,10 @@ msgstr "Pourquoi je reçois deux jetons GLV différents ?" msgid "Set TP/SL" msgstr "Définir TP/SL" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "Tous les Réseaux" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "Sélectionner un token" msgid "Click here to give us your feedback on GMX" msgstr "Cliquez ici pour nous donner votre avis sur GMX" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "Exposition au PnL des traders du marché" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "Optimal" @@ -4773,6 +4788,10 @@ msgstr "Collatéral ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "Vous avez une position existante avec {0} comme collatéral. Cet ordre ne sera pas valide pour cette position. <0>Passer au collatéral {1}" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "Vous retirez {0}, votre jeton de gas. Du gas est requis pour le trading express, veuillez donc conserver au moins <0>{1} en {2} ou changer votre jeton de gas dans <1>les paramètres." + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "Vous gagnerez {0}% de récompenses supplémentaires avec cette action." @@ -5853,6 +5872,10 @@ msgstr "<0>Passez au pool {0} pour un impact net potentiellement plus faible msgid "market" msgstr "marché" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "Le retrait de {0} vers {1} n'est actuellement pas pris en charge" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "Opportunités supplémentaires" msgid "Vested GMX not withdrawn" msgstr "GMX en vesting non retiré" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "Capacité maximale :" @@ -6872,10 +6890,6 @@ msgstr "Création du code" msgid "Swap failed." msgstr "Échange échoué." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "Obtenez des remises sur les frais et gagnez des rabais via le programme msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "L'opération de vente de {gmOrGlvLabel} a échoué. Vos fonds sont en sécurité et actuellement stockés dans votre compte GMX. Vous pouvez passer à la chaîne de règlement et aller au pool {indexName} pour vendre manuellement vos tokens {gmOrGlvLabel}." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "Acheter ou Transférer {nativeTokenSymbol} vers {chainName}" @@ -7694,10 +7704,6 @@ msgstr "Financement réclamé." msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "Maintenir le levier n'est pas disponible car la position dépasse le levier max. autorisé. <0>En savoir plus." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "Stop Loss échoué" @@ -8255,10 +8261,6 @@ msgstr "L'ordre au marché sera annulable dans ..." msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "Le staking de GMX n’est actuellement pas pris en charge sur Botanix. Pour accéder à ces fonctionnalités, veuillez visiter les déploiements Arbitrum et Avalanche." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "Déposer {glvOrGm}" diff --git a/src/locales/ja/messages.po b/src/locales/ja/messages.po index 26d9799fa6..b56aa8215f 100644 --- a/src/locales/ja/messages.po +++ b/src/locales/ja/messages.po @@ -1274,6 +1274,11 @@ msgstr "指値スワップ失敗" msgid "Failed" msgstr "失敗" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "手数料なし" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "清算 {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "システム" msgid "Earn rewards by sharing your code!" msgstr "コードを共有して報酬を獲得!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "即時" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "オープンポジション" @@ -2937,6 +2947,11 @@ msgstr "GMXを始めるにはどうしたらいいですか?" msgid "Withdrawing from subaccount..." msgstr "サブアカウントから出金中…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "引き出し失敗" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "ステーク総額" @@ -3872,6 +3887,10 @@ msgstr "なぜ2つの異なるGLVトークンを受け取るのですか?" msgid "Set TP/SL" msgstr "TP/SLを設定" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "すべてのネットワーク" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "トークンを選択" msgid "Click here to give us your feedback on GMX" msgstr "ここをクリックしてGMXへのフィードバックをお寄せください" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "マーケットトレーダーの損益へのエクスポージャー" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "最適" @@ -4773,6 +4788,10 @@ msgstr "担保 ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "{0}を担保とする既存のポジションがあります。この注文はそのポジションには有効ではありません。<0>{1}担保に切り替える" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "{0}、ガス・トークンを引き出しています。エクスプレストレーディングにはガスが必要なので、{2}に少なくとも<0>{1}を保持するか<1>設定でガス・トークンを切り替えてください。" + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "このアクションで{0}%多くの報酬を獲得できます。" @@ -5853,6 +5872,10 @@ msgstr "ネット価格インパクトを低減するため<0>{0}プールに切 msgid "market" msgstr "市場" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "{0}から{1}への出金は現在サポートされていません" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "追加機会" msgid "Vested GMX not withdrawn" msgstr "ベスティング済みGMX未引き出し" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "最大容量:" @@ -6872,10 +6890,6 @@ msgstr "コード作成中" msgid "Swap failed." msgstr "スワップに失敗しました。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "GMX紹介プログラムを通じて手数料割引を受け取り、リ msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "{gmOrGlvLabel}の売却操作に失敗しました。お客様の資金は安全で、現在GMXアカウントに保管されています。決済チェーンに切り替えて{indexName}プールで{gmOrGlvLabel}トークンを手動で売却できます。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "{nativeTokenSymbol} を {chainName} に購入または転送" @@ -7694,10 +7704,6 @@ msgstr "資金が請求されました。" msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "ポジションが最大許容レバレッジを超えるため、レバレッジ維持が利用できません。<0>詳細を読む。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "ストップロス失敗" @@ -8255,10 +8261,6 @@ msgstr "マーケット注文は...でキャンセル可能" msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "現在BotanixではGMXステーキングはサポートされていません。これらの機能をご利用の場合はArbitrumおよびAvalancheのデプロイをご覧ください。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "{glvOrGm}を入金" diff --git a/src/locales/ko/messages.po b/src/locales/ko/messages.po index 3e6b803fab..19464ae9b5 100644 --- a/src/locales/ko/messages.po +++ b/src/locales/ko/messages.po @@ -1274,6 +1274,11 @@ msgstr "지정가 스왑 실패" msgid "Failed" msgstr "실패" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "수수료 없음" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "청산 {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "시스템" msgid "Earn rewards by sharing your code!" msgstr "코드를 공유하여 보상을 받으세요!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "즉시" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "오픈 포지션" @@ -2937,6 +2947,11 @@ msgstr "GMX를 어떻게 시작하나요?" msgid "Withdrawing from subaccount..." msgstr "서브계정에서 출금하는 중…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "출금 실패" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "총 스테이킹" @@ -3872,6 +3887,10 @@ msgstr "왜 두 가지 다른 GLV 토큰을 받나요?" msgid "Set TP/SL" msgstr "TP/SL 설정" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "모든 네트워크" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "토큰 선택" msgid "Click here to give us your feedback on GMX" msgstr "GMX에 대한 피드백을 제공하려면 여기를 클릭하세요" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "시장 거래자 PnL 노출" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "최적" @@ -4773,6 +4788,10 @@ msgstr "담보 ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "{0}을(를) 담보로 하는 기존 포지션이 있습니다. 이 주문은 해당 포지션에 유효하지 않습니다. <0>{1} 담보로 전환" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "{0}, 가스 토큰을 출금 중입니다. 익스프레스 트레이딩에 가스가 필요하므로 {2}에 최소 <0>{1}을 유지하거나 <1>설정에서 가스 토큰을 전환하세요." + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "이 작업으로 {0}% 더 많은 보상을 받게 됩니다." @@ -5853,6 +5872,10 @@ msgstr "더 낮은 순 가격 영향을 위해 <0>{0} 풀로 전환하세요 msgid "market" msgstr "시장" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "{0}을(를) {1}로 출금하는 것은 현재 지원되지 않습니다" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "추가 기회" msgid "Vested GMX not withdrawn" msgstr "베스팅된 GMX가 출금되지 않음" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "최대 용량:" @@ -6872,10 +6890,6 @@ msgstr "코드 생성 중" msgid "Swap failed." msgstr "스왑 실패." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "GMX 추천 프로그램을 통해 수수료 할인과 리베이트를 msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "{gmOrGlvLabel} 판매 작업이 실패했습니다. 자금은 안전하며 현재 GMX 계정에 보관되어 있습니다. 정산 체인으로 전환하고 {indexName} 풀로 이동하여 {gmOrGlvLabel} 토큰을 수동으로 판매할 수 있습니다." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "{nativeTokenSymbol}을 {chainName}으로 구매 또는 전송" @@ -7694,10 +7704,6 @@ msgstr "펀딩 청구됨." msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "포지션이 최대 허용 레버리지 초과로 레버리지 유지 불가. <0>더 읽기." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "스탑 로스 실패" @@ -8255,10 +8261,6 @@ msgstr "시장 주문 취소 가능 ..." msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "현재 Botanix에서는 GMX 스테이킹이 지원되지 않습니다. 이 기능에 접근하려면 Arbitrum 및 Avalanche 배포를 방문하세요." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "{glvOrGm} 입금" diff --git a/src/locales/pseudo/messages.po b/src/locales/pseudo/messages.po index ee5e6b9e33..cfc01efeed 100644 --- a/src/locales/pseudo/messages.po +++ b/src/locales/pseudo/messages.po @@ -1274,6 +1274,11 @@ msgstr "" msgid "Failed" msgstr "" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "" @@ -2054,6 +2059,11 @@ msgstr "" msgid "Earn rewards by sharing your code!" msgstr "" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "" @@ -2937,6 +2947,11 @@ msgstr "" msgid "Withdrawing from subaccount..." msgstr "" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "" @@ -3872,6 +3887,10 @@ msgstr "" msgid "Set TP/SL" msgstr "" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "" msgid "Click here to give us your feedback on GMX" msgstr "" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "" @@ -4773,6 +4788,10 @@ msgstr "" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "" + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "" @@ -5853,6 +5872,10 @@ msgstr "" msgid "market" msgstr "" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "" msgid "Vested GMX not withdrawn" msgstr "" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "" @@ -6872,10 +6890,6 @@ msgstr "" msgid "Swap failed." msgstr "" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "" msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "" @@ -7694,10 +7704,6 @@ msgstr "" msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "" @@ -8255,10 +8261,6 @@ msgstr "" msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "" diff --git a/src/locales/ru/messages.po b/src/locales/ru/messages.po index 748547ebe7..64292a9825 100644 --- a/src/locales/ru/messages.po +++ b/src/locales/ru/messages.po @@ -1274,6 +1274,11 @@ msgstr "Лимитный обмен не удался" msgid "Failed" msgstr "Ошибка" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "Без комиссии" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "Ликв. {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "Система" msgid "Earn rewards by sharing your code!" msgstr "Зарабатывайте награды, делясь своим кодом!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "Мгновенно" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "Открытые позиции" @@ -2937,6 +2947,11 @@ msgstr "Как начать на GMX?" msgid "Withdrawing from subaccount..." msgstr "Вывод с субаккаунта…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "Вывод не удался" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "Всего Стейкано" @@ -3872,6 +3887,10 @@ msgstr "Почему я получаю два разных токена GLV?" msgid "Set TP/SL" msgstr "Установить TP/SL" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "Все сети" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "Выбрать токен" msgid "Click here to give us your feedback on GMX" msgstr "Нажмите здесь, чтобы оставить отзыв о GMX" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "Экспозиция к PnL Трейдеров Рынка" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "Оптимальный" @@ -4773,6 +4788,10 @@ msgstr "Залог ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "У вас есть существующая позиция с {0} в качестве обеспечения. Этот ордер не будет действителен для этой позиции. <0>Переключиться на обеспечение {1}" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "Вы выводите {0}, ваш газовый токен. Газ требуется для экспресс-трейдинга, поэтому пожалуйста, оставьте как минимум <0>{1} в {2} или переключите газовый токен в <1>настройках." + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "Вы заработаете {0}% больше вознаграждений с этим действием." @@ -5853,6 +5872,10 @@ msgstr "<0>Переключитесь на пул {0} — возможно, msgid "market" msgstr "рынок" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "Вывод {0} на {1} в настоящее время не поддерживается" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "Дополнительные возможности" msgid "Vested GMX not withdrawn" msgstr "Вестинговый GMX не выведен" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "Максимальная ёмкость:" @@ -6872,10 +6890,6 @@ msgstr "Создание кода" msgid "Swap failed." msgstr "Обмен не удался." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "Получайте скидки на комиссии и зарабат msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "Операция продажи {gmOrGlvLabel} не удалась. Ваши средства в безопасности и сейчас хранятся на вашем аккаунте GMX. Вы можете переключиться на расчётную сеть и перейти в пул {indexName}, чтобы вручную продать токены {gmOrGlvLabel}." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "Купить или перевести {nativeTokenSymbol} в {chainName}" @@ -7694,10 +7704,6 @@ msgstr "Финансирование клеймлено." msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "Сохранение плеча недоступно, поскольку позиция превышает макс. допустимое плечо. <0>Подробнее." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "Неудачный стоп-лосс" @@ -8255,10 +8261,6 @@ msgstr "Рыночный ордер можно будет отменить че msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "Стейкинг GMX на Botanix пока не поддерживается. Для доступа к этим функциям перейдите на развертывания Arbitrum или Avalanche." -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "Депозит {glvOrGm}" diff --git a/src/locales/zh/messages.po b/src/locales/zh/messages.po index 01c118295d..fe88f82fb7 100644 --- a/src/locales/zh/messages.po +++ b/src/locales/zh/messages.po @@ -1274,6 +1274,11 @@ msgstr "限价交换失败" msgid "Failed" msgstr "失败" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "No fee" +msgstr "无费用" + #: src/context/SyntheticsStateContext/selectors/chartSelectors/selectChartLines.tsx msgid "Liq. {longOrShortText} - {marketIndexName}" msgstr "清算 {longOrShortText} - {marketIndexName}" @@ -2054,6 +2059,11 @@ msgstr "系统" msgid "Earn rewards by sharing your code!" msgstr "分享您的推荐码赚取奖励!" +#: src/components/GmxAccountModal/DepositView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Instant" +msgstr "立即" + #: src/components/TableMarketFilter/MarketFilterLongShort.tsx msgid "Open positions" msgstr "未平仓位" @@ -2937,6 +2947,11 @@ msgstr "如何在 GMX 上入门?" msgid "Withdrawing from subaccount..." msgstr "正在从子账户提取…" +#: src/components/GmxAccountModal/WithdrawalView.tsx +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawal failed" +msgstr "提取失败" + #: src/pages/Dashboard/GmxCard.tsx msgid "Total Staked" msgstr "总质押" @@ -3872,6 +3887,10 @@ msgstr "为什么我会收到两种不同的 GLV 代币?" msgid "Set TP/SL" msgstr "设置 TP/SL" +#: src/components/GmxAccountModal/SelectAssetToDepositView.tsx +msgid "All Networks" +msgstr "所有网络" + #: src/components/TVChart/ChartHeader.tsx #: src/components/TVChart/ChartHeader.tsx msgid "Net Rate / 1h" @@ -4130,10 +4149,6 @@ msgstr "选择代币" msgid "Click here to give us your feedback on GMX" msgstr "点击这里给我们关于 GMX 的反馈" -#: src/pages/PoolsDetails/PoolsDetails.tsx -#~ msgid "Exposure to Market Traders’ PnL" -#~ msgstr "对市场交易者 PnL 的敞口" - #: src/components/SettingsModal/TradingSettings.tsx msgid "Optimal" msgstr "最佳" @@ -4773,6 +4788,10 @@ msgstr "抵押品 ({0})" msgid "You have an existing position with {0} as collateral. This order will not be valid for that position. <0>Switch to {1} collateral" msgstr "您有一个以 {0} 作为抵押品的现有仓位。此订单对该仓位无效。<0>切换到 {1} 抵押品" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "You're withdrawing {0}, your gas token. Gas is required for express trading, so please keep at least <0>{1} in {2} or switch your gas token in <1>settings." +msgstr "您正在提取 {0},您的 gas 代币。快速交易需要 gas,因此请至少保留 <0>{1} 在 {2} 中,或在<1>设置中切换您的 gas 代币。" + #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/StakeModal.tsx msgid "You will earn {0}% more rewards with this action." msgstr "通过此操作,您将赚取 {0}% 更多的奖励。" @@ -5853,6 +5872,10 @@ msgstr "<0>切换至 {0} 资金池以可能获得更低的净价格冲击" msgid "market" msgstr "市场" +#: src/components/GmxAccountModal/WithdrawalView.tsx +msgid "Withdrawing {0} to {1} is not currently supported" +msgstr "目前不支持将 {0} 提取到 {1}" + #: src/components/Referrals/JoinReferralCode.tsx #: src/components/Referrals/JoinReferralCode.tsx msgid "Same as current active code" @@ -6353,11 +6376,6 @@ msgstr "更多机会" msgid "Vested GMX not withdrawn" msgstr "已归属 GMX 未提取" -#: src/components/Errors/ErrorBoundary.tsx -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Report issue" -#~ msgstr "" - #: src/components/Earn/Portfolio/AssetsList/GmxAssetCard/VestModal.tsx msgid "Max Capacity:" msgstr "最大容量:" @@ -6872,10 +6890,6 @@ msgstr "正在创建推荐码" msgid "Swap failed." msgstr "交换失败。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the problem persists." -#~ msgstr "" - #: src/components/BridgeModal/BridgeInModal.tsx #: src/components/BridgeModal/BridgeOutModal.tsx #: src/components/ClaimablePositionPriceImpactRebateModal/ClaimablePositionPriceImpactRebateModal.tsx @@ -6938,10 +6952,6 @@ msgstr "通过 GMX 推荐程序获得费用折扣并赚取返利。<0/>更多信 msgid "Sell {gmOrGlvLabel} operation failed. Your funds are safe and currently stored in your GMX account. You can switch to settlement chain and go to the {indexName} pool to manually sell your {gmOrGlvLabel} tokens." msgstr "{gmOrGlvLabel} 卖出操作失败。您的资金是安全的,目前存储在您的 GMX 账户中。您可以切换到结算链,前往 {indexName} 资金池手动卖出 {gmOrGlvLabel} 代币。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Something went wrong" -#~ msgstr "" - #: src/pages/BuyGMX/BuyGMX.tsx msgid "Buy or Transfer {nativeTokenSymbol} to {chainName}" msgstr "购买或转移 {nativeTokenSymbol} 到 {chainName}" @@ -7694,10 +7704,6 @@ msgstr "资金已申领。" msgid "Keep leverage is not available as Position exceeds max. allowed leverage. <0>Read more." msgstr "保持杠杆不可用,因为仓位超过最大允许杠杆。<0>阅读更多。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "Please try reloading or report the issue if the <0/> problem persists." -#~ msgstr "" - #: src/components/TradeHistory/keys.ts msgid "Failed Stop Loss" msgstr "止损失败" @@ -8255,10 +8261,6 @@ msgstr "市价订单将在 ... 可取消" msgid "Staking GMX is currently not supported on Botanix. For access to these features, please visit the Arbitrum and Avalanche deployments." msgstr "Botanix 当前不支持质押 GMX。如需使用这些功能,请访问 Arbitrum 和 Avalanche 部署。" -#: src/components/Errors/ErrorBoundary.tsx -#~ msgid "ERROR CODE: {errorCode}" -#~ msgstr "" - #: src/pages/PoolsDetails/PoolsDetailsHeader.tsx msgid "Deposit {glvOrGm}" msgstr "存入 {glvOrGm}"