From c1472d53630d9871496d4d792fc18bebba4e0058 Mon Sep 17 00:00:00 2001 From: rassadin11 Date: Thu, 14 May 2026 21:42:16 +0300 Subject: [PATCH] 14.05.2026 rip --- src/features/wallet/api/walletApi.ts | 36 ++++++++++++++++++ src/features/wallet/index.ts | 4 +- src/features/wallet/model/useWalletData.ts | 22 ++++++++++- src/widgets/swap-form/model/useSwapForm.ts | 17 +++++---- src/widgets/swap-form/ui/SwapForm.tsx | 44 ++++++++++++++++++++-- 5 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/features/wallet/api/walletApi.ts b/src/features/wallet/api/walletApi.ts index 652890c..e409e29 100644 --- a/src/features/wallet/api/walletApi.ts +++ b/src/features/wallet/api/walletApi.ts @@ -151,3 +151,39 @@ export async function getPortfolio(): Promise { const res = await walletGet<{ success: boolean; data: PortfolioData }>('/api/wallets/portfolio') return res.data } + +export interface TokenInfo { + chain: string + symbol: string + name: string + contract: string | null +} + +export interface RelayQuotePayload { + user: string + recipient: string + originChainId: number + destinationChainId: number + originCurrency: string + destinationCurrency: string + amount: string + tradeType: 'EXACT_INPUT' +} + +export interface RelayQuoteResponse { + details: { + currencyOut: { + amountFormatted: string + amountUsd: string + } + } +} + +export async function getTokensList(): Promise { + const res = await walletGet<{ success: boolean; data: TokenInfo[] }>('/api/tokens') + return res.data +} + +export async function getRelayQuote(payload: RelayQuotePayload): Promise { + return walletPost('/api/relay/quote', payload) +} diff --git a/src/features/wallet/index.ts b/src/features/wallet/index.ts index 0357ce7..d9837c6 100644 --- a/src/features/wallet/index.ts +++ b/src/features/wallet/index.ts @@ -1,3 +1,3 @@ -export { useAllWalletBalances, usePrices, useSendWallet, useWalletAddresses, useWalletBalance, usePortfolio } from './model/useWalletData' -export type { Chain, FormattedAmount, WalletBalanceData, PriceEntry, SendWalletPayload, SendWalletResponse, WalletAddress, PortfolioData, PortfolioChain, PortfolioNative, PortfolioToken } from './api/walletApi' +export { useAllWalletBalances, usePrices, useSendWallet, useWalletAddresses, useWalletBalance, usePortfolio, useTokensList, useRelayQuote } from './model/useWalletData' +export type { Chain, FormattedAmount, WalletBalanceData, PriceEntry, SendWalletPayload, SendWalletResponse, WalletAddress, PortfolioData, PortfolioChain, PortfolioNative, PortfolioToken, TokenInfo, RelayQuotePayload, RelayQuoteResponse } 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 c887f2b..f2e2270 100644 --- a/src/features/wallet/model/useWalletData.ts +++ b/src/features/wallet/model/useWalletData.ts @@ -1,5 +1,5 @@ import { useQuery, useQueries, useMutation } from '@tanstack/react-query' -import { getWalletBalance, getPrices, sendWallet, getWalletAddresses, getPortfolio, CHAINS, type Chain, type SendWalletPayload } from '../api/walletApi' +import { getWalletBalance, getPrices, sendWallet, getWalletAddresses, getPortfolio, getTokensList, getRelayQuote, CHAINS, type Chain, type SendWalletPayload, type RelayQuotePayload } from '../api/walletApi' export function useWalletBalance(chain: Chain) { return useQuery({ @@ -49,3 +49,23 @@ export function usePortfolio() { staleTime: 30_000, }) } + +export function useTokensList() { + return useQuery({ + queryKey: ['wallet', 'tokens'], + queryFn: getTokensList, + staleTime: 10 * 60 * 1000, + }) +} + +export function useRelayQuote(payload: RelayQuotePayload | null) { + return useQuery({ + queryKey: ['relay', 'quote', + payload?.originChainId, payload?.originCurrency, + payload?.destinationCurrency, payload?.amount, + ], + queryFn: () => getRelayQuote(payload!), + enabled: !!payload, + staleTime: 10_000, + }) +} diff --git a/src/widgets/swap-form/model/useSwapForm.ts b/src/widgets/swap-form/model/useSwapForm.ts index 07c53d2..0ec6a9d 100644 --- a/src/widgets/swap-form/model/useSwapForm.ts +++ b/src/widgets/swap-form/model/useSwapForm.ts @@ -14,16 +14,17 @@ export interface Token { network: string balance: number usdRate: number + decimals: number } const TOKENS: Record = { - BTC: { symbol: 'BTC', letter: '₿', logo: btc, color: '#F7931A', network: 'BITCOIN', balance: 0, usdRate: 67412 }, - ETH: { symbol: 'ETH', letter: 'E', logo: eth, color: '#627EEA', network: 'ETHEREUM', balance: 0, usdRate: 3521 }, - SOL: { symbol: 'SOL', letter: 'S', logo: sol, color: '#9945FF', network: 'SOLANA', balance: 0.994, usdRate: 163.84 }, - TRX: { symbol: 'TRX', letter: 'T', logo: trx, color: '#FF060A', network: 'TRON', balance: 0, usdRate: 0.12 }, - ARB: { symbol: 'ARB', letter: 'A', logo: arb, color: '#4A6DFF', network: 'ARBITRUM', balance: 0, usdRate: 0.92 }, - USDC: { symbol: 'USDC', letter: '$', color: '#2775CA', network: 'SOLANA', balance: 0, usdRate: 1 }, - USDT: { symbol: 'USDT', letter: '$', color: '#26A17B', network: 'ETHEREUM', balance: 0, usdRate: 1 }, + BTC: { symbol: 'BTC', letter: '₿', logo: btc, color: '#F7931A', network: 'BITCOIN', balance: 0, usdRate: 67412, decimals: 8 }, + ETH: { symbol: 'ETH', letter: 'E', logo: eth, color: '#627EEA', network: 'ETHEREUM', balance: 0, usdRate: 3521, decimals: 18 }, + SOL: { symbol: 'SOL', letter: 'S', logo: sol, color: '#9945FF', network: 'SOLANA', balance: 0.994, usdRate: 163.84, decimals: 9 }, + TRX: { symbol: 'TRX', letter: 'T', logo: trx, color: '#FF060A', network: 'TRON', balance: 0, usdRate: 0.12, decimals: 6 }, + ARB: { symbol: 'ARB', letter: 'A', logo: arb, color: '#4A6DFF', network: 'ARBITRUM', balance: 0, usdRate: 0.92, decimals: 18 }, + USDC: { symbol: 'USDC', letter: '$', color: '#2775CA', network: 'SOLANA', balance: 0, usdRate: 1, decimals: 6 }, + USDT: { symbol: 'USDT', letter: '$', color: '#26A17B', network: 'ETHEREUM', balance: 0, usdRate: 1, decimals: 6 }, } export const TOKENS_LIST: Token[] = Object.values(TOKENS) @@ -80,6 +81,7 @@ export function buildTokensFromBalance(data: WalletBalanceData): Token[] { network: data.chain, balance: parseFloat(data.native.formatted), usdRate: data.native.usdPrice, + decimals: data.native.decimals, }) for (const [sym, info] of Object.entries(data.tokens)) { @@ -92,6 +94,7 @@ export function buildTokensFromBalance(data: WalletBalanceData): Token[] { network: data.chain, balance: parseFloat(info.formatted), usdRate: info.usdPrice, + decimals: info.decimals, }) } diff --git a/src/widgets/swap-form/ui/SwapForm.tsx b/src/widgets/swap-form/ui/SwapForm.tsx index 81a8aea..7203ba5 100644 --- a/src/widgets/swap-form/ui/SwapForm.tsx +++ b/src/widgets/swap-form/ui/SwapForm.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import { PrimaryButton } from '@shared/ui' -import { useWalletBalance, type Chain } from '@features/wallet' +import { useWalletBalance, useWalletAddresses, useTokensList, useRelayQuote, type Chain } from '@features/wallet' +import { useDebounce } from '@shared/lib/hooks/useDebounce' import { TOKENS_LIST, buildTokensFromBalance, useSwapForm } from '../model/useSwapForm' import { RateRow } from './RateRow' import { SwapCard } from './SwapCard' @@ -10,6 +11,15 @@ import styles from './SwapForm.module.css' const RATE = 82.2578 +const CHAIN_ID: Record = { ETH: 1, BSC: 56, SOL: 792703809 } +const NATIVE_ADDR: Record = { + SOL: '11111111111111111111111111111111', + DEFAULT: '0x0000000000000000000000000000000000000000', +} +function nativeAddr(chain: string) { + return NATIVE_ADDR[chain] ?? NATIVE_ADDR.DEFAULT +} + export function SwapForm() { const { fromAmount, toAmount, fromUsd, toUsd, @@ -30,6 +40,34 @@ export function SwapForm() { setToToken(t => tokenOptions.find(o => o.symbol === t.symbol) ?? (tokenOptions[1] ?? tokenOptions[0])) }, [walletData, fromNetwork]) + const debouncedAmount = useDebounce(fromAmount, 500) + const { data: addresses } = useWalletAddresses() + const { data: tokensList } = useTokensList() + + const chainId = CHAIN_ID[fromNetwork] + const walletAddress = addresses?.find(a => a.chain === fromNetwork)?.address + const fromContract = tokensList?.find(t => t.chain === fromNetwork && t.symbol === fromToken.symbol)?.contract ?? nativeAddr(fromNetwork) + const toContract = tokensList?.find(t => t.chain === fromNetwork && t.symbol === toToken.symbol)?.contract ?? nativeAddr(fromNetwork) + + const parsedAmount = parseFloat(debouncedAmount) + const quotePayload = chainId && walletAddress && parsedAmount > 0 + ? { + user: walletAddress, + recipient: walletAddress, + originChainId: chainId, + destinationChainId: chainId, + originCurrency: fromContract, + destinationCurrency: toContract, + amount: Math.round(parsedAmount * Math.pow(10, fromToken.decimals)).toString(), + tradeType: 'EXACT_INPUT' as const, + } + : null + + const { data: quoteData } = useRelayQuote(quotePayload) + + const displayToAmount = quoteData?.details.currencyOut.amountFormatted ?? toAmount + const displayToUsd = quoteData?.details.currencyOut.amountUsd ?? toUsd + return (