import { r as guardedFetch } from "./endpoint-allowlist-C4ojr3jY.mjs";
//#region extensions/crypto/src/services/market-cache.ts
const DEFAULT_TTLS = {
	trending: 6e4,
	new_pairs: 3e4,
	token_price: 15e3,
	token_search: 6e4,
	token_profile: 12e4,
	whale_data: 6e4,
	leaderboard: 3e5,
	gas_price: 1e4
};
var MarketCache = class {
	cache = /* @__PURE__ */ new Map();
	config;
	stats = {
		hits: 0,
		misses: 0,
		staleServes: 0,
		evictions: 0
	};
	constructor(config = {}) {
		this.config = {
			defaultTtlMs: config.defaultTtlMs ?? 3e4,
			ttlOverrides: config.ttlOverrides ?? {},
			maxEntries: config.maxEntries ?? 500,
			serveStaleOnError: config.serveStaleOnError ?? true
		};
	}
	/**
	* Get a cached value, or fetch it using the provided function.
	* This is the primary interface — wraps any async fetch with caching.
	*/
	async getOrFetch(category, key, fetcher) {
		const cacheKey = this.buildKey(category, key);
		const existing = this.cache.get(cacheKey);
		if (existing && Date.now() < existing.expiresAt) {
			this.stats.hits++;
			existing.hitCount++;
			return existing.data;
		}
		try {
			const data = await fetcher();
			this.set(category, key, data);
			this.stats.misses++;
			return data;
		} catch (err) {
			if (existing && this.config.serveStaleOnError) {
				this.stats.staleServes++;
				existing.stale = true;
				return existing.data;
			}
			throw err;
		}
	}
	/**
	* Manually set a cache entry.
	*/
	set(category, key, data) {
		const cacheKey = this.buildKey(category, key);
		const ttl = this.getTtl(category);
		if (this.cache.size >= this.config.maxEntries && !this.cache.has(cacheKey)) this.evictOldest();
		this.cache.set(cacheKey, {
			key: cacheKey,
			category,
			data,
			cachedAt: Date.now(),
			expiresAt: Date.now() + ttl,
			hitCount: 0,
			stale: false
		});
	}
	/**
	* Get a cached value (without fetching). Returns null if not cached or expired.
	*/
	get(category, key) {
		const cacheKey = this.buildKey(category, key);
		const entry = this.cache.get(cacheKey);
		if (!entry) return null;
		if (Date.now() >= entry.expiresAt) {
			entry.stale = true;
			return null;
		}
		this.stats.hits++;
		entry.hitCount++;
		return entry.data;
	}
	/**
	* Invalidate a specific cache entry.
	*/
	invalidate(category, key) {
		const cacheKey = this.buildKey(category, key);
		return this.cache.delete(cacheKey);
	}
	/**
	* Invalidate all entries in a category.
	*/
	invalidateCategory(category) {
		let count = 0;
		for (const [key, entry] of this.cache) if (entry.category === category) {
			this.cache.delete(key);
			count++;
		}
		return count;
	}
	/**
	* Clear all cache entries.
	*/
	clear() {
		this.cache.clear();
	}
	/**
	* Get cache statistics.
	*/
	getStats() {
		const byCategory = {};
		for (const entry of this.cache.values()) {
			if (!byCategory[entry.category]) byCategory[entry.category] = {
				entries: 0,
				hits: 0
			};
			byCategory[entry.category].entries++;
			byCategory[entry.category].hits += entry.hitCount;
		}
		const totalRequests = this.stats.hits + this.stats.misses;
		return {
			entries: this.cache.size,
			hits: this.stats.hits,
			misses: this.stats.misses,
			staleServes: this.stats.staleServes,
			evictions: this.stats.evictions,
			hitRate: totalRequests > 0 ? Math.round(this.stats.hits / totalRequests * 1e4) / 100 : 0,
			byCategory
		};
	}
	/**
	* Get all entries for diagnostics (without data payloads).
	*/
	getEntryMetadata() {
		const now = Date.now();
		return Array.from(this.cache.values()).map((e) => ({
			key: e.key,
			category: e.category,
			cachedAt: e.cachedAt,
			expiresAt: e.expiresAt,
			hitCount: e.hitCount,
			stale: e.stale || now >= e.expiresAt,
			ageMs: now - e.cachedAt,
			ttlRemainingMs: Math.max(0, e.expiresAt - now)
		}));
	}
	buildKey(category, key) {
		return `${category}:${key}`;
	}
	getTtl(category) {
		return this.config.ttlOverrides?.[category] ?? DEFAULT_TTLS[category] ?? this.config.defaultTtlMs;
	}
	evictOldest() {
		let oldestKey = null;
		let oldestTime = Infinity;
		for (const [key, entry] of this.cache) if (Date.now() >= entry.expiresAt) {
			this.cache.delete(key);
			this.stats.evictions++;
			return;
		}
		for (const [key, entry] of this.cache) if (entry.cachedAt < oldestTime) {
			oldestTime = entry.cachedAt;
			oldestKey = key;
		}
		if (oldestKey) {
			this.cache.delete(oldestKey);
			this.stats.evictions++;
		}
	}
};
let _instance = null;
function getMarketCache(config) {
	if (!_instance) _instance = new MarketCache(config);
	return _instance;
}
//#endregion
//#region extensions/crypto/src/services/dexscreener-service.ts
/**
* DexScreener Service — shared API client for token data.
*
* Centralizes all DexScreener calls. Previously duplicated across
* defi-price.ts, defi-balance.ts, and market-intel.ts.
*/
const BASE_URL = "https://api.dexscreener.com";
const CHAIN_ALIASES = {
	base: "base",
	ethereum: "ethereum",
	eth: "ethereum",
	arbitrum: "arbitrum",
	arb: "arbitrum",
	optimism: "optimism",
	op: "optimism",
	polygon: "polygon",
	matic: "polygon"
};
function resolveChain(input) {
	return CHAIN_ALIASES[input.toLowerCase()] || input;
}
/**
* Infer the cache category from the DexScreener API path.
*/
function inferCacheCategory(path) {
	if (path.includes("/token-boosts/") || path.includes("/trending")) return "trending";
	if (path.includes("/latest/dex/pairs/")) return "new_pairs";
	if (path.includes("/tokens/") || path.includes("/simple/price")) return "token_price";
	if (path.includes("/search")) return "token_search";
	if (path.includes("/profiles")) return "token_profile";
	return "token_price";
}
async function fetchDexScreener(path) {
	const cache = getMarketCache();
	const category = inferCacheCategory(path);
	return cache.getOrFetch(category, `dexscreener:${path}`, async () => {
		const response = await guardedFetch(`${BASE_URL}${path}`, {
			headers: { Accept: "application/json" },
			signal: AbortSignal.timeout(15e3)
		});
		if (!response.ok) throw new Error(`DexScreener API error: ${response.status} ${response.statusText}`);
		return response.json();
	});
}
/**
* Search for a token by symbol, name, or address.
* Returns pairs sorted by liquidity (highest first).
*/
async function searchToken(query, chain) {
	let data;
	if (query.startsWith("0x") && query.length === 42 && chain) data = await fetchDexScreener(`/tokens/v1/${resolveChain(chain)}/${query}`);
	else data = await fetchDexScreener(`/latest/dex/search?q=${encodeURIComponent(query)}`);
	return (data?.pairs ?? (Array.isArray(data) ? data : [])).filter((p) => !chain || p.chainId === resolveChain(chain)).sort((a, b) => (b.liquidity?.usd ?? 0) - (a.liquidity?.usd ?? 0));
}
/**
* Get the USD price for a token. Returns 0 if not found.
*/
async function getTokenPriceUsd(query, chain = "base") {
	try {
		const top = (await searchToken(query, chain))[0];
		if (!top) return {
			priceUsd: 0,
			pair: null
		};
		return {
			priceUsd: parseFloat(top.priceUsd ?? "0"),
			pair: top
		};
	} catch {
		return {
			priceUsd: 0,
			pair: null
		};
	}
}
/**
* Get ETH price in USD (via WETH/USDC pair on Base).
*/
async function getEthPriceUsd() {
	try {
		const basePair = (await fetchDexScreener("/latest/dex/search?q=WETH%20USDC")).pairs?.find((p) => p.chainId === "base" && p.baseToken?.symbol === "WETH");
		return basePair ? parseFloat(basePair.priceUsd ?? "0") : 0;
	} catch {
		return 0;
	}
}
//#endregion
export { getMarketCache as a, resolveChain as i, getEthPriceUsd as n, getTokenPriceUsd as r, fetchDexScreener as t };

//# sourceMappingURL=dexscreener-service-BSUiBkYa.mjs.map