This commit is contained in:
2026-06-01 23:33:55 +03:00
parent b86f3209f5
commit 0409d63874
5 changed files with 76 additions and 108 deletions

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ЭКСА — Ваш мост в мир цифровых активов</title> <title>ЭКСА — Ваш мост в мир цифровых активов</title>
<script type="module" crossorigin src="/assets/index-lpDOSqEV.js"></script> <script type="module" crossorigin src="/assets/index-C426d72k.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CnYQBevk.css"> <link rel="stylesheet" crossorigin href="/assets/index-CnYQBevk.css">
</head> </head>
<body> <body>

View File

@@ -41,30 +41,20 @@ export interface WalletAddress {
derivationPath: string derivationPath: string
} }
export interface PortfolioToken {
symbol: string
amountFormatted: string
usd: number
}
export interface PortfolioNative {
amount: string
amountFormatted: string
usd: number
}
export interface PortfolioChain { export interface PortfolioChain {
chain: Chain
address: string address: string
stale: boolean native: FormattedAmount
native: PortfolioNative tokens: Record<string, FormattedAmount>
tokens: PortfolioToken[]
totalUsd: number totalUsd: number
stale: boolean
lastUpdated: number
} }
export interface PortfolioData { export interface PortfolioData {
totalUsd: number totalUsd: number
asOfMs: number hasErrors: boolean
chains: Record<Chain, PortfolioChain> perChain: Record<Chain, PortfolioChain>
} }
export const CHAINS: Chain[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL'] export const CHAINS: Chain[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL']

View File

@@ -1,3 +1,3 @@
export { useAllWalletBalances, usePrices, useSendWallet, useWalletAddresses, useWalletBalance, usePortfolio, useTokensList, useRelayQuote, useExecuteRelaySwap, useSignSwap, useTrxSwapQuote, useFetchTrxQuote, useExecuteTrxSwap, useJumperTokens, useJumperQuote, 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 type { Chain, FormattedAmount, WalletBalanceData, PriceEntry, SendWalletPayload, SendWalletResponse, WalletAddress, PortfolioData, PortfolioChain, TokenInfo, RelayQuotePayload, RelayQuoteResponse, RelaySwapResponse, RelaySwapStep, TrxSwapQuotePayload, TrxSwapQuoteData, JumperToken, JumperTokensMap, JumperQuote, JumperQuotePayload, JumperQuoteToken, JumperFeeCost, BridgeExecutePayload, BridgeExecuteResult } from './api/walletApi'
export { CHAINS } from './api/walletApi' export { CHAINS } from './api/walletApi'

View File

@@ -1,5 +1,5 @@
import { useWalletBalance, usePortfolio, CHAINS } from '@features/wallet' import { useWalletBalance, usePortfolio, CHAINS } from '@features/wallet'
import type { Chain } from '@features/wallet' import type { Chain, FormattedAmount } from '@features/wallet'
import { getCoinIcon } from '@shared/assets/coins' import { getCoinIcon } from '@shared/assets/coins'
import { truncateDecimals } from '@shared/lib/utils/truncateDecimals' import { truncateDecimals } from '@shared/lib/utils/truncateDecimals'
import { TOKENS, type Token } from './tokens' import { TOKENS, type Token } from './tokens'
@@ -39,102 +39,80 @@ function lookupStatic(ticker: string): Token | undefined {
return TOKENS.find((t) => t.ticker === ticker) return TOKENS.find((t) => t.ticker === ticker)
} }
function nativeRowFor(chain: Chain, native: FormattedAmount): Token {
const ticker = NATIVE_TICKER[chain]
const staticToken = lookupStatic(ticker)
return {
id: `${chain}-${ticker}`,
chain,
ticker,
name: NATIVE_NAME[chain],
logo: getCoinIcon(ticker) ?? staticToken?.logo,
color: staticToken?.color ?? DEFAULT_TOKEN_COLOR,
price: formatPrice(native.usdPrice),
change: 0,
bal: truncateDecimals(native.formatted),
usd: formatUsd(native.usdValue),
fav: false,
}
}
function tokenRowFor(chain: Chain, symbol: string, amount: FormattedAmount): Token {
const staticToken = lookupStatic(symbol)
return {
id: `${chain}-${symbol}`,
chain,
ticker: symbol,
name: staticToken?.name ?? symbol,
logo: getCoinIcon(symbol) ?? staticToken?.logo,
color: staticToken?.color ?? DEFAULT_TOKEN_COLOR,
price: formatPrice(amount.usdPrice),
change: 0,
bal: truncateDecimals(amount.formatted),
usd: formatUsd(amount.usdValue),
fav: false,
}
}
function hasBalance(amount: FormattedAmount): boolean {
return parseFloat(amount.formatted) > 0
}
export function useChainTokenRows(chain: Chain) { export function useChainTokenRows(chain: Chain) {
const { data, isLoading } = useWalletBalance(chain) const { data, isLoading } = useWalletBalance(chain)
if (!data) return { rows: [] as Token[], isLoading } if (!data) return { rows: [] as Token[], isLoading }
const nativeTicker = NATIVE_TICKER[chain] const nativeRow = nativeRowFor(chain, data.native)
const nativeStatic = lookupStatic(nativeTicker) const tokenRows = Object.entries(data.tokens ?? {}).map(([symbol, amount]) =>
tokenRowFor(chain, symbol, amount),
const nativeRow: Token = { )
ticker: nativeTicker,
name: NATIVE_NAME[chain],
logo: getCoinIcon(nativeTicker) ?? nativeStatic?.logo,
color: nativeStatic?.color ?? DEFAULT_TOKEN_COLOR,
price: formatPrice(data.native.usdPrice),
change: 0,
bal: truncateDecimals(data.native.formatted),
usd: formatUsd(data.native.usdValue),
fav: false,
}
const tokenRows: Token[] = Object.entries(data.tokens ?? {}).map(([symbol, amount]) => {
const staticToken = lookupStatic(symbol)
return {
ticker: symbol,
name: staticToken?.name ?? symbol,
logo: getCoinIcon(symbol) ?? staticToken?.logo,
color: staticToken?.color ?? DEFAULT_TOKEN_COLOR,
price: formatPrice(amount.usdPrice),
change: 0,
bal: truncateDecimals(amount.formatted),
usd: formatUsd(amount.usdValue),
fav: false,
}
})
return { rows: [nativeRow, ...tokenRows], isLoading } return { rows: [nativeRow, ...tokenRows], isLoading }
} }
function hasBalance(amountFormatted: string): boolean {
return parseFloat(amountFormatted) > 0
}
function unitPrice(usd: number, amountFormatted: string): number | null {
const amount = parseFloat(amountFormatted)
return amount > 0 && usd > 0 ? usd / amount : null
}
export function useAllWalletTokenRows() { export function useAllWalletTokenRows() {
const { data, isLoading } = usePortfolio() const { data, isLoading } = usePortfolio()
if (!data) return { rows: [] as Token[], isLoading } if (!data) return { rows: [] as Token[], isLoading }
const rows: Token[] = [] const entries: { row: Token; value: number }[] = []
for (const chain of CHAINS) { for (const chain of CHAINS) {
const chainData = data.chains?.[chain] const chainData = data.perChain?.[chain]
if (!chainData) continue if (!chainData) continue
const nativeTicker = NATIVE_TICKER[chain] if (chainData.native && hasBalance(chainData.native)) {
if (chainData.native && hasBalance(chainData.native.amountFormatted)) { entries.push({ row: nativeRowFor(chain, chainData.native), value: chainData.native.usdValue })
const nativeStatic = lookupStatic(nativeTicker)
rows.push({
id: `${chain}-${nativeTicker}`,
chain,
ticker: nativeTicker,
name: NATIVE_NAME[chain],
logo: getCoinIcon(nativeTicker) ?? nativeStatic?.logo,
color: nativeStatic?.color ?? DEFAULT_TOKEN_COLOR,
price: formatPrice(unitPrice(chainData.native.usd, chainData.native.amountFormatted)),
change: 0,
bal: truncateDecimals(chainData.native.amountFormatted),
usd: formatUsd(chainData.native.usd),
fav: false,
})
} }
for (const token of chainData.tokens ?? []) { for (const [symbol, amount] of Object.entries(chainData.tokens ?? {})) {
if (!hasBalance(token.amountFormatted)) continue if (!hasBalance(amount)) continue
const staticToken = lookupStatic(token.symbol) entries.push({ row: tokenRowFor(chain, symbol, amount), value: amount.usdValue })
rows.push({
id: `${chain}-${token.symbol}`,
chain,
ticker: token.symbol,
name: staticToken?.name ?? token.symbol,
logo: getCoinIcon(token.symbol) ?? staticToken?.logo,
color: staticToken?.color ?? DEFAULT_TOKEN_COLOR,
price: formatPrice(unitPrice(token.usd, token.amountFormatted)),
change: 0,
bal: truncateDecimals(token.amountFormatted),
usd: formatUsd(token.usd),
fav: false,
})
} }
} }
rows.sort((a, b) => (parseFloat(b.usd.replace(/[$,—]/g, '')) || 0) - (parseFloat(a.usd.replace(/[$,—]/g, '')) || 0)) entries.sort((a, b) => b.value - a.value)
return { rows, isLoading } return { rows: entries.map((e) => e.row), isLoading }
} }