Files
frontend/src/features/wallet/api/walletApi.ts
2026-05-28 20:24:56 +03:00

429 lines
10 KiB
TypeScript

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<string, FormattedAmount>
}
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<Chain, PortfolioChain>
}
export const CHAINS: Chain[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL']
async function walletGet<T>(path: string, allowRetry: boolean = true): Promise<T> {
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<T>(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<T>(
path: string,
body: unknown,
allowRetry: boolean = true,
extraHeaders: Record<string, string> = {}
): Promise<T> {
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<T>(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<WalletAddress[]> {
const res = await walletGet<{ success: boolean; data: WalletAddress[] }>('/api/wallets')
return res.data
}
export async function getWalletBalance(chain: Chain): Promise<WalletBalanceData> {
const res = await walletGet<{ success: boolean; data: WalletBalanceData }>(`/api/wallets/${chain}/balance`)
return res.data
}
export async function getPrices(symbols: string[]): Promise<Record<string, PriceEntry>> {
const res = await walletGet<{ success: boolean; data: Record<string, PriceEntry> }>(
`/api/prices?symbols=${symbols.join(',')}`
)
return res.data
}
export async function sendWallet(chain: Chain, payload: SendWalletPayload): Promise<SendWalletResponse> {
return walletPost<SendWalletResponse>(`/api/wallets/${chain}/send`, payload)
}
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
}
}
fees: {
gas: {
amountUsd: string
}
}
}
export async function getTokensList(): Promise<TokenInfo[]> {
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<string, JumperToken[]>
export async function getJumperTokens(): Promise<JumperTokensMap> {
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<JumperQuote> {
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<BridgeExecuteResult> {
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<RelayQuoteResponse> {
return walletPost<RelayQuoteResponse>('/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<RelaySwapResponse> {
return walletPost<RelaySwapResponse>('/api/relay/execute/swap', payload)
}
export async function signRawEvmTx(
chain: 'ETH' | 'BSC',
txData: RelaySwapStep['items'][0]['data']
): Promise<unknown> {
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<unknown> {
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<TrxSwapQuoteData> {
const res = await walletPost<{ success: boolean; data: TrxSwapQuoteData }>(
'/api/wallets/TRX/swap/quote',
payload
)
return res.data
}
export async function executeTrxSwap(quoteId: string): Promise<unknown> {
return walletPost(
'/api/wallets/TRX/swap',
{ quoteId },
true,
{ 'Idempotency-Key': `trx-${Date.now()}` }
)
}
export async function createWallet(): Promise<void> {
await walletPost<unknown>('/api/wallets/create', {})
}
export async function revealMnemonic(): Promise<string> {
const res = await walletPost<{ success: boolean; data: { mnemonic: string } }>('/api/wallets/mnemonic/reveal', { confirm: 'I_UNDERSTAND_SEED_IS_SECRET' })
return res.data.mnemonic
}