mirror of
https://github.com/mortdeus/legacy-cc.git
synced 2026-10-09 12:22:24 +00:00
init
This commit is contained in:
286
1_solana sniper bot(node) using gRPC/README.md
Normal file
286
1_solana sniper bot(node) using gRPC/README.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# 🚀 Solana Raydium Sniper Bot
|
||||
|
||||
A high-performance Solana trading bot that automatically snipes new token launches on Raydium, PumpFun, and PumpSwap using gRPC streaming for real-time transaction monitoring.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **Real-time Monitoring**: Uses gRPC streaming to detect new token launches instantly
|
||||
- **Multi-Pool Support**: Supports Raydium LaunchLab, PumpFun, PumpSwap, and Raydium CPMM
|
||||
- **Automated Trading**: Automatically buys and sells tokens based on configurable parameters
|
||||
- **Risk Management**: Built-in stop-loss, profit-taking, and position monitoring
|
||||
- **Multiple Swap Methods**: Support for Solana, JITO, Nozomi, and 0slot trading
|
||||
- **Position Tracking**: Monitors active positions and manages exit strategies
|
||||
- **Graceful Shutdown**: Safely closes all positions before stopping
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ gRPC Stream │───▶│ Transaction │───▶│ Trading Engine │
|
||||
│ (Triton One) │ │ Parser │ │ (Main Bot) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Pool Detection │ │ Position │
|
||||
│ (PumpFun, etc.) │ │ Management │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Jupiter API │ │ Profit/Loss │
|
||||
│ Swap Execution │ │ Monitoring │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- Node.js 16+
|
||||
- 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-sniper-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
|
||||
```
|
||||
|
||||
### 4. Run the Bot
|
||||
|
||||
```bash
|
||||
# Start the sniper bot
|
||||
npm start
|
||||
|
||||
# Or for development with auto-restart
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## ⚙️ 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 |
|
||||
|
||||
### Swap Methods
|
||||
|
||||
| Method | Description | Use Case |
|
||||
|--------|-------------|----------|
|
||||
| `solana` | Standard Solana prioritization | General trading |
|
||||
| `race` | JITO MEV protection | MEV protection |
|
||||
| `nozomi` | Nozomi RPC with tips | Ultra-fast execution |
|
||||
| `0slot` | 0-slot transaction | Maximum speed |
|
||||
|
||||
### Pool Types Supported
|
||||
|
||||
- **Raydium LaunchLab**: New token launches
|
||||
- **PumpFun**: Pump.fun platform
|
||||
- **PumpSwap**: PumpSwap platform
|
||||
- **Raydium CPMM**: Constant Product Market Maker
|
||||
|
||||
## 📊 How It Works
|
||||
|
||||
### 1. Transaction Monitoring
|
||||
- Bot connects to Solana gRPC stream via Triton One
|
||||
- Monitors for `MintTo` instructions indicating new token launches
|
||||
- Filters transactions by SOL transfer amounts (1-85 SOL)
|
||||
|
||||
### 2. Transaction Parsing
|
||||
- Parses transaction data to identify pool type and parameters
|
||||
- Extracts liquidity, fees, and trading direction
|
||||
- Determines if transaction meets sniper criteria
|
||||
|
||||
### 3. Trading Execution
|
||||
- Automatically executes buy orders when criteria are met
|
||||
- Uses Jupiter API for optimal swap routing
|
||||
- Implements configurable slippage and prioritization fees
|
||||
|
||||
### 4. Position Management
|
||||
- Tracks active positions with entry/exit criteria
|
||||
- Monitors for profit targets and stop losses
|
||||
- Automatically closes positions based on conditions
|
||||
|
||||
## 🔧 Advanced Configuration
|
||||
|
||||
### Custom Pool Filters
|
||||
|
||||
```javascript
|
||||
// Enable/disable specific pool types
|
||||
ENABLE_PUMPFUN=true
|
||||
ENABLE_PUMPSWAP=true
|
||||
ENABLE_RAYDIUM_LAUNCHLAB=true
|
||||
ENABLE_RAYDIUM_CPMM=true
|
||||
```
|
||||
|
||||
### Risk Management
|
||||
|
||||
```javascript
|
||||
// Maximum concurrent positions
|
||||
MAX_POSITIONS=5
|
||||
|
||||
// Minimum transaction age
|
||||
MIN_TX_AGE=1
|
||||
```
|
||||
|
||||
### Notifications
|
||||
|
||||
```javascript
|
||||
// Telegram notifications
|
||||
TELEGRAM_BOT_TOKEN=your_bot_token
|
||||
TELEGRAM_CHAT_ID=your_chat_id
|
||||
|
||||
// Email notifications
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_USER=your_email@gmail.com
|
||||
SMTP_PASS=your_app_password
|
||||
```
|
||||
|
||||
## 📈 Trading Strategies
|
||||
|
||||
### Conservative Strategy
|
||||
```bash
|
||||
SNIPERAMOUNT=0.05
|
||||
PROFIT_TARGET=1.5
|
||||
STOP_LOSS=0.7
|
||||
MAX_HOLD_TIME=600000
|
||||
MIN_LIQUIDITY=20
|
||||
```
|
||||
|
||||
### Aggressive Strategy
|
||||
```bash
|
||||
SNIPERAMOUNT=0.2
|
||||
PROFIT_TARGET=3.0
|
||||
STOP_LOSS=0.3
|
||||
MAX_HOLD_TIME=180000
|
||||
MIN_LIQUIDITY=5
|
||||
```
|
||||
|
||||
## 🛡️ Safety Features
|
||||
|
||||
- **Balance Checks**: Verifies wallet balance before trading
|
||||
- **Liquidity Validation**: Ensures sufficient pool liquidity
|
||||
- **Position Limits**: Maximum concurrent position management
|
||||
- **Graceful Shutdown**: Safely closes all positions on exit
|
||||
- **Error Handling**: Comprehensive error handling and logging
|
||||
- **Retry Logic**: Automatic retry for failed transactions
|
||||
|
||||
## 📝 Logging
|
||||
|
||||
The bot provides detailed logging with color-coded output:
|
||||
|
||||
- 🚀 **Blue**: Bot startup and configuration
|
||||
- 🎯 **Green**: Successful trades and profit targets
|
||||
- 🛑 **Red**: Errors and stop losses
|
||||
- ⚠️ **Yellow**: Warnings and position updates
|
||||
- 📊 **Cyan**: Position information and PnL
|
||||
|
||||
## 🚨 Important Notes
|
||||
|
||||
### Security
|
||||
- **Never share your private key**
|
||||
- Use dedicated trading wallets
|
||||
- Regularly rotate API keys
|
||||
- Monitor bot activity
|
||||
|
||||
### Risk Disclaimer
|
||||
- This bot is for educational purposes
|
||||
- Cryptocurrency trading involves significant risk
|
||||
- Past performance doesn't guarantee future results
|
||||
- Use at your own risk
|
||||
|
||||
### Legal Compliance
|
||||
- Ensure compliance with local regulations
|
||||
- Check tax implications of automated trading
|
||||
- Consult with financial advisors if needed
|
||||
|
||||
## 🔍 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. **No Trades Executing**
|
||||
- Verify transaction filters
|
||||
- Check liquidity requirements
|
||||
- Review pool type settings
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging by setting:
|
||||
```bash
|
||||
DEBUG=true
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the 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
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For support and questions:
|
||||
- Create an issue on GitHub
|
||||
- Join our Discord community
|
||||
- Check the documentation
|
||||
|
||||
---
|
||||
|
||||
**⚠️ Disclaimer: This software is for educational purposes only. Use at your own risk. The authors are not responsible for any financial losses.**
|
||||
84
1_solana sniper bot(node) using gRPC/SETUP.md
Normal file
84
1_solana sniper 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!**
|
||||
24
1_solana sniper bot(node) using gRPC/env.template
Normal file
24
1_solana sniper bot(node) using gRPC/env.template
Normal file
@@ -0,0 +1,24 @@
|
||||
# Nozomi settings (optional)
|
||||
NOZOMI_URL=
|
||||
NOZOMI_UUID=
|
||||
|
||||
# Private key (required)
|
||||
PRIVATE_KEY=
|
||||
|
||||
# gRPC settings (📞Contact us to receive a one-time trial version)
|
||||
GRPCTOKEN=
|
||||
GRPC_ENDPOINT=
|
||||
RPC_URL=https://api.mainnet-beta.solana.com
|
||||
|
||||
# Sniper amount (in SOL)
|
||||
SNIPERAMOUNT=0.1
|
||||
|
||||
# Swap method: "0slot", "nozomi", "race", "solana"
|
||||
SWAP_METHOD=solana
|
||||
ENABLE_SWAP_TIP=true
|
||||
SLIPPAGE_BPS=5000
|
||||
PRIORITIZATION_FEE_LAMPORTS=20001
|
||||
|
||||
# Retry settings
|
||||
MAX_RETRIES=2
|
||||
RETRY_DELAY=500
|
||||
182
1_solana sniper bot(node) using gRPC/fuc.js
Normal file
182
1_solana sniper 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
1_solana sniper bot(node) using gRPC/grpc.js
Normal file
204
1_solana sniper 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()
|
||||
28
1_solana sniper bot(node) using gRPC/index.js
Normal file
28
1_solana sniper bot(node) using gRPC/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
import { pump_geyser } from "./main.js";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config()
|
||||
|
||||
const privateKey = process.env.PRIVATE_KEY; // Use private key directly
|
||||
|
||||
if (!privateKey) {
|
||||
console.error("Error: PRIVATE_KEY is not set in environment variables.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const { getBalance } = await import("./swap.js");
|
||||
const balance = await getBalance();
|
||||
if (balance < 1) {
|
||||
console.error("Error: Wallet balance is below 1 SOL. Current balance:", balance, "SOL");
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error checking wallet balance:", err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
pump_geyser()
|
||||
|
||||
194
1_solana sniper bot(node) using gRPC/main.js
Normal file
194
1_solana sniper bot(node) using gRPC/main.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import { newlunched_subscribeCommand, stopNewLaunch } from "./grpc.js";
|
||||
import { token_buy, token_sell, getSplTokenBalance, getPublicKeyFromPrivateKey } from "./fuc.js";
|
||||
import chalk from "chalk";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// Trading configuration
|
||||
const SNIPER_AMOUNT = parseFloat(process.env.SNIPERAMOUNT || "0.1"); // SOL amount to snipe with
|
||||
const PROFIT_TARGET = parseFloat(process.env.PROFIT_TARGET || "2.0"); // 2x profit target
|
||||
const STOP_LOSS = parseFloat(process.env.STOP_LOSS || "0.5"); // 50% stop loss
|
||||
const MAX_HOLD_TIME = parseInt(process.env.MAX_HOLD_TIME || "300000"); // 5 minutes in ms
|
||||
|
||||
// Track active positions
|
||||
const activePositions = new Map();
|
||||
|
||||
export const pump_geyser = async () => {
|
||||
try {
|
||||
const walletKey = getPublicKeyFromPrivateKey();
|
||||
// INSERT_YOUR_CODE
|
||||
console.log(chalk.magentaBright(`
|
||||
██████╗██████╗ ██╗ ██╗██████╗ ████████╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██╔════╝██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗ ██║██╔════╝
|
||||
██║ ██████╔╝ ██║ ██╔╝██████╔╝ ██║ ██║ ██║█████╔╝ ██║██╔██╗ ██║██║ ██╗
|
||||
██║ ██╔══██╗ ██╔═╝ ██╔═══╝ ██║ ██║ ██║██╔═██╗ ██║██║╚██╗██║██║ ██║
|
||||
╚██████╗██║ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██╗██║██║ ╚████║╚██████╔╝
|
||||
╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
`));
|
||||
console.log(chalk.blue.bold("🚀 Starting Solana Raydium Sniper Bot..."));
|
||||
console.log(chalk.blue(`🔑 Wallet Public Key: ${chalk.yellow(walletKey)}`));
|
||||
console.log(chalk.blue(`💰 Sniper Amount: ${chalk.green(SNIPER_AMOUNT)} SOL`));
|
||||
console.log(chalk.blue(`🎯 Profit Target: ${chalk.green(PROFIT_TARGET)}x`));
|
||||
console.log(chalk.blue(`🛑 Stop Loss: ${chalk.green(STOP_LOSS)}x`));
|
||||
console.log(chalk.blue(`⏱️ Max Hold Time: ${chalk.green(MAX_HOLD_TIME/1000)}s`));
|
||||
|
||||
// Start monitoring for new token launches
|
||||
await newlunched_subscribeCommand();
|
||||
|
||||
// Set up position monitoring
|
||||
setInterval(monitorPositions, 5000); // Check positions every 5 seconds
|
||||
|
||||
// Set up graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log(chalk.yellow("\n🛑 Shutting down sniper bot..."));
|
||||
stopNewLaunch();
|
||||
|
||||
// Close all positions before exit
|
||||
for (const [mint, position] of activePositions) {
|
||||
try {
|
||||
console.log(chalk.yellow(`🔄 Closing position for ${mint}...`));
|
||||
await closePosition(mint, position);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error closing position for ${mint}:`, error.message));
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red("Error in pump_geyser:", error));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Monitor active positions for profit taking or stop loss
|
||||
async function monitorPositions() {
|
||||
for (const [mint, position] of activePositions) {
|
||||
try {
|
||||
const currentBalance = await getSplTokenBalance(mint);
|
||||
if (!currentBalance || currentBalance <= 0) {
|
||||
console.log(chalk.yellow(`⚠️ No balance for ${mint}, removing from active positions`));
|
||||
activePositions.delete(mint);
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentValue = currentBalance * position.currentPrice;
|
||||
const profitRatio = currentValue / position.entryValue;
|
||||
const holdTime = Date.now() - position.entryTime;
|
||||
|
||||
// Check profit target
|
||||
if (profitRatio >= PROFIT_TARGET) {
|
||||
console.log(chalk.green(`🎯 Profit target reached for ${mint}: ${profitRatio.toFixed(2)}x`));
|
||||
await closePosition(mint, position);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check stop loss
|
||||
if (profitRatio <= STOP_LOSS) {
|
||||
console.log(chalk.red(`🛑 Stop loss triggered for ${mint}: ${profitRatio.toFixed(2)}x`));
|
||||
await closePosition(mint, position);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check max hold time
|
||||
if (holdTime >= MAX_HOLD_TIME) {
|
||||
console.log(chalk.yellow(`⏰ Max hold time reached for ${mint}, closing position`));
|
||||
await closePosition(mint, position);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update current price (you might want to implement price fetching here)
|
||||
// For now, we'll use a simple approach
|
||||
position.currentPrice = position.entryPrice; // Placeholder
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error monitoring position for ${mint}:`, error.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close a position by selling tokens
|
||||
async function closePosition(mint, position) {
|
||||
try {
|
||||
const currentBalance = await getSplTokenBalance(mint);
|
||||
if (!currentBalance || currentBalance <= 0) {
|
||||
console.log(chalk.yellow(`No balance to sell for ${mint}`));
|
||||
activePositions.delete(mint);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`🔄 Closing position for ${mint}, selling ${currentBalance} tokens`));
|
||||
|
||||
const txid = await token_sell(mint, currentBalance, position.poolStatus, true, position.context);
|
||||
|
||||
if (txid && txid !== "stop") {
|
||||
console.log(chalk.green(`✅ Position closed for ${mint}: ${txid}`));
|
||||
|
||||
// Calculate final PnL
|
||||
const finalValue = currentBalance * position.currentPrice;
|
||||
const pnl = finalValue - position.entryValue;
|
||||
const pnlRatio = (pnl / position.entryValue) * 100;
|
||||
|
||||
console.log(chalk.cyan(`📊 Final PnL for ${mint}: ${pnl.toFixed(4)} SOL (${pnlRatio.toFixed(2)}%)`));
|
||||
} else {
|
||||
console.log(chalk.red(`❌ Failed to close position for ${mint}`));
|
||||
}
|
||||
|
||||
activePositions.delete(mint);
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error closing position for ${mint}:`, error.message));
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle new token launches (called from grpc.js)
|
||||
export const handleNewTokenLaunch = async (tokenMint, poolStatus, context) => {
|
||||
try {
|
||||
console.log(chalk.green(`🎯 New token launch detected: ${tokenMint}`));
|
||||
console.log(chalk.blue(`🏊 Pool type: ${poolStatus}`));
|
||||
|
||||
// Check if we already have a position in this token
|
||||
if (activePositions.has(tokenMint)) {
|
||||
console.log(chalk.yellow(`⚠️ Already have position in ${tokenMint}, skipping`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Execute snipe
|
||||
console.log(chalk.blue(`🚀 Sniping ${tokenMint} with ${SNIPER_AMOUNT} SOL...`));
|
||||
|
||||
const txid = await token_buy(tokenMint, SNIPER_AMOUNT, poolStatus, context);
|
||||
|
||||
if (txid) {
|
||||
console.log(chalk.green(`✅ Snipe successful! TX: ${txid}`));
|
||||
|
||||
// Add to active positions
|
||||
activePositions.set(tokenMint, {
|
||||
entryTime: Date.now(),
|
||||
entryValue: SNIPER_AMOUNT,
|
||||
entryPrice: 1, // Placeholder - you might want to calculate actual price
|
||||
currentPrice: 1,
|
||||
poolStatus: poolStatus,
|
||||
context: context,
|
||||
txid: txid
|
||||
});
|
||||
|
||||
console.log(chalk.cyan(`📊 Position opened for ${tokenMint}`));
|
||||
console.log(chalk.cyan(`💰 Entry Value: ${SNIPER_AMOUNT} SOL`));
|
||||
console.log(chalk.cyan(`🎯 Profit Target: ${PROFIT_TARGET}x`));
|
||||
console.log(chalk.cyan(`🛑 Stop Loss: ${STOP_LOSS}x`));
|
||||
|
||||
} else {
|
||||
console.log(chalk.red(`❌ Snipe failed for ${tokenMint}`));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error handling new token launch for ${tokenMint}:`, error.message));
|
||||
}
|
||||
};
|
||||
|
||||
// // Export for external use
|
||||
// export { activePositions, handleNewTokenLaunch };
|
||||
10961
1_solana sniper bot(node) using gRPC/package-lock.json
generated
Normal file
10961
1_solana sniper bot(node) using gRPC/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
1_solana sniper bot(node) using gRPC/package.json
Normal file
44
1_solana sniper 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
1_solana sniper bot(node) using gRPC/parsingtransaction.js
Normal file
266
1_solana sniper 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
1_solana sniper bot(node) using gRPC/raydium_cpmm_idl.json
Normal file
1619
1_solana sniper bot(node) using gRPC/raydium_cpmm_idl.json
Normal file
File diff suppressed because it is too large
Load Diff
4304
1_solana sniper bot(node) using gRPC/raydium_launchpad_idl.json
Normal file
4304
1_solana sniper bot(node) using gRPC/raydium_launchpad_idl.json
Normal file
File diff suppressed because it is too large
Load Diff
7
1_solana sniper bot(node) using gRPC/swap(offchain).js
Normal file
7
1_solana sniper 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
1_solana sniper bot(node) using gRPC/swap.js
Normal file
307
1_solana sniper 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;
|
||||
};
|
||||
Reference in New Issue
Block a user