14.05.2026 rip

This commit is contained in:
2026-05-14 21:42:16 +03:00
parent c2a1ca3ee5
commit c1472d5363
5 changed files with 110 additions and 13 deletions

View File

@@ -151,3 +151,39 @@ export async function getPortfolio(): Promise<PortfolioData> {
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<TokenInfo[]> {
const res = await walletGet<{ success: boolean; data: TokenInfo[] }>('/api/tokens')
return res.data
}
export async function getRelayQuote(payload: RelayQuotePayload): Promise<RelayQuoteResponse> {
return walletPost<RelayQuoteResponse>('/api/relay/quote', payload)
}

View File

@@ -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'

View File

@@ -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,
})
}

View File

@@ -14,16 +14,17 @@ export interface Token {
network: string
balance: number
usdRate: number
decimals: number
}
const TOKENS: Record<string, Token> = {
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,
})
}

View File

@@ -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<string, number> = { ETH: 1, BSC: 56, SOL: 792703809 }
const NATIVE_ADDR: Record<string, string> = {
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 (
<div className={styles.form}>
<SwapCard
@@ -51,8 +89,8 @@ export function SwapForm() {
mode="to"
token={toToken}
tokenOptions={tokenOptions}
amount={toAmount}
usd={toUsd}
amount={displayToAmount}
usd={displayToUsd}
slippage="0.16%"
onTokenChange={setToToken}
/>