diff --git a/src/features/wallet/index.ts b/src/features/wallet/index.ts index 04a174d..b2a287e 100644 --- a/src/features/wallet/index.ts +++ b/src/features/wallet/index.ts @@ -1,3 +1,3 @@ -export { useAllWalletBalances, usePrices, useSendWallet, useWalletAddresses, useWalletBalance, usePortfolio, useTokensList, useRelayQuote, useExecuteRelaySwap, useSignSwap, useTrxSwapQuote, useFetchTrxQuote, useExecuteTrxSwap, useJumperTokens, useFetchJumperQuote, useExecuteBridge, useCreateWallet, useRevealMnemonic } from './model/useWalletData' +export { useAllWalletBalances, usePrices, useSendWallet, useWalletAddresses, useWalletBalance, usePortfolio, useTokensList, useRelayQuote, useExecuteRelaySwap, useSignSwap, useTrxSwapQuote, useFetchTrxQuote, useExecuteTrxSwap, useJumperTokens, useJumperQuote, useFetchJumperQuote, useExecuteBridge, useCreateWallet, useRevealMnemonic } from './model/useWalletData' export type { Chain, FormattedAmount, WalletBalanceData, PriceEntry, SendWalletPayload, SendWalletResponse, WalletAddress, PortfolioData, PortfolioChain, PortfolioNative, PortfolioToken, TokenInfo, RelayQuotePayload, RelayQuoteResponse, RelaySwapResponse, RelaySwapStep, TrxSwapQuotePayload, TrxSwapQuoteData, JumperToken, JumperTokensMap, JumperQuote, JumperQuotePayload, JumperQuoteToken, JumperFeeCost, BridgeExecutePayload, BridgeExecuteResult } from './api/walletApi' export { CHAINS } from './api/walletApi' diff --git a/src/features/wallet/model/useWalletData.ts b/src/features/wallet/model/useWalletData.ts index bd60693..ac0136f 100644 --- a/src/features/wallet/model/useWalletData.ts +++ b/src/features/wallet/model/useWalletData.ts @@ -66,6 +66,19 @@ export function useJumperTokens() { }) } +export function useJumperQuote(payload: JumperQuotePayload | null) { + return useQuery({ + queryKey: ['wallet', 'jumper', 'quote', + payload?.fromChain, payload?.toChain, + payload?.fromToken, payload?.toToken, + payload?.fromAmount, payload?.fromAddress, payload?.toAddress, + ], + queryFn: () => getJumperQuote(payload!), + enabled: !!payload, + staleTime: 10_000, + }) +} + export function useFetchJumperQuote() { return useMutation({ mutationFn: (payload: JumperQuotePayload) => getJumperQuote(payload) }) } diff --git a/src/widgets/bridge-form/ui/BridgeForm.tsx b/src/widgets/bridge-form/ui/BridgeForm.tsx index 48b0d74..86fd0ba 100644 --- a/src/widgets/bridge-form/ui/BridgeForm.tsx +++ b/src/widgets/bridge-form/ui/BridgeForm.tsx @@ -2,12 +2,14 @@ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useQueryClient } from '@tanstack/react-query' import { - useJumperTokens, useWalletBalance, useWalletAddresses, useFetchJumperQuote, useExecuteBridge, - type Chain, type JumperToken, type WalletBalanceData, type JumperQuote, + useJumperTokens, useWalletBalance, useWalletAddresses, useJumperQuote, useFetchJumperQuote, useExecuteBridge, + type Chain, type JumperToken, type WalletBalanceData, type JumperQuote, type JumperQuotePayload, } from '@features/wallet' import { Notification, PrimaryButton } from '@shared/ui' import { ROUTES } from '@shared/config/routes' -import { toBaseUnits } from '@shared/lib/utils/baseUnits' +import { useDebounce } from '@shared/lib/hooks/useDebounce' +import { toBaseUnits, fromBaseUnits } from '@shared/lib/utils/baseUnits' +import { truncateDecimals } from '@shared/lib/utils/truncateDecimals' import { TOKEN_META, buildTokensFromBalance, useSwapForm, type Token, @@ -93,32 +95,41 @@ export function BridgeForm() { setToToken(t => toTokenOptions.find(o => o.symbol === t.symbol) ?? toTokenOptions[0]) }, [jumperData, toWalletData, toNetwork]) + const debouncedAmount = useDebounce(fromAmount, 500) + const parsedAmount = parseFloat(debouncedAmount) + const fromJumper = jumperData?.[CHAIN_ID_BY_NET[fromNetwork]]?.find(t => t.symbol === fromToken.symbol) const toJumper = jumperData?.[CHAIN_ID_BY_NET[toNetwork]]?.find(t => t.symbol === toToken.symbol) const fromAddress = addresses?.find(a => a.chain === fromNetwork)?.address const toAddress = addresses?.find(a => a.chain === toNetwork)?.address - const parsedAmount = parseFloat(fromAmount) - const canQuote = !!fromJumper && !!toJumper && !!fromAddress && !!toAddress && parsedAmount > 0 - function handleConfirm() { - if (!fromJumper || !toJumper || !fromAddress || !toAddress) return - setErrorMessage(null) - fetchQuote( - { + const quotePayload: JumperQuotePayload | null = + fromJumper && toJumper && fromAddress && toAddress && parsedAmount > 0 + ? { fromChain: CHAIN_ID_BY_NET[fromNetwork], toChain: CHAIN_ID_BY_NET[toNetwork], fromToken: fromJumper.address, toToken: toJumper.address, - fromAmount: toBaseUnits(fromAmount, fromToken.decimals), + fromAmount: toBaseUnits(debouncedAmount, fromToken.decimals), fromAddress, toAddress, slippage: 0.005, - }, - { - onSuccess: (q) => setQuote(q), - onError: (err) => setErrorMessage(err instanceof Error ? err.message : 'Не удалось получить котировку'), - }, - ) + } + : null + + const { data: quoteData, isFetching: isQuoting } = useJumperQuote(quotePayload) + + const displayToAmount = quoteData + ? truncateDecimals(fromBaseUnits(quoteData.estimate.toAmount, quoteData.action.toToken.decimals), 8) + : '0' + + function handleConfirm() { + if (!quotePayload) return + setErrorMessage(null) + fetchQuote(quotePayload, { + onSuccess: (q) => setQuote(q), + onError: (err) => setErrorMessage(err instanceof Error ? err.message : 'Не удалось получить котировку'), + }) } function handleExecute() { @@ -183,12 +194,12 @@ export function BridgeForm() { mode="to" token={toToken} tokenOptions={toTokenOptions} - amount="0" + amount={displayToAmount} onTokenChange={setToToken} hideNetworkSelect /> - + {quote && (