This commit is contained in:
2026-06-18 16:03:06 +03:00
parent 7236bab31b
commit 5c74b54c82
6 changed files with 32 additions and 23 deletions

View File

@@ -1,9 +1,16 @@
import { COIN_ICONS } from '@shared/assets/coins' import { COIN_ICONS } from '@shared/assets/coins'
/** Wrapper-токены → их нативные аналоги (WETH→ETH, WBTC→BTC). */
const NATIVE_SYMBOL: Record<string, string> = { WETH: 'ETH', WBTC: 'BTC' }
/** Отображаемый символ: wrapper-токены показываем как нативные (WETH→ETH, WBTC→BTC). */
export function displaySymbol(symbol: string): string {
return NATIVE_SYMBOL[symbol] ?? symbol
}
/** Иконка по символу токена: WETH→ETH, WBTC→BTC, иначе по тикеру (может вернуть undefined). */ /** Иконка по символу токена: WETH→ETH, WBTC→BTC, иначе по тикеру (может вернуть undefined). */
export function coinIcon(symbol: string): string | undefined { export function coinIcon(symbol: string): string | undefined {
const map: Record<string, string> = { WETH: 'ETH', WBTC: 'BTC' } return COIN_ICONS[displaySymbol(symbol)]
return COIN_ICONS[map[symbol] ?? symbol]
} }
/** feeTier (сотые доли процента) → человекочитаемые проценты: 3000 → «0.3%». */ /** feeTier (сотые доли процента) → человекочитаемые проценты: 3000 → «0.3%». */

View File

@@ -2,7 +2,7 @@ import { PrimaryButton } from '@shared/ui'
import { truncateDecimals } from '@shared/lib/utils/truncateDecimals' import { truncateDecimals } from '@shared/lib/utils/truncateDecimals'
import type { Pool, LpQuote } from '@features/pools' import type { Pool, LpQuote } from '@features/pools'
import { useWalletBalance, type WalletBalanceData } from '@features/wallet' import { useWalletBalance, type WalletBalanceData } from '@features/wallet'
import { formatFeeTier } from '../model/meta' import { formatFeeTier, displaySymbol } from '../model/meta'
import styles from './LpDepositCard.module.css' import styles from './LpDepositCard.module.css'
interface Props { interface Props {
@@ -26,10 +26,11 @@ interface Props {
const DASH = '—' const DASH = '—'
const decimalRe = /^(\d+\.?\d*|\.?\d*)$/ const decimalRe = /^(\d+\.?\d*|\.?\d*)$/
// Баланс ноги по символу: нативный токен (chain === символ) берём из native, остальное — из tokens. // Баланс ноги по символу. Wrapper-токен нативной монеты (WETH на ETH) показываем как баланс
// нативной монеты; остальные токены берём из tokens по исходному символу.
function legBalance(wb: WalletBalanceData | undefined, symbol: string): string { function legBalance(wb: WalletBalanceData | undefined, symbol: string): string {
if (!wb) return '0' if (!wb) return '0'
if (symbol === wb.chain) return wb.native.formatted if (displaySymbol(symbol) === wb.chain) return wb.native.formatted
return wb.tokens[symbol]?.formatted ?? '0' return wb.tokens[symbol]?.formatted ?? '0'
} }
@@ -141,7 +142,7 @@ export function LpDepositCard({
<div className={styles.card}> <div className={styles.card}>
<div className={styles.top}> <div className={styles.top}>
<span className={styles.pair}> <span className={styles.pair}>
{pool.symbol0}/{pool.symbol1} {displaySymbol(pool.symbol0)}/{displaySymbol(pool.symbol1)}
</span> </span>
<span className={styles.fee}>{formatFeeTier(pool.feeTier)}</span> <span className={styles.fee}>{formatFeeTier(pool.feeTier)}</span>
</div> </div>
@@ -149,20 +150,20 @@ export function LpDepositCard({
<span className={styles.tag}>Суммы взноса</span> <span className={styles.tag}>Суммы взноса</span>
<div className={styles.amounts}> <div className={styles.amounts}>
<AmountInput <AmountInput
symbol={pool.symbol0} symbol={displaySymbol(pool.symbol0)}
value={amount0} value={amount0}
onChange={onAmount0Change} onChange={onAmount0Change}
balance={legBalance(walletBalance, pool.symbol0)} balance={legBalance(walletBalance, pool.symbol0)}
/> />
<AmountInput <AmountInput
symbol={pool.symbol1} symbol={displaySymbol(pool.symbol1)}
value={amount1} value={amount1}
onChange={onAmount1Change} onChange={onAmount1Change}
balance={legBalance(walletBalance, pool.symbol1)} balance={legBalance(walletBalance, pool.symbol1)}
/> />
</div> </div>
<span className={styles.tag}>Диапазон цен ({pool.symbol1} за {pool.symbol0})</span> <span className={styles.tag}>Диапазон цен ({displaySymbol(pool.symbol1)} за {displaySymbol(pool.symbol0)})</span>
<div className={styles.range}> <div className={styles.range}>
<PriceInput label="Мин." value={priceLower} onChange={onPriceLowerChange} /> <PriceInput label="Мин." value={priceLower} onChange={onPriceLowerChange} />
<PriceInput label="Макс." value={priceUpper} onChange={onPriceUpperChange} /> <PriceInput label="Макс." value={priceUpper} onChange={onPriceUpperChange} />
@@ -184,11 +185,11 @@ export function LpDepositCard({
<div className={styles.quote} data-loading={quoteFetching ? 'true' : undefined}> <div className={styles.quote} data-loading={quoteFetching ? 'true' : undefined}>
<div className={styles.quoteRow}> <div className={styles.quoteRow}>
<span className={styles.quoteLabel}>Взнос {pool.symbol0}</span> <span className={styles.quoteLabel}>Взнос {displaySymbol(pool.symbol0)}</span>
<span className={styles.quoteValue}>{amount0Out}</span> <span className={styles.quoteValue}>{amount0Out}</span>
</div> </div>
<div className={styles.quoteRow}> <div className={styles.quoteRow}>
<span className={styles.quoteLabel}>Взнос {pool.symbol1}</span> <span className={styles.quoteLabel}>Взнос {displaySymbol(pool.symbol1)}</span>
<span className={styles.quoteValue}>{amount1Out}</span> <span className={styles.quoteValue}>{amount1Out}</span>
</div> </div>
<div className={styles.quoteRow}> <div className={styles.quoteRow}>
@@ -203,7 +204,7 @@ export function LpDepositCard({
{insufficient && ( {insufficient && (
<p className={styles.warning}> <p className={styles.warning}>
Недостаточно средств{shortLeg ? `: не хватает ${shortLeg.symbol}` : ''}. Пополните баланс. Недостаточно средств{shortLeg ? `: не хватает ${displaySymbol(shortLeg.symbol)}` : ''}. Пополните баланс.
</p> </p>
)} )}

View File

@@ -1,7 +1,7 @@
import { Spinner } from '@shared/ui' import { Spinner } from '@shared/ui'
import { truncateDecimals } from '@shared/lib/utils/truncateDecimals' import { truncateDecimals } from '@shared/lib/utils/truncateDecimals'
import type { LpPosition } from '@features/pools' import type { LpPosition } from '@features/pools'
import { formatFeeTier } from '../model/meta' import { formatFeeTier, displaySymbol } from '../model/meta'
import styles from './LpPositionsPanel.module.css' import styles from './LpPositionsPanel.module.css'
interface Props { interface Props {
@@ -43,7 +43,7 @@ export function LpPositionsPanel({ positions, onRemove, removePending, isLoading
<div key={p.tokenId} className={styles.pos}> <div key={p.tokenId} className={styles.pos}>
<div className={styles.posTop}> <div className={styles.posTop}>
<span className={styles.coin}> <span className={styles.coin}>
{p.symbol0}/{p.symbol1} {displaySymbol(p.symbol0)}/{displaySymbol(p.symbol1)}
<span className={styles.fee}>{formatFeeTier(p.feeTier)}</span> <span className={styles.fee}>{formatFeeTier(p.feeTier)}</span>
</span> </span>
<span className={`${styles.badge} ${p.inRange ? styles.badgeActive : styles.badgeWarn}`}> <span className={`${styles.badge} ${p.inRange ? styles.badgeActive : styles.badgeWarn}`}>
@@ -59,17 +59,17 @@ export function LpPositionsPanel({ positions, onRemove, removePending, isLoading
</span> </span>
</div> </div>
<div className={styles.row}> <div className={styles.row}>
<span className={styles.label}>{p.symbol0}</span> <span className={styles.label}>{displaySymbol(p.symbol0)}</span>
<span className={styles.value}>{truncateDecimals(p.currentAmount0Human, 8)}</span> <span className={styles.value}>{truncateDecimals(p.currentAmount0Human, 8)}</span>
</div> </div>
<div className={styles.row}> <div className={styles.row}>
<span className={styles.label}>{p.symbol1}</span> <span className={styles.label}>{displaySymbol(p.symbol1)}</span>
<span className={styles.value}>{truncateDecimals(p.currentAmount1Human, 8)}</span> <span className={styles.value}>{truncateDecimals(p.currentAmount1Human, 8)}</span>
</div> </div>
<div className={styles.row}> <div className={styles.row}>
<span className={styles.label}>Комиссии к выводу</span> <span className={styles.label}>Комиссии к выводу</span>
<span className={`${styles.value} ${styles.success}`}> <span className={`${styles.value} ${styles.success}`}>
{truncateDecimals(p.feesOwed0Human, 8)} {p.symbol0} · {truncateDecimals(p.feesOwed1Human, 8)} {p.symbol1} {truncateDecimals(p.feesOwed0Human, 8)} {displaySymbol(p.symbol0)} · {truncateDecimals(p.feesOwed1Human, 8)} {displaySymbol(p.symbol1)}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import type { LpPosition } from '@features/pools' import type { LpPosition } from '@features/pools'
import { formatFeeTier } from '../model/meta' import { formatFeeTier, displaySymbol } from '../model/meta'
import styles from './LpRemoveModal.module.css' import styles from './LpRemoveModal.module.css'
interface Props { interface Props {
@@ -36,7 +36,7 @@ export function LpRemoveModal({ position, pending, onConfirm, onClose }: Props)
<div className={styles.token}> <div className={styles.token}>
<span className={styles.tokenLabel}>Позиция</span> <span className={styles.tokenLabel}>Позиция</span>
<span className={styles.tokenAmount}> <span className={styles.tokenAmount}>
{position.symbol0}/{position.symbol1} {displaySymbol(position.symbol0)}/{displaySymbol(position.symbol1)}
</span> </span>
<span className={styles.tokenMeta}> <span className={styles.tokenMeta}>
{formatFeeTier(position.feeTier)} · #{position.tokenId} {formatFeeTier(position.feeTier)} · #{position.tokenId}

View File

@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Spinner } from '@shared/ui' import { Spinner } from '@shared/ui'
import type { Pool } from '@features/pools' import type { Pool } from '@features/pools'
import { formatFeeTier } from '../model/meta' import { formatFeeTier, displaySymbol } from '../model/meta'
import styles from './PoolsTable.module.css' import styles from './PoolsTable.module.css'
interface Props { interface Props {
@@ -35,7 +35,7 @@ function formatApr(value: number | null): string {
} }
function pairName(p: Pool): string { function pairName(p: Pool): string {
return `${p.symbol0}/${p.symbol1}` return `${displaySymbol(p.symbol0)}/${displaySymbol(p.symbol1)}`
} }
// Числовые поля могут быть null — null уходит вниз независимо от направления. // Числовые поля могут быть null — null уходит вниз независимо от направления.
@@ -140,7 +140,7 @@ export function PoolsTable({ pools, selected, onSelect, loading, error }: Props)
> >
<td className={styles.tdLeft}> <td className={styles.tdLeft}>
<span className={styles.pairName}> <span className={styles.pairName}>
{p.symbol0}/{p.symbol1} {displaySymbol(p.symbol0)}/{displaySymbol(p.symbol1)}
</span> </span>
</td> </td>
<td> <td>

View File

@@ -11,6 +11,7 @@ import {
type LpDepositInput, type LpDepositInput,
type LpPosition, type LpPosition,
} from '@features/pools' } from '@features/pools'
import { displaySymbol } from '../model/meta'
import { PoolsTable } from './PoolsTable' import { PoolsTable } from './PoolsTable'
import { LpDepositCard } from './LpDepositCard' import { LpDepositCard } from './LpDepositCard'
import { LpPositionsPanel } from './LpPositionsPanel' import { LpPositionsPanel } from './LpPositionsPanel'
@@ -103,7 +104,7 @@ export function PoolsWidget() {
setToast({ status: 'info', message: 'Внесение в пул может занять несколько минут…' }) setToast({ status: 'info', message: 'Внесение в пул может занять несколько минут…' })
addM.mutate(input, { addM.mutate(input, {
onSuccess: () => { onSuccess: () => {
setToast({ status: 'success', message: `Депозит в пул ${pool.symbol0}/${pool.symbol1} выполнен` }) setToast({ status: 'success', message: `Депозит в пул ${displaySymbol(pool.symbol0)}/${displaySymbol(pool.symbol1)} выполнен` })
setAmount0('') setAmount0('')
setAmount1('') setAmount1('')
}, },