Skip to main content
Glama
main.js25.4 kB
/** * DeFi MCP Server - Main JavaScript Module * Handles client-side functionality for the web interface */ // Global variables let currentApiKey = null; let portfolioChart = null; let toastContainer = null; // Initialize application document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); /** * Initialize the application */ function initializeApp() { // Initialize toast container toastContainer = document.getElementById('toastContainer'); // Load API key from localStorage loadApiKey(); // Initialize feather icons if (typeof feather !== 'undefined') { feather.replace(); } // Setup global error handling setupErrorHandling(); // Initialize page-specific functionality const path = window.location.pathname; if (path.includes('dashboard')) { initializeDashboard(); } } /** * Setup global error handling */ function setupErrorHandling() { window.addEventListener('error', function(event) { console.error('Global error:', event.error); showToast('An unexpected error occurred', 'danger'); }); window.addEventListener('unhandledrejection', function(event) { console.error('Unhandled promise rejection:', event.reason); showToast('A network error occurred', 'danger'); }); } /** * API Key Management */ function loadApiKey() { const storedApiKey = localStorage.getItem('defi_mcp_api_key'); if (storedApiKey) { currentApiKey = storedApiKey; updateApiKeyDisplay(); } } function saveApiKey(apiKey) { currentApiKey = apiKey; localStorage.setItem('defi_mcp_api_key', apiKey); updateApiKeyDisplay(); } function updateApiKeyDisplay() { const apiKeyInput = document.getElementById('currentApiKey'); if (apiKeyInput && currentApiKey) { apiKeyInput.value = currentApiKey; apiKeyInput.type = 'password'; } } function generateApiKey() { // Generate a client-side API key (in production, this would be done server-side) const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'; let result = 'aya_'; for (let i = 0; i < 32; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } saveApiKey(result); showToast('New API key generated successfully', 'success'); // Close modal const modal = bootstrap.Modal.getInstance(document.getElementById('apiKeyModal')); if (modal) { modal.hide(); } } function toggleApiKeyVisibility() { const apiKeyInput = document.getElementById('currentApiKey'); const toggleIcon = document.getElementById('apiKeyToggle'); if (apiKeyInput.type === 'password') { apiKeyInput.type = 'text'; toggleIcon.setAttribute('data-feather', 'eye-off'); } else { apiKeyInput.type = 'password'; toggleIcon.setAttribute('data-feather', 'eye'); } feather.replace(); } /** * Modal Management */ function showApiKeyModal() { const modal = new bootstrap.Modal(document.getElementById('apiKeyModal')); modal.show(); } function showWalletModal() { const modal = new bootstrap.Modal(document.getElementById('walletModal')); modal.show(); } function showSwapModal() { const modal = new bootstrap.Modal(document.getElementById('swapModal')); modal.show(); } function showLendModal() { const modal = new bootstrap.Modal(document.getElementById('lendModal')); modal.show(); } function showFarmModal() { const modal = new bootstrap.Modal(document.getElementById('farmModal')); modal.show(); } /** * Wallet Management */ function generateWallet() { const blockchain = document.getElementById('walletBlockchain').value; if (!blockchain) { showToast('Please select a blockchain', 'warning'); return; } // In a real implementation, this would call the server to generate a wallet showToast(`Wallet generation for ${blockchain} requires server-side implementation`, 'info'); } function importWallet() { const privateKey = document.getElementById('importPrivateKey').value; const blockchain = document.getElementById('importBlockchain').value; if (!privateKey || !blockchain) { showToast('Please enter private key and select blockchain', 'warning'); return; } // Basic validation if (blockchain === 'solana') { // Solana private keys are base58 encoded if (privateKey.length < 32) { showToast('Invalid Solana private key format', 'danger'); return; } } else { // Ethereum/Polygon private keys const cleanKey = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey; if (cleanKey.length !== 64 || !/^[a-fA-F0-9]+$/.test(cleanKey)) { showToast('Invalid private key format', 'danger'); return; } } showToast(`Wallet import for ${blockchain} requires server-side implementation`, 'info'); // Clear the input for security document.getElementById('importPrivateKey').value = ''; } /** * Toast Notification System */ function showToast(message, type = 'info', duration = 5000) { if (!toastContainer) { toastContainer = document.getElementById('toastContainer'); } const toastId = 'toast_' + Date.now(); const iconMap = { success: 'check-circle', danger: 'alert-circle', warning: 'alert-triangle', info: 'info' }; const bgColorMap = { success: 'bg-success', danger: 'bg-danger', warning: 'bg-warning', info: 'bg-info' }; const toastHtml = ` <div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header ${bgColorMap[type]} text-white"> <i data-feather="${iconMap[type]}" class="me-2"></i> <strong class="me-auto">Notification</strong> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button> </div> <div class="toast-body"> ${message} </div> </div> `; toastContainer.insertAdjacentHTML('beforeend', toastHtml); const toastElement = document.getElementById(toastId); const toast = new bootstrap.Toast(toastElement, { autohide: duration > 0, delay: duration }); // Replace feather icons in the new toast feather.replace(); toast.show(); // Auto-remove toast after it's hidden toastElement.addEventListener('hidden.bs.toast', function() { toastElement.remove(); }); } /** * API Communication Helpers */ async function makeApiRequest(endpoint, options = {}) { if (!currentApiKey) { throw new Error('API key not configured. Please generate an API key first.'); } const defaultHeaders = { 'Content-Type': 'application/json', 'X-API-Key': currentApiKey }; const config = { headers: { ...defaultHeaders, ...options.headers }, ...options }; try { const response = await fetch(endpoint, config); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { console.error('API request failed:', error); throw error; } } /** * DeFi Operations */ async function executeSwap(swapData) { try { showToast('Executing swap...', 'info'); const result = await makeApiRequest('/api/v1/swap', { method: 'POST', body: JSON.stringify(swapData) }); if (result.success) { showToast(`Swap executed successfully! TX: ${result.tx_hash}`, 'success'); return result; } else { throw new Error(result.error || 'Swap failed'); } } catch (error) { showToast(`Swap failed: ${error.message}`, 'danger'); throw error; } } async function executeLend(lendData) { try { showToast('Executing lending operation...', 'info'); const result = await makeApiRequest('/api/v1/lend', { method: 'POST', body: JSON.stringify(lendData) }); if (result.success) { showToast(`Lending executed successfully! TX: ${result.tx_hash}`, 'success'); return result; } else { throw new Error(result.error || 'Lending failed'); } } catch (error) { showToast(`Lending failed: ${error.message}`, 'danger'); throw error; } } async function executeFarm(farmData) { try { showToast('Adding liquidity...', 'info'); const result = await makeApiRequest('/api/v1/farm', { method: 'POST', body: JSON.stringify(farmData) }); if (result.success) { showToast(`Liquidity added successfully! TX: ${result.tx_hash}`, 'success'); return result; } else { throw new Error(result.error || 'Farming failed'); } } catch (error) { showToast(`Farming failed: ${error.message}`, 'danger'); throw error; } } /** * Portfolio and Data Loading */ async function loadPortfolio(walletAddress) { try { const result = await makeApiRequest(`/api/v1/portfolio/${walletAddress}`); return result.portfolio || null; } catch (error) { console.error('Failed to load portfolio:', error); return null; } } async function loadPositions(walletAddress) { try { const result = await makeApiRequest(`/api/v1/positions/${walletAddress}`); return result.positions || []; } catch (error) { console.error('Failed to load positions:', error); return []; } } async function loadTransactions(walletAddress, page = 1, perPage = 50) { try { const result = await makeApiRequest(`/api/v1/transactions/${walletAddress}?page=${page}&per_page=${perPage}`); return result.transactions || []; } catch (error) { console.error('Failed to load transactions:', error); return []; } } /** * Dashboard Functionality */ function initializeDashboard() { // Load dashboard data loadDashboardData(); // Setup form handlers setupFormHandlers(); } function setupFormHandlers() { // Swap form const swapForm = document.getElementById('swapForm'); if (swapForm) { swapForm.addEventListener('submit', handleSwapSubmit); } // Lend form const lendForm = document.getElementById('lendForm'); if (lendForm) { lendForm.addEventListener('submit', handleLendSubmit); } // Farm form const farmForm = document.getElementById('farmForm'); if (farmForm) { farmForm.addEventListener('submit', handleFarmSubmit); } } async function handleSwapSubmit(event) { event.preventDefault(); if (!currentApiKey) { showToast('Please configure an API key first', 'warning'); showApiKeyModal(); return; } const formData = new FormData(event.target); const swapData = Object.fromEntries(formData.entries()); try { await executeSwap(swapData); // Close modal and refresh dashboard const modal = bootstrap.Modal.getInstance(document.getElementById('swapModal')); if (modal) { modal.hide(); } refreshDashboard(); } catch (error) { // Error already handled in executeSwap } } async function handleLendSubmit(event) { event.preventDefault(); if (!currentApiKey) { showToast('Please configure an API key first', 'warning'); showApiKeyModal(); return; } const formData = new FormData(event.target); const lendData = Object.fromEntries(formData.entries()); try { await executeLend(lendData); // Close modal and refresh dashboard const modal = bootstrap.Modal.getInstance(document.getElementById('lendModal')); if (modal) { modal.hide(); } refreshDashboard(); } catch (error) { // Error already handled in executeLend } } async function handleFarmSubmit(event) { event.preventDefault(); if (!currentApiKey) { showToast('Please configure an API key first', 'warning'); showApiKeyModal(); return; } const formData = new FormData(event.target); const farmData = Object.fromEntries(formData.entries()); try { await executeFarm(farmData); // Close modal and refresh dashboard const modal = bootstrap.Modal.getInstance(document.getElementById('farmModal')); if (modal) { modal.hide(); } refreshDashboard(); } catch (error) { // Error already handled in executeFarm } } function loadDashboardData() { // Update stats updateDashboardStats(); // Load portfolio chart loadPortfolioChart(); // Load recent data loadRecentTransactions(); loadActivePositions(); } function updateDashboardStats() { // In a real implementation, these would be calculated from actual data const elements = { 'totalPortfolioValue': '$0.00', 'activePositions': '0', 'pendingTransactions': '0', 'averageApy': '0.00%' }; Object.entries(elements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.textContent = value; } }); } function loadPortfolioChart() { const chartCanvas = document.getElementById('portfolioChart'); const emptyState = document.getElementById('portfolioEmpty'); const chartContainer = document.getElementById('portfolioChartContainer'); if (!chartCanvas) return; // Show empty state by default if (chartContainer) chartContainer.style.display = 'none'; if (emptyState) emptyState.style.display = 'block'; // In a real implementation, this would load actual portfolio data // and create a Chart.js chart if data is available } function loadRecentTransactions() { const tableElement = document.getElementById('transactionsTable'); const emptyElement = document.getElementById('transactionsEmpty'); const tbody = document.getElementById('transactionsBody'); if (!tbody) return; // Clear existing data tbody.innerHTML = ''; // Show empty state if (tableElement) tableElement.style.display = 'none'; if (emptyElement) emptyElement.style.display = 'block'; // In a real implementation, this would load actual transaction data } function loadActivePositions() { const tableElement = document.getElementById('positionsTable'); const emptyElement = document.getElementById('positionsEmpty'); const tbody = document.getElementById('positionsBody'); if (!tbody) return; // Clear existing data tbody.innerHTML = ''; // Show empty state if (tableElement) tableElement.style.display = 'none'; if (emptyElement) emptyElement.style.display = 'block'; // In a real implementation, this would load actual positions data } function refreshDashboard() { showToast('Refreshing dashboard...', 'info'); loadDashboardData(); // Re-initialize feather icons setTimeout(() => { feather.replace(); }, 100); } /** * Utility Functions */ function formatCurrency(amount, currency = 'USD') { return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency }).format(amount); } function formatPercentage(value) { return new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value / 100); } function formatAddress(address, length = 8) { if (!address || address.length <= length) return address; return `${address.slice(0, length / 2)}...${address.slice(-length / 2)}`; } function formatTimestamp(timestamp) { const date = new Date(timestamp); return date.toLocaleString(); } function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { showToast('Copied to clipboard', 'success'); }).catch(() => { showToast('Failed to copy to clipboard', 'danger'); }); } /** * Validation Functions */ function validateAddress(address, blockchain) { if (!address) return false; if (blockchain === 'solana') { // Solana addresses are base58 encoded, 32-44 characters return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); } else { // Ethereum/Polygon addresses return /^0x[a-fA-F0-9]{40}$/.test(address); } } function validateAmount(amount) { const num = parseFloat(amount); return !isNaN(num) && num > 0; } /** * Loading States */ function setLoading(element, loading = true) { if (loading) { element.classList.add('loading'); element.disabled = true; } else { element.classList.remove('loading'); element.disabled = false; } } function showSpinner(element) { const spinner = document.createElement('span'); spinner.className = 'spinner-border spinner-border-sm me-2'; spinner.setAttribute('role', 'status'); element.prepend(spinner); } function hideSpinner(element) { const spinner = element.querySelector('.spinner-border'); if (spinner) { spinner.remove(); } } // Chat functionality with enhanced NLP support let chatHistory = []; function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); if (!message) return; // Clear input immediately for better UX input.value = ''; // Add user message to chat addChatMessage('user', message); // Show typing indicator addTypingIndicator(); // Get wallet address if available const walletAddress = document.getElementById('walletAddress')?.value || ''; // Send to AI with enhanced context fetch('/api/v1/ai/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, wallet_address: walletAddress, blockchain: 'ethereum', conversation_context: chatHistory.slice(-5) // Send recent context }) }) .then(response => response.json()) .then(data => { removeTypingIndicator(); if (data.success && data.response) { const response = data.response; // Store in chat history chatHistory.push({ user: message, ai: response.message, timestamp: new Date().toISOString() }); addChatMessage('ai', response.message, response.suggestions); // Auto-focus back to input setTimeout(() => input.focus(), 100); } else { addChatMessage('ai', 'I\'m having some trouble understanding right now. Could you rephrase your question? I can help with DeFi strategies, portfolio analysis, yield farming, or any other crypto-related topics! 🤖'); } }) .catch(error => { removeTypingIndicator(); console.error('Chat error:', error); addChatMessage('ai', 'Connection issue detected. Let me try to help anyway - what specific DeFi or investment question can I assist with? 💬'); }); } // Enhanced message sending with Enter key and better UX document.addEventListener('DOMContentLoaded', function() { const chatInput = document.getElementById('chatInput'); if (chatInput) { chatInput.addEventListener('keypress', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Auto-focus on input for immediate interaction chatInput.focus(); // Add placeholder text suggestions const placeholders = [ "Why did you move my funds to Aave?", "Should I buy more ETH right now?", "What's the best yield farming strategy?", "How can I reduce my portfolio risk?", "Explain impermanent loss to me", "Which DeFi protocols are safest?", "How do I optimize for gas fees?", "What's happening with my portfolio?" ]; let placeholderIndex = 0; setInterval(() => { if (chatInput.value === '') { chatInput.placeholder = placeholders[placeholderIndex]; placeholderIndex = (placeholderIndex + 1) % placeholders.length; } }, 3000); } }); function addChatMessage(sender, message, suggestions = []) { const container = document.getElementById('chatContainer'); if (!container) return; const messageDiv = document.createElement('div'); messageDiv.className = `chat-message ${sender}-message mb-3`; const bubbleDiv = document.createElement('div'); bubbleDiv.className = `message-bubble ${sender} p-3 rounded`; if (sender === 'ai') { bubbleDiv.innerHTML = ` <div class="d-flex align-items-start"> <div class="me-2">🤖</div> <div class="flex-grow-1"> <strong class="text-info">AI Assistant:</strong><br> <span class="message-text">${message}</span> </div> </div> `; bubbleDiv.style.backgroundColor = '#f8f9fa'; bubbleDiv.style.border = '1px solid #dee2e6'; } else { bubbleDiv.innerHTML = ` <div class="d-flex align-items-start justify-content-end"> <div class="flex-grow-1 text-end"> <strong class="text-primary">You:</strong><br> <span class="message-text">${message}</span> </div> <div class="ms-2">👤</div> </div> `; bubbleDiv.style.backgroundColor = '#e3f2fd'; bubbleDiv.style.border = '1px solid #bbdefb'; messageDiv.style.marginLeft = '20%'; } messageDiv.appendChild(bubbleDiv); // Enhanced suggestions for AI messages if (sender === 'ai' && suggestions && suggestions.length > 0) { const suggestionsDiv = document.createElement('div'); suggestionsDiv.className = 'message-suggestions mt-2'; suggestionsDiv.innerHTML = '<small class="text-muted">💡 Try asking:</small><br>'; suggestions.forEach((suggestion, index) => { const btn = document.createElement('button'); btn.className = 'btn btn-outline-info btn-sm me-2 mb-1'; btn.textContent = suggestion; btn.onclick = () => { const input = document.getElementById('chatInput'); input.value = suggestion; input.focus(); // Auto-send after a brief delay for better UX setTimeout(() => sendMessage(), 100); }; suggestionsDiv.appendChild(btn); }); messageDiv.appendChild(suggestionsDiv); } container.appendChild(messageDiv); // Smooth scroll to bottom setTimeout(() => { container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); }, 100); } // Placeholder for typing indicator (implementation not provided in original snippet) function addTypingIndicator() { const container = document.getElementById('chatContainer'); if (!container) return; let typingIndicator = document.getElementById('typingIndicator'); if (!typingIndicator) { typingIndicator = document.createElement('div'); typingIndicator.id = 'typingIndicator'; typingIndicator.className = 'chat-message ai-message mb-3'; typingIndicator.innerHTML = ` <div class="message-bubble ai p-3 rounded d-flex align-items-center" style="background-color: #f8f9fa; border: 1px solid #dee2e6;"> <div class="spinner-border text-info me-2" role="status"></div> <span class="text-muted">AI Assistant is typing...</span> </div> `; container.appendChild(typingIndicator); } container.scrollTop = container.scrollHeight; } // Placeholder for removing typing indicator (implementation not provided in original snippet) function removeTypingIndicator() { const typingIndicator = document.getElementById('typingIndicator'); if (typingIndicator) { typingIndicator.remove(); } } /** * Export functions for global access */ window.DeFiMCP = { showApiKeyModal, showWalletModal, generateApiKey, toggleApiKeyVisibility, generateWallet, importWallet, showToast, refreshDashboard, executeSwap, executeLend, executeFarm, loadPortfolio, loadPositions, loadTransactions, validateAddress, validateAmount, formatCurrency, formatPercentage, formatAddress, formatTimestamp, copyToClipboard };

Latest Blog Posts

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/JMadhan1/OneDefi-MCP'

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