UNPKG

@mcpfun/mcp-server-ccxt

Version:

High-performance CCXT MCP server for cryptocurrency exchange integration

208 lines 7.4 kB
/** * Exchange Manager * Manages cryptocurrency exchange instances and provides utility functions * * 交易所管理器 * 管理加密货币交易所实例并提供实用函数 */ import * as ccxt from 'ccxt'; import { log, LogLevel } from '../utils/logging.js'; // List of supported exchanges // 支持的交易所列表 export const SUPPORTED_EXCHANGES = [ // 原有交易所 'binance', 'coinbase', 'kraken', 'kucoin', 'okx', 'gate', 'bybit', 'mexc', 'huobi', // 新增主流交易所 'bitget', 'coinex', 'cryptocom', 'hashkey', 'hyperliquid', // 延伸现有交易所的衍生品市场 'binanceusdm', 'binancecoinm', 'kucoinfutures', 'bitfinex', 'bitmex', 'gateio', 'woo', 'deribit', 'phemex', 'bingx' ]; // Exchange instance cache // 交易所实例缓存 const exchanges = {}; /** * Clear exchange instance cache * This is useful when proxy or other configurations change */ export function clearExchangeCache() { Object.keys(exchanges).forEach(key => { delete exchanges[key]; }); log(LogLevel.INFO, 'Exchange cache cleared'); } // Default exchange and market type // 默认交易所和市场类型 export const DEFAULT_EXCHANGE = process.env.DEFAULT_EXCHANGE || 'binance'; export const DEFAULT_MARKET_TYPE = process.env.DEFAULT_MARKET_TYPE || 'spot'; // Market types enum // 市场类型枚举 export var MarketType; (function (MarketType) { MarketType["SPOT"] = "spot"; MarketType["FUTURE"] = "future"; MarketType["SWAP"] = "swap"; MarketType["OPTION"] = "option"; MarketType["MARGIN"] = "margin"; })(MarketType || (MarketType = {})); /** * Get exchange instance * @param exchangeId Exchange ID * @returns Exchange instance * * 获取交易所实例 * @param exchangeId 交易所ID * @returns 交易所实例 */ /** * Get proxy configuration from environment * @returns Proxy configuration or null if proxy is disabled */ export function getProxyConfig() { const useProxy = process.env.USE_PROXY === 'true'; if (!useProxy) return null; const url = process.env.PROXY_URL; if (!url) { log(LogLevel.WARNING, 'USE_PROXY is true but PROXY_URL is not set'); return null; } const username = process.env.PROXY_USERNAME || undefined; const password = process.env.PROXY_PASSWORD || undefined; return { url, username, password }; } /** * Format proxy URL with authentication if provided * @param config Proxy configuration * @returns Formatted proxy URL */ function formatProxyUrl(config) { if (!config.username || !config.password) return config.url; // Extract protocol and host from URL const match = config.url.match(/^(https?|socks[45]):\/\/([^\/]+)/); if (!match) return config.url; const protocol = match[1]; const host = match[2]; return `${protocol}://${config.username}:${config.password}@${host}`; } /** * Get exchange instance with the default market type * @param exchangeId Exchange ID * @returns Exchange instance */ export function getExchange(exchangeId) { return getExchangeWithMarketType(exchangeId, DEFAULT_MARKET_TYPE); } /** * Get exchange instance with specific market type * @param exchangeId Exchange ID * @param marketType Market type (spot, future, etc.) * @returns Exchange instance */ export function getExchangeWithMarketType(exchangeId, marketType = MarketType.SPOT) { const id = (exchangeId || DEFAULT_EXCHANGE).toLowerCase(); const type = marketType || DEFAULT_MARKET_TYPE; // Create a cache key that includes both exchange ID and market type const cacheKey = `${id}:${type}`; if (!exchanges[cacheKey]) { if (!SUPPORTED_EXCHANGES.includes(id)) { throw new Error(`Exchange '${id}' not supported`); } const apiKey = process.env[`${id.toUpperCase()}_API_KEY`]; const secret = process.env[`${id.toUpperCase()}_SECRET`]; try { log(LogLevel.INFO, `Initializing exchange: ${id} (${type})`); // Use indexed access to create exchange instance const ExchangeClass = ccxt[id]; // Configure options with possible proxy const options = { apiKey, secret, enableRateLimit: true, options: {} }; // Configure market type specifics if (type !== MarketType.SPOT) { options.options.defaultType = type; } // Add proxy configuration if enabled const proxyConfig = getProxyConfig(); if (proxyConfig) { options.proxy = formatProxyUrl(proxyConfig); log(LogLevel.INFO, `Using proxy for ${id}`); } exchanges[cacheKey] = new ExchangeClass(options); } catch (error) { log(LogLevel.ERROR, `Failed to initialize exchange ${id} (${type}): ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to initialize exchange ${id} (${type}): ${error.message}`); } } return exchanges[cacheKey]; } /** * Get exchange instance with specific credentials * @param exchangeId Exchange ID * @param apiKey API key * @param secret API secret * @returns Exchange instance * * 使用特定凭据获取交易所实例 * @param exchangeId 交易所ID * @param apiKey API密钥 * @param secret API密钥秘密 * @returns 交易所实例 */ export function getExchangeWithCredentials(exchangeId, apiKey, secret, marketType = MarketType.SPOT) { try { if (!SUPPORTED_EXCHANGES.includes(exchangeId)) { throw new Error(`Exchange '${exchangeId}' not supported`); } const type = marketType || DEFAULT_MARKET_TYPE; // Configure options with possible proxy const options = { apiKey, secret, enableRateLimit: true, options: {} }; // Configure market type specifics if (type !== MarketType.SPOT) { options.options.defaultType = type; } // Add proxy configuration if enabled const proxyConfig = getProxyConfig(); if (proxyConfig) { options.proxy = formatProxyUrl(proxyConfig); log(LogLevel.INFO, `Using proxy for ${exchangeId} (${type}) with custom credentials`); } // Use indexed access to create exchange instance const ExchangeClass = ccxt[exchangeId]; return new ExchangeClass(options); } catch (error) { log(LogLevel.ERROR, `Failed to initialize exchange ${exchangeId} with credentials: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to initialize exchange ${exchangeId}: ${error.message}`); } } /** * Validate and format trading pair symbol * @param symbol Trading pair symbol * @returns Formatted trading pair symbol * * 验证和格式化交易对符号 * @param symbol 交易对符号 * @returns 格式化的交易对符号 */ export function validateSymbol(symbol) { // Simple validation to ensure symbol includes slash // 简单验证,确保符号包含斜杠 if (!symbol.includes('/')) { throw new Error(`Invalid symbol: ${symbol}, should be in format like BTC/USDT`); } return symbol.toUpperCase(); } //# sourceMappingURL=manager.js.map