110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
|
|
import type { AddressTokenBalance } from '@/services/api/addresses'
|
||
|
|
import type { TokenAggregationMarketSnapshot, TokenAggregationTokenSnapshot } from '@/services/api/tokenAggregation'
|
||
|
|
|
||
|
|
export interface ResolvedTokenMarket {
|
||
|
|
priceUsd?: number
|
||
|
|
liquidityUsd?: number
|
||
|
|
source?: 'token-aggregation' | 'blockscout' | 'derived'
|
||
|
|
pricingKind?: 'spot' | 'lp-share'
|
||
|
|
isCanonicalClone?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
export function isLikelyLpReceiptSymbol(symbol: string | undefined): boolean {
|
||
|
|
if (!symbol) return false
|
||
|
|
const upper = symbol.toUpperCase()
|
||
|
|
return upper.includes('DLP') || upper.endsWith('-LP') || upper.startsWith('LP-') || upper === 'LP'
|
||
|
|
}
|
||
|
|
|
||
|
|
export function formatBalanceUsdLabel(
|
||
|
|
priceUsd: number | undefined,
|
||
|
|
balanceUsd: number | undefined,
|
||
|
|
options?: { pricingKind?: 'spot' | 'lp-share'; atTransfer?: boolean },
|
||
|
|
): string {
|
||
|
|
if (options?.pricingKind === 'lp-share') {
|
||
|
|
return 'LP share — USD N/A'
|
||
|
|
}
|
||
|
|
if (balanceUsd != null) {
|
||
|
|
return options?.atTransfer ? `≈ ${formatUsd(balanceUsd)} (at transfer)` : `≈ ${formatUsd(balanceUsd)}`
|
||
|
|
}
|
||
|
|
if (priceUsd == null) {
|
||
|
|
return 'USD unavailable'
|
||
|
|
}
|
||
|
|
return 'USD unavailable'
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatUsd(value: number): string {
|
||
|
|
return new Intl.NumberFormat('en-US', {
|
||
|
|
style: 'currency',
|
||
|
|
currency: 'USD',
|
||
|
|
maximumFractionDigits: value >= 100 ? 0 : 2,
|
||
|
|
}).format(value)
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseUsd(value: string | number | null | undefined): number | undefined {
|
||
|
|
if (value == null) return undefined
|
||
|
|
const numeric = typeof value === 'number' ? value : Number(value)
|
||
|
|
return Number.isFinite(numeric) && numeric > 0 ? numeric : undefined
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resolveTokenMarketForBalance(
|
||
|
|
balance: Pick<AddressTokenBalance, 'token_address' | 'exchange_rate' | 'token_symbol'>,
|
||
|
|
tokenMarkets: Record<string, TokenAggregationTokenSnapshot>,
|
||
|
|
): ResolvedTokenMarket {
|
||
|
|
const key = balance.token_address.toLowerCase()
|
||
|
|
const aggregationSnapshot = tokenMarkets[key]
|
||
|
|
const aggregationMarket: TokenAggregationMarketSnapshot | null | undefined = aggregationSnapshot?.market
|
||
|
|
const aggregationPrice = aggregationMarket?.priceUsd
|
||
|
|
const blockscoutPrice = parseUsd(balance.exchange_rate)
|
||
|
|
const pricingKind = aggregationMarket?.pricingKind
|
||
|
|
?? (isLikelyLpReceiptSymbol(balance.token_symbol ?? aggregationSnapshot?.symbol) ? 'lp-share' : 'spot')
|
||
|
|
const isCanonicalClone = aggregationMarket?.isCanonicalClone
|
||
|
|
|
||
|
|
if (aggregationPrice != null && aggregationPrice > 0) {
|
||
|
|
return {
|
||
|
|
priceUsd: aggregationPrice,
|
||
|
|
liquidityUsd: aggregationMarket?.liquidityUsd,
|
||
|
|
source: 'token-aggregation',
|
||
|
|
pricingKind,
|
||
|
|
isCanonicalClone,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (blockscoutPrice != null) {
|
||
|
|
return {
|
||
|
|
priceUsd: blockscoutPrice,
|
||
|
|
liquidityUsd: aggregationMarket?.liquidityUsd,
|
||
|
|
source: 'blockscout',
|
||
|
|
pricingKind,
|
||
|
|
isCanonicalClone,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
priceUsd: undefined,
|
||
|
|
liquidityUsd: aggregationMarket?.liquidityUsd,
|
||
|
|
source: 'derived',
|
||
|
|
pricingKind,
|
||
|
|
isCanonicalClone,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function estimateTokenBalanceUsd(
|
||
|
|
rawAmount: string,
|
||
|
|
decimals: number,
|
||
|
|
priceUsd: number | undefined,
|
||
|
|
): number | undefined {
|
||
|
|
if (priceUsd == null || !(priceUsd > 0) || !rawAmount) return undefined
|
||
|
|
try {
|
||
|
|
const raw = BigInt(rawAmount)
|
||
|
|
if (raw <= 0n) return 0
|
||
|
|
const scale = 10n ** BigInt(decimals)
|
||
|
|
const whole = raw / scale
|
||
|
|
const fraction = raw % scale
|
||
|
|
const amount = Number(whole) + Number(fraction) / Number(scale)
|
||
|
|
if (!Number.isFinite(amount)) return undefined
|
||
|
|
return amount * priceUsd
|
||
|
|
} catch {
|
||
|
|
return undefined
|
||
|
|
}
|
||
|
|
}
|