mirror of
https://github.com/mortdeus/legacy-cc.git
synced 2026-10-08 03:42:05 +00:00
init
This commit is contained in:
445
2_copy trading bot(node) using gRPC/README.md
Normal file
445
2_copy trading bot(node) using gRPC/README.md
Normal 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.**
|
||||
84
2_copy trading bot(node) using gRPC/SETUP.md
Normal file
84
2_copy trading bot(node) using gRPC/SETUP.md
Normal 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!**
|
||||
180
2_copy trading bot(node) using gRPC/config.js
Normal file
180
2_copy trading bot(node) using gRPC/config.js
Normal 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;
|
||||
@@ -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();
|
||||
});
|
||||
192
2_copy trading bot(node) using gRPC/dashboard/public/index.html
Normal file
192
2_copy trading bot(node) using gRPC/dashboard/public/index.html
Normal 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>
|
||||
237
2_copy trading bot(node) using gRPC/dashboard/server.js
Normal file
237
2_copy trading bot(node) using gRPC/dashboard/server.js
Normal 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;
|
||||
157
2_copy trading bot(node) using gRPC/env.template
Normal file
157
2_copy trading bot(node) using gRPC/env.template
Normal 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
|
||||
182
2_copy trading bot(node) using gRPC/fuc.js
Normal file
182
2_copy trading bot(node) using gRPC/fuc.js
Normal 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();
|
||||
};
|
||||
|
||||
204
2_copy trading bot(node) using gRPC/grpc.js
Normal file
204
2_copy trading bot(node) using gRPC/grpc.js
Normal 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()
|
||||
154
2_copy trading bot(node) using gRPC/index.js
Normal file
154
2_copy trading bot(node) using gRPC/index.js
Normal 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);
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
325
2_copy trading bot(node) using gRPC/main.js
Normal file
325
2_copy trading bot(node) using gRPC/main.js
Normal 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 };
|
||||
10961
2_copy trading bot(node) using gRPC/package-lock.json
generated
Normal file
10961
2_copy trading bot(node) using gRPC/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
2_copy trading bot(node) using gRPC/package.json
Normal file
44
2_copy trading bot(node) using gRPC/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
266
2_copy trading bot(node) using gRPC/parsingtransaction.js
Normal file
266
2_copy trading bot(node) using gRPC/parsingtransaction.js
Normal 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);
|
||||
}
|
||||
}
|
||||
1619
2_copy trading bot(node) using gRPC/raydium_cpmm_idl.json
Normal file
1619
2_copy trading bot(node) using gRPC/raydium_cpmm_idl.json
Normal file
File diff suppressed because it is too large
Load Diff
4304
2_copy trading bot(node) using gRPC/raydium_launchpad_idl.json
Normal file
4304
2_copy trading bot(node) using gRPC/raydium_launchpad_idl.json
Normal file
File diff suppressed because it is too large
Load Diff
327
2_copy trading bot(node) using gRPC/services/notifications.js
Normal file
327
2_copy trading bot(node) using gRPC/services/notifications.js
Normal 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;
|
||||
413
2_copy trading bot(node) using gRPC/services/riskManager.js
Normal file
413
2_copy trading bot(node) using gRPC/services/riskManager.js
Normal 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;
|
||||
7
2_copy trading bot(node) using gRPC/swap(offchain).js
Normal file
7
2_copy trading bot(node) using gRPC/swap(offchain).js
Normal 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📞📞============//
|
||||
|
||||
}
|
||||
307
2_copy trading bot(node) using gRPC/swap.js
Normal file
307
2_copy trading bot(node) using gRPC/swap.js
Normal 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;
|
||||
};
|
||||
174
2_copy trading bot(node) using gRPC/utils/logger.js
Normal file
174
2_copy trading bot(node) using gRPC/utils/logger.js
Normal 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;
|
||||
Reference in New Issue
Block a user