PNG IHDR x sBIT|d pHYs + tEXtSoftware www.inkscape.org< ,tEXtComment
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use App\Models\Coin;
use Illuminate\Support\Collection;
class CryptoService
{
// Cache durations in seconds
private const CACHE_COINS = 300; // 5 minutes
private const CACHE_PRICES = 60; // 1 minute
private $apis = [
'huobi' => [
'url' => 'https://api.huobi.pro/market/tickers'
],
'gateio' => [
'url' => 'https://api.gateio.ws/api/v4/spot/tickers'
]
];
/**
* Get active coins from database (cached)
*/
public function getActiveCoins(): Collection
{
return Cache::remember('active_coins', self::CACHE_COINS, function () {
return Coin::active()->ordered()->get();
});
}
/**
* Get symbol mapping from database (cached)
*/
public function getSymbolMapping(): array
{
return Cache::remember('symbol_mapping', self::CACHE_COINS, function () {
return $this->getActiveCoins()
->pluck('price_symbol', 'symbol')
->toArray();
});
}
/**
* Get coin by symbol and network
*/
public function getCoin(string $symbol, ?string $network = null): ?Coin
{
return $this->getActiveCoins()
->where('symbol', strtoupper($symbol))
->where('network', $network)
->first();
}
/**
* Get coin by price symbol
*/
public function getCoinByPriceSymbol(string $priceSymbol): ?Coin
{
return $this->getActiveCoins()
->where('price_symbol', $priceSymbol)
->first();
}
/**
* Get all variants of a coin (different networks)
*/
public function getCoinVariants(string $symbol): Collection
{
return $this->getActiveCoins()
->where('symbol', strtoupper($symbol));
}
/**
* Get coin for display purposes
*/
public function getCoinForDisplay(string $symbol, ?string $network = null): ?array
{
$coin = $this->getCoin($symbol, $network);
if (!$coin) return null;
return [
'symbol' => $coin->symbol,
'name' => $coin->name,
'display_symbol' => $coin->display_symbol,
'display_name' => $coin->display_name,
'image_url' => $coin->image_url,
'network' => $coin->network,
'decimals' => $coin->decimals
];
}
/**
* Get all coins for display
*/
public function getAllCoinsForDisplay(): array
{
return $this->getActiveCoins()
->map(function ($coin) {
return [
'symbol' => $coin->symbol,
'name' => $coin->name,
'display_symbol' => $coin->display_symbol,
'display_name' => $coin->display_name,
'image_url' => $coin->image_url,
'network' => $coin->network,
'decimals' => $coin->decimals,
'sort_order' => $coin->sort_order
];
})
->toArray();
}
/**
* Validate address using coin-specific logic
*/
public function validateAddress(string $address, string $symbol, ?string $network = null): bool
{
$coin = $this->getCoin($symbol, $network);
return $coin ? $coin->validateAddress($address) : false;
}
/**
* Get current cryptocurrency prices
*/
public function getPrices(): array
{
return Cache::remember('crypto_prices', self::CACHE_PRICES, function () {
// Try each provider in sequence
foreach ($this->apis as $provider => $api) {
try {
$prices = $this->fetchFromProvider($provider);
if (!empty($prices)) {
return array_merge($this->getDefaultPrices(), $prices);
}
} catch (\Exception $e) {
Log::error("Error fetching prices from {$provider}", [
'error' => $e->getMessage(),
'api' => $api['url']
]);
continue;
}
}
Log::warning('All API providers failed, returning default prices');
return $this->getDefaultPrices();
});
}
/**
* Get icon URL for a coin by price symbol
*/
public function getIconUrl(string $symbol): ?string
{
$coin = $this->getCoinByPriceSymbol(strtolower($symbol));
return $coin ? $coin->image_url : null;
}
/**
* Get network icon URL
*/
public function getNetworkIcon(string $network): ?string
{
$networkMap = [
'trc20' => 'tron',
'bep20' => 'binancecoin',
'erc20' => 'ethereum',
];
$priceSymbol = $networkMap[strtolower($network)] ?? null;
return $priceSymbol ? $this->getIconUrl($priceSymbol) : null;
}
/**
* Clear all service caches
*/
public function clearCache(): void
{
$cacheKeys = [
'active_coins',
'symbol_mapping',
'crypto_prices',
'coins_for_display',
'coin_icons',
'network_icons'
];
foreach ($cacheKeys as $key) {
Cache::forget($key);
}
}
/**
* Refresh all service caches
*/
public function refreshCache(): void
{
$this->clearCache();
$this->getActiveCoins();
$this->getSymbolMapping();
}
/**
* Get standard symbol from exchange symbol
*/
public function getStandardSymbol(string $symbol): ?string
{
$mapping = $this->getSymbolMapping();
return $mapping[strtoupper($symbol)] ?? null;
}
// ================================================
// Private Methods - Internal Implementation Details
// ================================================
private function fetchFromProvider($provider)
{
$api = $this->apis[$provider];
$response = Http::timeout(5)
->retry(2, 100)
->get($api['url']);
if (!$response->successful()) {
Log::warning("Failed to fetch from {$provider}", [
'status' => $response->status(),
'body' => $response->body()
]);
return [];
}
$data = $response->json();
if (empty($data)) {
Log::warning("Empty response from {$provider}");
return [];
}
return match($provider) {
'huobi' => $this->formatHuobiResponse($data),
'gateio' => $this->formatGateioResponse($data),
default => []
};
}
private function formatHuobiResponse($data)
{
if (!isset($data['data']) || !is_array($data['data'])) {
return [];
}
return collect($data['data'])
->filter(function ($item) {
return str_ends_with($item['symbol'], 'usdt');
})
->mapWithKeys(function ($item) {
$symbol = str_replace('usdt', '', $item['symbol']);
$price = floatval($item['close']);
$open = floatval($item['open']);
$priceChange = $open > 0 ? (($price - $open) / $open) * 100 : 0;
$standardSymbol = $this->getStandardSymbol($symbol);
if (!$standardSymbol) return [];
return [
$standardSymbol => [
'usd' => $this->validatePrice($price),
'usd_24h_change' => $this->validatePriceChange($priceChange),
'last_updated' => now()->timestamp
]
];
})
->toArray();
}
private function formatGateioResponse($data)
{
if (!is_array($data)) {
return [];
}
return collect($data)
->filter(function ($item) {
return str_ends_with($item['currency_pair'], '_USDT');
})
->mapWithKeys(function ($item) {
$symbol = explode('_', $item['currency_pair'])[0];
$standardSymbol = $this->getStandardSymbol($symbol);
if (!$standardSymbol) return [];
return [
$standardSymbol => [
'usd' => $this->validatePrice($item['last']),
'usd_24h_change' => $this->validatePriceChange($item['change_percentage']),
'last_updated' => now()->timestamp
]
];
})
->toArray();
}
private function validatePrice($price)
{
$price = floatval($price);
return $price > 0 ? $price : 0;
}
private function validatePriceChange($change)
{
$change = floatval($change);
return max(-100, min(1000, $change));
}
private function getDefaultPrices()
{
$timestamp = now()->timestamp;
$defaultPrices = [];
// Get default prices from active coins in database
foreach ($this->getActiveCoins() as $coin) {
$defaultPrices[$coin->price_symbol] = [
'usd' => $coin->use_manual_price ? (float) $coin->manual_price_usd : 0,
'usd_24h_change' => 0,
'last_updated' => $timestamp
];
}
return $defaultPrices;
}
}
b IDATxytVսϓ22 A@IR:hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-E