import { getCsrfToken } from '@shared/api/csrf' import { tokenStore, refreshAccessToken } from '@shared/api/tokenStore' const WALLET_API_URL = 'https://app.cryptowallet.elcsa.ru' export type Chain = 'ETH' | 'BSC' | 'BTC' | 'TRX' | 'SOL' export interface FormattedAmount { raw: string formatted: string decimals: number usdPrice: number usdValue: number } export interface WalletBalanceData { chain: Chain address: string native: FormattedAmount tokens: Record } export interface PriceEntry { usd: number } export interface SendWalletPayload { to: string amount: string token?: string feeTier?: 'slow' | 'normal' | 'fast' } export interface SendWalletResponse { data: { txid: string; chain: Chain } } export interface WalletAddress { chain: Chain address: string derivationPath: string } export interface PortfolioToken { symbol: string amountFormatted: string usd: number } export interface PortfolioNative { amount: string amountFormatted: string usd: number } export interface PortfolioChain { address: string stale: boolean native: PortfolioNative tokens: PortfolioToken[] totalUsd: number } export interface PortfolioData { totalUsd: number asOfMs: number chains: Record } export const CHAINS: Chain[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL'] async function walletGet(path: string, allowRetry: boolean = true): Promise { const csrf = await getCsrfToken() const bearer = tokenStore.get() const res = await fetch(`${WALLET_API_URL}${path}`, { credentials: 'include', headers: { 'X-CSRF-Token': csrf, ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}), }, }) if (res.status === 401 && allowRetry) { try { await refreshAccessToken() return walletGet(path, false) } catch { tokenStore.clear() throw new Error('Unauthorized') } } const data = await res.json() if (!res.ok) throw data return data as T } async function walletPost( path: string, body: unknown, allowRetry: boolean = true, extraHeaders: Record = {} ): Promise { const csrf = await getCsrfToken() const bearer = tokenStore.get() const res = await fetch(`${WALLET_API_URL}${path}`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}), ...extraHeaders, }, body: JSON.stringify(body), }) if (res.status === 401 && allowRetry) { try { await refreshAccessToken() return walletPost(path, body, false, extraHeaders) } catch { tokenStore.clear() throw new Error('Unauthorized') } } const data = await res.json() if (!res.ok) throw data return data as T } export async function getWalletAddresses(): Promise { const res = await walletGet<{ success: boolean; data: WalletAddress[] }>('/api/wallets') return res.data } export async function getWalletBalance(chain: Chain): Promise { const res = await walletGet<{ success: boolean; data: WalletBalanceData }>(`/api/wallets/${chain}/balance`) return res.data } export async function getPrices(symbols: string[]): Promise> { const res = await walletGet<{ success: boolean; data: Record }>( `/api/prices?symbols=${symbols.join(',')}` ) return res.data } export async function sendWallet(chain: Chain, payload: SendWalletPayload): Promise { return walletPost(`/api/wallets/${chain}/send`, payload) } 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 } } fees: { gas: { amountUsd: string } } } export async function getTokensList(): Promise { const res = await walletGet<{ success: boolean; data: TokenInfo[] }>('/api/tokens') return res.data } export interface JumperToken { address: string chainId: number symbol: string decimals: number name: string coinKey?: string logoURI?: string priceUSD: string } export type JumperTokensMap = Record export async function getJumperTokens(): Promise { const res = await walletGet<{ tokens?: JumperTokensMap; data?: { tokens: JumperTokensMap } }>( '/api/jumper/tokens?chains=1,56,1151111081099710,728126428,20000000000001' ) return res.data?.tokens ?? res.tokens ?? {} } export interface JumperQuotePayload { fromChain: string toChain: string fromToken: string toToken: string fromAmount: string fromAddress: string toAddress: string slippage: number } export interface JumperQuoteToken { address: string chainId: number symbol: string decimals: number name: string logoURI?: string priceUSD: string } export interface JumperFeeCost { name: string description?: string token: JumperQuoteToken amount: string amountUSD: string percentage?: string included?: boolean } export interface JumperQuote { type: string id: string tool: string toolDetails: { key: string; name: string; logoURI?: string } action: { fromToken: JumperQuoteToken fromAmount: string toToken: JumperQuoteToken fromChainId: number toChainId: number slippage: number fromAddress: string toAddress: string } estimate: { tool: string approvalAddress?: string toAmountMin: string toAmount: string fromAmount: string feeCosts?: JumperFeeCost[] } } export async function getJumperQuote(payload: JumperQuotePayload): Promise { const qs = new URLSearchParams({ fromChain: payload.fromChain, toChain: payload.toChain, fromToken: payload.fromToken, toToken: payload.toToken, fromAmount: payload.fromAmount, fromAddress: payload.fromAddress, toAddress: payload.toAddress, slippage: String(payload.slippage), }).toString() const res = await walletGet<{ body?: JumperQuote; data?: { body?: JumperQuote } }>( `/api/jumper/quote-best?${qs}` ) return (res.data?.body ?? res.body) as JumperQuote } export interface BridgeExecutePayload { provider: string fromChain: number toChain: number fromToken: string toToken: string fromAmount: string fromAddress: string toAddress: string acceptedMinOut?: string } export interface BridgeExecuteResult { provider: string fromChain: number toChain: number toolName: string feeTxid?: string feeAmount?: string bridgeTxid: string fromAmount: string toAmountMin: string fromAmountUSD?: string toAmountUSD?: string trackerUrl?: string } export async function executeBridge(payload: BridgeExecutePayload): Promise { const res = await walletPost<{ data?: { success: boolean; data: BridgeExecuteResult } }>( '/api/bridge/execute', payload, true, { 'Idempotency-Key': crypto.randomUUID() } ) return (res.data?.data ?? res) as BridgeExecuteResult } export async function getRelayQuote(payload: RelayQuotePayload): Promise { return walletPost('/api/relay/quote', payload) } export interface RelaySwapStep { id: string action: string description: string kind: string items: Array<{ status: string data: { from: string to: string data: string value: string chainId: number gas: string maxFeePerGas: string maxPriorityFeePerGas: string } check: { endpoint: string method: string } }> requestId: string } export interface RelaySwapResponse { steps: RelaySwapStep[] fees: RelayQuoteResponse['fees'] details: { operation: string sender: string recipient: string currencyIn: { amount: string; amountFormatted: string; amountUsd: string; currency: { symbol: string } } currencyOut: { amount: string; amountFormatted: string; amountUsd: string; currency: { symbol: string } } totalImpact: { usd: string; percent: string } rate: string timeEstimate: number } } export async function executeRelaySwap(payload: RelayQuotePayload): Promise { return walletPost('/api/relay/execute/swap', payload) } export async function signRawEvmTx( chain: 'ETH' | 'BSC', txData: RelaySwapStep['items'][0]['data'] ): Promise { const key = `relay-${chain.toLowerCase()}-${Date.now()}` return walletPost(`/api/wallets/${chain}/sign-raw-evm-tx`, txData, true, { 'Idempotency-Key': key }) } export async function signSolTx(txData: unknown): Promise { return walletPost('/api/wallets/SOL/sign-and-broadcast-tx', txData) } export interface TrxSwapQuotePayload { from: string to: string amountHuman: string } export interface TrxSwapQuoteData { quoteId: string expiresIn: number expectedOutFormatted: string minOutFormatted: string fees: { network: { amountFormatted: string; asset: string; amountUsd: number } } } export async function getTrxSwapQuote(payload: TrxSwapQuotePayload): Promise { const res = await walletPost<{ success: boolean; data: TrxSwapQuoteData }>( '/api/wallets/TRX/swap/quote', payload ) return res.data } export async function executeTrxSwap(quoteId: string): Promise { return walletPost( '/api/wallets/TRX/swap', { quoteId }, true, { 'Idempotency-Key': `trx-${Date.now()}` } ) } export async function createWallet(): Promise { await walletPost('/api/wallets/create', {}) } export async function revealMnemonic(): Promise { const res = await walletPost<{ success: boolean; data: { mnemonic: string } }>('/api/wallets/mnemonic/reveal', { confirm: 'I_UNDERSTAND_SEED_IS_SECRET' }) return res.data.mnemonic }