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

View File

@@ -0,0 +1,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.**

View File

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

View File

@@ -0,0 +1,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

View File

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

View File

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

View File

@@ -0,0 +1,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()

View 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 };

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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