1
0
mirror of https://github.com/mortdeus/legacy-cc.git synced 2026-10-08 03:42:05 +00:00
This commit is contained in:
cryptoking1106
2025-09-18 18:12:50 -03:00
parent 936e12cfc7
commit 086ec82844
141 changed files with 67491 additions and 6883 deletions

View File

@@ -0,0 +1,445 @@
# 🚀 Solana Trading Bot v2.0
A high-performance, enterprise-grade Solana trading bot with advanced MEV protection, comprehensive risk management, and real-time monitoring capabilities.
## ✨ New Features in v2.0
- **🔒 Advanced Risk Management**: Daily loss limits, position limits, and automated risk scoring
- **📊 Real-time Dashboard**: Web-based monitoring interface with live charts and metrics
- **🔔 Multi-channel Notifications**: Telegram, Discord, and email notifications
- **📝 Structured Logging**: Winston-based logging with daily rotation and multiple levels
- **⚡ Performance Optimization**: Connection pooling, rate limiting, and retry mechanisms
- **🛡️ Enhanced Security**: IP whitelisting, rate limiting, and secure configuration management
- **📈 Advanced Analytics**: PnL tracking, win rate analysis, and risk metrics
- **🔄 Graceful Shutdown**: Safe position closure and cleanup on exit
## 🏗️ Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ gRPC Stream │───▶│ Transaction │───▶│ Trading Engine │
│ (Triton One) │ │ Parser │ │ (Main Bot) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Pool Detection │ │ Risk Manager │
│ (PumpFun, etc.) │ │ & Position Mgmt │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Jupiter API │ │ Notification │
│ Swap Execution │ │ Service │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Web Dashboard │ │ Logger & │
│ (Real-time UI) │ │ Analytics │
└─────────────────┘ └─────────────────┘
```
## 🚀 Quick Start
### 1. Prerequisites
- **Node.js 18+** (Latest LTS recommended)
- **Solana wallet** with SOL balance
- **Triton One gRPC** access
- **RPC endpoint** (Helius, QuickNode, etc.)
### 2. Installation
```bash
# Clone the repository
git clone <your-repo-url>
cd solana-trading-bot
# Install dependencies
npm install
# Copy environment template
cp env.template .env
```
### 3. Configuration
Edit the `.env` file with your configuration:
```bash
# Essential Configuration
PRIVATE_KEY=your_wallet_private_key
RPC_URL=https://your-rpc-endpoint.com
GRPC_ENDPOINT=https://your-grpc-endpoint.com
GRPCTOKEN=your_grpc_token
# Trading Parameters
SNIPERAMOUNT=0.1 # SOL amount per snipe
PROFIT_TARGET=2.0 # 2x profit target
STOP_LOSS=0.5 # 50% stop loss
MAX_HOLD_TIME=300000 # 5 minutes max hold
MIN_LIQUIDITY=10 # Minimum liquidity in SOL
MAX_POSITIONS=5 # Maximum concurrent positions
# Risk Management
MAX_DAILY_LOSS=1.0 # Maximum daily loss in SOL
MAX_SINGLE_LOSS=0.5 # Maximum single trade loss
TRADE_COOLDOWN=5000 # Cooldown between trades (ms)
# Dashboard (Optional)
ENABLE_DASHBOARD=true # Enable web dashboard
DASHBOARD_PORT=3000 # Dashboard port
# Notifications (Optional)
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
DISCORD_WEBHOOK_URL=your_webhook_url
```
### 4. Run the Bot
```bash
# Start the trading bot
npm start
# Or for development with auto-restart
npm run dev
# Start with dashboard enabled
ENABLE_DASHBOARD=true npm start
```
## 🌐 Web Dashboard
Access the real-time dashboard at `http://localhost:3000` (when enabled):
- **📊 Live Metrics**: Real-time PnL, positions, and risk levels
- **📈 Interactive Charts**: PnL trends and position distribution
- **🔍 Position Management**: View and manage active positions
- **📋 Trading History**: Complete trade history with analytics
- **⚙️ Configuration**: View and monitor bot settings
- **🚨 Emergency Controls**: Emergency position closure
## ⚙️ Configuration Options
### Trading Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `SNIPERAMOUNT` | 0.1 | SOL amount to use for each snipe |
| `PROFIT_TARGET` | 2.0 | Profit target multiplier (2x) |
| `STOP_LOSS` | 0.5 | Stop loss multiplier (50% loss) |
| `MAX_HOLD_TIME` | 300000 | Maximum time to hold position (5 min) |
| `MIN_LIQUIDITY` | 10 | Minimum liquidity required in SOL |
| `MAX_POSITIONS` | 5 | Maximum concurrent positions |
### Risk Management
| Parameter | Default | Description |
|-----------|---------|-------------|
| `MAX_DAILY_LOSS` | 1.0 | Maximum daily loss in SOL |
| `MAX_SINGLE_LOSS` | 0.5 | Maximum single trade loss in SOL |
| `TRADE_COOLDOWN` | 5000 | Cooldown between trades (ms) |
### Pool Filters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `ENABLE_PUMPFUN` | true | Enable PumpFun pool monitoring |
| `ENABLE_PUMPSWAP` | true | Enable PumpSwap pool monitoring |
| `ENABLE_RAYDIUM_LAUNCHLAB` | true | Enable Raydium LaunchLab monitoring |
| `ENABLE_RAYDIUM_CPMM` | true | Enable Raydium CPMM monitoring |
## 📊 Risk Management Features
### Automated Risk Controls
- **Daily Loss Limits**: Automatic trading halt when daily loss threshold reached
- **Position Limits**: Maximum concurrent position management
- **Trade Cooldowns**: Prevents rapid-fire trading
- **Risk Scoring**: Real-time risk level assessment (LOW/MEDIUM/HIGH)
### Position Management
- **Profit Targets**: Automatic exit at configured profit levels
- **Stop Losses**: Automatic exit at configured loss levels
- **Time-based Exits**: Maximum hold time enforcement
- **Emergency Closure**: Immediate closure of all positions
### Risk Metrics
- **Win Rate Analysis**: Track profitable vs. losing trades
- **PnL Tracking**: Real-time profit/loss monitoring
- **Risk Recommendations**: AI-powered trading suggestions
- **Performance Analytics**: Detailed trading performance metrics
## 🔔 Notification System
### Multi-channel Alerts
- **Telegram**: Real-time trading alerts and updates
- **Discord**: Webhook-based notifications with rich embeds
- **Email**: Detailed reports for important events
- **Dashboard**: Real-time web interface updates
### Notification Types
- **Trade Execution**: Buy/sell confirmations
- **Profit Targets**: When profit targets are reached
- **Stop Losses**: When stop losses are triggered
- **Risk Alerts**: High-risk situation notifications
- **Bot Status**: Startup, shutdown, and error notifications
## 📝 Logging & Monitoring
### Structured Logging
- **Multiple Levels**: Debug, Info, Warn, Error, Trade, Profit, Loss
- **Daily Rotation**: Automatic log file rotation and compression
- **Performance Tracking**: Operation timing and performance metrics
- **Error Context**: Detailed error information with stack traces
### Monitoring Features
- **Health Checks**: API endpoint health monitoring
- **Performance Metrics**: Response times and throughput
- **Error Tracking**: Comprehensive error logging and alerting
- **Audit Trail**: Complete trading activity audit log
## 🛡️ Security Features
### Access Control
- **Rate Limiting**: API request rate limiting
- **IP Whitelisting**: Configurable IP address restrictions
- **Authentication**: Secure API key management
- **Input Validation**: Comprehensive input sanitization
### Data Protection
- **Secure Configuration**: Environment variable management
- **Sensitive Data Masking**: Private key and API key protection
- **Audit Logging**: Complete access and action logging
- **Error Handling**: Secure error message handling
## 🔧 Advanced Configuration
### Performance Optimization
```bash
# Connection settings
CONNECTION_TIMEOUT=30000
MAX_RETRIES=3
RETRY_DELAY=1000
# Rate limiting
MAX_REQUESTS_PER_MINUTE=100
ENABLE_RATE_LIMITING=true
```
### Logging Configuration
```bash
# Log levels
LOG_LEVEL=info
DEBUG=false
LOG_TO_FILE=true
LOG_FILE_PATH=./logs/trading-bot.log
```
### MEV Protection
```bash
# MEV protection settings
ENABLE_MEV_PROTECTION=true
SWAP_METHOD=race # race, nozomi, 0slot, solana
PRIORITY_FEE=1000
```
## 📈 Trading Strategies
### Conservative Strategy
```bash
SNIPERAMOUNT=0.05
PROFIT_TARGET=1.5
STOP_LOSS=0.7
MAX_HOLD_TIME=600000
MIN_LIQUIDITY=20
MAX_DAILY_LOSS=0.5
```
### Aggressive Strategy
```bash
SNIPERAMOUNT=0.2
PROFIT_TARGET=3.0
STOP_LOSS=0.3
MAX_HOLD_TIME=180000
MIN_LIQUIDITY=5
MAX_DAILY_LOSS=2.0
```
### Balanced Strategy
```bash
SNIPERAMOUNT=0.1
PROFIT_TARGET=2.0
STOP_LOSS=0.5
MAX_HOLD_TIME=300000
MIN_LIQUIDITY=10
MAX_DAILY_LOSS=1.0
```
## 🚨 Emergency Procedures
### Emergency Stop
```bash
# Send SIGINT to gracefully shutdown
Ctrl+C
# Or use the dashboard emergency button
# Click "Emergency Close All" in the web interface
```
### Manual Position Closure
```bash
# Close specific position via dashboard
# Or modify the code to add CLI commands
```
## 🔍 Troubleshooting
### Common Issues
1. **gRPC Connection Failed**
- Verify `GRPC_ENDPOINT` and `GRPCTOKEN`
- Check network connectivity
- Ensure Triton One subscription is active
2. **Transaction Failures**
- Verify wallet has sufficient SOL
- Check RPC endpoint status
- Adjust slippage tolerance
3. **Dashboard Not Loading**
- Ensure `ENABLE_DASHBOARD=true`
- Check port availability
- Verify firewall settings
4. **Notification Failures**
- Verify API keys and tokens
- Check network connectivity
- Review notification service logs
### Debug Mode
Enable debug logging:
```bash
DEBUG=true
LOG_LEVEL=debug
```
### Log Files
Check log files for detailed information:
```bash
# Main logs
tail -f logs/trading-bot-*.log
# Error logs
tail -f logs/trading-bot-error-*.log
```
## 📚 API Reference
### Dashboard API Endpoints
- `GET /api/health` - Health check
- `GET /api/status` - Bot status and configuration
- `GET /api/risk` - Risk metrics and analytics
- `GET /api/positions` - Active positions
- `GET /api/history` - Trading history
- `GET /api/stats` - Daily statistics
- `POST /api/emergency/close-all` - Emergency position closure
### WebSocket Support
Real-time updates via WebSocket (planned for v2.1):
```javascript
// Future implementation
const ws = new WebSocket('ws://localhost:3000/api/ws');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Handle real-time updates
};
```
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
### Development Setup
```bash
# Install development dependencies
npm install
# Run linting
npm run lint
# Format code
npm run format
# Run tests
npm test
```
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- [Triton One](https://triton.one/) for gRPC streaming
- [Jupiter](https://jup.ag/) for swap aggregation
- [Solana Labs](https://solana.com/) for the blockchain
- [Raydium](https://raydium.io/) for the DEX platform
- [Winston](https://github.com/winstonjs/winston) for logging
- [Express](https://expressjs.com/) for the web framework
## 📞 Support
For support and questions:
- Create an issue on GitHub
- Join our Discord community
- Check the documentation
- Review the troubleshooting guide
## 🔮 Roadmap
### v2.1 (Q2 2024)
- WebSocket real-time updates
- Advanced charting and analytics
- Mobile-responsive dashboard
- API rate limiting improvements
### v2.2 (Q3 2024)
- Machine learning price prediction
- Advanced order types
- Portfolio rebalancing
- Multi-wallet support
### v3.0 (Q4 2024)
- Cross-chain support
- Advanced MEV strategies
- Institutional features
- Cloud deployment options
---
**⚠️ Disclaimer: This software is for educational purposes only. Use at your own risk. The authors are not responsible for any financial losses. Cryptocurrency trading involves significant risk and may not be suitable for all investors.**

View File

@@ -0,0 +1,84 @@
# 🚀 Quick Setup Guide
## ⚡ 5-Minute Setup
### 1. Install Dependencies
```bash
npm install
```
### 2. Configure Environment
```bash
# Copy the template
cp env.template .env
# Edit .env with your settings
# At minimum, you need:
PRIVATE_KEY=your_wallet_private_key
RPC_URL=https://your-rpc-endpoint.com
GRPC_ENDPOINT=https://your-grpc-endpoint.com
GRPCTOKEN=your_grpc_token
```
### 3. Test Configuration
```bash
npm run test-config
```
### 4. Run Demo (Optional)
```bash
npm run demo
```
### 5. Start Trading
```bash
npm start
```
## 🔑 Required Services
- **Solana Wallet**: Phantom, Solflare, or private key
- **RPC Endpoint**: [Helius](https://helius.xyz/), [QuickNode](https://quicknode.com/), or [Alchemy](https://alchemy.com/)
- **gRPC Access**: [Triton One](https://triton.one/) subscription
## 💰 Minimum Requirements
- **Wallet Balance**: At least 1 SOL
- **Sniper Amount**: 0.1 SOL (configurable)
- **Network**: Stable internet connection
## 🚨 First Time Setup
1. **Test with small amounts first**
2. **Use a dedicated trading wallet**
3. **Monitor the bot initially**
4. **Adjust parameters based on performance**
## 📱 Quick Commands
| Command | Description |
|---------|-------------|
| `npm start` | Start the sniper bot |
| `npm run dev` | Start with auto-restart |
| `npm run test-config` | Verify configuration |
| `npm run demo` | Run demo mode |
| `start.bat` | Windows startup script |
| `start.sh` | Linux/Mac startup script |
## 🔧 Troubleshooting
### Common Issues:
- **"Private key invalid"** → Check key format (base58/base64/JSON)
- **"RPC connection failed"** → Verify RPC URL and network
- **"gRPC error"** → Check Triton One subscription
- **"No trades executing"** → Verify transaction filters
### Need Help?
1. Check the README.md
2. Run `npm run test-config`
3. Check console logs for errors
4. Verify all environment variables
---
**⚡ Ready to snipe? Run `npm start` and watch the magic happen!**

View File

@@ -0,0 +1,180 @@
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import fs from 'fs';
// Load environment variables
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Validate required environment variables
const requiredEnvVars = [
'PRIVATE_KEY',
'RPC_URL',
'GRPC_ENDPOINT',
'GRPCTOKEN'
];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
}
// Configuration object
export const config = {
// Essential Configuration
wallet: {
privateKey: process.env.PRIVATE_KEY,
rpcUrl: process.env.RPC_URL,
grpcEndpoint: process.env.GRPC_ENDPOINT,
grpcToken: process.env.GRPCTOKEN,
},
// Trading Parameters
trading: {
sniperAmount: parseFloat(process.env.SNIPERAMOUNT || '0.1'),
profitTarget: parseFloat(process.env.PROFIT_TARGET || '2.0'),
stopLoss: parseFloat(process.env.STOP_LOSS || '0.5'),
maxHoldTime: parseInt(process.env.MAX_HOLD_TIME || '300000'),
minLiquidity: parseFloat(process.env.MIN_LIQUIDITY || '10'),
maxPositions: parseInt(process.env.MAX_POSITIONS || '5'),
minTxAge: parseInt(process.env.MIN_TX_AGE || '1'),
},
// Pool Filters
pools: {
pumpFun: process.env.ENABLE_PUMPFUN === 'true',
pumpSwap: process.env.ENABLE_PUMPSWAP === 'true',
raydiumLaunchLab: process.env.ENABLE_RAYDIUM_LAUNCHLAB === 'true',
raydiumCpmm: process.env.ENABLE_RAYDIUM_CPMM === 'true',
},
// Swap Configuration
swap: {
method: process.env.SWAP_METHOD || 'solana',
slippageTolerance: parseFloat(process.env.SLIPPAGE_TOLERANCE || '1.0'),
priorityFee: parseInt(process.env.PRIORITY_FEE || '1000'),
},
// Risk Management
risk: {
maxDailyLoss: parseFloat(process.env.MAX_DAILY_LOSS || '1.0'),
maxSingleLoss: parseFloat(process.env.MAX_SINGLE_LOSS || '0.5'),
tradeCooldown: parseInt(process.env.TRADE_COOLDOWN || '5000'),
},
// Notifications
notifications: {
telegram: {
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: process.env.TELEGRAM_CHAT_ID,
},
email: {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
discord: {
webhookUrl: process.env.DISCORD_WEBHOOK_URL,
},
},
// Logging & Monitoring
logging: {
level: process.env.LOG_LEVEL || 'info',
debug: process.env.DEBUG === 'true',
logToFile: process.env.LOG_TO_FILE === 'true',
logFilePath: process.env.LOG_FILE_PATH || './logs/trading-bot.log',
},
// API Keys
api: {
helius: process.env.HELIUS_API_KEY,
jupiter: process.env.JUPITER_API_KEY,
},
// Advanced Settings
advanced: {
mevProtection: process.env.ENABLE_MEV_PROTECTION === 'true',
backtestMode: process.env.BACKTEST_MODE === 'true',
paperTrading: process.env.PAPER_TRADING === 'true',
databaseUrl: process.env.DATABASE_URL || 'sqlite://./trades.db',
},
// Performance
performance: {
maxRetries: parseInt(process.env.MAX_RETRIES || '3'),
retryDelay: parseInt(process.env.RETRY_DELAY || '1000'),
connectionTimeout: parseInt(process.env.CONNECTION_TIMEOUT || '30000'),
},
// Security
security: {
rateLimiting: process.env.ENABLE_RATE_LIMITING === 'true',
maxRequestsPerMinute: parseInt(process.env.MAX_REQUESTS_PER_MINUTE || '100'),
ipWhitelist: process.env.ENABLE_IP_WHITELIST === 'false',
allowedIps: process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(',') : ['127.0.0.1', '::1'],
},
};
// Create logs directory if it doesn't exist
const logsDir = dirname(config.logging.logFilePath);
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Validation functions
export const validateConfig = () => {
const errors = [];
if (config.trading.sniperAmount <= 0) {
errors.push('SNIPERAMOUNT must be greater than 0');
}
if (config.trading.profitTarget <= 1.0) {
errors.push('PROFIT_TARGET must be greater than 1.0');
}
if (config.trading.stopLoss >= 1.0) {
errors.push('STOP_LOSS must be less than 1.0');
}
if (config.trading.maxHoldTime <= 0) {
errors.push('MAX_HOLD_TIME must be greater than 0');
}
if (config.trading.minLiquidity <= 0) {
errors.push('MIN_LIQUIDITY must be greater than 0');
}
if (errors.length > 0) {
throw new Error(`Configuration validation failed:\n${errors.join('\n')}`);
}
return true;
};
// Get configuration for specific module
export const getModuleConfig = (moduleName) => {
switch (moduleName) {
case 'trading':
return config.trading;
case 'swap':
return config.swap;
case 'risk':
return config.risk;
case 'notifications':
return config.notifications;
case 'logging':
return config.logging;
default:
return config;
}
};
// Export default configuration
export default config;

View File

@@ -0,0 +1,427 @@
// Dashboard JavaScript
class TradingBotDashboard {
constructor() {
this.charts = {};
this.refreshInterval = null;
this.init();
}
init() {
this.setupEventListeners();
this.initializeCharts();
this.startAutoRefresh();
this.loadInitialData();
}
setupEventListeners() {
// Refresh button
document.getElementById('refreshBtn').addEventListener('click', () => {
this.refreshAllData();
});
// Emergency close button
document.getElementById('emergencyCloseBtn').addEventListener('click', () => {
this.showEmergencyModal();
});
// Emergency modal
document.getElementById('confirmEmergencyClose').addEventListener('click', () => {
this.emergencyCloseAll();
});
document.getElementById('cancelEmergencyClose').addEventListener('click', () => {
this.hideEmergencyModal();
});
// Close modal on outside click
document.getElementById('emergencyModal').addEventListener('click', (e) => {
if (e.target.id === 'emergencyModal') {
this.hideEmergencyModal();
}
});
}
initializeCharts() {
// PnL Chart
const pnlCtx = document.getElementById('pnlChart').getContext('2d');
this.charts.pnl = new Chart(pnlCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Daily PnL (SOL)',
data: [],
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#ffffff' }
}
},
scales: {
x: {
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(156, 163, 175, 0.2)' }
},
y: {
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(156, 163, 175, 0.2)' }
}
}
}
});
// Positions Chart
const positionsCtx = document.getElementById('positionsChart').getContext('2d');
this.charts.positions = new Chart(positionsCtx, {
type: 'doughnut',
data: {
labels: ['Active', 'Closed'],
datasets: [{
data: [0, 0],
backgroundColor: ['#3b82f6', '#6b7280'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#ffffff' }
}
}
}
});
}
async loadInitialData() {
try {
await Promise.all([
this.loadStatus(),
this.loadRiskMetrics(),
this.loadPositions(),
this.loadHistory(),
this.loadConfiguration()
]);
} catch (error) {
console.error('Error loading initial data:', error);
this.showError('Failed to load dashboard data');
}
}
async refreshAllData() {
const refreshBtn = document.getElementById('refreshBtn');
refreshBtn.classList.add('animate-spin');
try {
await this.loadInitialData();
this.showSuccess('Dashboard refreshed successfully');
} catch (error) {
console.error('Error refreshing data:', error);
this.showError('Failed to refresh dashboard');
} finally {
refreshBtn.classList.remove('animate-spin');
}
}
startAutoRefresh() {
// Refresh data every 30 seconds
this.refreshInterval = setInterval(() => {
this.loadStatus();
this.loadRiskMetrics();
this.loadPositions();
}, 30000);
}
async loadStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
// Update bot status
const statusElement = document.getElementById('botStatus');
statusElement.innerHTML = `
<i class="fas fa-circle mr-2"></i>${data.bot.status}
`;
statusElement.className = `font-bold status-${data.bot.status}`;
} catch (error) {
console.error('Error loading status:', error);
}
}
async loadRiskMetrics() {
try {
const response = await fetch('/api/risk');
const data = await response.json();
// Update quick stats
document.getElementById('activePositions').textContent = data.positionSummary.activePositions;
document.getElementById('dailyPnL').textContent = `${data.dailyStats.netPnL.toFixed(4)} SOL`;
document.getElementById('winRate').textContent = `${data.dailyStats.winRate}%`;
document.getElementById('riskLevel').textContent = data.riskLevel;
// Update PnL chart
this.updatePnLChart(data.dailyStats);
// Update positions chart
this.updatePositionsChart(data.positionSummary);
} catch (error) {
console.error('Error loading risk metrics:', error);
}
}
async loadPositions() {
try {
const response = await fetch('/api/positions');
const data = await response.json();
this.updatePositionsTable(data.positions);
} catch (error) {
console.error('Error loading positions:', error);
}
}
async loadHistory() {
try {
const response = await fetch('/api/history');
const data = await response.json();
this.updateHistoryTable(data.history);
} catch (error) {
console.error('Error loading history:', error);
}
}
async loadConfiguration() {
try {
const response = await fetch('/api/config');
const data = await response.json();
this.updateConfigurationGrid(data);
} catch (error) {
console.error('Error loading configuration:', error);
}
}
updatePnLChart(dailyStats) {
const chart = this.charts.pnl;
// Add current data point
const now = new Date();
const timeLabel = now.toLocaleTimeString();
chart.data.labels.push(timeLabel);
chart.data.datasets[0].data.push(dailyStats.netPnL);
// Keep only last 20 data points
if (chart.data.labels.length > 20) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
}
updatePositionsChart(positionSummary) {
const chart = this.charts.positions;
chart.data.datasets[0].data = [
positionSummary.activePositions,
positionSummary.totalPositions || 0
];
chart.update();
}
updatePositionsTable(positions) {
const tbody = document.getElementById('positionsTableBody');
if (positions.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">No active positions</td>
</tr>
`;
return;
}
tbody.innerHTML = positions.map(position => `
<tr class="border-b border-gray-700">
<td class="p-3">
<div class="flex items-center space-x-2">
<span class="font-mono text-sm">${position.mint.substring(0, 8)}...</span>
<button class="text-blue-400 hover:text-blue-300" onclick="copyToClipboard('${position.mint}')">
<i class="fas fa-copy"></i>
</button>
</div>
</td>
<td class="p-3">${position.entryPrice.toFixed(6)}</td>
<td class="p-3">${(position.currentPrice || position.entryPrice).toFixed(6)}</td>
<td class="p-3 ${position.pnl >= 0 ? 'text-green-400' : 'text-red-400'}">
${(position.pnl || 0).toFixed(4)} SOL
</td>
<td class="p-3">${this.formatDuration(position.holdTime)}</td>
<td class="p-3">
<button class="bg-red-600 hover:bg-red-700 px-3 py-1 rounded text-sm transition-colors"
onclick="closePosition('${position.mint}')">
Close
</button>
</td>
</tr>
`).join('');
}
updateHistoryTable(history) {
const tbody = document.getElementById('historyTableBody');
if (history.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">No trading history</td>
</tr>
`;
return;
}
tbody.innerHTML = history.slice(-10).reverse().map(trade => `
<tr class="border-b border-gray-700">
<td class="p-3">${new Date(trade.timestamp).toLocaleString()}</td>
<td class="p-3">
<span class="px-2 py-1 rounded text-xs ${trade.type === 'buy' ? 'bg-green-600' : 'bg-red-600'}">
${trade.type.toUpperCase()}
</span>
</td>
<td class="p-3">
<span class="font-mono text-sm">${trade.tokenMint.substring(0, 8)}...</span>
</td>
<td class="p-3">${trade.amount.toFixed(4)}</td>
<td class="p-3">${trade.price.toFixed(6)}</td>
<td class="p-3 ${trade.pnl >= 0 ? 'text-green-400' : 'text-red-400'}">
${trade.pnl ? trade.pnl.toFixed(4) : '-'}
</td>
</tr>
`).join('');
}
updateConfigurationGrid(config) {
const grid = document.getElementById('configGrid');
const configItems = [
{ label: 'Sniper Amount', value: `${config.trading.sniperAmount} SOL` },
{ label: 'Profit Target', value: `${config.trading.profitTarget}x` },
{ label: 'Stop Loss', value: `${config.trading.stopLoss}x` },
{ label: 'Max Hold Time', value: `${config.trading.maxHoldTime / 1000}s` },
{ label: 'Max Positions', value: config.trading.maxPositions },
{ label: 'Max Daily Loss', value: `${config.risk.maxDailyLoss} SOL` },
{ label: 'Swap Method', value: config.swap.method },
{ label: 'Slippage', value: `${config.swap.slippageTolerance}%` },
{ label: 'Priority Fee', value: `${config.swap.priorityFee} lamports` }
];
grid.innerHTML = configItems.map(item => `
<div class="bg-gray-800 p-4 rounded-lg">
<div class="text-sm text-gray-400">${item.label}</div>
<div class="text-lg font-semibold">${item.value}</div>
</div>
`).join('');
}
showEmergencyModal() {
document.getElementById('emergencyModal').classList.remove('hidden');
document.getElementById('emergencyModal').classList.add('flex');
}
hideEmergencyModal() {
document.getElementById('emergencyModal').classList.add('hidden');
document.getElementById('emergencyModal').classList.remove('flex');
}
async emergencyCloseAll() {
try {
this.hideEmergencyModal();
const response = await fetch('/api/emergency/close-all', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason: 'dashboard_emergency' })
});
const result = await response.json();
if (result.success) {
this.showSuccess('Emergency closure initiated');
this.refreshAllData();
} else {
this.showError('Failed to initiate emergency closure');
}
} catch (error) {
console.error('Error during emergency closure:', error);
this.showError('Error during emergency closure');
}
}
formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
showSuccess(message) {
this.showNotification(message, 'success');
}
showError(message) {
this.showNotification(message, 'error');
}
showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 ${
type === 'success' ? 'bg-green-600' : 'bg-red-600'
}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
}
// Global functions for table actions
window.copyToClipboard = function(text) {
navigator.clipboard.writeText(text).then(() => {
// Could show a toast notification here
});
};
window.closePosition = function(mint) {
// Implement individual position closure
console.log('Closing position for:', mint);
};
// Initialize dashboard when page loads
document.addEventListener('DOMContentLoaded', () => {
window.dashboard = new TradingBotDashboard();
});

View File

@@ -0,0 +1,192 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solana Trading Bot Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.status-running { color: #10b981; }
.status-stopped { color: #ef4444; }
.status-warning { color: #f59e0b; }
.refresh-btn:hover { transform: rotate(180deg); transition: transform 0.3s ease; }
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<!-- Header -->
<header class="gradient-bg p-6 shadow-lg">
<div class="container mx-auto flex justify-between items-center">
<div class="flex items-center space-x-4">
<i class="fas fa-robot text-3xl"></i>
<div>
<h1 class="text-2xl font-bold">Solana Trading Bot</h1>
<p class="text-blue-100">Advanced MEV Protection & Risk Management</p>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="text-right">
<div class="text-sm text-blue-100">Status</div>
<div id="botStatus" class="font-bold status-running">
<i class="fas fa-circle mr-2"></i>Running
</div>
</div>
<button id="refreshBtn" class="refresh-btn bg-white bg-opacity-20 p-3 rounded-full hover:bg-opacity-30 transition-all">
<i class="fas fa-sync-alt text-white"></i>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto p-6 space-y-6">
<!-- Quick Stats -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Active Positions</p>
<p id="activePositions" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-chart-line text-2xl text-blue-400"></i>
</div>
</div>
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Daily PnL</p>
<p id="dailyPnL" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-coins text-2xl text-green-400"></i>
</div>
</div>
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Win Rate</p>
<p id="winRate" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-trophy text-2xl text-yellow-400"></i>
</div>
</div>
<div class="card rounded-lg p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-200 text-sm">Risk Level</p>
<p id="riskLevel" class="text-2xl font-bold">-</p>
</div>
<i class="fas fa-shield-alt text-2xl text-red-400"></i>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- PnL Chart -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Daily PnL Trend</h3>
<canvas id="pnlChart" height="200"></canvas>
</div>
<!-- Positions Chart -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Active Positions</h3>
<canvas id="positionsChart" height="200"></canvas>
</div>
</div>
<!-- Active Positions Table -->
<div class="card rounded-lg p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Active Positions</h3>
<button id="emergencyCloseBtn" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-exclamation-triangle mr-2"></i>Emergency Close All
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left p-3">Token</th>
<th class="text-left p-3">Entry Price</th>
<th class="text-left p-3">Current Price</th>
<th class="text-left p-3">PnL</th>
<th class="text-left p-3">Hold Time</th>
<th class="text-left p-3">Actions</th>
</tr>
</thead>
<tbody id="positionsTableBody">
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">Loading positions...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Trading History -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Recent Trades</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left p-3">Time</th>
<th class="text-left p-3">Type</th>
<th class="text-left p-3">Token</th>
<th class="text-left p-3">Amount</th>
<th class="text-left p-3">Price</th>
<th class="text-left p-3">PnL</th>
</tr>
</thead>
<tbody id="historyTableBody">
<tr>
<td colspan="6" class="text-center p-6 text-gray-400">Loading history...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Configuration -->
<div class="card rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Bot Configuration</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="configGrid">
<!-- Configuration will be populated here -->
</div>
</div>
</main>
<!-- Emergency Close Modal -->
<div id="emergencyModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-gray-800 p-6 rounded-lg max-w-md w-full mx-4">
<h3 class="text-xl font-bold text-red-400 mb-4">
<i class="fas fa-exclamation-triangle mr-2"></i>Emergency Close All
</h3>
<p class="text-gray-300 mb-4">This will immediately close all active positions. Are you sure?</p>
<div class="flex space-x-3">
<button id="confirmEmergencyClose" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg transition-colors">
Yes, Close All
</button>
<button id="cancelEmergencyClose" class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg transition-colors">
Cancel
</button>
</div>
</div>
</div>
<script src="dashboard.js"></script>
</body>
</html>

View File

@@ -0,0 +1,237 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import { config } from '../config.js';
import logger from '../utils/logger.js';
import riskManager from '../services/riskManager.js';
import notificationService from '../services/notifications.js';
const app = express();
const PORT = process.env.DASHBOARD_PORT || 3000;
// Rate limiting
const rateLimiter = new RateLimiterMemory({
keyGenerator: (req) => req.ip,
points: config.security.maxRequestsPerMinute,
duration: 60,
});
// Middleware
app.use(helmet());
app.use(cors());
app.use(compression());
app.use(express.json());
app.use(express.static('dashboard/public'));
// Rate limiting middleware
app.use(async (req, res, next) => {
try {
await rateLimiter.consume(req.ip);
next();
} catch (rejRes) {
res.status(429).json({
error: 'Too many requests',
retryAfter: Math.round(rejRes.msBeforeNext / 1000),
});
}
});
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
});
});
// Get bot status and configuration
app.get('/api/status', (req, res) => {
try {
const status = {
bot: {
status: 'running',
uptime: process.uptime(),
version: '2.0.0',
timestamp: new Date().toISOString(),
},
config: {
trading: config.trading,
risk: config.risk,
pools: config.pools,
},
};
res.json(status);
} catch (error) {
logger.error('Error getting bot status', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get risk metrics
app.get('/api/risk', (req, res) => {
try {
const riskMetrics = riskManager.getRiskMetrics();
res.json(riskMetrics);
} catch (error) {
logger.error('Error getting risk metrics', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get active positions
app.get('/api/positions', (req, res) => {
try {
const positions = riskManager.getActivePositions();
const summary = riskManager.getPositionSummary();
res.json({
positions,
summary,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error getting positions', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get trading history
app.get('/api/history', (req, res) => {
try {
const history = riskManager.tradeHistory || [];
const dailyStats = riskManager.getDailyStats();
res.json({
history,
dailyStats,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error getting trading history', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get daily statistics
app.get('/api/stats', (req, res) => {
try {
const dailyStats = riskManager.getDailyStats();
res.json(dailyStats);
} catch (error) {
logger.error('Error getting daily stats', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Emergency actions
app.post('/api/emergency/close-all', async (req, res) => {
try {
const { reason } = req.body;
const results = await riskManager.emergencyCloseAll(reason || 'dashboard_request');
res.json({
success: true,
message: 'Emergency closure initiated',
results,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error initiating emergency closure', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Send test notification
app.post('/api/notifications/test', async (req, res) => {
try {
const { message, type } = req.body;
await notificationService.sendNotification(
message || 'Test notification from dashboard',
type || 'info',
{ source: 'dashboard' }
);
res.json({
success: true,
message: 'Test notification sent',
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Error sending test notification', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Update configuration (read-only for now, could be extended)
app.get('/api/config', (req, res) => {
try {
// Return safe configuration (no sensitive data)
const safeConfig = {
trading: config.trading,
risk: config.risk,
pools: config.pools,
swap: config.swap,
logging: config.logging,
};
res.json(safeConfig);
} catch (error) {
logger.error('Error getting configuration', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// WebSocket endpoint for real-time updates (placeholder)
app.get('/api/ws', (req, res) => {
res.json({
message: 'WebSocket endpoint - implement with Socket.IO for real-time updates',
timestamp: new Date().toISOString(),
});
});
// Error handling middleware
app.use((error, req, res, next) => {
logger.error('Dashboard error', error);
res.status(500).json({
error: 'Internal server error',
message: error.message,
timestamp: new Date().toISOString(),
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Endpoint not found',
timestamp: new Date().toISOString(),
});
});
// Start server
const server = app.listen(PORT, () => {
logger.info(`Dashboard server started on port ${PORT}`);
logger.info(`Dashboard available at: http://localhost:${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down dashboard server');
server.close(() => {
logger.info('Dashboard server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down dashboard server');
server.close(() => {
logger.info('Dashboard server closed');
process.exit(0);
});
});
export default app;

View File

@@ -0,0 +1,157 @@
# =============================================================================
# SOLANA TRADING BOT CONFIGURATION
# =============================================================================
# =============================================================================
# ESSENTIAL CONFIGURATION
# =============================================================================
# Your wallet private key (base58 encoded)
PRIVATE_KEY=your_wallet_private_key_here
# Solana RPC endpoint (Helius, QuickNode, Alchemy, etc.)
RPC_URL=https://your-rpc-endpoint.com
# Triton One gRPC endpoint for transaction streaming
GRPC_ENDPOINT=https://your-grpc-endpoint.com
# Triton One authentication token
GRPCTOKEN=your_grpc_token_here
# =============================================================================
# TRADING PARAMETERS
# =============================================================================
# Amount of SOL to use for each snipe
SNIPERAMOUNT=0.1
# Profit target multiplier (e.g., 2.0 = 2x profit)
PROFIT_TARGET=2.0
# Stop loss multiplier (e.g., 0.5 = 50% loss)
STOP_LOSS=0.5
# Maximum time to hold position in milliseconds (5 minutes = 300000)
MAX_HOLD_TIME=300000
# Minimum liquidity required in SOL
MIN_LIQUIDITY=10
# Maximum concurrent positions
MAX_POSITIONS=5
# Minimum transaction age in seconds
MIN_TX_AGE=1
# =============================================================================
# POOL FILTERS
# =============================================================================
# Enable/disable specific pool types
ENABLE_PUMPFUN=true
ENABLE_PUMPSWAP=true
ENABLE_RAYDIUM_LAUNCHLAB=true
ENABLE_RAYDIUM_CPMM=true
# =============================================================================
# SWAP CONFIGURATION
# =============================================================================
# Swap method: solana, race, nozomi, 0slot
SWAP_METHOD=solana
# Slippage tolerance percentage
SLIPPAGE_TOLERANCE=1.0
# Priority fee in lamports
PRIORITY_FEE=1000
# =============================================================================
# RISK MANAGEMENT
# =============================================================================
# Maximum daily loss in SOL
MAX_DAILY_LOSS=1.0
# Maximum single trade loss in SOL
MAX_SINGLE_LOSS=0.5
# Cooldown period between trades in milliseconds
TRADE_COOLDOWN=5000
# =============================================================================
# NOTIFICATIONS
# =============================================================================
# Telegram bot configuration
TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_CHAT_ID=your_chat_id_here
# Email notifications
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password_here
# Discord webhook
DISCORD_WEBHOOK_URL=your_webhook_url_here
# =============================================================================
# LOGGING & MONITORING
# =============================================================================
# Log level: debug, info, warn, error
LOG_LEVEL=info
# Enable debug mode
DEBUG=false
# Log to file
LOG_TO_FILE=true
# Log file path
LOG_FILE_PATH=./logs/trading-bot.log
# =============================================================================
# API KEYS (Optional)
# =============================================================================
# Helius API key for enhanced RPC
HELIUS_API_KEY=your_helius_key_here
# Jupiter API key for better swap rates
JUPITER_API_KEY=your_jupiter_key_here
# =============================================================================
# ADVANCED SETTINGS
# =============================================================================
# Enable MEV protection
ENABLE_MEV_PROTECTION=true
# Enable backtesting mode
BACKTEST_MODE=false
# Enable paper trading
PAPER_TRADING=false
# Database connection (for position tracking)
DATABASE_URL=sqlite://./trades.db
# =============================================================================
# PERFORMANCE OPTIMIZATION
# =============================================================================
# Transaction retry attempts
MAX_RETRIES=3
# Retry delay in milliseconds
RETRY_DELAY=1000
# Connection timeout in milliseconds
CONNECTION_TIMEOUT=30000
# =============================================================================
# SECURITY
# =============================================================================
# Enable rate limiting
ENABLE_RATE_LIMITING=true
# Maximum requests per minute
MAX_REQUESTS_PER_MINUTE=100
# Enable IP whitelist
ENABLE_IP_WHITELIST=false
# Allowed IP addresses (comma-separated)
ALLOWED_IPS=127.0.0.1,::1

View File

@@ -0,0 +1,182 @@
import { Connection, PublicKey, LAMPORTS_PER_SOL, Keypair } from "@solana/web3.js";
import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token";
import chalk from "chalk";
import dotenv from "dotenv";
import bs58 from "bs58";
dotenv.config();
import { swap } from "./swap.js";
// import { buy_pumpfun, buy_pumpswap, sell_pumpfun, sell_pumpswap } from "./swapsdk_0slot.js";
// import { buy_raydium_CPMM, buy_raydium_launchpad, sell_raydium_CPMM, sell_raydium_launchpad } from "./swapRaydium.js";
const RPC_URL = process.env.RPC_URL;
const connection = new Connection(RPC_URL, "confirmed");
//============functions============//
export const token_buy = async (mint, sol_amount, pool_status, context) => {
if (!mint) {
throw new Error("mint is required and was not provided.");
}
const currentUTC = new Date();
const txid = await swap("BUY", mint, sol_amount * LAMPORTS_PER_SOL);
// let txid = "";
console.log(chalk.green(`🟢BUY tokenAmount:::${sol_amount} pool_status: ${pool_status} `));
//============off chain sign ultra fast============//
// if (pool_status == "pumpfun") {
// txid = await buy_pumpfun(mint, sol_amount * LAMPORTS_PER_SOL, context);//off chain sign ultra fast
// } else if (pool_status == "pumpswap") {
// txid = await buy_pumpswap(mint, sol_amount * LAMPORTS_PER_SOL, context.pool);
// } else if (pool_status == "raydium_launchlab") {
// txid = await buy_raydium_launchpad(mint, sol_amount * LAMPORTS_PER_SOL, context);
// } else {
// txid = await buy_raydium_CPMM(mint, sol_amount * LAMPORTS_PER_SOL);
// }
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ Total BUY time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
return txid;
};
export const token_sell = async (mint, tokenAmount, pool_status, isFull, context) => {
try {
if (!mint) {
throw new Error("mint is required and was not provided.");
}
console.log(chalk.red(`🔴SELL tokenAmount:::${tokenAmount} pool_status: ${pool_status} `));
const currentUTC = new Date();
//============off chain sign ultra fast============//
// let txid = "";
// if (pool_status == "pumpfun") {
// txid = await sell_pumpfun(mint, tokenAmount, isFull, context);
// } else if (pool_status == "pumpswap") {
// txid = await sell_pumpswap(mint, tokenAmount, context.pool, isFull);
// } else if (pool_status == "raydium_launchlab") {
// txid = await sell_raydium_launchpad(mint, tokenAmount, isFull);
// } else {
// txid = await sell_raydium_CPMM(mint, tokenAmount, isFull);
// }
const txid = await swap("SELL", mint, tokenAmount);
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ Total SELL time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
if (txid === "stop") {
console.log(chalk.red(`[${new Date().toISOString()}] 🛑 Swap returned "stop" - no balance for ${mint}`));
return "stop";
}
if (txid) {
console.log(chalk.green(`Successfully sold ${tokenAmount} tokens : https://solscan.io/tx/${txid}`));
return txid;
}
return null;
} catch (error) {
console.error("Error in token_sell:", error.message);
if (error.response?.data) {
console.error("API Error details:", error.response.data);
}
return null;
}
};
export const getSplTokenBalance = async (mint) => {
if (!mint) {
console.log("🔄 Token balance error: Mint address is undefined or null.");
throw new Error("Mint address is undefined or null.");
}
let mintPubkey;
try {
mintPubkey = new PublicKey(mint);
} catch (err) {
console.log("🔄 Token balance error: Invalid mint address provided.");
throw err;
}
// const publicKey = getPublicKeyFromPrivateKey();
const publicKey = getPublicKeyFromPrivateKey();
const ata = await getAssociatedTokenAddress(mintPubkey, new PublicKey(publicKey));
let account;
try {
account = await getAccount(connection, ata);
} catch (err) {
// Handle TokenAccountNotFoundError gracefully
if (
err.name === "TokenAccountNotFoundError" ||
(err.message && (
err.message.includes("Failed to find account") ||
err.message.includes("Account does not exist") ||
err.message.includes("could not find account")
))
) {
// No account found, treat as zero balance
console.log("🔄 Token balance: Account not found, returning 0.");
return null;
}
// If the error is related to an invalid mint, log and throw error
if (err.message && err.message.includes("Invalid param")) {
console.log("🔄 Token balance error: Invalid mint param.");
throw err;
}
// Other errors
console.log("🔄 Token balance error:", err.message || err);
throw err;
}
return Number(account.amount); // Convert BigInt to Number
};
export const checkWalletBalance = async () => {
try {
const pubkey = getPublicKeyFromPrivateKey();
const balanceLamports = await connection.getBalance(pubkey);
const balance = balanceLamports / LAMPORTS_PER_SOL;
return { balance };
} catch (err) {
console.error("Error checking wallet balance:", err.message || err);
throw err;
}
};
export const getKeypairFromPrivateKey = (privateKeyString) => {
try {
// Try base58 first
try {
const decoded = bs58.decode(privateKeyString);
return Keypair.fromSecretKey(decoded);
} catch (e) {
// Not base58, try base64
try {
const decoded = Buffer.from(privateKeyString, 'base64');
return Keypair.fromSecretKey(decoded);
} catch (e2) {
// Not base64, try JSON array
try {
const arr = JSON.parse(privateKeyString);
const uint8arr = new Uint8Array(arr);
return Keypair.fromSecretKey(uint8arr);
} catch (e3) {
throw new Error('Invalid private key format. Supported formats: base58, base64, or JSON array');
}
}
}
} catch (err) {
throw new Error('Failed to decode private key: ' + err.message);
}
};
export const getPublicKeyFromPrivateKey = () => {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("Private key is required and was not provided.");
}
const keypair = getKeypairFromPrivateKey(privateKey);
return keypair.publicKey.toString();
};

View File

@@ -0,0 +1,204 @@
import "dotenv/config";
import Client from "@triton-one/yellowstone-grpc";
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";
import { decodeInstruction } from '@solana/spl-token';
import { Connection, Keypair } from '@solana/web3.js';
import chalk from "chalk";
import { tOutPut } from "./parsingtransaction.js";
import { handleNewTokenLaunch } from "./main.js";
import dotenv from 'dotenv'
dotenv.config();
const GRPCTOKEN=process.env.GRPCTOKEN
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT
// Pre-define constants
const SOLANA_TOKEN = "So11111111111111111111111111111111111111112";
const RAYDIUM_FEE = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"//"7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5";
const Raydium_launchpad_authority = "WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh"
// Create default client
const defaultClient = new Client(
GRPC_ENDPOINT,
GRPCTOKEN
);
export let isNewLaunchRunning = true;
export const stopNewLaunch = () => {
isNewLaunchRunning = false;
console.log(chalk.red("New launch monitoring stopped"));
};
// Default request args
const defaultArgs = {
accounts: {},
slots: {},
transactions: {
pumpfun: {
vote: false,
failed: false,
signature: undefined,
accountInclude: [RAYDIUM_FEE],
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.PROCESSED,
};
// This function checks for MintTo instructions by comparing with log messages
async function checkMintTo(data) {
const tx = data.transaction?.transaction;
const meta = data.transaction;
if (!tx || !meta?.transaction?.meta?.logMessages) return;
// Find if MintTo is present in log messages
const mintToLog = meta.transaction.meta.logMessages.find((log) =>
typeof log === "string" && log.toLowerCase().includes("instruction: mintto")
);
if (mintToLog) {
console.log("🩸🩸🩸🩸🩸 MintTo found in logs!");
return true
} else {
// No MintTo found in logs
return false
}
}
async function handleStream(client = defaultClient, args = defaultArgs) {
const stream = await client.subscribe(args);
const streamClosed = new Promise((resolve, reject) => {
stream.on("error", (error) => {
console.error("Stream Error:", error);
reject(error);
stream.end();
});
stream.on("end", resolve);
stream.on("close", resolve);
});
stream.on("data", async (data) => {
// Return early if monitoring is disabled
if (!isNewLaunchRunning) {
stream.end();
return;
}
try {
// console.log(chalk.green("start__________new token streaming_________"));
if (!data?.transaction?.transaction) {
return null;
}
const mintTo = await checkMintTo(data)
if(!mintTo){
return null
}
console.log(`[${new Date().toISOString()}] 🩸🩸🩸🩸🩸 MintTo found in logs!`);
const preTokenBalances = data?.transaction?.transaction?.meta?.preTokenBalances;
const postTokenBalances = data?.transaction?.transaction?.meta?.postTokenBalances;
if (!preTokenBalances || !postTokenBalances) {
console.log("Token balances not found in transaction data");
return null;
}
let pre_sol = 0;
let post_sol = 0;
let pre_token = 0;
let post_token = 0;
let token_mint = "";
let token_owner = "";
for (const balance of postTokenBalances) {
if (balance.owner !== Raydium_launchpad_authority) {
if (balance.mint !== SOLANA_TOKEN) {
post_token = balance.uiTokenAmount.uiAmount || 0;
token_mint = balance.mint;
token_owner = balance.owner;
}
} else {
post_sol = balance.uiTokenAmount.uiAmount || 0;
}
}
for (const balance of preTokenBalances) {
if (balance.owner !== Raydium_launchpad_authority) {
if (balance.mint !== SOLANA_TOKEN) {
pre_token = balance.uiTokenAmount.uiAmount || 0;
}
}
}
const solChanges = post_sol-pre_sol;
const tokenChanges = post_token-pre_token;
console.log(chalk.bgBlue.bold(`🪙 Token Mint:`), chalk.white(token_mint));
console.log(chalk.bgMagenta.bold(`👤 Token Owner:`), chalk.white(token_owner));
console.log(chalk.bgYellow.bold(`💸 SOL Balance Change:`), chalk.yellow(`${solChanges > 0 ? "+" : ""}${solChanges}`));
console.log(chalk.bgCyan.bold(`🔄 Token Balance Change:`), chalk.cyan(`${tokenChanges > 0 ? "+" : ""}${tokenChanges}`));
if (solChanges> 0.1) {
console.log(chalk.bgGreen("Found large SOL transfer:", solChanges));
// Parse transaction data to get pool information
const parsedData = await tOutPut(data);
if (parsedData) {
console.log(chalk.cyan(`Pool status: Raydium launchpad`));
// Call the main bot logic to handle the new token launch
await handleNewTokenLaunch(token_mint, parsedData.pool_status, parsedData.context);
} else {
console.log(chalk.yellow("Failed to parse transaction data"));
}
return token_mint;
}
return null;
} catch (error) {
console.error("Error processing transaction data:", error);
return null;
}
});
try {
await stream.write(args);
} catch (error) {
console.error("Subscription request failed:", error);
throw error;
}
await streamClosed;
}
export async function newlunched_subscribeCommand(client = defaultClient, args = defaultArgs) {
// Set monitoring flag to true when starting
isNewLaunchRunning = true;
console.log(chalk.green("New launch monitoring started"));
while (isNewLaunchRunning) {
try {
await handleStream(client, args);
} catch (error) {
console.error("Stream error, restarting in 1 second...", error);
// Only wait and retry if monitoring is still enabled
if (isNewLaunchRunning) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
console.log("New launch monitoring stopped");
}
// Export client and args for external use
export { defaultClient, defaultArgs };
// Remove the auto-execution to prevent conflicts
// newlunched_subscribeCommand()

View File

@@ -0,0 +1,154 @@
import { pump_geyser } from "./main.js";
import { config, validateConfig } from "./config.js";
import logger from "./utils/logger.js";
import notificationService from "./services/notifications.js";
import riskManager from "./services/riskManager.js";
// Start dashboard server if enabled
let dashboardServer = null;
if (process.env.ENABLE_DASHBOARD === 'true') {
try {
const dashboardApp = await import("./dashboard/server.js");
dashboardServer = dashboardApp.default;
logger.info("Dashboard server started");
} catch (error) {
logger.warn("Failed to start dashboard server", error);
}
}
// Validate configuration before starting
try {
validateConfig();
logger.info("Configuration validated successfully");
} catch (error) {
logger.error("Configuration validation failed", error);
process.exit(1);
}
// Check wallet balance
const checkWalletBalance = async () => {
try {
const { getBalance } = await import("./swap.js");
const balance = await getBalance();
if (balance < 1) {
logger.error("Wallet balance is below 1 SOL. Current balance:", balance, "SOL");
await notificationService.sendNotification(
`❌ Insufficient wallet balance: ${balance} SOL`,
'error',
{ balance, required: 1 }
);
process.exit(1);
}
logger.info(`Wallet balance: ${balance} SOL`);
await notificationService.sendNotification(
`✅ Bot startup successful. Wallet balance: ${balance} SOL`,
'success',
{ balance }
);
return balance;
} catch (err) {
logger.error("Error checking wallet balance", err);
await notificationService.sendNotification(
`❌ Failed to check wallet balance: ${err.message}`,
'error',
{ error: err.message }
);
process.exit(1);
}
};
// Main startup function
const main = async () => {
try {
logger.info("🚀 Starting Solana Trading Bot...");
// Check wallet balance
await checkWalletBalance();
// Start the main bot
await pump_geyser();
// Send startup notification
await notificationService.notifyBotStatus("Started", {
timestamp: new Date().toISOString(),
config: {
trading: config.trading,
risk: config.risk,
pools: config.pools,
},
});
logger.info("✅ Bot startup completed successfully");
} catch (error) {
logger.error("❌ Bot startup failed", error);
await notificationService.notifyError(error, "Bot Startup");
process.exit(1);
}
};
// Handle process signals
process.on('SIGINT', async () => {
logger.info("🛑 SIGINT received, shutting down...");
await handleShutdown("SIGINT");
});
process.on('SIGTERM', async () => {
logger.info("🛑 SIGTERM received, shutting down...");
await handleShutdown("SIGTERM");
});
process.on('uncaughtException', async (error) => {
logger.error("Uncaught exception", error);
await notificationService.notifyError(error, "Uncaught Exception");
await handleShutdown("Uncaught Exception");
});
process.on('unhandledRejection', async (reason, promise) => {
logger.error("Unhandled rejection", { reason, promise });
await notificationService.notifyError(new Error(reason), "Unhandled Rejection");
});
// Graceful shutdown handler
const handleShutdown = async (reason) => {
try {
logger.info("Initiating graceful shutdown...");
// Get final statistics
const finalStats = riskManager.getDailyStats();
const riskMetrics = riskManager.getRiskMetrics();
// Send shutdown notification
await notificationService.notifyBotStatus("Shutdown", {
reason,
finalStats,
riskMetrics,
timestamp: new Date().toISOString(),
});
// Close dashboard server if running
if (dashboardServer) {
logger.info("Closing dashboard server...");
// Note: In a real implementation, you'd want to properly close the Express server
}
logger.info("Graceful shutdown completed");
process.exit(0);
} catch (error) {
logger.error("Error during shutdown", error);
process.exit(1);
}
};
// Start the bot
main().catch(async (error) => {
logger.error("Fatal error in main function", error);
await notificationService.notifyError(error, "Main Function");
process.exit(1);
});

View File

@@ -0,0 +1,15 @@
{
"keep": {
"days": true,
"amount": 14
},
"auditLog": "logs\\.6dee68a8e2d6fa545f978ea2a4cd18a2469f8eef-audit.json",
"files": [
{
"date": 1755198754557,
"name": "logs\\trading-bot-2025-08-14.log",
"hash": "2ce46b1fe2271a1eaab915e581771fb25f76310f7424154bac8bcfe21bdb8a8a"
}
],
"hashType": "sha256"
}

View File

@@ -0,0 +1,15 @@
{
"keep": {
"days": true,
"amount": 30
},
"auditLog": "logs\\.a961c1e9547c0e8ccd2f7b24907043cafbaced36-audit.json",
"files": [
{
"date": 1755198754559,
"name": "logs\\trading-bot-error-2025-08-14.log",
"hash": "da7d7bef5942bf1d56fce13f254a63f28454652870987c7bfd3303f17eef1ad2"
}
],
"hashType": "sha256"
}

View File

@@ -0,0 +1,20 @@
{"level":"info","message":"Configuration validated successfully","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:39.094Z"}
{"level":"info","message":"🚀 Starting Solana Trading Bot...","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:39.096Z"}
{"level":"info","message":"Wallet balance: 0.004996182 SOL","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.970Z"}
{"level":"info","message":"🚀 Starting Solana Raydium Sniper Bot...","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.973Z"}
{"level":"info","message":"🔑 Wallet Public Key: DopsYMstRqBm3SSsn1vY6sjhFPjXbtQeTPoXWsoopodF","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.973Z"}
{"level":"info","message":"💰 Sniper Amount: 0.00001 SOL","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.974Z"}
{"level":"info","message":"🎯 Profit Target: 2x","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.974Z"}
{"level":"info","message":"🛑 Stop Loss: 0.5x","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.974Z"}
{"level":"info","message":"⏱️ Max Hold Time: 300s","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.975Z"}
{"level":"info","message":"📊 Max Positions: 5","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:40.975Z"}
{"context":null,"level":"info","message":"New token launch detected: 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","poolStatus":"raydium","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:57.819Z"}
{"level":"info","message":"Executing sniper trade for 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:12:57.819Z"}
{"level":"error","message":"Failed to execute sniper trade for 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:02.572Z"}
{"context":{"amountIn":"309346784","amountOut":"1416041927389","platformFee":"3093468","poolState":"H6Cnafust6WPiduKfuuYoaYLLVHJankcuvRYjVdBrW8n","poolStatus":{"normal":{}},"protocolFee":"773367","realBaseAfter":"687438457793402","realBaseBefore":"686022415866013","realQuoteAfter":"53486586492","realQuoteBefore":"53181106543","shareFee":"0","totalBaseSell":"793100000000000","tradeDirection":0,"virtualBase":"1073025605596382","virtualQuote":"30000852951"},"level":"info","message":"New token launch detected: EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","poolStatus":"raydium_launchlab","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:28.663Z"}
{"level":"info","message":"Executing sniper trade for EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:28.663Z"}
{"level":"error","message":"Failed to execute sniper trade for EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.143Z"}
{"context":null,"level":"info","message":"New token launch detected: 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","poolStatus":"raydium","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.214Z"}
{"level":"info","message":"Executing sniper trade for 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.214Z"}
{"level":"error","message":"Failed to execute sniper trade for 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:35.375Z"}
{"level":"info","message":"🛑 SIGINT received, shutting down...","service":"solana-trading-bot","timestamp":"2025-08-14T19:14:09.666Z"}

View File

@@ -0,0 +1,3 @@
{"level":"error","message":"Failed to execute sniper trade for 6BuNjmVX86rP7iYKWi8mWGZujFYGsb91z2NNxxiabonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:02.572Z"}
{"level":"error","message":"Failed to execute sniper trade for EJhqXKJEncSx1HJjS5ZpKdiKGGgLiRgNPvo8JZvw5Guj","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:32.143Z"}
{"level":"error","message":"Failed to execute sniper trade for 3mEqoZn2s6CMtPAuD2jF9CGjEfPCeDAiVLsNj6Xrbonk","service":"solana-trading-bot","timestamp":"2025-08-14T19:13:35.375Z"}

View File

@@ -0,0 +1,325 @@
import { newlunched_subscribeCommand, stopNewLaunch } from "./grpc.js";
import { token_buy, token_sell, getSplTokenBalance, getPublicKeyFromPrivateKey } from "./fuc.js";
import { getBalance } from "./swap.js";
import { config, validateConfig } from "./config.js";
import logger from "./utils/logger.js";
import notificationService from "./services/notifications.js";
import riskManager from "./services/riskManager.js";
import chalk from "chalk";
// Trading configuration from config service
const tradingConfig = config.trading;
// Track active positions
const activePositions = new Map();
export const pump_geyser = async () => {
try {
// Validate configuration
validateConfig();
const walletKey = getPublicKeyFromPrivateKey();
// Log startup banner
console.log(chalk.magentaBright(`
██████╗██████╗ ██╗ ██╗██████╗ ████████╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗
██╔════╝██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗ ██║██╔════╝
██║ ██████╔╝ ██║ ██╔╝██████╔╝ ██║ ██║ ██║█████╔╝ ██║██╔██╗ ██║██║ ██╗
██║ ██╔══██╗ ██╔═╝ ██╔═══╝ ██║ ██║ ██║██╔═██╗ ██║██║╚██╗██║██║ ██║
╚██████╗██║ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██╗██║██║ ╚████║╚██████╔╝
╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝
`));
logger.info("🚀 Starting Solana Raydium Sniper Bot...");
logger.info(`🔑 Wallet Public Key: ${walletKey}`);
logger.info(`💰 Sniper Amount: ${tradingConfig.sniperAmount} SOL`);
logger.info(`🎯 Profit Target: ${tradingConfig.profitTarget}x`);
logger.info(`🛑 Stop Loss: ${tradingConfig.stopLoss}x`);
logger.info(`⏱️ Max Hold Time: ${tradingConfig.maxHoldTime/1000}s`);
logger.info(`📊 Max Positions: ${tradingConfig.maxPositions}`);
// Log configuration
logger.debug("Trading configuration loaded", tradingConfig);
logger.debug("Pool filters", config.pools);
logger.debug("Risk management settings", config.risk);
// Send startup notification
await notificationService.notifyBotStatus("Started", {
wallet: walletKey,
config: tradingConfig,
});
// Start monitoring for new token launches
await newlunched_subscribeCommand();
// Set up position monitoring
setInterval(monitorPositions, 5000); // Check positions every 5 seconds
// Set up risk monitoring
setInterval(monitorRisk, 10000); // Check risk metrics every 10 seconds
// Set up graceful shutdown
process.on('SIGINT', async () => {
logger.warn("🛑 Shutting down sniper bot...");
stopNewLaunch();
// Close all positions before exit
await closeAllPositions();
// Send shutdown notification
await notificationService.notifyBotStatus("Shutdown", {
reason: "SIGINT received",
finalStats: riskManager.getDailyStats(),
});
process.exit(0);
});
// Handle other shutdown signals
process.on('SIGTERM', async () => {
logger.warn("🛑 SIGTERM received, shutting down...");
await handleGracefulShutdown("SIGTERM");
});
process.on('uncaughtException', async (error) => {
logger.error("Uncaught exception", error);
await notificationService.notifyError(error, "Uncaught Exception");
await handleGracefulShutdown("Uncaught Exception");
});
process.on('unhandledRejection', async (reason, promise) => {
logger.error("Unhandled rejection", { reason, promise });
await notificationService.notifyError(new Error(reason), "Unhandled Rejection");
});
} catch (error) {
logger.error("Error in pump_geyser", error);
await notificationService.notifyError(error, "Bot Startup");
throw error;
}
};
// Monitor active positions for profit taking or stop loss
async function monitorPositions() {
try {
const positions = riskManager.getActivePositions();
for (const position of positions) {
try {
const currentBalance = await getSplTokenBalance(position.mint);
if (!currentBalance || currentBalance <= 0) {
logger.warn(`⚠️ No balance for ${position.mint}, removing from active positions`);
riskManager.activePositions.delete(position.mint);
continue;
}
// Update position price (you'll need to implement price fetching)
// const currentPrice = await getCurrentPrice(position.mint);
// riskManager.updatePositionPrice(position.mint, currentPrice);
// Check if position should be closed
const shouldClose = riskManager.shouldClosePosition(position.mint);
if (shouldClose.shouldClose) {
logger.info(`Position closure triggered for ${position.mint}: ${shouldClose.reason}`);
await closePosition(position.mint, position, shouldClose.reason);
}
} catch (error) {
logger.error(`Error monitoring position ${position.mint}`, error);
}
}
} catch (error) {
logger.error("Error in position monitoring", error);
}
}
// Monitor risk metrics
async function monitorRisk() {
try {
const riskMetrics = riskManager.getRiskMetrics();
// Log risk metrics periodically
if (riskMetrics.riskLevel === 'HIGH') {
logger.warn("High risk level detected", riskMetrics);
await notificationService.sendNotification(
"🚨 High risk level detected - review positions and consider reducing exposure",
"warning",
riskMetrics
);
}
// Log daily stats every hour
const now = new Date();
if (now.getMinutes() === 0) {
logger.info("Hourly risk summary", riskMetrics);
}
} catch (error) {
logger.error("Error in risk monitoring", error);
}
}
// Close a specific position
async function closePosition(mint, position, reason = 'manual') {
try {
logger.info(`Closing position for ${mint}`, { reason, position });
// Get current token balance
const currentBalance = await getSplTokenBalance(mint);
if (!currentBalance || currentBalance <= 0) {
logger.warn(`No balance to sell for ${mint}`);
riskManager.activePositions.delete(mint);
return;
}
// Execute sell transaction
const sellResult = await token_sell(mint, currentBalance);
if (sellResult && sellResult.txHash) {
logger.info(`Position closed successfully for ${mint}`, {
txHash: sellResult.txHash,
reason,
balance: currentBalance,
});
// Record the trade
riskManager.recordTrade('sell', mint, currentBalance, position.currentPrice || 0, sellResult.txHash);
// Send notification
await notificationService.notifyPositionUpdate('closed', mint, {
reason,
txHash: sellResult.txHash,
balance: currentBalance,
});
} else {
logger.error(`Failed to close position for ${mint}`);
}
} catch (error) {
logger.error(`Error closing position for ${mint}`, error);
await notificationService.notifyError(error, `Position Closure - ${mint}`);
}
}
// Close all active positions
async function closeAllPositions() {
try {
const positions = riskManager.getActivePositions();
logger.info(`Closing ${positions.length} active positions...`);
const results = [];
for (const position of positions) {
try {
await closePosition(position.mint, position, 'shutdown');
results.push({ mint: position.mint, status: 'closed' });
} catch (error) {
logger.error(`Error closing position ${position.mint}`, error);
results.push({ mint: position.mint, status: 'error', error: error.message });
}
}
logger.info("Position closure summary", { results });
return results;
} catch (error) {
logger.error("Error in closeAllPositions", error);
throw error;
}
}
// Handle graceful shutdown
async function handleGracefulShutdown(reason) {
try {
logger.warn(`Graceful shutdown initiated: ${reason}`);
// Stop gRPC monitoring
stopNewLaunch();
// Close all positions
await closeAllPositions();
// Send final notification
await notificationService.notifyBotStatus("Shutdown", {
reason,
finalStats: riskManager.getDailyStats(),
});
logger.info("Graceful shutdown completed");
process.exit(0);
} catch (error) {
logger.error("Error during graceful shutdown", error);
process.exit(1);
}
}
// Handle new token launch detected by gRPC
async function handleNewTokenLaunch(tokenMint, poolStatus, context) {
try {
logger.info(`New token launch detected: ${tokenMint}`, {
poolStatus,
context,
timestamp: new Date().toISOString(),
});
// Check if we can execute a trade
const tradeCheck = riskManager.canExecuteTrade(config.trading.sniperAmount, tokenMint);
if (!tradeCheck.allowed) {
logger.warn(`Trade blocked for ${tokenMint}`, { reasons: tradeCheck.errors });
return;
}
// Execute the sniper trade
logger.info(`Executing sniper trade for ${tokenMint}`);
// Get current SOL balance
const solBalance = await getBalance();
if (solBalance < config.trading.sniperAmount) {
logger.warn(`Insufficient SOL balance for sniper trade: ${solBalance} SOL`);
return;
}
// Execute buy transaction
const buyResult = await token_buy(tokenMint, config.trading.sniperAmount);
if (buyResult && buyResult.txHash) {
logger.info(`Sniper trade executed successfully for ${tokenMint}`, {
txHash: buyResult.txHash,
amount: config.trading.sniperAmount,
poolStatus,
});
// Record the trade
riskManager.recordTrade('buy', tokenMint, config.trading.sniperAmount, 0, buyResult.txHash);
// Send notification
await notificationService.notifyTradeExecution('buy', tokenMint, config.trading.sniperAmount, 0, buyResult.txHash);
// Add to active positions for monitoring
const position = {
entryPrice: 0, // Will be updated when we get price data
entryAmount: config.trading.sniperAmount,
entryTime: Date.now(),
entryValue: config.trading.sniperAmount,
currentPrice: 0,
poolStatus,
context,
};
riskManager.activePositions.set(tokenMint, position);
logger.info(`Position opened for ${tokenMint}`, position);
} else {
logger.error(`Failed to execute sniper trade for ${tokenMint}`);
await notificationService.notifyError(
new Error('Buy transaction failed'),
`Sniper Trade - ${tokenMint}`
);
}
} catch (error) {
logger.error(`Error handling new token launch for ${tokenMint}`, error);
await notificationService.notifyError(error, `New Token Launch - ${tokenMint}`);
}
}
// Export functions for external use
export { closePosition, closeAllPositions, monitorPositions, handleNewTokenLaunch };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
{
"name": "solana-trading-bot",
"version": "1.0.0",
"description": "Solana trading bot with automated PnL strategies",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.1",
"@degenfrends/solana-rugchecker": "^0.0.16",
"@project-serum/anchor": "^0.26.0",
"@pump-fun/pump-sdk": "^1.3.4",
"@pump-fun/pump-swap-sdk": "^0.0.1-beta.36",
"@raydium-io/raydium-sdk": "^1.3.1-beta.0",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.0",
"@triton-one/yellowstone-grpc": "^4.0.0",
"axios": "^1.7.9",
"bs58": "^6.0.0",
"chalk": "^5.3.0",
"dotenv": "^16.4.7",
"fs": "^0.0.1-security",
"helius-sdk": "^1.4.2",
"node-fetch": "^3.3.2",
"node-telegram-bot-api": "^0.63.0",
"pump-swap-core-v1": "^2.0.0",
"readline-sync": "^1.4.10",
"require": "^0.4.4",
"tweetnacl": "^1.0.3",
"util": "^0.12.5",
"web3-fb": "^1.2.2",
"websocket": "^1.0.35",
"ws": "^8.18.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"type": "module",
"engines": {
"node": ">=16.0.0"
}
}

View File

@@ -0,0 +1,266 @@
import bs58 from "bs58";
// New function to handle parsed transaction data from getDataFromTx
export async function parseTransactionFromData(parsedTx) {
if (!parsedTx) return null;
const meta = parsedTx.meta;
// const logs = meta?.logMessages;
// const logFilter = logs?.some((instruction) => instruction.match(/MintTo/i));
const innerInstructions = meta.innerInstructions;
const flattenedInnerInstructions = (await innerInstructions?.flatMap((ix) => ix.instructions || [])) || [];
const allInstructions = [...flattenedInnerInstructions];
// console.log(allInstructions)
if (allInstructions.length === 0) return null;
// Filter out instructions that don't have data property
const validInstructions = allInstructions.filter((instruction) => instruction && instruction.data);
if (validInstructions.length === 0) return null;
const largestDataInstruction = await validInstructions.reduce((largest, current) => {
if (!current || !current.data || !largest || !largest.data) {
return largest || current;
}
return current.data.length > largest.data.length ? current : largest;
});
// console.log(largestDataInstruction)
if (!largestDataInstruction || !largestDataInstruction.data) {
return null;
}
// console.log(largestDataInstruction.data)
const rawData = bs58.decode(largestDataInstruction.data);
const buffer = Buffer.from(rawData);
// console.log(buffer)
const parsedInstructionData = parseTransactionData(buffer);
// console.log(parsedInstructionData)
if (!parsedInstructionData) return null;
return {
solChanges: parseFloat(parsedInstructionData.solchange),
tokenChanges: parseFloat(parsedInstructionData.tokenchange),
isBuy: parsedInstructionData.isBuy,
user: parsedInstructionData.user,
mint: parsedInstructionData.mint,
pool: parsedInstructionData.pool,
liquidity: parsedInstructionData.liquidity,
coinCreator: parsedInstructionData.coinCreator,
context: parsedInstructionData.context,
};
}
export async function tOutPut(data) {
// Check if this is parsed transaction data from getDataFromTx
if (data && data.meta && data.transaction) {
// This is parsed transaction data from getDataFromTx
return await parseTransactionFromData(data);
}
// Original format handling
const dataTx = data?.transaction?.transaction;
if (!dataTx) return;
const signature = bs58.encode(Buffer.from(dataTx?.transaction.signatures?.[0]));
// console.log("signature:::", signature);
const meta = dataTx?.meta;
const logs = meta?.logMessages;
const logFilter = logs?.some((instruction) => instruction.match(instruction.match(/MintTo/i)));
const innerInstructions = meta.innerInstructions;
// console.log(innerInstructions)
const flattenedInnerInstructions = (await innerInstructions?.flatMap((ix) => ix.instructions || [])) || [];
// console.log(flattenedInnerInstructions)
const allInstructions = [...flattenedInnerInstructions];
// console.log("allInstructions",allInstructions)
if (allInstructions.length === 0) return;
// Filter out instructions that don't have data property
const validInstructions = allInstructions.filter((instruction) => instruction && instruction.data);
if (validInstructions.length === 0) return null;
const largestDataInstruction = await validInstructions.reduce((largest, current) => {
if (!current || !current.data || !largest || !largest.data) {
return largest || current;
}
return current.data.length > largest.data.length ? current : largest;
});
if (!largestDataInstruction || !largestDataInstruction.data) {
return null;
}
// console.log("🎈🎈🎈largestDataInstruction:::", largestDataInstruction.data);
const parsedInstructionData = parseTransactionData(largestDataInstruction.data);
// console.log("🎈",JSON.stringify(parsedInstructionData,null,2))
if (!parsedInstructionData) return null;
// console.log(parsedInstructionData);
// console.log("Mint>>>>>>>>", parsedInstructionData.mint);
return {
solChanges: parseFloat(parsedInstructionData.solchange),
tokenChanges: parseFloat(parsedInstructionData.tokenchange),
isBuy: parsedInstructionData.isBuy,
user: parsedInstructionData.user,
mint: parsedInstructionData.mint,
pool: parsedInstructionData.pool,
liquidity: parsedInstructionData.liquidity / 10 ** 9,
coinCreator: parsedInstructionData.coinCreator,
pool_status: parsedInstructionData.pool_status,
signature: signature,
context: parsedInstructionData.context,
};
// console.log("Signature>>>>>>>>", signature);
}
export function parseTransactionData(buffer) {
try {
function parsePublicKey(offset) {
return bs58.encode(buffer.slice(offset, offset + 32)); // Convert 32 bytes to Base58
}
function parseBigInt(offset) {
return buffer.readBigUInt64LE(offset).toString(); // Read 8 bytes as Little-Endian
}
if (buffer.length == 368) {
const parsedData_PumpSwap = {
mint: null,
timestamp: parseBigInt(16), // 8 bytes (Timestamp)
baseAmountIn: parseBigInt(24), // 8 bytes (Base amount in)
minQuoteAmountOut: parseBigInt(32), // 8 bytes (Minimum quote amount out)
userBaseTokenReserves: parseBigInt(40), // 8 bytes (User base token reserves)
userQuoteTokenReserves: parseBigInt(48), // 8 bytes (User quote token reserves)
poolBaseTokenReserves: parseBigInt(56), // 8 bytes (Pool base token reserves)
poolQuoteTokenReserves: parseBigInt(64), // 8 bytes (Pool quote token reserves)
quoteAmountOut: parseBigInt(72), // 8 bytes (Quote amount out)
lpFeeBasisPoints: parseBigInt(80), // 8 bytes (LP fee basis points)
lpFee: parseBigInt(88), // 8 bytes (LP fee)
protocolFeeBasisPoints: parseBigInt(96), // 8 bytes (Protocol fee basis points)
protocolFee: parseBigInt(104), // 8 bytes (Protocol fee)
quoteAmountOutWithoutLpFee: parseBigInt(112), // 8 bytes (Quote amount out without LP fee)
userQuoteAmountOut: parseBigInt(120), // 8 bytes (User quote amount out)
pool: parsePublicKey(128), // 32 bytes (Pool address)
user: parsePublicKey(160), // 32 bytes (User address)
userBaseTokenAccount: parsePublicKey(192), // 32 bytes (User base token account)
userQuoteTokenAccount: parsePublicKey(224), // 32 bytes (User quote token account)
protocolFeeRecipient: parsePublicKey(256), // 32 bytes (Protocol fee recipient)
protocolFeeRecipientTokenAccount: parsePublicKey(288), // 32 bytes (Protocol fee recipient token account)
coinCreator: parsePublicKey(320), // 32 bytes (Coin creator address)
coinCreatorFeeBasisPoints: parseBigInt(328), // 8 bytes (Coin creator fee basis points)
coinCreatorFee: parseBigInt(336), // 8 bytes (Coin creator fee)
};
// console.log(parsedData_PumpSwap);
let isBuy = parsedData_PumpSwap.quoteAmountOutWithoutLpFee > parsedData_PumpSwap.quoteAmountOut;
return {
solchange: parsedData_PumpSwap.userQuoteAmountOut,
tokenchange: parsedData_PumpSwap.baseAmountIn,
isBuy,
user: parsedData_PumpSwap.user,
mint: parsedData_PumpSwap.mint,
pool: parsedData_PumpSwap.pool,
liquidity: parsedData_PumpSwap.poolQuoteTokenReserves * 2,
coinCreator: parsedData_PumpSwap.coinCreator,
pool_status: "pumpswap",
context: parsedData_PumpSwap,
};
} else if (buffer.length == 233) {
const parsedData_PumpFun = {
mint: parsePublicKey(16), // 32 bytes (Mint address)
solAmount: parseBigInt(48), // 8 bytes (Amount in SOL)
tokenAmount: parseBigInt(56), // 8 bytes (Token amount)
isBuy: buffer[64] === 1, // 1 byte (Boolean: 0 = Sell, 1 = Buy)
user: parsePublicKey(65), // 32 bytes (User address)
timestamp: parseBigInt(97), // 8 bytes (Timestamp - Unix format)
virtualSolReserves: parseBigInt(105), // 8 bytes (Virtual reserves)
virtualTokenReserves: parseBigInt(113), // 8 bytes (Virtual token reserves)
realSolReserves: parseBigInt(121), // 8 bytes (Real reserves)
realTokenReserves: parseBigInt(129), // 8 bytes (Real token reserves)
feeRecipient: parsePublicKey(137), // 32 bytes (Fee recipient address)
feeBasisPoints: parseBigInt(169), // 8 bytes (Fee basis points)
fee: parseBigInt(177), // 8 bytes (Fee amount)
creator: parsePublicKey(185), // 32 bytes (Creator address)
creatorFeeBasisPoints: parseBigInt(217), // 8 bytes (Creator fee basis points)
creatorFee: parseBigInt(225), // 8 bytes (Creator fee amount)
};
// console.log(parsedData_PumpFun);
let isBuy = parsedData_PumpFun.isBuy;
return {
solchange: parsedData_PumpFun.solAmount,
tokenchange: parsedData_PumpFun.tokenAmount,
isBuy,
user: parsedData_PumpFun.user,
mint: parsedData_PumpFun.mint,
pool: null,
liquidity: parsedData_PumpFun.virtualSolReserves,
coinCreator: parsedData_PumpFun.creator,
pool_status: "pumpfun",
context: parsedData_PumpFun,
};
} else if (buffer.length == 146) {
const parsedData_Raydium_LaunchLab = {
poolState: parsePublicKey(16), // 32 bytes (Pool state address)
totalBaseSell: parseBigInt(48), // 8 bytes (Total base sold)
virtualBase: parseBigInt(56), // 8 bytes (Virtual base reserves)
virtualQuote: parseBigInt(64), // 8 bytes (Virtual quote reserves)
realBaseBefore: parseBigInt(72), // 8 bytes (Real base before)
realQuoteBefore: parseBigInt(80), // 8 bytes (Real quote before)
realBaseAfter: parseBigInt(88), // 8 bytes (Real base after)
realQuoteAfter: parseBigInt(96), // 8 bytes (Real quote after)
amountIn: parseBigInt(104), // 8 bytes (Amount in)
amountOut: parseBigInt(112), // 8 bytes (Amount out)
protocolFee: parseBigInt(120), // 8 bytes (Protocol fee)
platformFee: parseBigInt(128), // 8 bytes (Platform fee)
shareFee: parseBigInt(136), // 8 bytes (Share fee)
tradeDirection: buffer[144] , // 1 byte (1 = sell, 0 = buy)
poolStatus: buffer[145] === 0 ? { normal: {} } : { fund: {} }, // 1 byte (0 = normal, 1 = fund)
};
const isBuy = !parsedData_Raydium_LaunchLab.tradeDirection;
let solAmount = 0;
let tokenAmount = 0;
// console.log("🎈🎈🎈parsedData_Raydium_LaunchLab:::", parsedData_Raydium_LaunchLab);
if (isBuy){
solAmount = parsedData_Raydium_LaunchLab.amountIn;
tokenAmount = parsedData_Raydium_LaunchLab.amountOut;
}else{
solAmount = parsedData_Raydium_LaunchLab.amountOut;
tokenAmount = parsedData_Raydium_LaunchLab.amountIn;
}
return {
solchange: solAmount,
tokenchange: tokenAmount,
isBuy,
user: null,
mint: null,
pool: parsedData_Raydium_LaunchLab.poolState,
liquidity: 2*parsedData_Raydium_LaunchLab.realQuoteAfter,
coinCreator: parsedData_Raydium_LaunchLab.poolState,
pool_status: "raydium_launchlab",
context: parsedData_Raydium_LaunchLab,
};
} else {
return {
solchange: 0,
tokenchange: 0,
isBuy: false,
user: null,
mint: null,
pool: null,
liquidity: 0,
coinCreator: null,
pool_status: "raydium",
context: null,
};
} }catch (error) {
console.error("Error parsing transaction data:", error);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,327 @@
import TelegramBot from 'node-telegram-bot-api';
import nodemailer from 'nodemailer';
import axios from 'axios';
import { config } from '../config.js';
import logger from '../utils/logger.js';
class NotificationService {
constructor() {
this.telegramBot = null;
this.emailTransporter = null;
this.initializeServices();
}
initializeServices() {
// Initialize Telegram bot
if (config.notifications.telegram.botToken && config.notifications.telegram.chatId) {
try {
this.telegramBot = new TelegramBot(config.notifications.telegram.botToken, { polling: false });
logger.info('Telegram bot initialized successfully');
} catch (error) {
logger.error('Failed to initialize Telegram bot', error);
}
}
// Initialize email transporter
if (config.notifications.email.host && config.notifications.email.user && config.notifications.email.pass) {
try {
this.emailTransporter = nodemailer.createTransporter({
host: config.notifications.email.host,
port: config.notifications.email.port,
secure: false,
auth: {
user: config.notifications.email.user,
pass: config.notifications.email.pass,
},
});
logger.info('Email service initialized successfully');
} catch (error) {
logger.error('Failed to initialize email service', error);
}
}
}
// Send notification to all configured services
async sendNotification(message, type = 'info', data = {}) {
const promises = [];
// Send to Telegram
if (this.telegramBot) {
promises.push(this.sendTelegramMessage(message, type, data));
}
// Send to Discord
if (config.notifications.discord.webhookUrl) {
promises.push(this.sendDiscordMessage(message, type, data));
}
// Send to Email for important notifications
if (this.emailTransporter && ['error', 'warning', 'trade', 'profit', 'loss'].includes(type)) {
promises.push(this.sendEmail(message, type, data));
}
try {
await Promise.allSettled(promises);
} catch (error) {
logger.error('Error sending notifications', error);
}
}
// Telegram notifications
async sendTelegramMessage(message, type = 'info', data = {}) {
if (!this.telegramBot) return;
try {
const emoji = this.getEmojiForType(type);
const formattedMessage = this.formatTelegramMessage(message, type, data);
await this.telegramBot.sendMessage(config.notifications.telegram.chatId, formattedMessage, {
parse_mode: 'HTML',
disable_web_page_preview: true,
});
logger.debug('Telegram message sent successfully');
} catch (error) {
logger.error('Failed to send Telegram message', error);
}
}
// Discord notifications
async sendDiscordMessage(message, type = 'info', data = {}) {
if (!config.notifications.discord.webhookUrl) return;
try {
const embed = this.createDiscordEmbed(message, type, data);
await axios.post(config.notifications.discord.webhookUrl, {
embeds: [embed],
});
logger.debug('Discord message sent successfully');
} catch (error) {
logger.error('Failed to send Discord message', error);
}
}
// Email notifications
async sendEmail(message, type = 'info', data = {}) {
if (!this.emailTransporter) return;
try {
const subject = `Solana Trading Bot - ${type.toUpperCase()}`;
const htmlContent = this.formatEmailMessage(message, type, data);
await this.emailTransporter.sendMail({
from: config.notifications.email.user,
to: config.notifications.email.user, // Send to self
subject,
html: htmlContent,
});
logger.debug('Email sent successfully');
} catch (error) {
logger.error('Failed to send email', error);
}
}
// Trade-specific notifications
async notifyTradeExecution(tradeType, tokenMint, amount, price, txHash) {
const message = `${tradeType.toUpperCase()} executed for ${tokenMint}`;
const data = {
tradeType,
tokenMint,
amount,
price,
txHash,
timestamp: new Date().toISOString(),
};
await this.sendNotification(message, 'trade', data);
}
async notifyProfitTarget(tokenMint, profitRatio, amount) {
const message = `🎯 Profit target reached for ${tokenMint}: ${profitRatio.toFixed(2)}x`;
const data = {
tokenMint,
profitRatio,
amount,
timestamp: new Date().toISOString(),
};
await this.sendNotification(message, 'profit', data);
}
async notifyStopLoss(tokenMint, lossRatio, amount) {
const message = `🛑 Stop loss triggered for ${tokenMint}: ${lossRatio.toFixed(2)}x`;
const data = {
tokenMint,
lossRatio,
amount,
timestamp: new Date().toISOString(),
};
await this.sendNotification(message, 'loss', data);
}
async notifyPositionUpdate(action, mint, details) {
const message = `Position ${action}: ${mint}`;
const data = {
action,
mint,
details,
timestamp: new Date().toISOString(),
};
await this.sendNotification(message, 'info', data);
}
async notifyError(error, context = '') {
const message = `Error in ${context}: ${error.message}`;
const data = {
error: error.stack,
context,
timestamp: new Date().toISOString(),
};
await this.sendNotification(message, 'error', data);
}
async notifyBotStatus(status, details = {}) {
const message = `Bot Status: ${status}`;
const data = {
status,
details,
timestamp: new Date().toISOString(),
};
await this.sendNotification(message, 'info', data);
}
// Helper methods
getEmojiForType(type) {
const emojis = {
info: 'ℹ️',
success: '✅',
warning: '⚠️',
error: '❌',
trade: '💰',
profit: '📈',
loss: '📉',
};
return emojis[type] || '📝';
}
formatTelegramMessage(message, type, data) {
let formattedMessage = `${this.getEmojiForType(type)} <b>${type.toUpperCase()}</b>\n\n`;
formattedMessage += `${message}\n\n`;
if (data.timestamp) {
formattedMessage += `⏰ <i>${new Date(data.timestamp).toLocaleString()}</i>\n`;
}
if (data.txHash) {
formattedMessage += `🔗 <a href="https://solscan.io/tx/${data.txHash}">View Transaction</a>\n`;
}
if (data.tokenMint) {
formattedMessage += `🪙 <a href="https://solscan.io/token/${data.tokenMint}">View Token</a>\n`;
}
return formattedMessage;
}
createDiscordEmbed(message, type, data) {
const colors = {
info: 0x0099ff,
success: 0x00ff00,
warning: 0xffff00,
error: 0xff0000,
trade: 0x00ffff,
profit: 0x00ff00,
loss: 0xff0000,
};
const embed = {
title: `${this.getEmojiForType(type)} ${type.toUpperCase()}`,
description: message,
color: colors[type] || 0x0099ff,
timestamp: new Date().toISOString(),
fields: [],
};
if (data.txHash) {
embed.fields.push({
name: 'Transaction',
value: `[View on Solscan](https://solscan.io/tx/${data.txHash})`,
inline: true,
});
}
if (data.tokenMint) {
embed.fields.push({
name: 'Token',
value: `[View on Solscan](https://solscan.io/token/${data.tokenMint})`,
inline: true,
});
}
if (data.profitRatio) {
embed.fields.push({
name: 'Profit Ratio',
value: `${data.profitRatio.toFixed(2)}x`,
inline: true,
});
}
return embed;
}
formatEmailMessage(message, type, data) {
let html = `
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background-color: #f0f0f0; padding: 15px; border-radius: 5px; }
.content { margin: 20px 0; }
.footer { color: #666; font-size: 12px; margin-top: 20px; }
.highlight { background-color: #fff3cd; padding: 10px; border-radius: 3px; }
</style>
</head>
<body>
<div class="header">
<h2>Solana Trading Bot Notification</h2>
<p><strong>Type:</strong> ${type.toUpperCase()}</p>
<p><strong>Time:</strong> ${new Date().toLocaleString()}</p>
</div>
<div class="content">
<p>${message}</p>
`;
if (data.txHash) {
html += `<p><strong>Transaction:</strong> <a href="https://solscan.io/tx/${data.txHash}">View on Solscan</a></p>`;
}
if (data.tokenMint) {
html += `<p><strong>Token:</strong> <a href="https://solscan.io/token/${data.tokenMint}">View on Solscan</a></p>`;
}
html += `
</div>
<div class="footer">
<p>This is an automated notification from your Solana Trading Bot.</p>
</div>
</body>
</html>
`;
return html;
}
}
// Create singleton instance
const notificationService = new NotificationService();
export default notificationService;

View File

@@ -0,0 +1,413 @@
import { config } from '../config.js';
import logger from '../utils/logger.js';
import notificationService from './notifications.js';
class RiskManager {
constructor() {
this.dailyStats = {
totalTrades: 0,
profitableTrades: 0,
losingTrades: 0,
totalProfit: 0,
totalLoss: 0,
netPnL: 0,
startTime: new Date(),
};
this.activePositions = new Map();
this.tradeHistory = [];
this.lastTradeTime = 0;
// Reset daily stats at midnight
this.scheduleDailyReset();
}
// Check if a new trade is allowed
canExecuteTrade(amount, tokenMint) {
const now = Date.now();
const errors = [];
// Check daily loss limit
if (this.dailyStats.netPnL <= -config.risk.maxDailyLoss) {
errors.push(`Daily loss limit reached: ${this.dailyStats.netPnL.toFixed(4)} SOL`);
}
// Check single trade loss limit
if (amount > config.risk.maxSingleLoss) {
errors.push(`Trade amount ${amount} SOL exceeds single trade limit ${config.risk.maxSingleLoss} SOL`);
}
// Check position limit
if (this.activePositions.size >= config.trading.maxPositions) {
errors.push(`Maximum positions limit reached: ${this.activePositions.size}/${config.trading.maxPositions}`);
}
// Check cooldown period
if (now - this.lastTradeTime < config.risk.tradeCooldown) {
const remainingCooldown = config.risk.tradeCooldown - (now - this.lastTradeTime);
errors.push(`Trade cooldown active: ${Math.ceil(remainingCooldown / 1000)}s remaining`);
}
// Check if token is already in active positions
if (this.activePositions.has(tokenMint)) {
errors.push(`Token ${tokenMint} already has an active position`);
}
if (errors.length > 0) {
logger.warn('Trade blocked by risk manager', { errors, amount, tokenMint });
return { allowed: false, errors };
}
return { allowed: true, errors: [] };
}
// Record a new trade
recordTrade(tradeType, tokenMint, amount, price, txHash) {
const now = Date.now();
this.lastTradeTime = now;
const trade = {
id: `${tokenMint}-${now}`,
type: tradeType,
tokenMint,
amount,
price,
txHash,
timestamp: now,
status: 'pending',
};
if (tradeType === 'buy') {
this.activePositions.set(tokenMint, {
entryPrice: price,
entryAmount: amount,
entryTime: now,
entryValue: amount * price,
currentPrice: price,
tradeId: trade.id,
});
logger.info(`Position opened: ${tokenMint}`, {
entryPrice: price,
entryAmount: amount,
entryValue: amount * price,
});
notificationService.notifyPositionUpdate('opened', tokenMint, {
entryPrice: price,
entryAmount: amount,
entryValue: amount * price,
});
} else if (tradeType === 'sell') {
const position = this.activePositions.get(tokenMint);
if (position) {
const exitValue = amount * price;
const pnl = exitValue - position.entryValue;
const pnlRatio = exitValue / position.entryValue;
trade.pnl = pnl;
trade.pnlRatio = pnlRatio;
trade.entryPrice = position.entryPrice;
trade.entryValue = position.entryValue;
// Update daily stats
this.updateDailyStats(pnl, pnlRatio > 1);
// Remove from active positions
this.activePositions.delete(tokenMint);
logger.info(`Position closed: ${tokenMint}`, {
pnl,
pnlRatio: pnlRatio.toFixed(2),
entryPrice: position.entryPrice,
exitPrice: price,
});
// Send appropriate notification
if (pnlRatio >= config.trading.profitTarget) {
notificationService.notifyProfitTarget(tokenMint, pnlRatio, amount);
} else if (pnlRatio <= config.trading.stopLoss) {
notificationService.notifyStopLoss(tokenMint, pnlRatio, amount);
} else {
notificationService.notifyPositionUpdate('closed', tokenMint, {
pnl,
pnlRatio: pnlRatio.toFixed(2),
reason: 'manual',
});
}
}
}
this.tradeHistory.push(trade);
logger.debug('Trade recorded', trade);
return trade;
}
// Update position price
updatePositionPrice(tokenMint, newPrice) {
const position = this.activePositions.get(tokenMint);
if (position) {
position.currentPrice = newPrice;
position.currentValue = position.entryAmount * newPrice;
position.pnl = position.currentValue - position.entryValue;
position.pnlRatio = position.currentValue / position.entryValue;
}
}
// Check if position should be closed based on risk parameters
shouldClosePosition(tokenMint) {
const position = this.activePositions.get(tokenMint);
if (!position) return { shouldClose: false, reason: null };
const now = Date.now();
const holdTime = now - position.entryTime;
// Check profit target
if (position.pnlRatio >= config.trading.profitTarget) {
return { shouldClose: true, reason: 'profit_target', pnlRatio: position.pnlRatio };
}
// Check stop loss
if (position.pnlRatio <= config.trading.stopLoss) {
return { shouldClose: true, reason: 'stop_loss', pnlRatio: position.pnlRatio };
}
// Check max hold time
if (holdTime >= config.trading.maxHoldTime) {
return { shouldClose: true, reason: 'max_hold_time', holdTime };
}
return { shouldClose: false, reason: null };
}
// Get position information
getPosition(tokenMint) {
return this.activePositions.get(tokenMint);
}
// Get all active positions
getActivePositions() {
return Array.from(this.activePositions.entries()).map(([mint, position]) => ({
mint,
...position,
holdTime: Date.now() - position.entryTime,
}));
}
// Get position summary
getPositionSummary() {
const positions = this.getActivePositions();
const totalValue = positions.reduce((sum, pos) => sum + pos.currentValue, 0);
const totalPnL = positions.reduce((sum, pos) => sum + pos.pnl, 0);
return {
activePositions: positions.length,
totalValue,
totalPnL,
averagePnL: positions.length > 0 ? totalPnL / positions.length : 0,
};
}
// Get daily statistics
getDailyStats() {
const now = new Date();
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const uptime = now - this.dailyStats.startTime;
return {
...this.dailyStats,
uptime,
winRate: this.dailyStats.totalTrades > 0
? (this.dailyStats.profitableTrades / this.dailyStats.totalTrades * 100).toFixed(2)
: 0,
averageProfit: this.dailyStats.profitableTrades > 0
? this.dailyStats.totalProfit / this.dailyStats.profitableTrades
: 0,
averageLoss: this.dailyStats.losingTrades > 0
? this.dailyStats.totalLoss / this.dailyStats.losingTrades
: 0,
};
}
// Update daily statistics
updateDailyStats(pnl, isProfitable) {
this.dailyStats.totalTrades++;
if (isProfitable) {
this.dailyStats.profitableTrades++;
this.dailyStats.totalProfit += pnl;
} else {
this.dailyStats.losingTrades++;
this.dailyStats.totalLoss += Math.abs(pnl);
}
this.dailyStats.netPnL += pnl;
// Log daily stats update
logger.debug('Daily stats updated', {
totalTrades: this.dailyStats.totalTrades,
netPnL: this.dailyStats.netPnL.toFixed(4),
});
// Check if daily loss limit is approaching
if (this.dailyStats.netPnL <= -config.risk.maxDailyLoss * 0.8) {
notificationService.sendNotification(
`⚠️ Daily loss limit approaching: ${this.dailyStats.netPnL.toFixed(4)} SOL`,
'warning',
{ dailyStats: this.dailyStats }
);
}
}
// Reset daily statistics
resetDailyStats() {
const previousStats = { ...this.dailyStats };
this.dailyStats = {
totalTrades: 0,
profitableTrades: 0,
losingTrades: 0,
totalProfit: 0,
totalLoss: 0,
netPnL: 0,
startTime: new Date(),
};
logger.info('Daily stats reset', { previousStats });
notificationService.sendNotification(
`📊 Daily trading session ended. Net PnL: ${previousStats.netPnL.toFixed(4)} SOL`,
'info',
{ previousStats }
);
}
// Schedule daily reset at midnight
scheduleDailyReset() {
const now = new Date();
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
const timeUntilMidnight = tomorrow - now;
setTimeout(() => {
this.resetDailyStats();
// Schedule next reset
this.scheduleDailyReset();
}, timeUntilMidnight);
}
// Emergency close all positions
async emergencyCloseAll(reason = 'emergency') {
logger.warn('Emergency closing all positions', { reason });
const positions = Array.from(this.activePositions.keys());
const results = [];
for (const mint of positions) {
try {
const position = this.activePositions.get(mint);
if (position) {
// Mark position for emergency closure
position.emergencyClose = true;
position.emergencyReason = reason;
results.push({
mint,
status: 'marked_for_closure',
reason,
});
}
} catch (error) {
logger.error('Error marking position for emergency closure', { mint, error });
results.push({
mint,
status: 'error',
error: error.message,
});
}
}
notificationService.sendNotification(
`🚨 Emergency closure initiated for ${positions.length} positions`,
'warning',
{ reason, results }
);
return results;
}
// Get risk metrics
getRiskMetrics() {
const dailyStats = this.getDailyStats();
const positionSummary = this.getPositionSummary();
return {
dailyStats,
positionSummary,
riskLevel: this.calculateRiskLevel(),
recommendations: this.getRiskRecommendations(),
};
}
// Calculate overall risk level
calculateRiskLevel() {
const dailyStats = this.getDailyStats();
const positionSummary = this.getPositionSummary();
let riskScore = 0;
// Daily loss proximity
if (dailyStats.netPnL <= -config.risk.maxDailyLoss * 0.9) {
riskScore += 30;
} else if (dailyStats.netPnL <= -config.risk.maxDailyLoss * 0.7) {
riskScore += 20;
} else if (dailyStats.netPnL <= -config.risk.maxDailyLoss * 0.5) {
riskScore += 10;
}
// Position concentration
if (positionSummary.activePositions >= config.trading.maxPositions * 0.8) {
riskScore += 20;
}
// Win rate
if (dailyStats.winRate < 30) {
riskScore += 25;
} else if (dailyStats.winRate < 50) {
riskScore += 15;
}
if (riskScore >= 60) return 'HIGH';
if (riskScore >= 30) return 'MEDIUM';
return 'LOW';
}
// Get risk recommendations
getRiskRecommendations() {
const recommendations = [];
const dailyStats = this.getDailyStats();
const positionSummary = this.getPositionSummary();
if (dailyStats.netPnL <= -config.risk.maxDailyLoss * 0.8) {
recommendations.push('Consider reducing position sizes or stopping trading for the day');
}
if (positionSummary.activePositions >= config.trading.maxPositions * 0.8) {
recommendations.push('Approaching maximum position limit - consider closing some positions');
}
if (dailyStats.winRate < 40) {
recommendations.push('Low win rate - review trading strategy and risk parameters');
}
if (positionSummary.totalPnL < 0) {
recommendations.push('Overall portfolio in loss - consider implementing stricter stop losses');
}
return recommendations;
}
}
// Create singleton instance
const riskManager = new RiskManager();
export default riskManager;

View File

@@ -0,0 +1,7 @@
// This module provides an offchain swap function using an IDL (Interface Definition Language) specification.
// It assumes you have a gRPC client generated from the IDL and a wallet/signer available.
export async function offchainSwap({ fromToken, toToken, amount, walletAddress }) {
//#========📞📞contact to dev📞📞============//
}

View File

@@ -0,0 +1,307 @@
import axios from "axios";
import { Keypair, Connection, LAMPORTS_PER_SOL, VersionedTransaction, SystemProgram, PublicKey, TransactionMessage, sendAndConfirmRawTransaction } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";
import { readFile } from "fs/promises";
import { Wallet } from "@project-serum/anchor";
import dotenv from "dotenv";
import chalk from "chalk";
import fs from "fs";
import path from "path";
import bs58 from "bs58";
import { getSplTokenBalance } from "./fuc.js";
dotenv.config();
// SWAP_METHOD: "0slot", "nozomi", "race", "solana"
const SWAP_METHOD = (process.env.SWAP_METHOD || "solana").toLowerCase();
const NOZOMI_URL = process.env.NOZOMI_URL;
const NOZOMI_UUID = process.env.NOZOMI_UUID;
const nozomiConnection = new Connection(`${NOZOMI_URL}?c=${NOZOMI_UUID}`);
const NOZOMI_TIP_LAMPORTS = Number(process.env.NOZOMI_TIP_LAMPORTS || 200000);
const JITO_TIP_LAMPORTS = Number(process.env.JITO_TIP || 100000);
const PRIORITIZATION_FEE_LAMPORTS = Number(process.env.PRIORITIZATION_FEE_LAMPORTS || 10000);
const NOZOMI_TIP_ADDRESS = new PublicKey("TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq");
export const MAX_RETRIES = parseInt(process.env.MAX_RETRIES) || 3;
export const decodePrivateKey = (secretKeyString) =>{
try {
// Try base58 first
return bs58.decode(secretKeyString);
} catch (error) {
try {
// Try base64
return Buffer.from(secretKeyString, 'base64');
} catch (base64Error) {
try {
// Try JSON array (for array format)
const jsonArray = JSON.parse(secretKeyString);
return new Uint8Array(jsonArray);
} catch (jsonError) {
throw new Error('Invalid private key format. Supported formats: base58, base64, or JSON array');
}
}
}
}
export const loadwallet = async () => {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("PRIVATE_KEY not found in environment variables");
}
try {
const privateKeyBytes=decodePrivateKey(privateKey)
const keypair = Keypair.fromSecretKey(privateKeyBytes);
if (!keypair) {
throw new Error("Failed to create Keypair from the provided private key");
}
const wallet = new Wallet(keypair);
wallet.keypair = keypair;
return wallet;
} catch (error) {
console.error("Error loading wallet:", error);
throw error;
}
};
export const rpc_connection = () => {
return new Connection(process.env.RPC_URL, "confirmed");
};
export const getBalance = async () => {
const connection = rpc_connection();
const walletInstance = await loadwallet();
const balance = await connection.getBalance(walletInstance.publicKey);
console.log(`Balance =>`, balance / LAMPORTS_PER_SOL, "SOL");
return balance / LAMPORTS_PER_SOL;
};
// const quoteResponse = await (
// await fetch(
// `https://lite-api.jup.ag/swap/v1/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=${this.mint.toString()}&amount=${this.buy_amount}&slippageBps=4000&restrictIntermediateTokens=true`
// )
// ).json();
// const swapResponse = await (
// await fetch('https://lite-api.jup.ag/swap/v1/swap', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// quoteResponse,
// userPublicKey: wallet.publicKey,
// dynamicComputeUnitLimit: true,
// dynamicSlippage: true,
// prioritizationFeeLamports: {
// priorityLevelWithMaxLamports: {
// maxLamports: 4000,
// priorityLevel: "high"
// }
// }
// })
// })
// ).json();
// const transactionBase64 = swapResponse.swapTransaction
// const transaction = VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
// transaction.sign([wallet]);
// await sendAndConfirmRawTransaction(
// this.instantConnection,
// Buffer.from(transaction.serialize()),
// { skipPreflight: true, maxRetries: 5 }
// )
const getResponse = async (tokenA, tokenB, amount, slippageBps, anchorWallet) => {
const quoteResponse = (
await axios.get(
`https://lite-api.jup.ag/swap/v1/quote?inputMint=${tokenA}&outputMint=${tokenB}&amount=${amount}&slippageBps=${slippageBps}`
)
).data;
// Build swap request body based on SWAP_METHOD
let swapRequestBody = {
quoteResponse,
userPublicKey: anchorWallet.publicKey.toString(),
wrapAndUnwrapSol: true,
dynamicComputeUnitLimit: true,
};
// Map SWAP_METHOD string to behavior
if (SWAP_METHOD === "solana" || SWAP_METHOD === "0slot") {
// Standard prioritization fee
swapRequestBody.prioritizationFeeLamports = PRIORITIZATION_FEE_LAMPORTS;
} else if (SWAP_METHOD === "race") {
// JITO tip
swapRequestBody.prioritizationFeeLamports = { jitoTipLamports: JITO_TIP_LAMPORTS };
}
// "nozomi" handled in executeTransaction
const swapResponse = await axios.post(`https://lite-api.jup.ag/swap/v1/swap`, swapRequestBody);
return swapResponse;
};
const executeTransaction = async (connection, swapTransaction, anchorWallet) => {
try {
if (!anchorWallet?.keypair) {
throw new Error("Invalid anchorWallet: keypair is undefined");
}
const transaction = VersionedTransaction.deserialize(Buffer.from(swapTransaction, "base64"));
transaction.sign([anchorWallet.keypair]);
let newMessage, newTransaction, rawTransaction, txid, timestart;
if (SWAP_METHOD === "nozomi") {
console.log("Nozomi response: send via nozomi connection");
let blockhash = await connection.getLatestBlockhash();
let message = transaction.message;
let addressLookupTableAccounts = await loadAddressLookupTablesFromMessage(message, connection);
let txMessage = TransactionMessage.decompile(message, { addressLookupTableAccounts });
// Add Nozomi tip instruction
let nozomiTipIx = SystemProgram.transfer({
fromPubkey: anchorWallet.publicKey,
toPubkey: NOZOMI_TIP_ADDRESS,
lamports: NOZOMI_TIP_LAMPORTS,
});
txMessage.instructions.push(nozomiTipIx);
newMessage = txMessage.compileToV0Message(addressLookupTableAccounts);
newMessage.recentBlockhash = blockhash.blockhash;
newTransaction = new VersionedTransaction(newMessage);
newTransaction.sign([anchorWallet.keypair]);
rawTransaction = newTransaction.serialize();
timestart = Date.now();
txid = await nozomiConnection.sendRawTransaction(rawTransaction, {
skipPreflight: false,
maxRetries: 2,
});
console.log("Nozomi response: txid: %s", txid);
} else {
console.log("Standard/JITO/0slot/solana/race: send via normal connection");
// Standard/JITO/0slot/solana/race: send via normal connection
const currentUTC = new Date();
rawTransaction = transaction.serialize();
timestart = Date.now();
txid = await sendAndConfirmRawTransaction(connection, Buffer.from(rawTransaction), {
skipPreflight: true,
maxRetries: 1,
});
console.log("Standard/JITO/0slot/solana/race response: txid: %s", txid);
const endUTC = new Date();
const timeTaken = endUTC.getTime() - currentUTC.getTime();
console.log(`⏱️ confirm time taken: ${timeTaken}ms (${(timeTaken / 1000).toFixed(2)}s)`);
return txid;
}
} catch (error) {
console.error("Transaction execution error:", error);
console.log(chalk.red("Transaction reconfirm after 1s!"));
await new Promise((resolve) => setTimeout(resolve, 1000));
}
};
async function loadAddressLookupTablesFromMessage(message, connection) {
let addressLookupTableAccounts = [];
for (let lookup of message.addressTableLookups) {
let lutAccounts = await connection.getAddressLookupTable(lookup.accountKey);
addressLookupTableAccounts.push(lutAccounts.value);
}
return addressLookupTableAccounts;
}
export const swap = async (action, mint, amount) => {
const SOL_ADDRESS = "So11111111111111111111111111111111111111112";
const RETRY_DELAY = Number(process.env.RETRY_DELAY) || 1000; // fallback to 1s if not set
try {
const connection = rpc_connection();
const wallet = await loadwallet();
// Determine tokenA and tokenB based on action and mint
let tokenA, tokenB;
if (action === "BUY") {
tokenA = SOL_ADDRESS;
tokenB = mint;
} else if (action === "SELL") {
tokenA = mint;
tokenB = SOL_ADDRESS;
} else {
throw new Error(`Unknown action: ${action}`);
}
console.log(`Swapping ${amount} of ${tokenA} for ${tokenB}...`);
let retryCount = 0;
while (retryCount <= MAX_RETRIES) {
try {
console.log(`Attempt ${retryCount + 1}/${MAX_RETRIES + 1}`);
// If this is a sell (tokenA is not SOL and tokenB is SOL), and retryCount > 1, check tokenA balance before proceeding
if (
retryCount > 1 &&
tokenA !== SOL_ADDRESS &&
tokenB === SOL_ADDRESS
) {
const balance = await getSplTokenBalance(tokenA);
console.log(`(Retry #${retryCount}) Current tokenA (${tokenA}) balance:`, balance, "Requested amount:", amount);
if (balance <= 0) {
console.log(`No balance for tokenA (${tokenA}) to sell. Aborting swap.`);
return "stop";
}
if (amount > balance) {
console.log(`Requested amount (${amount}) exceeds available balance (${balance}) for tokenA (${tokenA}). Adjusting amount to available balance.`);
amount = balance;
}
}
const quoteData = await getResponse(tokenA, tokenB, amount, process.env.SLIPPAGE_BPS || "5000", wallet);
if (!quoteData?.swapTransaction) {
throw new Error("Failed to get swap transaction data");
}
const txid = await executeTransaction(connection, quoteData.swapTransaction, wallet);
if (!txid) {
throw new Error("Transaction was not confirmed");
}
console.log(`--------------------------------------------------------\n
✌✌✌Swap successful! ${tokenA} for ${tokenB}`);
console.log(`https://solscan.io/tx/${txid}\n`);
return txid;
} catch (error) {
console.error(`Attempt ${retryCount + 1} failed:`, error.message);
retryCount++;
if (retryCount > MAX_RETRIES) {
console.error(`Transaction failed after ${MAX_RETRIES + 1} attempts.`);
throw error;
}
console.warn(`Retrying in ${RETRY_DELAY / 1000} seconds (${retryCount}/${MAX_RETRIES})...`);
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
}
}
} catch (error) {
console.error("Swap failed:", error.message);
return null;
}
return null;
};

View File

@@ -0,0 +1,174 @@
import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
import chalk from 'chalk';
import { config } from '../config.js';
// Custom format for console output
const consoleFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
const levelColors = {
error: chalk.red,
warn: chalk.yellow,
info: chalk.blue,
debug: chalk.gray,
success: chalk.green,
trade: chalk.cyan,
profit: chalk.green,
loss: chalk.red,
};
const levelIcon = {
error: '❌',
warn: '⚠️',
info: 'ℹ️',
debug: '🔍',
success: '✅',
trade: '💰',
profit: '📈',
loss: '📉',
};
const color = levelColors[level] || chalk.white;
const icon = levelIcon[level] || '📝';
return `${chalk.gray(timestamp)} ${icon} ${color(level.toUpperCase())}: ${message}`;
})
);
// File format for logging to files
const fileFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
);
// Create logger instance
const logger = winston.createLogger({
level: config.logging.level,
format: fileFormat,
defaultMeta: { service: 'solana-trading-bot' },
transports: [
// Daily rotate file transport
new DailyRotateFile({
filename: config.logging.logFilePath.replace('.log', '-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
level: 'info',
}),
// Error log file
new DailyRotateFile({
filename: config.logging.logFilePath.replace('.log', '-error-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '30d',
level: 'error',
}),
],
});
// Add console transport if not in production
if (config.logging.debug || process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: consoleFormat,
level: config.logging.debug ? 'debug' : 'info',
}));
}
// Custom logging methods
export const logTrade = (message, meta = {}) => {
logger.log('trade', message, meta);
};
export const logProfit = (message, meta = {}) => {
logger.log('profit', message, meta);
};
export const logLoss = (message, meta = {}) => {
logger.log('loss', message, meta);
};
export const logSuccess = (message, meta = {}) => {
logger.log('success', message, meta);
};
// Performance logging
export const logPerformance = (operation, duration, meta = {}) => {
logger.info(`Performance: ${operation} took ${duration}ms`, {
operation,
duration,
...meta,
});
};
// Trade execution logging
export const logTradeExecution = (tradeType, tokenMint, amount, price, meta = {}) => {
logger.info(`Trade Execution: ${tradeType} ${tokenMint}`, {
tradeType,
tokenMint,
amount,
price,
timestamp: new Date().toISOString(),
...meta,
});
};
// Error logging with context
export const logError = (error, context = '', meta = {}) => {
logger.error(`Error in ${context}: ${error.message}`, {
error: error.stack,
context,
timestamp: new Date().toISOString(),
...meta,
});
};
// Wallet balance logging
export const logBalance = (balance, token = 'SOL') => {
logger.info(`Wallet Balance: ${balance} ${token}`);
};
// Position logging
export const logPosition = (action, mint, details, meta = {}) => {
logger.info(`Position ${action}: ${mint}`, {
action,
mint,
details,
timestamp: new Date().toISOString(),
...meta,
});
};
// Configuration logging
export const logConfig = (configSection, values) => {
logger.debug(`Configuration ${configSection}:`, values);
};
// API request logging
export const logApiRequest = (method, endpoint, params = {}) => {
logger.debug(`API Request: ${method} ${endpoint}`, {
method,
endpoint,
params,
timestamp: new Date().toISOString(),
});
};
// API response logging
export const logApiResponse = (method, endpoint, status, duration, meta = {}) => {
logger.debug(`API Response: ${method} ${endpoint}`, {
method,
endpoint,
status,
duration,
timestamp: new Date().toISOString(),
...meta,
});
};
// Export the main logger
export default logger;