Skip to main content
Glama

Bybit MCP Server

test-order.js10.2 kB
#!/usr/bin/env node // Simple Node.js test script to test the $80 ETH order with exact Cloud Functions signature logic import crypto from 'crypto'; import fetch from 'node-fetch'; import dotenv from 'dotenv'; dotenv.config(); const config = { accessKey: process.env.ACCESS_KEY || '', secretKey: process.env.SECRET_KEY || '', demo: process.env.DEMO?.toLowerCase() === 'true', testnet: process.env.TESTNET?.toLowerCase() === 'true' }; // Determine base URL let baseUrl; if (config.demo) { baseUrl = 'https://api-demo.bybit.com'; console.log('Using Demo environment: https://api-demo.bybit.com'); } else if (config.testnet) { baseUrl = 'https://api-testnet.bybit.com'; console.log('Using Testnet environment: https://api-testnet.bybit.com'); } else { baseUrl = 'https://api.bybit.com'; console.log('Using Production environment: https://api.bybit.com'); } async function makeBybitRequest(endpoint, method = 'GET', params = {}) { try { const timestamp = Date.now().toString(); // Create signature - EXACT same logic as your Cloud Functions let queryString = ''; if (method === 'GET') { const sortedParams = Object.keys(params) .sort() .map(key => `${key}=${params[key]}`) .join('&'); queryString = sortedParams; } else { // For POST requests - exact Node.js JSON.stringify behavior queryString = params && Object.keys(params).length > 0 ? JSON.stringify(params) : ''; } // Signature payload - exact same format as your Cloud Functions const signaturePayload = timestamp + config.accessKey + queryString; const signature = crypto .createHmac('sha256', config.secretKey) .update(signaturePayload) .digest('hex'); const headers = { 'X-BAPI-API-KEY': config.accessKey, 'X-BAPI-SIGN': signature, 'X-BAPI-TIMESTAMP': timestamp, 'Content-Type': 'application/json', 'User-Agent': 'BybitMCP-Node-Test/1.0' }; const url = method === 'GET' && queryString ? `${baseUrl}${endpoint}?${queryString}` : `${baseUrl}${endpoint}`; const requestOptions = { method, headers, timeout: 10000 }; if (method === 'POST' && params && Object.keys(params).length > 0) { requestOptions.body = JSON.stringify(params); } console.log(`Making ${method} request to: ${url}`); if (method === 'POST') { console.log('Request body:', JSON.stringify(params, null, 2)); console.log('Signature payload:', signaturePayload); console.log('Signature:', signature); } const response = await fetch(url, requestOptions); const data = await response.json(); return data; } catch (error) { console.error('Request failed:', error.message); return { error: error.message }; } } async function testTradeExecution() { console.log('🚀 Node.js Bybit Trade Execution Test'); console.log('=' * 45); try { // 1. Get current ETHUSDT price console.log('\n1. Getting ETHUSDT price...'); const ticker = await makeBybitRequest('/v5/market/tickers', 'GET', { category: 'linear', symbol: 'ETHUSDT' }); if (ticker.error || ticker.retCode !== 0) { console.log('❌ Price error:', ticker.error || ticker.retMsg); return; } const price = parseFloat(ticker.result.list[0].lastPrice); console.log(` Current price: $${price}`); // 2. Check wallet balance console.log('\n2. Checking balance...'); const balance = await makeBybitRequest('/v5/account/wallet-balance', 'GET', { accountType: 'UNIFIED' }); if (balance.error || balance.retCode !== 0) { console.log('❌ Balance error:', balance.error || balance.retMsg); return; } const available = parseFloat(balance.result.list[0].totalAvailableBalance); console.log(` Available: ${available} USDT`); // 3. Get instrument info to validate position sizes console.log('\n3. Getting ETHUSDT trading rules...'); const instrumentInfo = await makeBybitRequest('/v5/market/instruments-info', 'GET', { category: 'linear', symbol: 'ETHUSDT' }); if (instrumentInfo.error || instrumentInfo.retCode !== 0) { console.log('❌ Instrument info error:', instrumentInfo.error || instrumentInfo.retMsg); return; } const instrument = instrumentInfo.result.list[0]; const lotSizeFilter = instrument.lotSizeFilter; const minOrderQty = parseFloat(lotSizeFilter.minOrderQty); const maxOrderQty = parseFloat(lotSizeFilter.maxOrderQty); const qtyStep = parseFloat(lotSizeFilter.qtyStep); console.log(` Min order quantity: ${minOrderQty} ETH`); console.log(` Max order quantity: ${maxOrderQty} ETH`); console.log(` Quantity step: ${qtyStep} ETH`); // 4. Calculate order for $80 with proper validation const targetAmount = 80.0; let rawOrderQty = targetAmount / price; // Round to the nearest valid quantity step let orderQty = Math.round(rawOrderQty / qtyStep) * qtyStep; // Ensure it meets minimum requirements if (orderQty < minOrderQty) { orderQty = minOrderQty; console.log(` ⚠️ Adjusted to minimum quantity: ${orderQty} ETH`); } // Ensure it doesn't exceed maximum if (orderQty > maxOrderQty) { orderQty = maxOrderQty; console.log(` ⚠️ Adjusted to maximum quantity: ${orderQty} ETH`); } // Format to the correct decimal places based on qtyStep const decimalPlaces = qtyStep.toString().split('.')[1]?.length || 0; const formattedQty = orderQty.toFixed(decimalPlaces); const estimatedCost = (orderQty * price).toFixed(2); console.log('\n4. Validated order calculation:'); console.log(` Target amount: $${targetAmount}`); console.log(` Raw quantity: ${rawOrderQty.toFixed(6)} ETH`); console.log(` Validated quantity: ${formattedQty} ETH`); console.log(` Estimated cost: $${estimatedCost}`); if (parseFloat(estimatedCost) > available) { console.log(`❌ Insufficient balance! Need $${estimatedCost} but only have $${available}`); return; } // 5. Check current positions to determine position mode console.log('\n5. Checking current position mode...'); const currentPositions = await makeBybitRequest('/v5/position/list', 'GET', { category: 'linear', symbol: 'ETHUSDT' }); let positionIdx = '0'; // Default to one-way mode if (currentPositions.retCode === 0 && currentPositions.result.list.length > 0) { // Check if we have separate long/short positions (hedge mode) const positions = currentPositions.result.list; const hasLongPosition = positions.some(p => p.side === 'Buy'); const hasShortPosition = positions.some(p => p.side === 'Sell'); if (hasLongPosition || hasShortPosition) { positionIdx = '1'; // Long position in hedge mode console.log(' Position mode: Hedge mode detected'); } else { console.log(' Position mode: One-way mode (default)'); } } else { console.log(' Position mode: One-way mode (no existing positions)'); } // 6. Place the order console.log('\n6. Placing ETHUSDT long order...'); console.log(` Symbol: ETHUSDT`); console.log(` Side: Buy (Long)`); console.log(` Type: Market`); console.log(` Quantity: ${formattedQty} ETH`); console.log(` Position Index: ${positionIdx} (${positionIdx === '0' ? 'One-way' : 'Long'})`); const orderParams = { category: 'linear', symbol: 'ETHUSDT', side: 'Buy', orderType: 'Market', qty: formattedQty, // Use the formatted string quantity timeInForce: 'IOC', orderFilter: 'Order', isLeverage: 0, positionIdx: positionIdx }; const order = await makeBybitRequest('/v5/order/create', 'POST', orderParams); if (order.error || order.retCode !== 0) { console.log(`❌ Order error: ${order.error || order.retMsg}`); // Check specific error types const errorMsg = (order.error || order.retMsg || '').toLowerCase(); if (errorMsg.includes('sign')) { console.log('💡 Signature error - comparing with Python implementation'); } else if (errorMsg.includes('insufficient') || errorMsg.includes('balance')) { console.log('💡 Insufficient balance - expected in demo mode'); } else if (errorMsg.includes('minimum') || errorMsg.includes('qty')) { console.log('💡 Quantity issue - let\'s try adjusting the amount'); } } else { const orderId = order.result.orderId; console.log(`✅ Order placed successfully!`); console.log(` Order ID: ${orderId}`); console.log(` Quantity: ${orderQty} ETH`); console.log(` Estimated value: $${estimatedCost}`); // 5. Check position after a moment console.log('\n5. Checking position (waiting 3 seconds)...'); await new Promise(resolve => setTimeout(resolve, 3000)); const positions = await makeBybitRequest('/v5/position/list', 'GET', { category: 'linear', symbol: 'ETHUSDT' }); if (positions.retCode === 0) { const ethPos = positions.result.list.find(p => p.symbol === 'ETHUSDT' && parseFloat(p.size) > 0 ); if (ethPos) { console.log(`✅ Position opened successfully!`); console.log(` Size: ${ethPos.size} ETH`); console.log(` Entry price: $${ethPos.avgPrice}`); console.log(` Unrealized PnL: $${ethPos.unrealisedPnl}`); console.log(` Position value: $${(parseFloat(ethPos.size) * parseFloat(ethPos.avgPrice)).toFixed(2)}`); } else { console.log('ℹ️ No position found yet (order may still be processing)'); } } else { console.log(`⚠️ Could not check positions: ${positions.retMsg}`); } } } catch (error) { console.error('Test failed:', error); } console.log('\n🎉 Test completed!'); } // Run the test testTradeExecution().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kondisettyravi/mcp-bybit-node'

If you have feedback or need assistance with the MCP directory API, please join our Discord server