`: Set stop loss for a token
173 |
174 | ## Technical Details
175 |
176 | ### Dependencies
177 |
178 | - `anchor_client`: Solana program interaction
179 | - `yellowstone-grpc-proto`: Real-time blockchain data
180 | - `tokio`: Asynchronous runtime
181 | - `spl_token`: Solana token operations
182 | - `reqwest`: HTTP client for API interactions
183 | - `serde`: Serialization/deserialization
184 | - `colored`: Terminal output formatting
185 |
186 | ### Key Modules
187 |
188 | - `common`: Configuration, constants, and utilities
189 | - `core`: Core functionality for tokens and transactions
190 | - `dex`: DEX-specific implementations (Pump.fun)
191 | - `engine`: Trading engine and monitoring systems
192 | - `services`: External service integrations (Telegram, Jito, ZeroSlot)
193 | - `error`: Error handling
194 |
195 | ## Advanced Features
196 |
197 | ### Dynamic Retracement Selling
198 |
199 | The bot implements a sophisticated retracement-based selling strategy that:
200 | - Tracks the highest price point (ATH) for each token
201 | - Calculates retracement percentages from ATH
202 | - Triggers partial sells at different retracement levels based on overall PnL
203 | - Adjusts selling percentages based on profit levels
204 |
205 | ### Bundle Transaction Detection
206 |
207 | Detects and analyzes bundle transactions to identify potential token manipulations:
208 | - Monitors for multiple transactions in a single bundle
209 | - Identifies developer buying patterns
210 | - Helps avoid tokens with suspicious transaction patterns
211 |
212 | ### Automatic Token Analysis
213 |
214 | For each new token, the bot analyzes:
215 | - Bonding curve parameters
216 | - Virtual and real reserves
217 | - Market cap and liquidity
218 | - Developer wallet activity
219 | - Historical price movements
220 |
221 | ## Security Considerations
222 |
223 | - Private keys are stored in environment variables
224 | - Blacklist functionality to avoid known scam tokens
225 | - Transaction simulation before submission
226 | - Error handling and retry mechanisms
227 |
228 | ## How to Run
229 |
230 | ### Normal Operation
231 |
232 | ```bash
233 | cargo run
234 | ```
235 |
236 | ### Run Dev Wallet Test
237 |
238 | To test the dev wallet detection and notification deduplication functionality:
239 |
240 | ```bash
241 | cargo run -- --test-dev-wallet
242 | ```
243 |
244 | This will:
245 | 1. Create a simulated token
246 | 2. Identify the dev wallet (the signer of the mint transaction)
247 | 3. Send a notification via Telegram (if credentials are provided)
248 | 4. Attempt to send a second notification for the same token to test deduplication
249 |
250 | ## Configuration
251 |
252 | Set the following environment variables before running:
253 |
254 | ```bash
255 | # Telegram Settings
256 | export TELEGRAM_BOT_TOKEN="your_bot_token"
257 | export TELEGRAM_CHAT_ID="your_chat_id"
258 |
259 | # Filter Settings
260 | export MARKET_CAP_ENABLED="true"
261 | export MIN_MARKET_CAP="8.0"
262 | export MAX_MARKET_CAP="15.0"
263 |
264 | export VOLUME_ENABLED="true"
265 | export MIN_VOLUME="5.0"
266 | export MAX_VOLUME="12.0"
267 |
268 | export BUY_SELL_COUNT_ENABLED="true"
269 | export MIN_NUMBER_OF_BUY_SELL="50"
270 | export MAX_NUMBER_OF_BUY_SELL="2000"
271 |
272 | export SOL_INVESTED="1.0"
273 | export SOL_INVESTED_ENABLED="true"
274 |
275 | export LAUNCHER_SOL_ENABLED="true"
276 | export MIN_LAUNCHER_SOL_BALANCE="0.0"
277 | export MAX_LAUNCHER_SOL_BALANCE="1.0"
278 |
279 | export DEV_BUY_ENABLED="true"
280 | export MIN_DEV_BUY="5.0"
281 | export MAX_DEV_BUY="30.0"
282 |
283 | export BUNDLE_CHECK="true"
284 | ```
285 |
286 | ## Telegram Commands
287 |
288 | - `/start` or `/filters` - Display filter settings UI
289 | - `/config` - Show configuration file location
290 |
291 | @src current project don't use token age.
292 | we have to calculate that : get token created time in from_json function and save it on ParsedTransactionInfo struct , and use it in real filter logic
293 |
294 | ---
295 |
296 | ## 📞 Contact Information
297 | For questions, feedback, or collaboration opportunities, feel free to reach out:
298 |
299 |
300 |
301 | 📧 **Email**: [fenrow325@gmail.com](mailto:fenrow325@gmail.com)
302 | 📱 **Telegram**: [@fenroW](https://t.me/fenrow)
303 | 🎮 **Discord**: [@fenroW](https://discord.com/users/fenrow_325)
304 |
305 |
306 |
307 | ---
--------------------------------------------------------------------------------
/TIME_SERIES_ANALYSIS.md:
--------------------------------------------------------------------------------
1 | # Time Series Analysis & Advanced Trading Strategies
2 |
3 | ## Overview
4 |
5 | This implementation enhances the existing pump.fun token trading bot with comprehensive time series analysis capabilities, sophisticated entry and exit criteria, advanced risk management, and market-aware trading strategies.
6 |
7 | ## Key Components
8 |
9 | ### Time Series Analysis Module
10 | - **Data Collection**: Efficiently captures and stores token price, volume, and transaction history.
11 | - **Technical Indicators**: Calculates various technical indicators including:
12 | - Simple Moving Averages (SMA) - 5, 10, 20, 50 periods
13 | - Exponential Moving Averages (EMA) - 5, 10, 20, 50 periods
14 | - Relative Strength Index (RSI)
15 | - Moving Average Convergence Divergence (MACD)
16 | - Bollinger Bands
17 | - Price Momentum
18 | - Rate of Change
19 | - **Signal Generation**: Produces actionable buy/sell signals with confidence scores.
20 | - **Serialization Support**: Properly handles data persistence with serializable types.
21 |
22 | ### Enhanced Entry Criteria
23 | - **Momentum-Based Entry**: Identifies tokens with positive price momentum.
24 | - **Buy/Sell Ratio Analysis**: Favors tokens with strong buying pressure.
25 | - **RSI Conditions**: Buys oversold tokens (RSI < 30) and sells overbought tokens (RSI > 70).
26 | - **Moving Average Crossovers**: Detects golden crosses (short-term MA crossing above long-term MA) for entries.
27 | - **Bollinger Band Rebounds**: Identifies price rebounds from lower Bollinger Band.
28 | - **Token Age Verification**: Validates that tokens are indeed new launches.
29 |
30 | ### Dynamic Exit Strategies
31 | - **Volatility-Adjusted Take Profit**: Sets take profit levels based on Bollinger Band width.
32 | - **Multiple Profit Targets**: Implements partial exits at various profit levels.
33 | - **Trailing Stops**: Dynamically adjusts stop loss as price increases.
34 | - **Time-Based Exit Adjustments**: Tightens stops the longer a position is held.
35 |
36 | ### Advanced Risk Management
37 | - **Position Sizing**: Calculates position size based on:
38 | - Technical indicator confidence levels
39 | - Portfolio risk percentage
40 | - Token volatility
41 | - **Daily Budget Controls**: Enforces maximum daily exposure.
42 | - **Risk-Adjusted Stop Losses**: Tighter stops for higher-risk tokens.
43 |
44 | ### Bonding Curve Analysis
45 | - **Curve Steepness Evaluation**: Identifies optimal entry points on bonding curves.
46 | - **Liquidity Depth Analysis**: Ensures sufficient liquidity before trade execution.
47 | - **Price Impact Calculation**: Estimates slippage for different position sizes.
48 |
49 | ## Configuration Parameters
50 |
51 | | Parameter | Default | Description |
52 | |-----------|---------|-------------|
53 | | MIN_BUY_CONFIDENCE | 65 | Minimum confidence score to execute a buy (0-100) |
54 | | MIN_SELL_CONFIDENCE | 70 | Minimum confidence score to execute a sell (0-100) |
55 | | DAILY_BUY_BUDGET | 2.0 | Maximum SOL to spend per day |
56 | | MAX_TIME_SERIES_POINTS | 100 | Number of data points to store per token |
57 | | TIME_SERIES_INTERVAL_SECS | 15 | Interval between data points in seconds |
58 | | TAKE_PROFIT_PERCENT | 20.0 | Default take profit percentage |
59 | | STOP_LOSS_PERCENT | 10.0 | Default stop loss percentage |
60 |
61 | ## Integration
62 |
63 | The time series analysis module seamlessly integrates with the existing token trading system:
64 |
65 | 1. **Data Collection**: Token data is continuously collected from the token tracker.
66 | 2. **Signal Generation**: Technical analysis generates buy/sell signals with confidence scores.
67 | 3. **Buy Execution**: High-confidence buy signals are routed to the buy manager.
68 | 4. **Dynamic Exit Parameters**: Exit parameters are adjusted based on market conditions.
69 | 5. **Sell Execution**: Sell signals trigger the sell manager with optimized parameters.
70 |
71 | ## Usage
72 |
73 | The enhanced trading system can be started using:
74 |
75 | ```rust
76 | start_enhanced_trading_system(
77 | app_state,
78 | swap_config,
79 | blacklist_enabled,
80 | take_profit_percent,
81 | stop_loss_percent,
82 | telegram_bot_token,
83 | telegram_chat_id,
84 | ).await
85 | ```
86 |
87 | ## Technical Indicator Implementation Details
88 |
89 | ### Simple Moving Average (SMA)
90 | Calculates the average price over a specified period.
91 |
92 | ```rust
93 | fn calculate_sma(&self, prices: &[f64], period: usize) -> f64 {
94 | if prices.len() < period {
95 | return 0.0;
96 | }
97 |
98 | let sum: f64 = prices[prices.len() - period..].iter().sum();
99 | sum / period as f64
100 | }
101 | ```
102 |
103 | ### Exponential Moving Average (EMA)
104 | Gives more weight to recent prices for faster response to price changes.
105 |
106 | ```rust
107 | fn calculate_ema(&self, prices: &[f64], period: usize) -> f64 {
108 | if prices.len() < period {
109 | return 0.0;
110 | }
111 |
112 | let mut ema = self.calculate_sma(prices, period);
113 | let multiplier = 2.0 / (period as f64 + 1.0);
114 |
115 | for i in prices.len() - period + 1..prices.len() {
116 | ema = (prices[i] - ema) * multiplier + ema;
117 | }
118 |
119 | ema
120 | }
121 | ```
122 |
123 | ### Relative Strength Index (RSI)
124 | Measures the speed and change of price movements, indicating overbought/oversold conditions.
125 |
126 | ```rust
127 | fn calculate_rsi(&self, prices: &[f64], period: usize) -> f64 {
128 | if prices.len() < period + 1 {
129 | return 50.0; // Default to neutral
130 | }
131 |
132 | let mut gains = 0.0;
133 | let mut losses = 0.0;
134 |
135 | for i in prices.len() - period..prices.len() {
136 | let change = prices[i] - prices[i - 1];
137 | if change >= 0.0 {
138 | gains += change;
139 | } else {
140 | losses -= change;
141 | }
142 | }
143 |
144 | if losses == 0.0 {
145 | return 100.0; // All gains, no losses
146 | }
147 |
148 | let rs = gains / losses;
149 | 100.0 - (100.0 / (1.0 + rs))
150 | }
151 | ```
152 |
153 | ### Bollinger Bands
154 | Shows price volatility and potential reversal points using standard deviation.
155 |
156 | ```rust
157 | fn calculate_bollinger_bands(&self, prices: &[f64], period: usize, std_dev_multiplier: f64) -> (f64, f64, f64) {
158 | if prices.len() < period {
159 | let price = prices.last().unwrap_or(&0.0);
160 | return (*price, *price, *price);
161 | }
162 |
163 | // Calculate SMA
164 | let sma = self.calculate_sma(prices, period);
165 |
166 | // Calculate Standard Deviation
167 | let price_slice = &prices[prices.len() - period..];
168 | let sum_squares: f64 = price_slice.iter().map(|p| (p - sma).powi(2)).sum();
169 | let std_dev = (sum_squares / period as f64).sqrt();
170 |
171 | // Calculate bands
172 | let upper_band = sma + (std_dev_multiplier * std_dev);
173 | let lower_band = sma - (std_dev_multiplier * std_dev);
174 |
175 | (sma, upper_band, lower_band)
176 | }
177 | ```
178 |
179 | ## Performance and Optimization
180 |
181 | The implementation is designed for efficiency with:
182 |
183 | - **Interval-Based Data Collection**: Controls data frequency to avoid excessive CPU usage.
184 | - **Fixed-Size Data Storage**: Limits memory usage by maintaining a fixed-size data window.
185 | - **Mutex Guards**: Properly managed to avoid deadlocks across async boundaries.
186 | - **Isolated Function Pattern**: Ensures MutexGuard isn't held across await points.
187 | - **Serializable Data Structures**: Time series data can be serialized for persistence.
188 |
189 | ## Data Serialization
190 |
191 | To handle persistence, we've implemented serializable versions of our data structures:
192 |
193 | ```rust
194 | // For internal use with timestamps
195 | #[derive(Debug, Clone)]
196 | pub struct TimeSeriesDataPoint {
197 | pub timestamp: Instant, // Non-serializable std::time::Instant
198 | pub price: f64,
199 | pub volume: f64,
200 | pub buy_count: u32,
201 | pub sell_count: u32,
202 | pub market_cap: Option,
203 | pub iso_timestamp: DateTime, // For serialization
204 | }
205 |
206 | // For persistence/serialization
207 | #[derive(Debug, Clone, Serialize, Deserialize)]
208 | pub struct SerializableTimeSeriesDataPoint {
209 | pub timestamp: String, // ISO 8601 timestamp string
210 | pub price: f64,
211 | pub volume: f64,
212 | pub buy_count: u32,
213 | pub sell_count: u32,
214 | pub market_cap: Option,
215 | }
216 | ```
217 |
218 | ## Extending the System
219 |
220 | The time series analysis system is designed for easy extension:
221 |
222 | 1. **Adding New Indicators**: Implement additional technical indicators by adding new calculation methods.
223 | 2. **Custom Signal Logic**: Develop sophisticated signal generation by modifying `generate_signals()`.
224 | 3. **Risk Profile Integration**: Connect with external risk assessment systems for better position sizing.
225 | 4. **Market-Wide Analysis**: Incorporate Solana ecosystem-wide metrics for market sentiment.
226 | 5. **Data Persistence**: Implement storage and retrieval using the serializable data structures.
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Shell script for cross-compiling Rust projects for Windows from Ubuntu
4 |
5 | set -e
6 |
7 | # Constants
8 | TARGET_X86_64="x86_64-pc-windows-gnu"
9 | TARGET_I686="i686-pc-windows-gnu"
10 |
11 | # Function to print messages
12 | function echo_info() {
13 | echo "[INFO] $1"
14 | }
15 |
16 | # Update and install required packages
17 | echo_info "Updating package list..."
18 | sudo apt update
19 |
20 | echo_info "Installing mingw-w64..."
21 | sudo apt install -y mingw-w64
22 |
23 | echo_info "Adding Rust targets..."
24 | rustup target add $TARGET_X86_64
25 | rustup target add $TARGET_I686
26 |
27 | # Build the project for x86_64 Windows
28 | echo_info "Building for 64-bit Windows..."
29 | cargo build --target=$TARGET_X86_64
30 |
31 | # Build the project for i686 Windows
32 | echo_info "Building for 32-bit Windows..."
33 | cargo build --target=$TARGET_I686
34 |
35 | echo_info "Build completed!"
36 |
--------------------------------------------------------------------------------
/check_error.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if the original errors are still in the compiled output
4 | echo "Checking for E0282 type annotation errors..."
5 | cargo check --lib 2>&1 | grep -A 2 "error\[E0282\].*line.*760"
6 | cargo check --lib 2>&1 | grep -A 2 "error\[E0282\].*line.*854"
7 | cargo check --lib 2>&1 | grep -A 2 "error\[E0282\].*line.*879"
8 |
9 | echo "Checking for E0424 self reference errors..."
10 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*759"
11 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*760"
12 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*761"
13 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*762"
14 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*764"
15 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*765"
16 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*854"
17 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*875"
18 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*876"
19 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*877"
20 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*879"
21 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*880"
22 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*881"
23 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*882"
24 | cargo check --lib 2>&1 | grep -A 2 "error\[E0424\].*self.*line.*883"
25 |
26 | echo "If no output appears above this line, the errors have been fixed!"
--------------------------------------------------------------------------------
/check_error2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if the errors are still in the compiled output
4 | echo "Checking for E0308 mismatched types errors..."
5 | cargo check --lib 2>&1 | grep -A 2 "error\[E0308\].*expected .Arc., found .Arc>."
6 | cargo check --lib 2>&1 | grep -A 2 "error\[E0308\].*expected .&Arc., found .&Arc>."
7 |
8 | echo "Checking for E0599 no method named lock errors..."
9 | cargo check --lib 2>&1 | grep -A 2 "error\[E0599\].*no method named .lock. found for struct"
10 |
11 | echo "If no output appears above this line, the errors have been fixed!"
--------------------------------------------------------------------------------
/log/_pumpfun_buy_tx.log:
--------------------------------------------------------------------------------
1 | Transaction: SubscribeUpdateTransaction { transaction: Some(SubscribeUpdateTransactionInfo { signature: [231, 110, 219, 40, 194, 48, 159, 224, 64, 185, 72, 40, 168, 2, 69, 226, 239, 126, 38, 209, 254, 29, 225, 101, 224, 24, 137, 63, 14, 34, 236, 227, 201, 236, 80, 71, 92, 40, 179, 100, 16, 27, 164, 195, 214, 170, 46, 3, 248, 217, 220, 197, 142, 135, 113, 246, 184, 230, 118, 190, 224, 242, 216, 7], is_vote: false, transaction: Some(Transaction { signatures: [[231, 110, 219, 40, 194, 48, 159, 224, 64, 185, 72, 40, 168, 2, 69, 226, 239, 126, 38, 209, 254, 29, 225, 101, 224, 24, 137, 63, 14, 34, 236, 227, 201, 236, 80, 71, 92, 40, 179, 100, 16, 27, 164, 195, 214, 170, 46, 3, 248, 217, 220, 197, 142, 135, 113, 246, 184, 230, 118, 190, 224, 242, 216, 7]], message: Some(Message { header: Some(MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 11 }), account_keys: [[215, 90, 98, 179, 164, 254, 47, 56, 199, 123, 194, 154, 88, 243, 24, 180, 161, 93, 154, 11, 240, 234, 124, 99, 29, 99, 51, 42, 173, 205, 178, 10], [166, 81, 232, 61, 150, 239, 175, 228, 103, 54, 184, 154, 165, 44, 16, 237, 80, 73, 139, 251, 252, 49, 187, 81, 103, 186, 24, 83, 45, 121, 140, 35], [131, 132, 116, 41, 46, 103, 90, 148, 180, 54, 236, 176, 169, 152, 137, 66, 50, 138, 131, 221, 198, 35, 56, 2, 150, 18, 103, 197, 205, 97, 23, 203], [92, 174, 255, 29, 106, 19, 188, 217, 204, 140, 122, 141, 187, 167, 218, 214, 203, 150, 110, 135, 81, 181, 211, 166, 218, 90, 141, 68, 214, 218, 67, 6], [130, 200, 126, 247, 228, 126, 15, 82, 199, 194, 75, 52, 94, 185, 36, 147, 111, 193, 170, 96, 148, 179, 109, 56, 81, 42, 191, 62, 141, 129, 25, 252], [51, 197, 17, 193, 120, 182, 64, 61, 192, 147, 230, 110, 135, 1, 200, 181, 70, 180, 109, 186, 103, 67, 204, 181, 102, 233, 227, 55, 226, 241, 127, 141], [136, 241, 255, 163, 162, 223, 230, 23, 189, 196, 227, 87, 50, 81, 163, 34, 227, 252, 174, 129, 229, 164, 87, 57, 14, 100, 117, 28, 0, 164, 101, 226], [3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0], [10, 241, 195, 67, 33, 136, 202, 58, 99, 81, 53, 161, 58, 24, 149, 27, 168, 54, 240, 158, 220, 246, 219, 95, 93, 80, 90, 50, 112, 80, 255, 153], [140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89], [39, 179, 89, 60, 111, 7, 199, 143, 177, 185, 92, 14, 57, 0, 253, 57, 205, 108, 197, 206, 12, 16, 222, 95, 16, 236, 230, 12, 40, 11, 116, 95], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], [147, 255, 101, 186, 115, 58, 105, 199, 69, 59, 167, 240, 106, 127, 213, 38, 55, 179, 121, 200, 2, 79, 247, 45, 37, 182, 36, 45, 78, 138, 128, 37], [58, 134, 94, 105, 238, 15, 84, 128, 202, 188, 246, 99, 87, 228, 220, 47, 24, 213, 141, 69, 193, 234, 116, 137, 251, 55, 35, 217, 121, 60, 114, 166], [6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0], [172, 241, 54, 235, 1, 252, 28, 78, 136, 61, 35, 200, 181, 132, 74, 181, 154, 55, 246, 106, 221, 87, 197, 233, 172, 59, 83, 224, 89, 211, 92, 100], [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176]],
2 | recent_blockhash: [214, 244, 173, 248, 144, 238, 253, 86, 43, 52, 158, 107, 196, 148, 27, 228, 22, 32, 221, 186, 23, 111, 117, 168, 5, 28, 164, 124, 8, 137, 247, 144], instructions: [CompiledInstruction { program_id_index: 7, accounts: [8], data: [2, 160, 134, 1, 0] }, CompiledInstruction { program_id_index: 7, accounts: [], data: [3, 128, 150, 152, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 9, accounts: [0, 1, 0, 10, 11, 12], data: [1] }, CompiledInstruction { program_id_index: 13, accounts: [14, 2, 10, 3, 4, 1, 0, 11, 12, 15, 16, 17], data: [0, 192, 158, 230, 5, 0, 0, 0, 0, 252, 162, 190, 204, 117, 0, 0, 0] }, CompiledInstruction { program_id_index: 11, accounts: [0, 5], data: [2, 0, 0, 0, 64, 66, 15, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 11, accounts: [0, 6], data: [2, 0, 0, 0, 64, 66, 15, 0, 0, 0, 0, 0] }], versioned: false, address_table_lookups: [] }) }), meta: Some(TransactionStatusMeta { err: None, fee: 1005000, pre_balances: [202203953, 0, 5747167575356, 42043608356, 2039280, 701124783572, 14375310, 1, 0, 731913600, 1461600, 1, 934087680, 1141440, 290898417, 1009200, 137104014, 1141440], post_balances: [98159673, 2039280, 5747168555555, 42141628157, 2039280, 701125783572, 15375310, 1, 0, 731913600, 1461600, 1, 934087680, 1141440, 290898417, 1009200, 137104014, 1141440], inner_instructions: [InnerInstructions { index: 2, instructions: [InnerInstruction { program_id_index: 12, accounts: [10], data: [21, 7, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 11, accounts: [0, 1], data: [0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], stack_height: Some(2) }, InnerInstruction { program_id_index: 12, accounts: [1], data: [22], stack_height: Some(2) }, InnerInstruction { program_id_index: 12, accounts: [1, 10], data: [18, 215, 90, 98, 179, 164, 254, 47, 56, 199, 123, 194, 154, 88, 243, 24, 180, 161, 93, 154, 11, 240, 234, 124, 99, 29, 99, 51, 42, 173, 205, 178, 10], stack_height: Some(2) }] }, InnerInstructions { index: 3, instructions: [InnerInstruction { program_id_index: 17, accounts: [14, 2, 10, 3, 4, 1, 0, 11, 12, 15, 16, 17], data: [102, 6, 61, 18, 1, 218, 235, 234, 200, 246, 23, 92, 141, 0, 0, 0, 192, 158, 230, 5, 0, 0, 0, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 12, accounts: [4, 1, 3], data: [3, 200, 246, 23, 92, 141, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 11, accounts: [0, 3], data: [2, 0, 0, 0, 217, 169, 215, 5, 0, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 11, accounts: [0, 2], data: [2, 0, 0, 0, 231, 244, 14, 0, 0, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 17, accounts: [16], data: [228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238, 39, 179, 89, 60, 111, 7, 199, 143, 177, 185, 92, 14, 57, 0, 253, 57, 205, 108, 197, 206, 12, 16, 222, 95, 16, 236, 230, 12, 40, 11, 116, 95, 217, 169, 215, 5, 0, 0, 0, 0, 200, 246, 23, 92, 141, 0, 0, 0, 1, 215, 90, 98, 179, 164, 254, 47, 56, 199, 123, 194, 154, 88, 243, 24, 180, 161, 93, 154, 11, 240, 234, 124, 99, 29, 99, 51, 42, 173, 205, 178, 10, 170, 163, 24, 104, 0, 0, 0, 0, 253, 26, 209, 203, 16, 0, 0, 0, 109, 119, 15, 49, 214, 149, 1, 0, 253, 110, 173, 207, 9, 0, 0, 0, 109, 223, 252, 228, 68, 151, 0, 0], stack_height: Some(3) }] }], inner_instructions_none: false, log_messages: ["Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]", "Program log: CreateIdempotent", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: GetAccountDataSize", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 94295 compute units", "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [2]", "Program 11111111111111111111111111111111 success", "Program log: Initialize the associated token account", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: InitializeImmutableOwner", "Program log: Please upgrade to SPL Token 2022 for immutable owner support", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 87708 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: InitializeAccount3", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4188 of 83826 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 20345 of 99700 compute units", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", "Program AxiomQpD1TrYEHNYLts8h3ko1NHdtxfgNgHryj2hJJx4 invoke [1]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", "Program log: Instruction: Buy", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 58452 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [3]", "Program 11111111111111111111111111111111 success", "Program 11111111111111111111111111111111 invoke [3]", "Program 11111111111111111111111111111111 success", "Program data: vdt/007mYe4ns1k8bwfHj7G5XA45AP05zWzFzgwQ3l8Q7OYMKAt0X9mp1wUAAAAAyPYXXI0AAAAB11pis6T+LzjHe8KaWPMYtKFdmgvw6nxjHWMzKq3NsgqqoxhoAAAAAP0a0csQAAAAbXcPMdaVAQD9bq3PCQAAAG3f/ORElwAA", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [3]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 1997 of 45903 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 32499 of 75630 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program AxiomQpD1TrYEHNYLts8h3ko1NHdtxfgNgHryj2hJJx4 consumed 36300 of 79355 compute units", "Program AxiomQpD1TrYEHNYLts8h3ko1NHdtxfgNgHryj2hJJx4 success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"], log_messages_none: false,
3 | pre_token_balances: [
4 | TokenBalance { account_index: 4, mint: "3fyWzCoaEzJewjXfhrHFEndHWVGdVLWZEAQoQ86Lpump", ui_token_amount: Some(UiTokenAmount { ui_amount: 373829290.810933, decimals: 6, amount: "373829290810933", ui_amount_string: "373829290.810933" }), owner: "7EoH1YUsASscDWxgk364EqnpL2HbUrP8APDFZVkLkegu", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }],
5 | post_token_balances: [TokenBalance { account_index: 1, mint: "3fyWzCoaEzJewjXfhrHFEndHWVGdVLWZEAQoQ86Lpump", ui_token_amount: Some(UiTokenAmount { ui_amount: 607135.463112, decimals: 6, amount: "607135463112", ui_amount_string: "607135.463112" }), owner: "FVebN3A8HcSC8ZmuMhXRM4prfWc2WbZYoWEVrRj6gZiR", program_id: pump"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
6 | TokenBalance { account_index: 4, mint: "3fyWzCoaEzJewjXfhrHFEndHWVGdVLWZEAQoQ86Lpump", ui_token_amount: Some(UiTokenAmount { ui_amount: 373222155.347821, decimals: 6, amount: "373222155347821", ui_amount_string: "373222155.347821" }), owner: "7EoH1YUsASscDWxgk364EqnpL2HbUrP8APDFZVkLkegu", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], rewards: [], loaded_writable_addresses: [], loaded_readonly_addresses: [], return_data: None, return_data_none: true, compute_units_consumed: Some(57245) }), index: 1077 }), slot: 337984963 }
7 |
8 |
--------------------------------------------------------------------------------
/log/_pumpfun_sell_tx.log:
--------------------------------------------------------------------------------
1 | Transaction: SubscribeUpdateTransaction { transaction: Some(SubscribeUpdateTransactionInfo { signature: [12, 91, 5, 74, 226, 66, 253, 119, 219, 111, 249, 219, 249, 99, 226, 185, 13, 97, 165, 114, 246, 108, 83, 177, 130, 217, 53, 87, 158, 215, 150, 6, 64, 79, 221, 174, 141, 248, 75, 12, 167, 37, 237, 74, 148, 204, 112, 4, 250, 166, 17, 231, 59, 82, 56, 249, 170, 223, 249, 81, 5, 43, 49, 6], is_vote: false, transaction: Some(Transaction { signatures: [[12, 91, 5, 74, 226, 66, 253, 119, 219, 111, 249, 219, 249, 99, 226, 185, 13, 97, 165, 114, 246, 108, 83, 177, 130, 217, 53, 87, 158, 215, 150, 6, 64, 79, 221, 174, 141, 248, 75, 12, 167, 37, 237, 74, 148, 204, 112, 4, 250, 166, 17, 231, 59, 82, 56, 249, 170, 223, 249, 81, 5, 43, 49, 6]], message: Some(Message { header: Some(MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 9 }), account_keys: [[39, 145, 191, 193, 93, 236, 41, 194, 20, 126, 83, 64, 86, 160, 18, 70, 177, 136, 116, 92, 227, 244, 102, 21, 251, 156, 105, 221, 75, 227, 106, 24], [96, 140, 204, 29, 252, 233, 97, 180, 59, 119, 156, 25, 21, 5, 166, 226, 211, 191, 69, 213, 164, 219, 70, 24, 173, 118, 200, 45, 97, 117, 69, 53], [27, 144, 16, 57, 1, 238, 122, 71, 88, 77, 9, 173, 21, 79, 6, 84, 204, 190, 77, 43, 19, 59, 79, 142, 242, 166, 89, 221, 6, 184, 220, 206], [36, 158, 224, 196, 119, 130, 145, 63, 167, 254, 33, 54, 221, 89, 238, 238, 151, 246, 8, 211, 14, 216, 101, 233, 207, 102, 61, 5, 150, 252, 176, 188], [249, 171, 170, 213, 74, 239, 240, 20, 68, 52, 160, 171, 122, 212, 100, 80, 8, 232, 241, 147, 16, 52, 37, 69, 1, 184, 158, 23, 130, 216, 79, 190], [94, 17, 59, 3, 115, 173, 69, 49, 209, 145, 126, 20, 153, 40, 44, 162, 190, 127, 83, 39, 52, 84, 226, 251, 203, 13, 89, 144, 253, 113, 234, 58], [136, 241, 255, 163, 162, 223, 230, 23, 189, 196, 227, 87, 50, 81, 163, 34, 227, 252, 174, 129, 229, 164, 87, 57, 14, 100, 117, 28, 0, 164, 101, 226], [3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0], [10, 241, 195, 67, 33, 136, 202, 58, 99, 81, 53, 161, 58, 24, 149, 27, 168, 54, 240, 158, 220, 246, 219, 95, 93, 80, 90, 50, 112, 80, 255, 153], [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176], [58, 134, 94, 105, 238, 15, 84, 128, 202, 188, 246, 99, 87, 228, 220, 47, 24, 213, 141, 69, 193, 234, 116, 137, 251, 55, 35, 217, 121, 60, 114, 166], [170, 31, 134, 105, 13, 14, 157, 198, 242, 89, 53, 114, 159, 157, 213, 145, 162, 101, 173, 197, 125, 246, 12, 150, 47, 76, 145, 216, 139, 113, 120, 255], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89], [6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], [172, 241, 54, 235, 1, 252, 28, 78, 136, 61, 35, 200, 181, 132, 74, 181, 154, 55, 246, 106, 221, 87, 197, 233, 172, 59, 83, 224, 89, 211, 92, 100]], recent_blockhash: [214, 244, 173, 248, 144, 238, 253, 86, 43, 52, 158, 107, 196, 148, 27, 228, 22, 32, 221, 186, 23, 111, 117, 168, 5, 28, 164, 124, 8, 137, 247, 144], instructions: [CompiledInstruction { program_id_index: 7, accounts: [8], data: [2, 160, 134, 1, 0] }, CompiledInstruction { program_id_index: 7, accounts: [], data: [3, 0, 9, 61, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 9, accounts: [10, 1, 11, 2, 3, 4, 0, 12, 13, 14, 15, 9], data: [51, 230, 133, 164, 1, 127, 131, 173, 142, 200, 39, 111, 54, 4, 0, 0, 188, 64, 117, 7, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 12, accounts: [0, 5], data: [2, 0, 0, 0, 155, 221, 23, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 12, accounts: [0, 6], data: [2, 0, 0, 0, 128, 26, 6, 0, 0, 0, 0, 0] }], versioned: false, address_table_lookups: [] }) }), meta: Some(TransactionStatusMeta { err: None, fee: 405000, pre_balances: [1123324762, 5133659401479, 3217206694, 2039280, 2039280, 13984546529416, 2954590, 1, 0, 1141440, 290898417, 1461600, 1, 731913600, 934087680, 137104014], post_balances: [1277361698, 5133660981338, 3059220840, 2039280, 2039280, 13984548093475, 3354590, 1, 0, 1141440, 290898417, 1461600, 1, 731913600, 934087680, 137104014], inner_instructions: [InnerInstructions { index: 2, instructions: [InnerInstruction { program_id_index: 14, accounts: [4, 3, 0], data: [3, 142, 200, 39, 111, 54, 4, 0, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 9, accounts: [15], data: [228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238, 170, 31, 134, 105, 13, 14, 157, 198, 242, 89, 53, 114, 159, 157, 213, 145, 162, 101, 173, 197, 125, 246, 12, 150, 47, 76, 145, 216, 139, 113, 120, 255, 62, 172, 106, 9, 0, 0, 0, 0, 142, 200, 39, 111, 54, 4, 0, 0, 0, 39, 145, 191, 193, 93, 236, 41, 194, 20, 126, 83, 64, 86, 160, 18, 70, 177, 136, 116, 92, 227, 244, 102, 21, 251, 156, 105, 221, 75, 227, 106, 24, 169, 163, 24, 104, 0, 0, 0, 0, 104, 229, 82, 178, 7, 0, 0, 0, 200, 13, 176, 49, 167, 117, 3, 0, 104, 57, 47, 182, 0, 0, 0, 0, 200, 117, 157, 229, 21, 119, 2, 0], stack_height: Some(2) }] }], inner_instructions_none: false, log_messages: ["Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [1]", "Program log: Instruction: Sell", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 79582 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program data: vdt/007mYe6qH4ZpDQ6dxvJZNXKfndWRomWtxX32DJYvTJHYi3F4/z6sagkAAAAAjsgnbzYEAAAAJ5G/wV3sKcIUflNAVqASRrGIdFzj9GYV+5xp3UvjahipoxhoAAAAAGjlUrIHAAAAyA2wMad1AwBoOS+2AAAAAMh1neUVdwIA", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 1997 of 71081 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 31391 of 99700 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"], log_messages_none: false,
2 | pre_token_balances: [
3 | TokenBalance {
4 | account_index: 3,
5 | mint: "CT6BzZbuB6EWxLU3JNELKA5uRGy3Mjf1PzSnvuxvpump",
6 | ui_token_amount: Some(UiTokenAmount { ui_amount: 896154044.11833, decimals: 6, amount: "896154044118330", ui_amount_string: "896154044.11833" }), owner: "2rbRFsc3PYqEkYjqpkyC9wUsnehdywhQjC9YXU12bhmP", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
7 | },
8 | TokenBalance { account_index: 4, mint: "CT6BzZbuB6EWxLU3JNELKA5uRGy3Mjf1PzSnvuxvpump", ui_token_amount: Some(UiTokenAmount { ui_amount: 4631839.62331, decimals: 6, amount: "4631839623310", ui_amount_string: "4631839.62331" }), owner: "3fToUvzR4UhmXumybMRHEgpniGkhcMbaMo8SEkEFiswm", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], post_token_balances: [TokenBalance { account_index: 3, mint: "CT6BzZbuB6EWxLU3JNELKA5uRGy3Mjf1PzSnvuxvpump", ui_token_amount: Some(UiTokenAmount { ui_amount: 900785883.74164, decimals: 6, amount: "900785883741640", ui_amount_string: "900785883.74164" }), owner: "2rbRFsc3PYqEkYjqpkyC9wUsnehdywhQjC9YXU12bhmP", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 4, mint: "CT6BzZbuB6EWxLU3JNELKA5uRGy3Mjf1PzSnvuxvpump", ui_token_amount: Some(UiTokenAmount { ui_amount: 0.0, decimals: 6, amount: "0", ui_amount_string: "0" }), owner: "3fToUvzR4UhmXumybMRHEgpniGkhcMbaMo8SEkEFiswm", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], rewards: [], loaded_writable_addresses: [], loaded_readonly_addresses: [], return_data: None, return_data_none: true, compute_units_consumed: Some(31991) }), index: 36 }), slot: 337984962 }
9 |
--------------------------------------------------------------------------------
/log/pumpfun_buy_tx_1746445226684.log:
--------------------------------------------------------------------------------
1 | Transaction: SubscribeUpdateTransaction { transaction: Some(SubscribeUpdateTransactionInfo { signature: [194, 238, 212, 200, 131, 24, 8, 61, 146, 51, 78, 250, 98, 80, 224, 161, 156, 139, 162, 159, 102, 241, 195, 152, 113, 187, 35, 29, 9, 253, 115, 4, 93, 175, 1, 133, 83, 0, 178, 183, 59, 250, 21, 165, 231, 127, 101, 168, 79, 82, 35, 251, 99, 132, 181, 60, 247, 227, 133, 197, 118, 237, 71, 9], is_vote: false, transaction: Some(Transaction { signatures: [[194, 238, 212, 200, 131, 24, 8, 61, 146, 51, 78, 250, 98, 80, 224, 161, 156, 139, 162, 159, 102, 241, 195, 152, 113, 187, 35, 29, 9, 253, 115, 4, 93, 175, 1, 133, 83, 0, 178, 183, 59, 250, 21, 165, 231, 127, 101, 168, 79, 82, 35, 251, 99, 132, 181, 60, 247, 227, 133, 197, 118, 237, 71, 9]], message: Some(Message { header: Some(MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 11 }), account_keys: [[192, 69, 57, 70, 187, 133, 249, 39, 206, 254, 167, 159, 67, 81, 213, 172, 208, 207, 150, 38, 71, 226, 202, 186, 164, 24, 224, 252, 86, 156, 167, 205], [49, 127, 120, 203, 80, 114, 71, 38, 221, 40, 76, 67, 203, 203, 54, 178, 254, 136, 182, 194, 63, 46, 188, 250, 215, 106, 201, 159, 86, 245, 0, 80], [94, 101, 149, 63, 58, 105, 164, 215, 240, 34, 97, 10, 102, 159, 185, 136, 208, 121, 72, 4, 89, 96, 155, 245, 50, 133, 166, 124, 33, 95, 207, 251], [120, 82, 28, 177, 121, 206, 187, 133, 137, 181, 86, 162, 213, 236, 148, 210, 73, 134, 130, 253, 249, 187, 42, 245, 173, 100, 228, 145, 204, 65, 83, 218], [133, 140, 195, 137, 26, 80, 167, 203, 50, 66, 39, 205, 36, 45, 190, 143, 85, 116, 28, 207, 157, 180, 102, 131, 223, 226, 52, 183, 123, 32, 62, 80], [138, 85, 7, 116, 153, 201, 1, 165, 192, 17, 247, 161, 85, 86, 253, 215, 193, 65, 14, 149, 102, 212, 192, 136, 14, 119, 227, 209, 185, 167, 16, 127], [173, 17, 230, 164, 252, 41, 68, 164, 250, 130, 81, 190, 248, 21, 66, 110, 27, 251, 40, 198, 182, 100, 102, 119, 96, 124, 106, 217, 245, 102, 166, 70], [224, 111, 193, 136, 58, 37, 13, 157, 155, 142, 70, 238, 114, 248, 189, 114, 111, 104, 5, 231, 64, 207, 121, 84, 160, 99, 143, 3, 101, 112, 160, 121], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176], [3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0], [6, 155, 136, 87, 254, 171, 129, 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1], [6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0], [6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], [58, 134, 94, 105, 238, 15, 84, 128, 202, 188, 246, 99, 87, 228, 220, 47, 24, 213, 141, 69, 193, 234, 116, 137, 251, 55, 35, 217, 121, 60, 114, 166], [98, 146, 246, 22, 89, 15, 27, 63, 244, 22, 250, 12, 40, 145, 90, 167, 84, 27, 219, 66, 154, 196, 157, 102, 214, 3, 191, 185, 228, 142, 21, 49], [140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89], [172, 241, 54, 235, 1, 252, 28, 78, 136, 61, 35, 200, 181, 132, 74, 181, 154, 55, 246, 106, 221, 87, 197, 233, 172, 59, 83, 224, 89, 211, 92, 100], [194, 29, 204, 235, 167, 123, 6, 175, 116, 83, 36, 173, 25, 143, 121, 124, 239, 67, 186, 222, 40, 75, 33, 19, 198, 65, 15, 228, 91, 226, 0, 194]], recent_blockhash: [28, 163, 65, 88, 219, 104, 19, 158, 179, 193, 7, 221, 7, 201, 41, 112, 226, 55, 135, 177, 223, 215, 117, 200, 146, 32, 54, 95, 60, 68, 78, 106], instructions: [CompiledInstruction { program_id_index: 10, accounts: [], data: [2, 160, 134, 1, 0] }, CompiledInstruction { program_id_index: 10, accounts: [], data: [3, 80, 195, 0, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 18, accounts: [9, 0, 11, 15, 14, 6, 15, 5, 4, 2, 7, 17, 8, 13, 16, 12, 3, 1], data: [254, 170, 88, 67, 216, 196, 208, 47, 17, 81, 240, 253, 115, 54, 192, 239, 95, 1, 1, 0, 184, 100, 217, 69, 0, 0, 0, 35, 252, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 161, 7, 0, 0, 0, 0, 0, 192, 132, 207, 79, 0, 0, 0, 0, 196, 156, 41, 116, 36, 40, 0, 0, 1, 203, 40, 43, 26, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 8, accounts: [0, 3], data: [2, 0, 0, 0, 35, 252, 44, 0, 0, 0, 0, 0] }], versioned: true, address_table_lookups: [] }) }), meta: Some(TransactionStatusMeta { err: None, fee: 10000, pre_balances: [575955985409, 1559040, 443583060, 14269297, 2039280, 4536687, 5445267414362, 2039280, 1, 1141440, 1, 1035589724898, 1009200, 934087680, 290898417, 1461800, 731913600, 137104014, 1141440], post_balances: [575955066558, 1559040, 0, 17217428, 2039280, 441708746, 5445271786083, 2039280, 1, 1141440, 1, 1035589724898, 1009200, 934087680, 290898417, 1461800, 731913600, 137104014, 1141440], inner_instructions: [InnerInstructions { index: 2, instructions: [InnerInstruction { program_id_index: 13, accounts: [2, 0, 0, 0], data: [9], stack_height: Some(2) }, InnerInstruction { program_id_index: 9, accounts: [14, 6, 15, 5, 4, 7, 0, 8, 13, 12, 17, 9], data: [102, 6, 61, 18, 1, 218, 235, 234, 172, 242, 207, 217, 3, 14, 0, 0, 100, 108, 81, 26, 0, 0, 0, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 13, accounts: [4, 7, 5], data: [3, 172, 242, 207, 217, 3, 14, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 8, accounts: [0, 5], data: [2, 0, 0, 0, 91, 183, 14, 26, 0, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 8, accounts: [0, 6], data: [2, 0, 0, 0, 9, 181, 66, 0, 0, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 9, accounts: [17], data: [228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238, 98, 146, 246, 22, 89, 15, 27, 63, 244, 22, 250, 12, 40, 145, 90, 167, 84, 27, 219, 66, 154, 196, 157, 102, 214, 3, 191, 185, 228, 142, 21, 49, 91, 183, 14, 26, 0, 0, 0, 0, 172, 242, 207, 217, 3, 14, 0, 0, 1, 192, 69, 57, 70, 187, 133, 249, 39, 206, 254, 167, 159, 67, 81, 213, 172, 208, 207, 150, 38, 71, 226, 202, 186, 164, 24, 224, 252, 86, 156, 167, 205, 169, 163, 24, 104, 0, 0, 0, 0, 202, 212, 78, 22, 7, 0, 0, 0, 241, 139, 109, 232, 207, 193, 3, 0, 202, 40, 43, 26, 0, 0, 0, 0, 241, 243, 90, 156, 62, 195, 2, 0], stack_height: Some(3) }] }], inner_instructions_none: false, log_messages: ["Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program E4kT3ceuMGBUfV4DvUFePe1c8anPN6wPi2yd5nFxBVpm invoke [1]", "Program log: 0 swap", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: CloseAccount", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2997 of 91505 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", "Program log: Instruction: Buy", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 59685 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [3]", "Program 11111111111111111111111111111111 success", "Program 11111111111111111111111111111111 invoke [3]", "Program 11111111111111111111111111111111 success", "Program data: vdt/007mYe5ikvYWWQ8bP/QW+gwokVqnVBvbQprEnWbWA7+55I4VMVu3DhoAAAAArPLP2QMOAAABwEU5RruF+SfO/qefQ1HVrNDPliZH4sq6pBjg/Facp82poxhoAAAAAMrUThYHAAAA8Ytt6M/BAwDKKCsaAAAAAPHzWpw+wwIA", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [3]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 1997 of 47136 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 36999 of 81363 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program E4kT3ceuMGBUfV4DvUFePe1c8anPN6wPi2yd5nFxBVpm consumed 56020 of 99700 compute units", "Program E4kT3ceuMGBUfV4DvUFePe1c8anPN6wPi2yd5nFxBVpm success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"], log_messages_none: false,
2 |
3 | pre_token_balances: [TokenBalance { account_index: 2, mint: "So11111111111111111111111111111111111111112", ui_token_amount: Some(UiTokenAmount { ui_amount: 0.44154378, decimals: 9, amount: "441543780", ui_amount_string: "0.44154378" }), owner: "DwYVzJaAW683T474NP2nAe7S84cLrbhsUWqCcKko9Lr8", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 4, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 999933333.99107, decimals: 6, amount: "999933333991070", ui_amount_string: "999933333.99107" }), owner: "AJzTpn6D6eKiz2S6FeptUxnP1frABWQwGXya5yDeS2Rt", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 7, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 0.0, decimals: 6, amount: "0", ui_amount_string: "0" }), owner: "DwYVzJaAW683T474NP2nAe7S84cLrbhsUWqCcKko9Lr8", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }],
4 |
5 | post_token_balances: [TokenBalance { account_index: 4, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 984523632.01637, decimals: 6, amount: "984523632016370", ui_amount_string: "984523632.01637" }), owner: "AJzTpn6D6eKiz2S6FeptUxnP1frABWQwGXya5yDeS2Rt", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 7, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 15409701.9747, decimals: 6, amount: "15409701974700", ui_amount_string: "15409701.9747" }), owner: "DwYVzJaAW683T474NP2nAe7S84cLrbhsUWqCcKko9Lr8", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], rewards: [], loaded_writable_addresses: [], loaded_readonly_addresses: [], return_data: None, return_data_none: true, compute_units_consumed: Some(56470) }), index: 1107 }), slot: 337984962 }
--------------------------------------------------------------------------------
/log/pumpfun_buy_tx_1746445226689.log:
--------------------------------------------------------------------------------
1 | Transaction: SubscribeUpdateTransaction {
2 | transaction:
3 | Some(SubscribeUpdateTransactionInfo {
4 | signature:
5 | [135, 76, 177, 85, 202, 186, 214, 77, 8, 76, 216, 103, 120, 119, 123, 215, 123, 244, 150, 167, 8, 169, 211, 166, 75, 254, 15, 168, 236, 139, 219, 160, 89, 131, 49, 58, 110, 10, 220, 112, 55, 38, 236, 137, 249, 149, 106, 222, 96, 251, 195, 111, 201, 142, 65, 37, 82, 14, 236, 246, 87, 163, 245, 5
6 | ],
7 | is_vote: false,
8 | transaction:
9 | Some(Transaction
10 | {
11 | signatures:
12 | [[135, 76, 177, 85, 202, 186, 214, 77, 8, 76, 216, 103, 120, 119, 123, 215, 123, 244, 150, 167, 8, 169, 211, 166, 75, 254, 15, 168, 236, 139, 219, 160, 89, 131, 49, 58, 110, 10, 220, 112, 55, 38, 236, 137, 249, 149, 106, 222, 96, 251, 195, 111, 201, 142, 65, 37, 82, 14, 236, 246, 87, 163, 245, 5]],
13 | message:
14 | Some(
15 | Message { header: Some(MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 11 }), account_keys: [[113, 194, 9, 213, 111, 117, 31, 199, 106, 14, 153, 255, 113, 120, 185, 72, 137, 231, 136, 44, 151, 55, 78, 60, 108, 162, 119, 3, 113, 28, 12, 51], [132, 145, 160, 203, 65, 208, 144, 37, 63, 212, 138, 57, 87, 233, 173, 129, 88, 41, 237, 131, 18, 195, 153, 37, 28, 130, 158, 221, 77, 165, 221, 163], [99, 131, 115, 0, 14, 162, 44, 178, 100, 211, 74, 255, 100, 160, 75, 94, 250, 191, 187, 116, 221, 205, 4, 137, 151, 177, 152, 21, 71, 215, 209, 16], [129, 2, 99, 162, 94, 172, 199, 1, 208, 88, 136, 35, 185, 190, 155, 108, 109, 207, 217, 232, 71, 64, 167, 108, 150, 23, 234, 95, 144, 125, 19, 247], [113, 249, 38, 10, 33, 37, 8, 91, 213, 225, 214, 91, 253, 41, 215, 96, 117, 78, 160, 217, 244, 45, 211, 182, 139, 233, 173, 199, 195, 3, 120, 216], [94, 17, 59, 3, 115, 173, 69, 49, 209, 145, 126, 20, 153, 40, 44, 162, 190, 127, 83, 39, 52, 84, 226, 251, 203, 13, 89, 144, 253, 113, 234, 58], [120, 82, 28, 177, 121, 206, 187, 133, 137, 181, 86, 162, 213, 236, 148, 210, 73, 134, 130, 253, 249, 187, 42, 245, 173, 100, 228, 145, 204, 65, 83, 218], [3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0], [10, 241, 195, 67, 33, 136, 202, 58, 99, 81, 53, 161, 58, 24, 149, 27, 168, 54, 240, 158, 220, 246, 219, 95, 93, 80, 90, 50, 112, 80, 255, 153], [140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89], [30, 74, 58, 31, 194, 57, 195, 217, 54, 50, 102, 207, 20, 89, 62, 48, 133, 87, 182, 208, 96, 216, 100, 29, 64, 60, 202, 30, 58, 183, 233, 31], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], [147, 255, 101, 186, 115, 58, 105, 199, 69, 59, 167, 240, 106, 127, 213, 38, 55, 179, 121, 200, 2, 79, 247, 45, 37, 182, 36, 45, 78, 138, 128, 37], [58, 134, 94, 105, 238, 15, 84, 128, 202, 188, 246, 99, 87, 228, 220, 47, 24, 213, 141, 69, 193, 234, 116, 137, 251, 55, 35, 217, 121, 60, 114, 166], [6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0], [172, 241, 54, 235, 1, 252, 28, 78, 136, 61, 35, 200, 181, 132, 74, 181, 154, 55, 246, 106, 221, 87, 197, 233, 172, 59, 83, 224, 89, 211, 92, 100], [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176]
16 | ],
17 | recent_blockhash: [214, 244, 173, 248, 144, 238, 253, 86, 43, 52, 158, 107, 196, 148, 27, 228, 22, 32, 221, 186, 23, 111, 117, 168, 5, 28, 164, 124, 8, 137, 247, 144],
18 | instructions: [
19 | CompiledInstruction { program_id_index: 7, accounts: [8], data: [2, 160, 134, 1, 0] }, CompiledInstruction { program_id_index: 7, accounts: [], data: [3, 160, 134, 1, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 9, accounts: [0, 1, 0, 10, 11, 12], data: [1] }, CompiledInstruction { program_id_index: 13, accounts: [14, 2, 10, 3, 4, 1, 0, 11, 12, 15, 16, 17], data: [0, 32, 111, 33, 4, 0, 0, 0, 0, 143, 150, 41, 88, 138, 0, 0, 0] }, CompiledInstruction { program_id_index: 11, accounts: [0, 5], data: [2, 0, 0, 0, 96, 174, 10, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 11, accounts: [0, 6], data: [2, 0, 0, 0, 16, 39, 0, 0, 0, 0, 0, 0] }], versioned: false, address_table_lookups: [] }) }), meta: Some(TransactionStatusMeta { err: None, fee: 15000, pre_balances: [2638556108, 0, 5920687668686, 20599917815, 2039280, 13984548093475, 17235825, 1, 0, 731913600, 1461600, 1, 934087680, 1141440, 290898417, 1009200, 137104014, 1141440], post_balances: [2566491828, 2039280, 5920688354825, 20668531676, 2039280, 13984548793475, 17245825, 1, 0, 731913600, 1461600, 1, 934087680, 1141440, 290898417, 1009200, 137104014, 1141440], inner_instructions: [InnerInstructions { index: 2, instructions: [InnerInstruction { program_id_index: 12, accounts: [10], data: [21, 7, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 11, accounts: [0, 1], data: [0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], stack_height: Some(2) }, InnerInstruction { program_id_index: 12, accounts: [1], data: [22], stack_height: Some(2) }, InnerInstruction { program_id_index: 12, accounts: [1, 10], data: [18, 113, 194, 9, 213, 111, 117, 31, 199, 106, 14, 153, 255, 113, 120, 185, 72, 137, 231, 136, 44, 151, 55, 78, 60, 108, 162, 119, 3, 113, 28, 12, 51], stack_height: Some(2) }] }, InnerInstructions { index: 3, instructions: [InnerInstruction { program_id_index: 17, accounts: [14, 2, 10, 3, 4, 1, 0, 11, 12, 15, 16, 17], data: [102, 6, 61, 18, 1, 218, 235, 234, 177, 114, 146, 153, 200, 0, 0, 0, 32, 111, 33, 4, 0, 0, 0, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 12, accounts: [4, 1, 3], data: [3, 177, 114, 146, 153, 200, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 11, accounts: [0, 3], data: [2, 0, 0, 0, 229, 246, 22, 4, 0, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 11, accounts: [0, 2], data: [2, 0, 0, 0, 59, 120, 10, 0, 0, 0, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 17, accounts: [16], data: [228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238, 30, 74, 58, 31, 194, 57, 195, 217, 54, 50, 102, 207, 20, 89, 62, 48, 133, 87, 182, 208, 96, 216, 100, 29, 64, 60, 202, 30, 58, 183, 233, 31, 229, 246, 22, 4, 0, 0, 0, 0, 177, 114, 146, 153, 200, 0, 0, 0, 1, 113, 194, 9, 213, 111, 117, 31, 199, 106, 14, 153, 255, 113, 120, 185, 72, 137, 231, 136, 44, 151, 55, 78, 60, 108, 162, 119, 3, 113, 28, 12, 51, 169, 163, 24, 104, 0, 0, 0, 0, 220, 167, 235, 203, 11, 0, 0, 0, 218, 118, 240, 103, 214, 65, 2, 0, 220, 251, 199, 207, 4, 0, 0, 0, 218, 222, 221, 27, 69, 67, 1, 0], stack_height: Some(3) }] }], inner_instructions_none: false,
20 |
21 | log_messages: [
22 | "Program ComputeBudget111111111111111111111111111111 invoke [1]",
23 | "Program ComputeBudget111111111111111111111111111111 success",
24 | "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success",
25 | "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]",
26 | "Program log: CreateIdempotent",
27 | "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
28 | "Program log: Instruction: GetAccountDataSize", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 86795 compute units", "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [2]", "Program 11111111111111111111111111111111 success", "Program log: Initialize the associated token account", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: InitializeImmutableOwner", "Program log: Please upgrade to SPL Token 2022 for immutable owner support", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 80208 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: InitializeAccount3", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4188 of 76326 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 27845 of 99700 compute units", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", "Program AxiomQpD1TrYEHNYLts8h3ko1NHdtxfgNgHryj2hJJx4 invoke [1]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]",
29 | "Program log: Instruction: Buy", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 50981 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [3]", "Program 11111111111111111111111111111111 success", "Program 11111111111111111111111111111111 invoke [3]", "Program 11111111111111111111111111111111 success", "Program data: vdt/007mYe4eSjofwjnD2TYyZs8UWT4whVe20GDYZB1APMoeOrfpH+X2FgQAAAAAsXKSmcgAAAABccIJ1W91H8dqDpn/cXi5SInniCyXN048bKJ3A3EcDDOpoxhoAAAAANyn68sLAAAA2nbwZ9ZBAgDc+8fPBAAAANre3RtFQwEA", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [3]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 1997 of 38432 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 32471 of 68131 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program AxiomQpD1TrYEHNYLts8h3ko1NHdtxfgNgHryj2hJJx4 consumed 36271 of 71855 compute units", "Program AxiomQpD1TrYEHNYLts8h3ko1NHdtxfgNgHryj2hJJx4 success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"], log_messages_none: false,
30 |
31 | pre_token_balances: [TokenBalance { account_index: 4, mint: "33EsucfvG76ShJakpoWVr5iYLQsKhcA6eWF1wmFApump", ui_token_amount: Some(UiTokenAmount { ui_amount: 563200646.011275, decimals: 6, amount: "563200646011275", ui_amount_string: "563200646.011275" }), owner: "9gbhx5e6dQpY7xAofiVcHatKRp5PjGYWCqqxfbTqF5in", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }],
32 |
33 | post_token_balances: [TokenBalance { account_index: 1, mint: "33EsucfvG76ShJakpoWVr5iYLQsKhcA6eWF1wmFApump", ui_token_amount: Some(UiTokenAmount { ui_amount: 861569.970865, decimals: 6, amount: "861569970865", ui_amount_string: "861569.970865" }), owner: "8f4gM9bVY7suZhbkwWcAMbDwNb4vxfxPADoX8SaSsnhG", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
34 | TokenBalance { account_index: 4, mint: "33EsucfvG76ShJakpoWVr5iYLQsKhcA6eWF1wmFApump", ui_token_amount: Some(UiTokenAmount { ui_amount: 562339076.04041, decimals: 6, amount: "562339076040410", ui_amount_string: "562339076.04041" }), owner: "9gbhx5e6dQpY7xAofiVcHatKRp5PjGYWCqqxfbTqF5in", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], rewards: [], loaded_writable_addresses: [], loaded_readonly_addresses: [], return_data: None, return_data_none: true, compute_units_consumed: Some(64716) }), index: 1374 }), slot: 337984962 }
35 |
--------------------------------------------------------------------------------
/log/pumpfun_sell_tx_1746445226685.log:
--------------------------------------------------------------------------------
1 | Transaction: SubscribeUpdateTransaction { transaction: Some(SubscribeUpdateTransactionInfo { signature: [68, 214, 64, 108, 241, 152, 143, 207, 77, 217, 250, 41, 44, 214, 197, 201, 134, 175, 123, 40, 7, 243, 224, 134, 45, 181, 35, 21, 184, 4, 85, 132, 203, 231, 35, 79, 52, 211, 75, 235, 88, 58, 104, 11, 85, 214, 194, 34, 57, 82, 144, 24, 180, 148, 33, 78, 41, 107, 120, 126, 208, 22, 203, 2], is_vote: false, transaction: Some(Transaction { signatures: [[68, 214, 64, 108, 241, 152, 143, 207, 77, 217, 250, 41, 44, 214, 197, 201, 134, 175, 123, 40, 7, 243, 224, 134, 45, 181, 35, 21, 184, 4, 85, 132, 203, 231, 35, 79, 52, 211, 75, 235, 88, 58, 104, 11, 85, 214, 194, 34, 57, 82, 144, 24, 180, 148, 33, 78, 41, 107, 120, 126, 208, 22, 203, 2]], message: Some(Message { header: Some(MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 11 }), account_keys: [[192, 69, 57, 70, 187, 133, 249, 39, 206, 254, 167, 159, 67, 81, 213, 172, 208, 207, 150, 38, 71, 226, 202, 186, 164, 24, 224, 252, 86, 156, 167, 205], [49, 127, 120, 203, 80, 114, 71, 38, 221, 40, 76, 67, 203, 203, 54, 178, 254, 136, 182, 194, 63, 46, 188, 250, 215, 106, 201, 159, 86, 245, 0, 80], [94, 101, 149, 63, 58, 105, 164, 215, 240, 34, 97, 10, 102, 159, 185, 136, 208, 121, 72, 4, 89, 96, 155, 245, 50, 133, 166, 124, 33, 95, 207, 251], [120, 82, 28, 177, 121, 206, 187, 133, 137, 181, 86, 162, 213, 236, 148, 210, 73, 134, 130, 253, 249, 187, 42, 245, 173, 100, 228, 145, 204, 65, 83, 218], [133, 140, 195, 137, 26, 80, 167, 203, 50, 66, 39, 205, 36, 45, 190, 143, 85, 116, 28, 207, 157, 180, 102, 131, 223, 226, 52, 183, 123, 32, 62, 80], [138, 85, 7, 116, 153, 201, 1, 165, 192, 17, 247, 161, 85, 86, 253, 215, 193, 65, 14, 149, 102, 212, 192, 136, 14, 119, 227, 209, 185, 167, 16, 127], [173, 17, 230, 164, 252, 41, 68, 164, 250, 130, 81, 190, 248, 21, 66, 110, 27, 251, 40, 198, 182, 100, 102, 119, 96, 124, 106, 217, 245, 102, 166, 70], [224, 111, 193, 136, 58, 37, 13, 157, 155, 142, 70, 238, 114, 248, 189, 114, 111, 104, 5, 231, 64, 207, 121, 84, 160, 99, 143, 3, 101, 112, 160, 121], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176], [3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0], [6, 155, 136, 87, 254, 171, 129, 132, 251, 104, 127, 99, 70, 24, 192, 53, 218, 196, 57, 220, 26, 235, 59, 85, 152, 160, 240, 0, 0, 0, 0, 1], [6, 167, 213, 23, 25, 44, 92, 81, 33, 140, 201, 76, 61, 74, 241, 127, 88, 218, 238, 8, 155, 161, 253, 68, 227, 219, 217, 138, 0, 0, 0, 0], [6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], [58, 134, 94, 105, 238, 15, 84, 128, 202, 188, 246, 99, 87, 228, 220, 47, 24, 213, 141, 69, 193, 234, 116, 137, 251, 55, 35, 217, 121, 60, 114, 166], [98, 146, 246, 22, 89, 15, 27, 63, 244, 22, 250, 12, 40, 145, 90, 167, 84, 27, 219, 66, 154, 196, 157, 102, 214, 3, 191, 185, 228, 142, 21, 49], [140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89], [172, 241, 54, 235, 1, 252, 28, 78, 136, 61, 35, 200, 181, 132, 74, 181, 154, 55, 246, 106, 221, 87, 197, 233, 172, 59, 83, 224, 89, 211, 92, 100], [194, 29, 204, 235, 167, 123, 6, 175, 116, 83, 36, 173, 25, 143, 121, 124, 239, 67, 186, 222, 40, 75, 33, 19, 198, 65, 15, 228, 91, 226, 0, 194]], recent_blockhash: [28, 163, 65, 88, 219, 104, 19, 158, 179, 193, 7, 221, 7, 201, 41, 112, 226, 55, 135, 177, 223, 215, 117, 200, 146, 32, 54, 95, 60, 68, 78, 106], instructions: [CompiledInstruction { program_id_index: 10, accounts: [], data: [2, 240, 73, 2, 0] }, CompiledInstruction { program_id_index: 18, accounts: [9, 0, 15, 11, 14, 6, 15, 5, 4, 7, 2, 17, 8, 13, 16, 12, 3, 1], data: [254, 170, 88, 67, 216, 196, 208, 47, 17, 81, 240, 253, 115, 54, 192, 239, 95, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 252, 44, 0, 0, 0, 0, 0, 232, 3, 0, 0, 0, 0, 0, 0, 32, 161, 7, 0, 0, 0, 0, 0, 192, 132, 207, 79, 0, 0, 0, 0, 196, 156, 41, 116, 36, 40, 0, 0, 1, 203, 40, 43, 26, 0, 0, 0, 0] }], versioned: true, address_table_lookups: [] }) }), meta: Some(TransactionStatusMeta { err: None, fee: 5000, pre_balances: [575955066558, 1559040, 0, 17217428, 2039280, 1767450311, 5445271786083, 2039280, 1, 1141440, 1, 1035589724898, 1009200, 934087680, 290898417, 1461800, 731913600, 137104014, 1141440], post_balances: [576429686742, 0, 0, 17217625, 2039280, 1291665400, 5445276543933, 0, 1, 1141440, 1, 1035589724898, 1009200, 934087680, 290898417, 1461800, 731913600, 137104014, 1141440], inner_instructions: [InnerInstructions { index: 1, instructions: [InnerInstruction { program_id_index: 9, accounts: [14, 6, 15, 5, 4, 7, 0, 8, 16, 13, 17, 9], data: [51, 230, 133, 164, 1, 127, 131, 173, 172, 242, 207, 217, 3, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 13, accounts: [7, 4, 0], data: [3, 172, 242, 207, 217, 3, 14, 0, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 9, accounts: [17], data: [228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238, 98, 146, 246, 22, 89, 15, 27, 63, 244, 22, 250, 12, 40, 145, 90, 167, 84, 27, 219, 66, 154, 196, 157, 102, 214, 3, 191, 185, 228, 142, 21, 49, 207, 230, 91, 28, 0, 0, 0, 0, 172, 242, 207, 217, 3, 14, 0, 0, 0, 192, 69, 57, 70, 187, 133, 249, 39, 206, 254, 167, 159, 67, 81, 213, 172, 208, 207, 150, 38, 71, 226, 202, 186, 164, 24, 224, 252, 86, 156, 167, 205, 169, 163, 24, 104, 0, 0, 0, 0, 248, 35, 248, 72, 7, 0, 0, 0, 217, 225, 19, 78, 175, 167, 3, 0, 248, 119, 212, 76, 0, 0, 0, 0, 217, 73, 1, 2, 30, 169, 2, 0], stack_height: Some(3) }, InnerInstruction { program_id_index: 13, accounts: [7, 0, 0, 0], data: [9], stack_height: Some(2) }, InnerInstruction { program_id_index: 8, accounts: [0, 3], data: [2, 0, 0, 0, 197, 0, 0, 0, 0, 0, 0, 0], stack_height: Some(2) }] }], inner_instructions_none: false, log_messages: ["Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program E4kT3ceuMGBUfV4DvUFePe1c8anPN6wPi2yd5nFxBVpm invoke [1]", "Program log: 1 swap", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", "Program log: Instruction: Sell", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 116616 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program data: vdt/007mYe5ikvYWWQ8bP/QW+gwokVqnVBvbQprEnWbWA7+55I4VMc/mWxwAAAAArPLP2QMOAAAAwEU5RruF+SfO/qefQ1HVrNDPliZH4sq6pBjg/Facp82poxhoAAAAAPgj+EgHAAAA2eETTq+nAwD4d9RMAAAAANlJAQIeqQIA", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [3]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 1997 of 108115 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 32889 of 138232 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: CloseAccount", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2998 of 103035 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [2]", "Program 11111111111111111111111111111111 success", "Program E4kT3ceuMGBUfV4DvUFePe1c8anPN6wPi2yd5nFxBVpm consumed 52678 of 149850 compute units", "Program E4kT3ceuMGBUfV4DvUFePe1c8anPN6wPi2yd5nFxBVpm success"], log_messages_none: false, pre_token_balances: [TokenBalance { account_index: 4, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 940386599.19851, decimals: 6, amount: "940386599198510", ui_amount_string: "940386599.19851" }), owner: "AJzTpn6D6eKiz2S6FeptUxnP1frABWQwGXya5yDeS2Rt", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 7, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 15409701.9747, decimals: 6, amount: "15409701974700", ui_amount_string: "15409701.9747" }), owner: "DwYVzJaAW683T474NP2nAe7S84cLrbhsUWqCcKko9Lr8", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], post_token_balances: [TokenBalance { account_index: 4, mint: "7dnvoiZYAsL26NZjAgrSm28mZJggU9D2pHfF7rKQMSxg", ui_token_amount: Some(UiTokenAmount { ui_amount: 955796301.17321, decimals: 6, amount: "955796301173210", ui_amount_string: "955796301.17321" }), owner: "AJzTpn6D6eKiz2S6FeptUxnP1frABWQwGXya5yDeS2Rt", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], rewards: [], loaded_writable_addresses: [], loaded_readonly_addresses: [], return_data: None, return_data_none: true, compute_units_consumed: Some(52828) }), index: 1109 }), slot: 337984962 }
--------------------------------------------------------------------------------
/log/pumpfun_sell_tx_1746445227405.log:
--------------------------------------------------------------------------------
1 | Transaction: SubscribeUpdateTransaction { transaction: Some(SubscribeUpdateTransactionInfo { signature: [113, 148, 126, 10, 239, 192, 138, 24, 120, 155, 31, 245, 128, 73, 156, 215, 45, 136, 245, 1, 252, 227, 205, 168, 62, 96, 40, 214, 172, 172, 166, 18, 133, 98, 19, 62, 86, 39, 166, 114, 163, 131, 90, 51, 116, 150, 19, 112, 215, 8, 213, 50, 136, 214, 170, 32, 11, 10, 22, 18, 48, 51, 210, 3], is_vote: false, transaction: Some(Transaction { signatures: [[113, 148, 126, 10, 239, 192, 138, 24, 120, 155, 31, 245, 128, 73, 156, 215, 45, 136, 245, 1, 252, 227, 205, 168, 62, 96, 40, 214, 172, 172, 166, 18, 133, 98, 19, 62, 86, 39, 166, 114, 163, 131, 90, 51, 116, 150, 19, 112, 215, 8, 213, 50, 136, 214, 170, 32, 11, 10, 22, 18, 48, 51, 210, 3]], message: Some(Message { header: Some(MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 8 }), account_keys: [[23, 3, 124, 12, 110, 122, 3, 134, 191, 217, 130, 13, 68, 4, 52, 19, 75, 70, 149, 136, 165, 72, 95, 225, 243, 40, 95, 3, 151, 166, 181, 246], [80, 224, 222, 96, 206, 138, 25, 81, 79, 120, 114, 189, 205, 40, 226, 218, 96, 25, 147, 152, 9, 251, 217, 2, 110, 240, 63, 93, 230, 110, 38, 148], [130, 96, 69, 39, 145, 143, 218, 159, 43, 120, 142, 124, 213, 255, 115, 179, 148, 83, 111, 156, 248, 52, 22, 223, 11, 241, 34, 64, 114, 48, 67, 125], [173, 17, 230, 164, 252, 41, 68, 164, 250, 130, 81, 190, 248, 21, 66, 110, 27, 251, 40, 198, 182, 100, 102, 119, 96, 124, 106, 217, 245, 102, 166, 70], [206, 235, 249, 248, 68, 101, 153, 139, 124, 246, 119, 182, 210, 234, 206, 4, 20, 107, 224, 183, 50, 63, 138, 27, 59, 118, 143, 218, 245, 7, 10, 64], [252, 2, 136, 240, 169, 177, 9, 109, 17, 183, 41, 142, 215, 33, 219, 164, 225, 21, 203, 172, 28, 106, 143, 24, 45, 46, 48, 241, 83, 250, 243, 223], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176], [3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0], [6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169], [58, 134, 94, 105, 238, 15, 84, 128, 202, 188, 246, 99, 87, 228, 220, 47, 24, 213, 141, 69, 193, 234, 116, 137, 251, 55, 35, 217, 121, 60, 114, 166], [67, 216, 165, 87, 111, 32, 195, 209, 55, 57, 237, 145, 106, 8, 244, 62, 111, 206, 237, 191, 153, 29, 2, 17, 209, 83, 214, 180, 29, 231, 104, 239], [140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89], [172, 241, 54, 235, 1, 252, 28, 78, 136, 61, 35, 200, 181, 132, 74, 181, 154, 55, 246, 106, 221, 87, 197, 233, 172, 59, 83, 224, 89, 211, 92, 100]], recent_blockhash: [135, 158, 89, 48, 102, 189, 89, 214, 157, 126, 78, 155, 104, 94, 23, 98, 139, 243, 138, 153, 131, 221, 79, 252, 193, 222, 120, 182, 176, 176, 73, 25], instructions: [CompiledInstruction { program_id_index: 8, accounts: [], data: [2, 87, 21, 1, 0] }, CompiledInstruction { program_id_index: 8, accounts: [], data: [3, 218, 117, 15, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 7, accounts: [10, 3, 11, 5, 2, 4, 0, 6, 12, 9, 13, 7], data: [51, 230, 133, 164, 1, 127, 131, 173, 48, 208, 16, 121, 39, 35, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0] }, CompiledInstruction { program_id_index: 6, accounts: [0, 1], data: [2, 0, 0, 0, 64, 13, 3, 0, 0, 0, 0, 0] }], versioned: false, address_table_lookups: [] }) }), meta: Some(TransactionStatusMeta { err: None, fee: 76937, pre_balances: [1684209529, 2229882417, 2039280, 5445290836849, 2039280, 3259969187, 1, 1141440, 1, 934087680, 290898417, 1461600, 731913600, 137104014], post_balances: [2948259142, 2230082417, 2039280, 5445303607825, 2039280, 1982871661, 1, 1141440, 1, 934087680, 290898417, 1461600, 731913600, 137104014], inner_instructions: [InnerInstructions { index: 2, instructions: [InnerInstruction { program_id_index: 9, accounts: [4, 2, 0], data: [3, 48, 208, 16, 121, 39, 35, 0, 0], stack_height: Some(2) }, InnerInstruction { program_id_index: 7, accounts: [13], data: [228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238, 67, 216, 165, 87, 111, 32, 195, 209, 55, 57, 237, 145, 106, 8, 244, 62, 111, 206, 237, 191, 153, 29, 2, 17, 209, 83, 214, 180, 29, 231, 104, 239, 54, 246, 30, 76, 0, 0, 0, 0, 48, 208, 16, 121, 39, 35, 0, 0, 0, 23, 3, 124, 12, 110, 122, 3, 134, 191, 217, 130, 13, 68, 4, 52, 19, 75, 70, 149, 136, 165, 72, 95, 225, 243, 40, 95, 3, 151, 166, 181, 246, 170, 163, 24, 104, 0, 0, 0, 0, 109, 28, 43, 114, 7, 0, 0, 0, 199, 197, 131, 23, 118, 147, 3, 0, 109, 112, 7, 118, 0, 0, 0, 0, 199, 45, 113, 203, 228, 148, 2, 0], stack_height: Some(2) }] }], inner_instructions_none: false, log_messages: ["Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [1]", "Program log: Instruction: Sell", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: Transfer", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 52082 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program data: vdt/007mYe5D2KVXbyDD0Tc57ZFqCPQ+b87tv5kdAhHRU9a0Hedo7zb2HkwAAAAAMNAQeScjAAAAFwN8DG56A4a/2YINRAQ0E0tGlYilSF/h8yhfA5emtfaqoxhoAAAAAG0cK3IHAAAAx8WDF3aTAwBtcAd2AAAAAMctccvklAIA", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 1997 of 43581 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 29890 of 70699 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"], log_messages_none: false, pre_token_balances: [TokenBalance { account_index: 2, mint: "5ZqvGfw1Ut4rkg9iJT4pbRNXbfwtJoHoHKSZNy37pump", ui_token_amount: Some(UiTokenAmount { ui_amount: 894907898.226071, decimals: 6, amount: "894907898226071", ui_amount_string: "894907898.226071" }), owner: "HxjywZ4kUaMW7dn6cjUK7ftyHBZR3yz5aUpvJrMzmBN6", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 4, mint: "5ZqvGfw1Ut4rkg9iJT4pbRNXbfwtJoHoHKSZNy37pump", ui_token_amount: Some(UiTokenAmount { ui_amount: 38652441.841712, decimals: 6, amount: "38652441841712", ui_amount_string: "38652441.841712" }), owner: "2YqTcTMDFD2vvVHcc8Wd61zwLiWaDZvAKrzXFW1adZ3K", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], post_token_balances: [TokenBalance { account_index: 2, mint: "5ZqvGfw1Ut4rkg9iJT4pbRNXbfwtJoHoHKSZNy37pump", ui_token_amount: Some(UiTokenAmount { ui_amount: 933560340.067783, decimals: 6, amount: "933560340067783", ui_amount_string: "933560340.067783" }), owner: "HxjywZ4kUaMW7dn6cjUK7ftyHBZR3yz5aUpvJrMzmBN6", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, TokenBalance { account_index: 4, mint: "5ZqvGfw1Ut4rkg9iJT4pbRNXbfwtJoHoHKSZNy37pump", ui_token_amount: Some(UiTokenAmount { ui_amount: 0.0, decimals: 6, amount: "0", ui_amount_string: "0" }), owner: "2YqTcTMDFD2vvVHcc8Wd61zwLiWaDZvAKrzXFW1adZ3K", program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }], rewards: [], loaded_writable_addresses: [], loaded_readonly_addresses: [], return_data: None, return_data_none: true, compute_units_consumed: Some(30340) }), index: 548 }), slot: 337984964 }
--------------------------------------------------------------------------------
/prototype/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pumpfun-analyzer"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | serde = { version = "1.0.192", features = ["derive"] }
8 | serde_json = "1.0.108"
9 | tokio = { version = "1.34.0", features = ["full"] }
10 | chrono = "0.4.31"
11 | clap = { version = "4.4.10", features = ["derive"] }
12 | colored = "2.0.4"
13 | base64 = "0.21.5"
14 | bs58 = "0.5.0"
15 | anyhow = "1.0.75"
16 | regex = "1.10.2"
17 | futures = "0.3.29"
18 | lazy_static = "1.4.0"
19 |
20 | [lib]
21 | name = "pumpfun_analyzer"
22 | path = "src/lib.rs"
23 |
24 | [[bin]]
25 | name = "pumpfun-cli"
26 | path = "src/main.rs"
--------------------------------------------------------------------------------
/prototype/src/lib.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use std::collections::HashMap;
3 | use anyhow::{Result, anyhow};
4 | use std::fs::File;
5 | use std::io::{BufRead, BufReader};
6 | use std::path::Path;
7 | use regex::Regex;
8 | use lazy_static::lazy_static;
9 |
10 | #[derive(Debug, Serialize, Deserialize, Clone)]
11 | pub struct TokenBalance {
12 | pub account_index: usize,
13 | pub mint: String,
14 | pub ui_token_amount: Option,
15 | pub owner: String,
16 | pub program_id: String,
17 | }
18 |
19 |
20 | #[derive(Debug, Serialize, Deserialize, Clone)]
21 | pub struct PumpTransaction {
22 | pub transaction_type: TransactionType,
23 | pub signature: String,
24 | pub slot: u64,
25 | pub timestamp: Option,
26 | pub success: bool,
27 | pub fee: u64,
28 | pub log_messages: Vec,
29 | pub token_balances: TokenBalances,
30 | pub key_accounts: HashMap,
31 | }
32 |
33 | #[derive(Debug, Serialize, Deserialize, Clone)]
34 | pub struct TokenBalances {
35 | pub pre_balances: Vec,
36 | pub post_balances: Vec,
37 | }
38 |
39 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
40 | pub enum TransactionType {
41 | Buy,
42 | Sell,
43 | Mint,
44 | Unknown,
45 | }
46 |
47 | /// Parses a transaction log file and returns a PumpTransaction
48 | pub fn parse_transaction_log(file_path: &Path) -> Result {
49 | let file = File::open(file_path)?;
50 | let reader = BufReader::new(file);
51 |
52 | lazy_static! {
53 | static ref SIGNATURE_RE: Regex = Regex::new(r"Transaction Signature: \[(.*?)\]").unwrap();
54 | static ref SLOT_RE: Regex = Regex::new(r"slot: (\d+)").unwrap();
55 | static ref SUCCESS_RE: Regex = Regex::new(r"Transaction Success: (\w+)").unwrap();
56 | static ref FEE_RE: Regex = Regex::new(r"fee: (\d+)").unwrap();
57 | static ref LOG_START_RE: Regex = Regex::new(r"==== BEGIN TRANSACTION LOG ====").unwrap();
58 | static ref LOG_END_RE: Regex = Regex::new(r"==== END TRANSACTION LOG ====").unwrap();
59 | static ref LOG_LINE_RE: Regex = Regex::new(r"LOG\[\d+\]: (.*)").unwrap();
60 | static ref INSTRUCTION_RE: Regex = Regex::new(r"Program log: Instruction: (\w+)").unwrap();
61 | }
62 |
63 | let mut tx = PumpTransaction {
64 | transaction_type: TransactionType::Unknown,
65 | signature: String::new(),
66 | slot: 0,
67 | timestamp: None,
68 | success: false,
69 | fee: 0,
70 | log_messages: Vec::new(),
71 | token_balances: TokenBalances {
72 | pre_balances: Vec::new(),
73 | post_balances: Vec::new(),
74 | },
75 | key_accounts: HashMap::new(),
76 | };
77 |
78 | let mut in_log_section = false;
79 | let mut content = String::new();
80 |
81 | for line in reader.lines() {
82 | let line = line?;
83 | content.push_str(&line);
84 | content.push('\n');
85 |
86 | // Extract signature
87 | if let Some(captures) = SIGNATURE_RE.captures(&line) {
88 | if let Some(signature_match) = captures.get(1) {
89 | tx.signature = signature_match.as_str().to_string();
90 | }
91 | }
92 |
93 | // Extract slot
94 | if let Some(captures) = SLOT_RE.captures(&line) {
95 | if let Some(slot_match) = captures.get(1) {
96 | tx.slot = slot_match.as_str().parse()?;
97 | }
98 | }
99 |
100 | // Extract success
101 | if let Some(captures) = SUCCESS_RE.captures(&line) {
102 | if let Some(success_match) = captures.get(1) {
103 | tx.success = success_match.as_str() == "true";
104 | }
105 | }
106 |
107 | // Extract fee
108 | if let Some(captures) = FEE_RE.captures(&line) {
109 | if let Some(fee_match) = captures.get(1) {
110 | tx.fee = fee_match.as_str().parse()?;
111 | }
112 | }
113 |
114 | // Handle log section
115 | if LOG_START_RE.is_match(&line) {
116 | in_log_section = true;
117 | continue;
118 | }
119 |
120 | if LOG_END_RE.is_match(&line) {
121 | in_log_section = false;
122 | continue;
123 | }
124 |
125 | if in_log_section {
126 | if let Some(captures) = LOG_LINE_RE.captures(&line) {
127 | if let Some(log_match) = captures.get(1) {
128 | let log_message = log_match.as_str().to_string();
129 | tx.log_messages.push(log_message.clone());
130 |
131 | // Determine transaction type from instruction
132 | if let Some(captures) = INSTRUCTION_RE.captures(&log_message) {
133 | if let Some(instruction_match) = captures.get(1) {
134 | let instruction = instruction_match.as_str();
135 | match instruction {
136 | "Buy" => tx.transaction_type = TransactionType::Buy,
137 | "Sell" => tx.transaction_type = TransactionType::Sell,
138 | "Create" | "MintTo" => {
139 | if tx.transaction_type != TransactionType::Buy && tx.transaction_type != TransactionType::Sell {
140 | tx.transaction_type = TransactionType::Mint;
141 | }
142 | },
143 | _ => {}
144 | }
145 | }
146 | }
147 | }
148 | }
149 | }
150 | }
151 |
152 | // Process token balances from the content
153 | extract_token_balances(&content, &mut tx)?;
154 |
155 | // Extract key accounts
156 | extract_key_accounts(&content, &mut tx)?;
157 |
158 | Ok(tx)
159 | }
160 |
161 | fn extract_token_balances(content: &str, tx: &mut PumpTransaction) -> Result<()> {
162 | lazy_static! {
163 | static ref PRE_BALANCE_RE: Regex = Regex::new(r"pre_token_balances: \[(.*?)\]").unwrap();
164 | static ref POST_BALANCE_RE: Regex = Regex::new(r"post_token_balances: \[(.*?)\]").unwrap();
165 | static ref TOKEN_BALANCE_LINE_RE: Regex = Regex::new(r"TokenBalance \{ account_index: (\d+), mint: \"([^\"]+)\", ui_token_amount: Some\(UiTokenAmount \{ ui_amount: ([^,]+), decimals: (\d+), amount: \"([^\"]+)\", ui_amount_string: \"([^\"]+)\" \}\), owner: \"([^\"]+)\", program_id: \"([^\"]+)\" \}").unwrap();
166 | }
167 |
168 | // Extract pre_token_balances
169 | if let Some(captures) = PRE_BALANCE_RE.captures(content) {
170 | if let Some(balances_match) = captures.get(1) {
171 | let balances_str = balances_match.as_str();
172 | for captures in TOKEN_BALANCE_LINE_RE.captures_iter(balances_str) {
173 | let account_index = captures[1].parse::()?;
174 | let mint = captures[2].to_string();
175 | let ui_amount = captures[3].parse::()?;
176 | let decimals = captures[4].parse::()?;
177 | let amount = captures[5].to_string();
178 | let ui_amount_string = captures[6].to_string();
179 | let owner = captures[7].to_string();
180 | let program_id = captures[8].to_string();
181 |
182 | tx.token_balances.pre_balances.push(TokenBalance {
183 | account_index,
184 | mint,
185 | ui_token_amount: Some(UiTokenAmount {
186 | ui_amount,
187 | decimals,
188 | amount,
189 | ui_amount_string,
190 | }),
191 | owner,
192 | program_id,
193 | });
194 | }
195 | }
196 | }
197 |
198 | // Extract post_token_balances
199 | if let Some(captures) = POST_BALANCE_RE.captures(content) {
200 | if let Some(balances_match) = captures.get(1) {
201 | let balances_str = balances_match.as_str();
202 | for captures in TOKEN_BALANCE_LINE_RE.captures_iter(balances_str) {
203 | let account_index = captures[1].parse::()?;
204 | let mint = captures[2].to_string();
205 | let ui_amount = captures[3].parse::()?;
206 | let decimals = captures[4].parse::()?;
207 | let amount = captures[5].to_string();
208 | let ui_amount_string = captures[6].to_string();
209 | let owner = captures[7].to_string();
210 | let program_id = captures[8].to_string();
211 |
212 | tx.token_balances.post_balances.push(TokenBalance {
213 | account_index,
214 | mint,
215 | ui_token_amount: Some(UiTokenAmount {
216 | ui_amount,
217 | decimals,
218 | amount,
219 | ui_amount_string,
220 | }),
221 | owner,
222 | program_id,
223 | });
224 | }
225 | }
226 | }
227 |
228 | Ok(())
229 | }
230 |
231 | fn extract_key_accounts(content: &str, tx: &mut PumpTransaction) -> Result<()> {
232 | lazy_static! {
233 | static ref ACCOUNT_KEYS_RE: Regex = Regex::new(r"Key\[(\d+)\]: \[(.*?)\]").unwrap();
234 | }
235 |
236 | for captures in ACCOUNT_KEYS_RE.captures_iter(content) {
237 | let index = captures[1].parse::()?;
238 | let key_data = captures[2].to_string();
239 | tx.key_accounts.insert(format!("key_{}", index), key_data);
240 | }
241 |
242 | Ok(())
243 | }
244 |
245 | /// Calculates the token amount difference between pre and post balances
246 | pub fn calculate_token_amount_change(tx: &PumpTransaction) -> HashMap {
247 | let mut changes = HashMap::new();
248 |
249 | // Create a map of mint -> pre_balance
250 | let mut pre_balances = HashMap::new();
251 | for balance in &tx.token_balances.pre_balances {
252 | if let Some(token_amount) = &balance.ui_token_amount {
253 | pre_balances.insert(balance.mint.clone(), (token_amount.ui_amount, balance.owner.clone()));
254 | }
255 | }
256 |
257 | // Calculate differences with post_balances
258 | for balance in &tx.token_balances.post_balances {
259 | if let Some(token_amount) = &balance.ui_token_amount {
260 | let key = format!("{}:{}", balance.mint, balance.owner);
261 | let pre_amount = pre_balances
262 | .get(&balance.mint)
263 | .map(|(amount, owner)| {
264 | if *owner == balance.owner { *amount } else { 0.0 }
265 | })
266 | .unwrap_or(0.0);
267 |
268 | let change = token_amount.ui_amount - pre_amount;
269 | changes.insert(key, change);
270 | }
271 | }
272 |
273 | changes
274 | }
275 |
276 | /// Generate a summary of the transaction
277 | pub fn generate_transaction_summary(tx: &PumpTransaction) -> String {
278 | let tx_type = match tx.transaction_type {
279 | TransactionType::Buy => "Buy",
280 | TransactionType::Sell => "Sell",
281 | TransactionType::Mint => "Mint",
282 | TransactionType::Unknown => "Unknown",
283 | };
284 |
285 | let changes = calculate_token_amount_change(tx);
286 |
287 | let mut summary = format!("Transaction Type: {}\n", tx_type);
288 | summary.push_str(&format!("Signature: {}\n", tx.signature));
289 | summary.push_str(&format!("Slot: {}\n", tx.slot));
290 | summary.push_str(&format!("Success: {}\n", tx.success));
291 | summary.push_str(&format!("Fee: {} lamports\n", tx.fee));
292 |
293 | summary.push_str("\nToken Balance Changes:\n");
294 | for (key, change) in changes {
295 | let parts: Vec<&str> = key.split(':').collect();
296 | if parts.len() == 2 {
297 | let (mint, owner) = (parts[0], parts[1]);
298 | let change_str = if change > 0.0 {
299 | format!("+{:.6}", change)
300 | } else {
301 | format!("{:.6}", change)
302 | };
303 | summary.push_str(&format!(" Mint: {}\n Owner: {}\n Change: {}\n\n", mint, owner, change_str));
304 | }
305 | }
306 |
307 | summary
308 | }
--------------------------------------------------------------------------------
/prototype/src/main.rs:
--------------------------------------------------------------------------------
1 | use pumpfun_analyzer::{parse_transaction_log, generate_transaction_summary, PumpTransaction, TransactionType};
2 | use anyhow::{Result, anyhow};
3 | use clap::{Parser, Subcommand};
4 | use colored::*;
5 | use std::fs::{self, File};
6 | use std::io::Write;
7 | use std::path::{Path, PathBuf};
8 | use std::collections::HashMap;
9 |
10 | #[derive(Parser)]
11 | #[command(name = "pumpfun-cli")]
12 | #[command(about = "CLI for analyzing PumpFun transactions", long_about = None)]
13 | struct Cli {
14 | #[command(subcommand)]
15 | command: Commands,
16 | }
17 |
18 | #[derive(Subcommand)]
19 | enum Commands {
20 | /// Parse a single transaction log file
21 | Parse {
22 | /// Path to the log file
23 | #[arg(short, long)]
24 | file: PathBuf,
25 |
26 | /// Output path for the JSON result
27 | #[arg(short, long)]
28 | output: Option,
29 | },
30 |
31 | /// Analyze multiple transaction logs
32 | Analyze {
33 | /// Directory containing log files
34 | #[arg(short, long)]
35 | dir: PathBuf,
36 |
37 | /// Filter by transaction type (buy, sell, mint)
38 | #[arg(short, long)]
39 | transaction_type: Option,
40 |
41 | /// Output directory for analysis results
42 | #[arg(short, long)]
43 | output: Option,
44 | },
45 | }
46 |
47 | fn process_single_file(file_path: &Path, output_path: Option<&Path>) -> Result<()> {
48 | println!("{} {}", "Parsing".green(), file_path.display());
49 |
50 | let tx = parse_transaction_log(file_path)?;
51 | let summary = generate_transaction_summary(&tx);
52 |
53 | println!("\n{}", summary);
54 |
55 | if let Some(output) = output_path {
56 | let json = serde_json::to_string_pretty(&tx)?;
57 | let mut file = File::create(output)?;
58 | file.write_all(json.as_bytes())?;
59 | println!("{} {}", "Results saved to".green(), output.display());
60 | }
61 |
62 | Ok(())
63 | }
64 |
65 | fn analyze_directory(dir_path: &Path, tx_type_filter: Option<&str>, output_dir: Option<&Path>) -> Result<()> {
66 | if !dir_path.exists() || !dir_path.is_dir() {
67 | return Err(anyhow!("Invalid directory path: {}", dir_path.display()));
68 | }
69 |
70 | println!("{} {}", "Analyzing files in".green(), dir_path.display());
71 |
72 | let mut transactions = Vec::new();
73 | let mut type_counts = HashMap::new();
74 | // let mut total_fees = 0u64;
75 | let mut tx_type_filter = Hash
76 |
77 | // Process files in directory
78 | for entry in fs::read_dir(dir_path)? {
79 | let entry = entry?;
80 | let path = entry.path();
81 |
82 | if path.is_file() && path.extension().map_or(false, |ext| ext == "log") {
83 | match parse_transaction_log(&path) {
84 | Ok(tx) => {
85 | // Apply transaction type filter if specified
86 | let tx_type_str = match tx.transaction_type {
87 | TransactionType::Buy => "buy",
88 | TransactionType::Sell => "sell",
89 | TransactionType::Mint => "mint",
90 | TransactionType::Unknown => "unknown",
91 | };
92 |
93 | if let Some(filter) = tx_type_filter {
94 | if filter.to_lowercase() != tx_type_str {
95 | continue;
96 | }
97 | }
98 |
99 | // Collect statistics
100 | *type_counts.entry(tx_type_str.to_string()).or_insert(0) += 1;
101 | total_fees += tx.fee;
102 |
103 | // Add to collections
104 | transactions.push(tx);
105 | }
106 | Err(e) => {
107 | eprintln!("{} {} - {}", "Error parsing".red(), path.display(), e);
108 | }
109 | }
110 | }
111 | }
112 |
113 | // Display summary
114 | println!("\n{}", "=== Analysis Summary ===".cyan().bold());
115 | println!("Total transactions: {}", transactions.len());
116 | println!("Transaction types:");
117 | for (tx_type, count) in &type_counts {
118 | println!(" {}: {}", tx_type, count);
119 | }
120 | println!("Total fees: {} lamports", total_fees);
121 |
122 | // Save results if output directory specified
123 | if let Some(output) = output_dir {
124 | if !output.exists() {
125 | fs::create_dir_all(output)?;
126 | }
127 |
128 | // Save all transactions to a single JSON file
129 | let all_txs_path = output.join("all_transactions.json");
130 | let json = serde_json::to_string_pretty(&transactions)?;
131 | let mut file = File::create(all_txs_path)?;
132 | file.write_all(json.as_bytes())?;
133 |
134 | // Save summary to a text file
135 | let summary_path = output.join("summary.txt");
136 | let mut summary_file = File::create(summary_path)?;
137 | writeln!(summary_file, "=== Analysis Summary ===")?;
138 | writeln!(summary_file, "Total transactions: {}", transactions.len())?;
139 | writeln!(summary_file, "Transaction types:")?;
140 | for (tx_type, count) in &type_counts {
141 | writeln!(summary_file, " {}: {}", tx_type, count)?;
142 | }
143 | writeln!(summary_file, "Total fees: {} lamports", total_fees)?;
144 |
145 | println!("{} {}", "Results saved to".green(), output.display());
146 | }
147 |
148 | Ok(())
149 | }
150 |
151 | #[tokio::main]
152 | async fn main() -> Result<()> {
153 | let cli = Cli::parse();
154 |
155 | match cli.command {
156 | Commands::Parse { file, output } => {
157 | process_single_file(&file, output.as_deref())?;
158 | }
159 | Commands::Analyze { dir, transaction_type, output } => {
160 | analyze_directory(&dir, transaction_type.as_deref(), output.as_deref())?;
161 | }
162 | }
163 |
164 | Ok(())
165 | }
--------------------------------------------------------------------------------
/src/common/blacklist.rs:
--------------------------------------------------------------------------------
1 | use std::fs;
2 | use std::io::{Error, ErrorKind};
3 | use std::collections::HashSet;
4 | use std::path::Path;
5 | use std::time::Instant;
6 | use serde::{Serialize, Deserialize};
7 | use tokio::sync::Mutex;
8 | use std::sync::Arc;
9 |
10 | // Use io::Result only where needed, not as a general import
11 | type Result = std::io::Result;
12 |
13 | /// Blacklist of tokens that should not be traded
14 | #[derive(Clone)]
15 | pub struct Blacklist {
16 | /// Addresses of tokens in the blacklist
17 | addresses: HashSet,
18 | /// Path to the blacklist file
19 | file_path: String,
20 | }
21 |
22 | // Implement custom serialization and deserialization
23 | impl Serialize for Blacklist {
24 | fn serialize(&self, serializer: S) -> std::result::Result
25 | where
26 | S: serde::Serializer,
27 | {
28 | // We only need to serialize the addresses, which is what we'll save to disk
29 | self.addresses.serialize(serializer)
30 | }
31 | }
32 |
33 | impl<'de> Deserialize<'de> for Blacklist {
34 | fn deserialize(deserializer: D) -> std::result::Result
35 | where
36 | D: serde::Deserializer<'de>,
37 | {
38 | // When deserializing, we only care about the addresses
39 | let addresses = HashSet::::deserialize(deserializer)?;
40 |
41 | // Default values for the other fields - these will be set properly in the new() method
42 | Ok(Self {
43 | addresses,
44 | file_path: String::new(),
45 | })
46 | }
47 | }
48 |
49 | impl Blacklist {
50 | /// Create a new blacklist from a JSON file
51 | pub fn new(file_path: &str) -> Result {
52 | let path = Path::new(file_path);
53 |
54 | // If file doesn't exist, create an empty blacklist
55 | if !path.exists() {
56 | return Ok(Self {
57 | addresses: HashSet::new(),
58 | file_path: file_path.to_string(),
59 | });
60 | }
61 |
62 | // Read from file
63 | let file_content = fs::read_to_string(file_path)?;
64 | let addresses: HashSet = if file_content.trim().is_empty() {
65 | HashSet::new()
66 | } else {
67 | match serde_json::from_str(&file_content) {
68 | Ok(addresses) => addresses,
69 | Err(e) => {
70 | return Err(Error::new(
71 | ErrorKind::InvalidData,
72 | format!("Failed to parse blacklist JSON: {}", e),
73 | ));
74 | }
75 | }
76 | };
77 |
78 | Ok(Self {
79 | addresses,
80 | file_path: file_path.to_string(),
81 | })
82 | }
83 |
84 | /// Create a new blacklist with a specified set of addresses
85 | pub fn with_addresses(addresses: HashSet, file_path: &str) -> Self {
86 | Self {
87 | addresses,
88 | file_path: file_path.to_string(),
89 | }
90 | }
91 |
92 | /// Create an empty blacklist
93 | pub fn empty(file_path: &str) -> Self {
94 | Self {
95 | addresses: HashSet::new(),
96 | file_path: file_path.to_string(),
97 | }
98 | }
99 |
100 | /// Get the number of addresses in the blacklist
101 | pub fn len(&self) -> usize {
102 | self.addresses.len()
103 | }
104 |
105 | /// Check if the blacklist is empty
106 | pub fn is_empty(&self) -> bool {
107 | self.addresses.is_empty()
108 | }
109 |
110 | /// Check if an address is in the blacklist
111 | pub fn is_blacklisted(&self, address: &str) -> bool {
112 | self.addresses.contains(address)
113 | }
114 |
115 | /// Add an address to the blacklist
116 | pub fn add_address(&mut self, address: &str) -> bool {
117 | self.addresses.insert(address.to_string())
118 | }
119 |
120 | /// Remove an address from the blacklist
121 | pub fn remove_address(&mut self, address: &str) -> bool {
122 | self.addresses.remove(address)
123 | }
124 |
125 | /// Get all addresses in the blacklist
126 | pub fn get_addresses(&self) -> Vec {
127 | self.addresses.iter().cloned().collect()
128 | }
129 |
130 | /// Save the blacklist to the file
131 | pub fn save(&self) -> Result<()> {
132 | let json = serde_json::to_string_pretty(&self.addresses)?;
133 | fs::write(&self.file_path, json)?;
134 | Ok(())
135 | }
136 | }
137 |
138 | /// Thread-safe blacklist manager
139 | #[derive(Clone)]
140 | pub struct BlacklistManager {
141 | blacklist: Arc>,
142 | save_interval_ms: u64,
143 | last_save: Arc>,
144 | }
145 |
146 | impl BlacklistManager {
147 | /// Create a new blacklist manager
148 | pub fn new(blacklist: Blacklist, save_interval_ms: u64) -> Self {
149 | Self {
150 | blacklist: Arc::new(Mutex::new(blacklist)),
151 | save_interval_ms,
152 | last_save: Arc::new(std::sync::Mutex::new(Instant::now())),
153 | }
154 | }
155 |
156 | /// Get a clone of the blacklist
157 | pub fn get_blacklist_arc(&self) -> Arc> {
158 | self.blacklist.clone()
159 | }
160 |
161 | /// Check if an address is in the blacklist
162 | pub async fn is_blacklisted(&self, address: &str) -> bool {
163 | let blacklist = self.blacklist.lock().await;
164 | blacklist.is_blacklisted(address)
165 | }
166 |
167 | /// Add an address to the blacklist
168 | pub async fn add_address(&self, address: &str) -> bool {
169 | let mut blacklist = self.blacklist.lock().await;
170 | let result = blacklist.add_address(address);
171 | self.check_and_save().await;
172 | result
173 | }
174 |
175 | /// Remove an address from the blacklist
176 | pub async fn remove_address(&self, address: &str) -> bool {
177 | let mut blacklist = self.blacklist.lock().await;
178 | let result = blacklist.remove_address(address);
179 | self.check_and_save().await;
180 | result
181 | }
182 |
183 | /// Check if it's time to save and save the blacklist
184 | pub async fn check_and_save(&self) -> bool {
185 | let mut last_save = self.last_save.lock().unwrap();
186 | let elapsed = last_save.elapsed().as_millis() as u64;
187 |
188 | if elapsed >= self.save_interval_ms {
189 | // Time to save
190 | let blacklist = self.blacklist.lock().await;
191 |
192 | match blacklist.save() {
193 | Ok(_) => {
194 | *last_save = Instant::now();
195 | true
196 | },
197 | Err(e) => {
198 | eprintln!("Failed to save blacklist: {}", e);
199 | false
200 | }
201 | }
202 | } else {
203 | false
204 | }
205 | }
206 |
207 | /// Force save the blacklist
208 | pub async fn save(&self) -> Result<()> {
209 | let blacklist = self.blacklist.lock().await;
210 | let result = blacklist.save();
211 |
212 | if result.is_ok() {
213 | let mut last_save = self.last_save.lock().unwrap();
214 | *last_save = Instant::now();
215 | }
216 |
217 | result
218 | }
219 | }
220 |
221 | #[cfg(test)]
222 | mod tests {
223 | use super::*;
224 | use tempfile::NamedTempFile;
225 |
226 | #[test]
227 | fn test_blacklist_basics() {
228 | let temp_file = NamedTempFile::new().unwrap();
229 | let temp_path = temp_file.path().to_str().unwrap().to_string();
230 |
231 | let mut blacklist = Blacklist::empty(&temp_path);
232 |
233 | // Test add and check
234 | assert!(blacklist.add_address("token1"));
235 | assert!(blacklist.is_blacklisted("token1"));
236 | assert_eq!(blacklist.len(), 1);
237 |
238 | // Test remove
239 | assert!(blacklist.remove_address("token1"));
240 | assert!(!blacklist.is_blacklisted("token1"));
241 | assert_eq!(blacklist.len(), 0);
242 | }
243 |
244 | #[tokio::test]
245 | async fn test_blacklist_manager() {
246 | let temp_file = NamedTempFile::new().unwrap();
247 | let temp_path = temp_file.path().to_str().unwrap().to_string();
248 |
249 | let blacklist = Blacklist::empty(&temp_path);
250 | let manager = BlacklistManager::new(blacklist, 5000);
251 |
252 | // Add token
253 | assert!(manager.add_address("token1").await);
254 | assert!(manager.is_blacklisted("token1").await);
255 |
256 | // Save
257 | assert!(manager.save().await.is_ok());
258 |
259 | // Check file contents
260 | let content = fs::read_to_string(&temp_path).unwrap();
261 | let parsed: HashSet = serde_json::from_str(&content).unwrap();
262 | assert!(parsed.contains("token1"));
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/common/constants.rs:
--------------------------------------------------------------------------------
1 | pub const INIT_MSG: &str = "
2 | @@@@@@ @@@@@@@@ @@@@@@@ @@@@@@@ @@@ @@@ @@@ @@@@@@@@ @@@@@@
3 | @@@@@@@ @@@@@@@@ @@@@@@@ @@@@@@@ @@@ @@@@ @@@ @@@@@@@@@ @@@@@@@
4 | !@@ @@! @@! @@! @@! @@!@!@@@ !@@ !@@
5 | !@! !@! !@! !@! !@! !@!!@!@! !@! !@!
6 | !!@@!! @!!!:! @!! @!! !!@ @!@ !!@! !@! @!@!@ !!@@!!
7 | !!@!!! !!!!!: !!! !!! !!! !@! !!! !!! !!@!! !!@!!!
8 | !:! !!: !!: !!: !!: !!: !!! :!! !!: !:!
9 | !:! :!: :!: :!: :!: :!: !:! :!: !:: !:!
10 | :::: :: :: :::: :: :: :: :: :: ::: :::: :::: ::
11 | :: : : : :: :: : : : :: : :: :: : :: : :
12 | ";
13 |
14 | pub const RUN_MSG: &str = "
15 | @@@@@@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@ @@@@@@@
16 | @@@@@@@@ @@@ @@@ @@@@ @@@ @@@@@@@@ @@@@@@@@ @@@@@@@
17 | @@! @@@ @@! @@@ @@!@!@@@ @@! @@@ @@! @@@ @@!
18 | !@! @!@ !@! @!@ !@!!@!@! !@ @!@ !@! @!@ !@!
19 | @!@!!@! @!@ !@! @!@ !!@! @!@!@!@ @!@ !@! @!!
20 | !!@!@! !@! !!! !@! !!! !!!@!!!! !@! !!! !!!
21 | !!: :!! !!: !!! !!: !!! !!: !!! !!: !!! !!:
22 | :!: !:! :!: !:! :!: !:! :!: !:! :!: !:! :!:
23 | :: ::: ::::: :: :: :: :: :::: ::::: :: ::
24 | : : : : : : :: : :: : :: : : : :
25 | ";
26 |
--------------------------------------------------------------------------------
/src/common/logger.rs:
--------------------------------------------------------------------------------
1 | use chrono::Local;
2 | use colored::*;
3 |
4 | const LOG_LEVEL: &str = "LOG";
5 |
6 | #[derive(Clone, Debug)]
7 | pub struct Logger {
8 | prefix: String,
9 | date_format: String,
10 | }
11 |
12 | impl Logger {
13 | // Constructor function to create a new Logger instance
14 | pub fn new(prefix: String) -> Self {
15 | Logger {
16 | prefix,
17 | date_format: String::from("%Y-%m-%d %H:%M:%S"),
18 | }
19 | }
20 |
21 | // Method to log a message with a prefix
22 | pub fn log(&self, message: String) -> String {
23 | let log = format!("{} {}", self.prefix_with_date(), message);
24 | println!("{}", log);
25 | log
26 | }
27 |
28 | pub fn debug(&self, message: String) -> String {
29 | let log = format!("{} [{}] {}", self.prefix_with_date(), "DEBUG", message);
30 | if LogLevel::new().is_debug() {
31 | println!("{}", log);
32 | }
33 | log
34 | }
35 | pub fn error(&self, message: String) -> String {
36 | let log = format!("{} [{}] {}", self.prefix_with_date(), "ERROR", message);
37 | println!("{}", log);
38 |
39 | log
40 | }
41 |
42 | fn prefix_with_date(&self) -> String {
43 | let date = Local::now();
44 | format!(
45 | "[{}] {}",
46 | date.format(&self.date_format.as_str().blue().bold()),
47 | self.prefix
48 | )
49 | }
50 |
51 | // Method to check if debug logging is enabled
52 | pub fn debug_enabled(&self) -> bool {
53 | LogLevel::new().is_debug()
54 | }
55 | }
56 |
57 | struct LogLevel<'a> {
58 | level: &'a str,
59 | }
60 | impl LogLevel<'_> {
61 | fn new() -> Self {
62 | let level = LOG_LEVEL;
63 | LogLevel { level }
64 | }
65 | fn is_debug(&self) -> bool {
66 | self.level.to_lowercase().eq("debug")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/common/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod blacklist;
2 | pub mod config;
3 | pub mod constants;
4 | pub mod logger;
5 | pub mod whitelist;
6 |
--------------------------------------------------------------------------------
/src/common/whitelist.rs:
--------------------------------------------------------------------------------
1 | use std::fs;
2 | use std::io::{Error, ErrorKind};
3 | use std::collections::HashSet;
4 | use std::path::Path;
5 | use std::time::Instant;
6 | use serde::{Serialize, Deserialize};
7 | use tokio::sync::Mutex;
8 | use std::sync::Arc;
9 |
10 | // Use io::Result only where needed, not as a general import
11 | type Result = std::io::Result;
12 |
13 | /// Whitelist with review cycle functionality
14 | ///
15 | /// Tracks tokens that are allowed for trading and periodically
16 | /// reviews them based on activity within a review cycle
17 | #[derive(Clone)]
18 | pub struct Whitelist {
19 | /// Addresses of tokens in the whitelist
20 | addresses: HashSet,
21 | /// Path to the whitelist file
22 | file_path: String,
23 | /// Time of the last review
24 | last_review: Instant,
25 | /// Duration of the review cycle in milliseconds
26 | review_cycle_ms: u64,
27 | /// Tokens seen in the current review cycle
28 | active_tokens: HashSet,
29 | }
30 |
31 | impl Whitelist {
32 | /// Create a new whitelist from a JSON file
33 | pub fn new(file_path: &str, review_cycle_ms: u64) -> Result {
34 | let path = Path::new(file_path);
35 |
36 | // If file doesn't exist, create an empty whitelist
37 | if !path.exists() {
38 | return Ok(Self {
39 | addresses: HashSet::new(),
40 | file_path: file_path.to_string(),
41 | last_review: Instant::now(),
42 | review_cycle_ms,
43 | active_tokens: HashSet::new(),
44 | });
45 | }
46 |
47 | // Read from file
48 | let file_content = fs::read_to_string(file_path)?;
49 | let addresses: HashSet = if file_content.trim().is_empty() {
50 | HashSet::new()
51 | } else {
52 | match serde_json::from_str(&file_content) {
53 | Ok(addresses) => addresses,
54 | Err(e) => {
55 | return Err(Error::new(
56 | ErrorKind::InvalidData,
57 | format!("Failed to parse whitelist JSON: {}", e),
58 | ));
59 | }
60 | }
61 | };
62 |
63 | Ok(Self {
64 | addresses,
65 | file_path: file_path.to_string(),
66 | last_review: Instant::now(),
67 | review_cycle_ms,
68 | active_tokens: HashSet::new(),
69 | })
70 | }
71 |
72 | /// Create a new whitelist with a specified set of addresses
73 | pub fn with_addresses(addresses: HashSet, file_path: &str, review_cycle_ms: u64) -> Self {
74 | Self {
75 | addresses,
76 | file_path: file_path.to_string(),
77 | last_review: Instant::now(),
78 | review_cycle_ms,
79 | active_tokens: HashSet::new(),
80 | }
81 | }
82 |
83 | /// Create an empty whitelist
84 | pub fn empty(file_path: &str, review_cycle_ms: u64) -> Self {
85 | Self {
86 | addresses: HashSet::new(),
87 | file_path: file_path.to_string(),
88 | last_review: Instant::now(),
89 | review_cycle_ms,
90 | active_tokens: HashSet::new(),
91 | }
92 | }
93 |
94 | /// Get the number of addresses in the whitelist
95 | pub fn len(&self) -> usize {
96 | self.addresses.len()
97 | }
98 |
99 | /// Check if the whitelist is empty
100 | pub fn is_empty(&self) -> bool {
101 | self.addresses.is_empty()
102 | }
103 |
104 | /// Check if an address is in the whitelist
105 | pub fn is_whitelisted(&self, address: &str) -> bool {
106 | self.addresses.contains(address)
107 | }
108 |
109 | /// Add an address to the whitelist and mark it as active in the current cycle
110 | pub fn add_address(&mut self, address: &str) -> bool {
111 | let is_new = self.addresses.insert(address.to_string());
112 | self.active_tokens.insert(address.to_string());
113 | is_new
114 | }
115 |
116 | /// Remove an address from the whitelist
117 | pub fn remove_address(&mut self, address: &str) -> bool {
118 | self.addresses.remove(address)
119 | }
120 |
121 | /// Mark an address as active in the current review cycle
122 | pub fn mark_as_active(&mut self, address: &str) {
123 | // Only add to active tokens if it's in the whitelist
124 | if self.is_whitelisted(address) {
125 | self.active_tokens.insert(address.to_string());
126 | }
127 | }
128 |
129 | /// Get all addresses in the whitelist
130 | pub fn get_addresses(&self) -> Vec {
131 | self.addresses.iter().cloned().collect()
132 | }
133 |
134 | /// Get active addresses in the current review cycle
135 | pub fn get_active_addresses(&self) -> Vec {
136 | self.active_tokens.iter().cloned().collect()
137 | }
138 |
139 | /// Check if a review cycle is complete and process it
140 | pub fn check_review_cycle(&mut self) -> bool {
141 | let elapsed = self.last_review.elapsed();
142 |
143 | if elapsed.as_millis() as u64 >= self.review_cycle_ms {
144 | // Review cycle completed
145 | self.process_review_cycle();
146 | true
147 | } else {
148 | false
149 | }
150 | }
151 |
152 | /// Process the review cycle by removing inactive tokens
153 | fn process_review_cycle(&mut self) {
154 | // Keep only tokens that were active in this cycle
155 | self.addresses.retain(|address| self.active_tokens.contains(address));
156 |
157 | // Reset the active tokens for the next cycle
158 | self.active_tokens.clear();
159 |
160 | // Reset the last review time
161 | self.last_review = Instant::now();
162 | }
163 |
164 | /// Save the whitelist to the file
165 | pub fn save(&self) -> Result<()> {
166 | let json = serde_json::to_string_pretty(&self.addresses)?;
167 | fs::write(&self.file_path, json)?;
168 | Ok(())
169 | }
170 | }
171 |
172 | // Implement custom serialization and deserialization
173 | impl Serialize for Whitelist {
174 | fn serialize(&self, serializer: S) -> std::result::Result
175 | where
176 | S: serde::Serializer,
177 | {
178 | // We only need to serialize the addresses, which is what we'll save to disk
179 | self.addresses.serialize(serializer)
180 | }
181 | }
182 |
183 | impl<'de> Deserialize<'de> for Whitelist {
184 | fn deserialize(deserializer: D) -> std::result::Result
185 | where
186 | D: serde::Deserializer<'de>,
187 | {
188 | // When deserializing, we only care about the addresses
189 | let addresses = HashSet::::deserialize(deserializer)?;
190 |
191 | // Default values for the other fields - these will be set properly in the new() method
192 | Ok(Self {
193 | addresses,
194 | file_path: String::new(),
195 | last_review: Instant::now(),
196 | review_cycle_ms: 0,
197 | active_tokens: HashSet::new(),
198 | })
199 | }
200 | }
201 |
202 | /// Thread-safe whitelist manager
203 | #[derive(Clone)]
204 | pub struct WhitelistManager {
205 | whitelist: Arc>,
206 | save_interval_ms: u64,
207 | last_save: Arc>,
208 | }
209 |
210 | impl WhitelistManager {
211 | /// Create a new whitelist manager
212 | pub fn new(whitelist: Whitelist, save_interval_ms: u64) -> Self {
213 | Self {
214 | whitelist: Arc::new(Mutex::new(whitelist)),
215 | save_interval_ms,
216 | last_save: Arc::new(std::sync::Mutex::new(Instant::now())),
217 | }
218 | }
219 |
220 | /// Get a clone of the whitelist
221 | pub fn get_whitelist_arc(&self) -> Arc> {
222 | self.whitelist.clone()
223 | }
224 |
225 | /// Check if an address is in the whitelist
226 | pub async fn is_whitelisted(&self, address: &str) -> bool {
227 | let whitelist = self.whitelist.lock().await;
228 | whitelist.is_whitelisted(address)
229 | }
230 |
231 | /// Mark an address as active in the current review cycle
232 | pub async fn mark_as_active(&self, address: &str) {
233 | let mut whitelist = self.whitelist.lock().await;
234 | whitelist.mark_as_active(address);
235 | }
236 |
237 | /// Add an address to the whitelist
238 | pub async fn add_address(&self, address: &str) -> bool {
239 | let mut whitelist = self.whitelist.lock().await;
240 | let result = whitelist.add_address(address);
241 | self.check_and_save().await;
242 | result
243 | }
244 |
245 | /// Remove an address from the whitelist
246 | pub async fn remove_address(&self, address: &str) -> bool {
247 | let mut whitelist = self.whitelist.lock().await;
248 | let result = whitelist.remove_address(address);
249 | self.check_and_save().await;
250 | result
251 | }
252 |
253 | /// Check review cycle and save if needed
254 | pub async fn check_review_cycle(&self) -> bool {
255 | let mut whitelist = self.whitelist.lock().await;
256 | let result = whitelist.check_review_cycle();
257 |
258 | if result {
259 | // If review cycle was processed, save the whitelist
260 | if let Err(e) = whitelist.save() {
261 | eprintln!("Failed to save whitelist after review cycle: {}", e);
262 | }
263 | }
264 |
265 | result
266 | }
267 |
268 | /// Check if it's time to save and save the whitelist
269 | pub async fn check_and_save(&self) -> bool {
270 | let mut last_save = self.last_save.lock().unwrap();
271 | let elapsed = last_save.elapsed().as_millis() as u64;
272 |
273 | if elapsed >= self.save_interval_ms {
274 | // Time to save
275 | let whitelist = self.whitelist.lock().await;
276 |
277 | match whitelist.save() {
278 | Ok(_) => {
279 | *last_save = Instant::now();
280 | true
281 | },
282 | Err(e) => {
283 | eprintln!("Failed to save whitelist: {}", e);
284 | false
285 | }
286 | }
287 | } else {
288 | false
289 | }
290 | }
291 |
292 | /// Force save the whitelist
293 | pub async fn save(&self) -> Result<()> {
294 | let whitelist = self.whitelist.lock().await;
295 | let result = whitelist.save();
296 |
297 | if result.is_ok() {
298 | let mut last_save = self.last_save.lock().unwrap();
299 | *last_save = Instant::now();
300 | }
301 |
302 | result
303 | }
304 | }
305 |
306 | #[cfg(test)]
307 | mod tests {
308 | use super::*;
309 | use tempfile::NamedTempFile;
310 |
311 | #[test]
312 | fn test_whitelist_basics() {
313 | let temp_file = NamedTempFile::new().unwrap();
314 | let temp_path = temp_file.path().to_str().unwrap().to_string();
315 |
316 | let mut whitelist = Whitelist::empty(&temp_path, 1000);
317 |
318 | // Test add and check
319 | assert!(whitelist.add_address("token1"));
320 | assert!(whitelist.is_whitelisted("token1"));
321 | assert_eq!(whitelist.len(), 1);
322 |
323 | // Test mark as active
324 | whitelist.mark_as_active("token1");
325 | assert!(whitelist.active_tokens.contains("token1"));
326 |
327 | // Test review cycle
328 | assert!(!whitelist.check_review_cycle()); // Not enough time has passed
329 |
330 | // Force review cycle processing
331 | whitelist.last_review = Instant::now() - Duration::from_millis(2000);
332 | assert!(whitelist.check_review_cycle());
333 |
334 | // After review, token1 should be removed since we cleared active_tokens
335 | assert!(!whitelist.is_whitelisted("token1"));
336 | assert_eq!(whitelist.len(), 0);
337 | }
338 |
339 | #[tokio::test]
340 | async fn test_whitelist_manager() {
341 | let temp_file = NamedTempFile::new().unwrap();
342 | let temp_path = temp_file.path().to_str().unwrap().to_string();
343 |
344 | let whitelist = Whitelist::empty(&temp_path, 1000);
345 | let manager = WhitelistManager::new(whitelist, 5000);
346 |
347 | // Add token
348 | assert!(manager.add_address("token1").await);
349 | assert!(manager.is_whitelisted("token1").await);
350 |
351 | // Mark as active
352 | manager.mark_as_active("token1").await;
353 |
354 | // Save
355 | assert!(manager.save().await.is_ok());
356 |
357 | // Check file contents
358 | let content = fs::read_to_string(&temp_path).unwrap();
359 | let parsed: HashSet = serde_json::from_str(&content).unwrap();
360 | assert!(parsed.contains("token1"));
361 | }
362 | }
--------------------------------------------------------------------------------
/src/core/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod token;
2 | pub mod tx;
3 |
--------------------------------------------------------------------------------
/src/core/token.rs:
--------------------------------------------------------------------------------
1 | use anchor_client::solana_sdk::{pubkey::Pubkey, signature::Keypair};
2 | use spl_token::state::{Account, Mint};
3 | use solana_program_pack::Pack;
4 | use spl_token_client::{
5 | client::{ProgramClient, ProgramRpcClient, ProgramRpcClientSendTransaction},
6 | token::{Token, TokenError, TokenResult},
7 | };
8 | use std::sync::Arc;
9 |
10 | pub fn get_associated_token_address(
11 | client: Arc,
12 | keypair: Arc,
13 | address: &Pubkey,
14 | owner: &Pubkey,
15 | ) -> Pubkey {
16 | let token_client = Token::new(
17 | Arc::new(ProgramRpcClient::new(
18 | client.clone(),
19 | ProgramRpcClientSendTransaction,
20 | )),
21 | &spl_token::ID,
22 | address,
23 | None,
24 | Arc::new(Keypair::from_bytes(&keypair.to_bytes()).expect("failed to copy keypair")),
25 | );
26 | token_client.get_associated_token_address(owner)
27 | }
28 |
29 | pub async fn get_account_info(
30 | client: Arc,
31 | address: Pubkey,
32 | account: Pubkey,
33 | ) -> TokenResult {
34 | let program_client = Arc::new(ProgramRpcClient::new(
35 | client.clone(),
36 | ProgramRpcClientSendTransaction,
37 | ));
38 | let account_data = program_client
39 | .get_account(account)
40 | .await
41 | .map_err(TokenError::Client)?
42 | .ok_or(TokenError::AccountNotFound)
43 | .inspect_err(|_err| {
44 | // logger.log(format!(
45 | // "get_account_info: {} {}: mint {}",
46 | // account, err, address
47 | // ));
48 | })?;
49 |
50 | if account_data.owner != spl_token::ID {
51 | return Err(TokenError::AccountInvalidOwner);
52 | }
53 |
54 | let account_state = Account::unpack(&account_data.data)?;
55 | if account_state.mint != address {
56 | return Err(TokenError::AccountInvalidMint);
57 | }
58 |
59 | Ok(account_state)
60 | }
61 |
62 | pub async fn get_mint_info(
63 | client: Arc,
64 | _keypair: Arc,
65 | address: Pubkey,
66 | ) -> TokenResult {
67 | let program_client = Arc::new(ProgramRpcClient::new(
68 | client.clone(),
69 | ProgramRpcClientSendTransaction,
70 | ));
71 | let account = program_client
72 | .get_account(address)
73 | .await
74 | .map_err(TokenError::Client)?
75 | .ok_or(TokenError::AccountNotFound)
76 | .inspect_err(|err| println!("{} {}: mint {}", address, err, address))?;
77 |
78 | if account.owner != spl_token::ID {
79 | return Err(TokenError::AccountInvalidOwner);
80 | }
81 |
82 | let mint_state = Mint::unpack(&account.data)
83 | .map_err(|_| TokenError::AccountInvalidMint)?;
84 | let decimals: Option = None;
85 | if let Some(decimals) = decimals {
86 | if decimals != mint_state.decimals {
87 | return Err(TokenError::InvalidDecimals);
88 | }
89 | }
90 |
91 | Ok(mint_state)
92 | }
93 |
--------------------------------------------------------------------------------
/src/core/tx.rs:
--------------------------------------------------------------------------------
1 | use std::{sync::Arc, time::Duration};
2 | use std::{str::FromStr, env};
3 | use anyhow::Result;
4 | use colored::Colorize;
5 | use anchor_client::solana_client::rpc_client::RpcClient;
6 | use anchor_client::solana_sdk::{
7 | hash::Hash,
8 | instruction::Instruction,
9 | signature::Keypair,
10 | signer::Signer,
11 | system_instruction, system_transaction,
12 | transaction::{Transaction, VersionedTransaction},
13 | };
14 | use spl_token::ui_amount_to_amount;
15 |
16 | use jito_json_rpc_client::jsonrpc_client::rpc_client::RpcClient as JitoRpcClient;
17 | use tokio::time::Instant;
18 |
19 | use crate::common::logger::Logger;
20 | use crate::{
21 | services::{
22 | jito::{self, JitoClient},
23 | nozomi,
24 | zeroslot::{self, ZeroSlotClient},
25 | },
26 | };
27 |
28 | pub async fn jito_confirm(
29 | client: &RpcClient,
30 | keypair: &Keypair,
31 | version_tx: VersionedTransaction,
32 | recent_block_hash: &Hash,
33 | logger: &Logger,
34 | ) -> Result> {
35 |
36 |
37 | }
38 |
39 | pub async fn new_signed_and_send(
40 | recent_blockhash: anchor_client::solana_sdk::hash::Hash,
41 | keypair: &Keypair,
42 | mut instructions: Vec,
43 | logger: &Logger,
44 | ) -> Result> {
45 | let start_time = Instant::now();
46 |
47 | let mut txs = vec![];
48 | let (tip_account, tip1_account) = jito::get_tip_account()?;
49 |
50 | // jito tip, the upper limit is 0.1
51 | let tip = jito::get_tip_value().await?;
52 | let fee = jito::get_priority_fee().await?;
53 | let tip_lamports = ui_amount_to_amount(tip, spl_token::native_mint::DECIMALS);
54 | let fee_lamports = ui_amount_to_amount(fee, spl_token::native_mint::DECIMALS);
55 |
56 | let jito_tip_instruction =
57 | system_instruction::transfer(&keypair.pubkey(), &tip_account, tip_lamports);
58 | let _jito_tip2_instruction =
59 | system_instruction::transfer(&keypair.pubkey(), &tip1_account, fee_lamports);
60 |
61 | // ADD Priority fee
62 | // -------------
63 | let unit_limit = get_unit_limit();
64 | let unit_price = get_unit_price();
65 |
66 | let modify_compute_units =
67 | anchor_client::solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(
68 | unit_limit,
69 | );
70 | let add_priority_fee =
71 | anchor_client::solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price(
72 | unit_price,
73 | );
74 | instructions.insert(1, modify_compute_units);
75 | instructions.insert(2, add_priority_fee);
76 |
77 | instructions.push(jito_tip_instruction);
78 | // instructions.push(jito_tip2_instruction);
79 |
80 | // send init tx
81 | let txn = Transaction::new_signed_with_payer(
82 | &instructions,
83 | Some(&keypair.pubkey()),
84 | &vec![keypair],
85 | recent_blockhash,
86 | );
87 |
88 | // let simulate_result = client.simulate_transaction(&txn)?;
89 | // logger.log("Tx Stimulate".to_string());
90 | // if let Some(logs) = simulate_result.value.logs {
91 | // for log in logs {
92 | // logger.log(log.to_string());
93 | // }
94 | // }
95 | // if let Some(err) = simulate_result.value.err {
96 | // return Err(anyhow::anyhow!("{}", err));
97 | // };
98 |
99 | let jito_client = Arc::new(JitoClient::new(
100 | format!("{}/api/v1/transactions", *jito::BLOCK_ENGINE_URL).as_str(),
101 | ));
102 | let sig = match jito_client.send_transaction(&txn).await {
103 | Ok(signature) => signature,
104 | Err(_) => {
105 | // logger.log(format!("{}", e));
106 | return Err(anyhow::anyhow!("Bundle status get timeout"
107 | .red()
108 | .italic()
109 | .to_string()));
110 | }
111 | };
112 | txs.push(sig.clone().to_string());
113 | logger.log(
114 | format!("[TXN-ELLAPSED(JITO)]: {:?}", start_time.elapsed())
115 | .yellow()
116 | .to_string(),
117 | );
118 |
119 | Ok(txs)
120 | }
121 |
122 | pub async fn new_signed_and_send_zeroslot(
123 | recent_blockhash: anchor_client::solana_sdk::hash::Hash,
124 | keypair: &Keypair,
125 | mut instructions: Vec,
126 | logger: &Logger,
127 | ) -> Result> {
128 | let start_time = Instant::now();
129 |
130 | let mut txs = vec![];
131 | let tip_account = zeroslot::get_tip_account()?;
132 |
133 | // zeroslot tip, the upper limit is 0.1
134 | let tip = zeroslot::get_tip_value().await?;
135 | let tip_lamports = ui_amount_to_amount(tip, spl_token::native_mint::DECIMALS);
136 |
137 | let zeroslot_tip_instruction =
138 | system_instruction::transfer(&keypair.pubkey(), &tip_account, tip_lamports);
139 | instructions.insert(0, zeroslot_tip_instruction);
140 |
141 | // send init tx
142 | let txn = Transaction::new_signed_with_payer(
143 | &instructions,
144 | Some(&keypair.pubkey()),
145 | &vec![keypair],
146 | recent_blockhash,
147 | );
148 |
149 | // let simulate_result = client.simulate_transaction(&txn)?;
150 | // logger.log("Tx Stimulate".to_string());
151 | // if let Some(logs) = simulate_result.value.logs {
152 | // for log in logs {
153 | // logger.log(log.to_string());
154 | // }
155 | // }
156 | // if let Some(err) = simulate_result.value.err {
157 | // return Err(anyhow::anyhow!("{}", err));
158 | // };
159 |
160 | let zeroslot_client = Arc::new(ZeroSlotClient::new((*zeroslot::ZERO_SLOT_URL).as_str()));
161 | let sig = match zeroslot_client.send_transaction(&txn).await {
162 | Ok(signature) => signature,
163 | Err(_) => {
164 | return Err(anyhow::anyhow!("send_transaction status get timeout"
165 | .red()
166 | .italic()
167 | .to_string()));
168 | }
169 | };
170 | txs.push(sig.clone().to_string());
171 | logger.log(
172 | format!("[TXN-ELLAPSED]: {:?}", start_time.elapsed())
173 | .yellow()
174 | .to_string(),
175 | );
176 |
177 | Ok(txs)
178 | }
179 |
180 | // prioritization fee = UNIT_PRICE * UNIT_LIMIT
181 | fn get_unit_price() -> u64 {
182 | env::var("UNIT_PRICE")
183 | .ok()
184 | .and_then(|v| u64::from_str(&v).ok())
185 | .unwrap_or(20000)
186 | }
187 |
188 | fn get_unit_limit() -> u32 {
189 | env::var("UNIT_LIMIT")
190 | .ok()
191 | .and_then(|v| u32::from_str(&v).ok())
192 | .unwrap_or(200_000)
193 | }
194 |
195 | pub async fn new_signed_and_send_nozomi(
196 | recent_blockhash: anchor_client::solana_sdk::hash::Hash,
197 | keypair: &Keypair,
198 | mut instructions: Vec,
199 | logger: &Logger,
200 | ) -> Result> {
201 | let start_time = Instant::now();
202 |
203 |
204 |
205 | Ok(txs)
206 | }
207 |
208 | pub async fn new_signed_and_send_spam(
209 | recent_blockhash: anchor_client::solana_sdk::hash::Hash,
210 | keypair: &std::sync::Arc,
211 | instructions: Vec,
212 | logger: &Logger,
213 | ) -> Result> {
214 | // Assuming keypair is already defined as Arc
215 | let logger_clone = logger.clone();
216 | let keypair_clone = Arc::clone(&keypair); // Clone the Arc for the first future
217 |
218 | let logger_clone1 = logger.clone();
219 | let keypair_clone1 = Arc::clone(&keypair); // Clone the Arc for the second future
220 |
221 | let logger_clone2 = logger.clone();
222 | let keypair_clone2 = Arc::clone(&keypair); // Clone the Arc for the second future
223 |
224 | // Clone instructions if necessary
225 | let instructions_clone_for_jito = instructions.clone();
226 | let instructions_clone_for_nozomi = instructions.clone();
227 | let instructions_clone_for_zeroslot = instructions.clone();
228 |
229 | // Create the futures for both transaction sending methods
230 | let jito_future = tokio::task::spawn(async move {
231 | new_signed_and_send(
232 | recent_blockhash,
233 | &keypair_clone,
234 | instructions_clone_for_jito,
235 | &logger_clone,
236 | )
237 | .await
238 | });
239 |
240 | let nozomi_future = tokio::task::spawn(async move {
241 | new_signed_and_send_nozomi(
242 | recent_blockhash,
243 | &keypair_clone1,
244 | instructions_clone_for_nozomi,
245 | &logger_clone1,
246 | )
247 | .await
248 | });
249 |
250 | let zeroslot_future = tokio::task::spawn(async move {
251 | new_signed_and_send_zeroslot(
252 | recent_blockhash,
253 | &keypair_clone2,
254 | instructions_clone_for_zeroslot,
255 | &logger_clone2,
256 | )
257 | .await
258 | });
259 |
260 | // Await both futures
261 | let results = futures::future::join_all(vec![jito_future, nozomi_future, zeroslot_future]).await;
262 |
263 | let mut successful_results = Vec::new();
264 | let mut errors: Vec = Vec::new();
265 |
266 | for result in results {
267 | match result {
268 | Ok(Ok(res)) => successful_results.push(res[0].clone()), // Push if success
269 | Ok(Err(e)) => errors.push(e.to_string()), // Collect error message
270 | Err(e) => errors.push(format!("Task failed: {:?}", e)), // Collect task failure
271 | }
272 | }
273 |
274 | // If there are any errors, print them
275 | if !errors.is_empty() {
276 | for error in &errors {
277 | eprintln!("{}", error); // Print errors to stderr
278 | }
279 |
280 | // If no successful results were collected, return an error
281 | if successful_results.is_empty() {
282 | return Err(anyhow::anyhow!(format!("All tasks failed with these errors: {:?}", errors)));
283 | }
284 | }
285 |
286 | Ok(successful_results)
287 | }
288 |
--------------------------------------------------------------------------------
/src/dex/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod pump_fun;
2 |
--------------------------------------------------------------------------------
/src/dex/pump_fun.rs:
--------------------------------------------------------------------------------
1 | use std::{str::FromStr, sync::Arc, time::Duration};
2 |
3 | use anyhow::{anyhow, Result};
4 | use borsh::from_slice;
5 | use borsh_derive::{BorshDeserialize, BorshSerialize};
6 | use colored::Colorize;
7 | use serde::{Deserialize, Serialize};
8 | use anchor_client::solana_sdk::{
9 | instruction::{AccountMeta, Instruction},
10 | pubkey::Pubkey,
11 | signature::Keypair,
12 | signer::Signer,
13 | system_program,
14 | };
15 | use spl_associated_token_account::{
16 | get_associated_token_address, instruction::create_associated_token_account,
17 | };
18 | use spl_token::{amount_to_ui_amount, ui_amount_to_amount};
19 | use spl_token_client::token::TokenError;
20 | use tokio::time::Instant;
21 |
22 | use crate::{
23 | common::{config::SwapConfig, logger::Logger},
24 | core::token,
25 | engine::{monitor::BondingCurveInfo, swap::{SwapDirection, SwapInType}},
26 | };
27 |
28 | pub const TEN_THOUSAND: u64 = 10000;
29 | pub const TOKEN_PROGRAM: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
30 | pub const RENT_PROGRAM: &str = "SysvarRent111111111111111111111111111111111";
31 | pub const ASSOCIATED_TOKEN_PROGRAM: &str = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
32 | pub const PUMP_GLOBAL: &str = "4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf";
33 | pub const PUMP_FEE_RECIPIENT: &str = "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM";
34 | pub const PUMP_PROGRAM: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
35 | // pub const PUMP_FUN_MINT_AUTHORITY: &str = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM";
36 | pub const PUMP_ACCOUNT: &str = "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1";
37 | pub const PUMP_BUY_METHOD: u64 = 16927863322537952870;
38 | pub const PUMP_SELL_METHOD: u64 = 12502976635542562355;
39 | pub const PUMP_FUN_CREATE_IX_DISCRIMINATOR: &[u8] = &[24, 30, 200, 40, 5, 28, 7, 119];
40 | pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 30_000_000_000;
41 | pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_073_000_000_000_000;
42 | pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
43 |
44 | #[derive(Clone)]
45 | pub struct Pump {
46 | pub rpc_nonblocking_client: Arc,
47 | pub keypair: Arc,
48 | pub rpc_client: Option>,
49 | }
50 |
51 | impl Pump {
52 | pub fn new(
53 | rpc_nonblocking_client: Arc,
54 | rpc_client: Arc,
55 | keypair: Arc,
56 | ) -> Self {
57 | Self {
58 | rpc_nonblocking_client,
59 | keypair,
60 | rpc_client: Some(rpc_client),
61 | }
62 | }
63 | }
64 |
65 |
66 | pub async fn get_bonding_curve_account(
67 | rpc_client: Arc,
68 | mint: Pubkey,
69 | program_id: Pubkey,
70 | ) -> Result<(Pubkey, Pubkey, BondingCurveReserves)> {
71 | let bonding_curve = get_pda(&mint, &program_id)?;
72 | let associated_bonding_curve = get_associated_token_address(&bonding_curve, &mint);
73 | let start_time = Instant::now();
74 | // println!("Start: {:?}", start_time.elapsed());
75 |
76 | let max_retries = 30;
77 | let time_exceed = 300;
78 | let timeout = Duration::from_millis(time_exceed);
79 | let mut retry_count = 0;
80 | let bonding_curve_data = loop {
81 | match rpc_client.get_account_data(&bonding_curve) {
82 | Ok(data) => {
83 | // println!("Done: {:?}", start_time.elapsed());
84 | break data;
85 | }
86 | Err(err) => {
87 | retry_count += 1;
88 | if retry_count > max_retries {
89 | return Err(anyhow!(
90 | "Failed to get bonding curve account data after {} retries: {}",
91 | max_retries,
92 | err
93 | ));
94 | }
95 | if start_time.elapsed() > timeout {
96 | return Err(anyhow!(
97 | "Failed to get bonding curve account data after {:?} timeout: {}",
98 | timeout,
99 | err
100 | ));
101 | }
102 | tokio::time::sleep(Duration::from_millis(10)).await;
103 | // println!("Retry {}: {:?}", retry_count, start_time.elapsed());
104 | }
105 | }
106 | };
107 |
108 | let bonding_curve_account =
109 | from_slice::(&bonding_curve_data).map_err(|e| {
110 | anyhow!(
111 | "Failed to deserialize bonding curve account: {}",
112 | e.to_string()
113 | )
114 | })?;
115 | let bonding_curve_reserves = BondingCurveReserves
116 | {
117 | virtual_token_reserves: bonding_curve_account.virtual_token_reserves,
118 | virtual_sol_reserves: bonding_curve_account.virtual_sol_reserves
119 | };
120 | Ok((
121 | bonding_curve,
122 | associated_bonding_curve,
123 | bonding_curve_reserves,
124 | ))
125 | }
126 |
127 | pub fn get_pda(mint: &Pubkey, program_id: &Pubkey) -> Result {
128 | let seeds = [b"bonding-curve".as_ref(), mint.as_ref()];
129 | let (bonding_curve, _bump) = Pubkey::find_program_address(&seeds, program_id);
130 | Ok(bonding_curve)
131 | }
132 |
133 | // https://frontend-api.pump.fun/coins/8zSLdDzM1XsqnfrHmHvA9ir6pvYDjs8UXz6B2Tydd6b2
134 | // pub async fn get_pump_info(
135 | // rpc_client: Arc,
136 | // mint: str,
137 | // ) -> Result {
138 | // let mint = Pubkey::from_str(&mint)?;
139 | // let program_id = Pubkey::from_str(PUMP_PROGRAM)?;
140 | // let (bonding_curve, associated_bonding_curve, bonding_curve_account) =
141 | // get_bonding_curve_account(rpc_client, &mint, &program_id).await?;
142 |
143 | // let pump_info = PumpInfo {
144 | // mint: mint.to_string(),
145 | // bonding_curve: bonding_curve.to_string(),
146 | // associated_bonding_curve: associated_bonding_curve.to_string(),
147 | // raydium_pool: None,
148 | // raydium_info: None,
149 | // complete: bonding_curve_account.complete,
150 | // virtual_sol_reserves: bonding_curve_account.virtual_sol_reserves,
151 | // virtual_token_reserves: bonding_curve_account.virtual_token_reserves,
152 | // total_supply: bonding_curve_account.token_total_supply,
153 | // };
154 | // Ok(pump_info)
155 | // }
156 |
157 | /// Token pool information structure
158 | #[derive(Debug, Clone)]
159 | pub struct PoolInfo {
160 | /// Token mint address
161 | pub token_mint: String,
162 | /// Current liquidity in SOL
163 | pub liquidity: f64,
164 | /// Current token price in SOL
165 | pub price: f64,
166 | /// Virtual SOL reserves (from bonding curve)
167 | pub virtual_sol_reserves: f64,
168 | /// Virtual token reserves (from bonding curve)
169 | pub virtual_token_reserves: f64,
170 | }
171 |
--------------------------------------------------------------------------------
/src/engine/advanced_trading.rs:
--------------------------------------------------------------------------------
1 | // The provided Rust code defines an AdvancedTradingManager for sophisticated token trading strategies.
2 |
3 | // Its main functions and implementation details are:
4 |
5 | // Risk Profiling: Classifies tokens into risk categories (Low, Medium, High, VeryHigh) based on metrics like market cap, volume, buy/sell ratio, and token age.
6 |
7 | // Entry Criteria Calculation: Uses technical indicators (momentum, volatility, buy/sell ratio, liquidity growth, market sentiment) and bonding curve analysis to determine if a token has a buy signal and calculates an entry confidence score.
8 |
9 | // Dynamic Exit Strategy: Sets multi-level take-profit and stop-loss thresholds tailored to the token's risk profile, including trailing stops and time-based exits.
10 |
11 | // Position Sizing: Applies the Kelly criterion to determine optimal position size based on confidence and portfolio value, with safety caps.
12 |
13 | // Trade Evaluation: Evaluates tokens for entry signals, checks if trading is active, and decides position size before logging the trade decision.
--------------------------------------------------------------------------------
/src/engine/bonding_curve.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use anyhow::{Result, anyhow};
3 | use colored::Colorize;
4 | use anchor_client::solana_sdk::pubkey::Pubkey;
5 |
6 | use crate::common::logger::Logger;
7 | use crate::dex::pump_fun::Pump;
8 |
9 | /// Represents the bonding curve parameters for a token
10 | #[derive(Debug, Clone)]
11 | pub struct BondingCurve {
12 | /// Current liquidity in SOL
13 | pub liquidity: f64,
14 | /// How quickly price rises with buys (steepness)
15 | pub curve_steepness: f64,
16 | /// Price at 0 tokens bought
17 | pub initial_price: f64,
18 | /// Current token price
19 | pub current_price: f64,
20 | /// Last calculated liquidity depth
21 | pub liquidity_depth: f64,
22 | /// History of liquidity changes
23 | pub liquidity_history: Vec<(u64, f64)>, // timestamp, liquidity
24 | }
25 |
26 | impl BondingCurve {
27 | /// Create a new bonding curve analysis
28 | pub fn new(initial_price: f64, current_price: f64, liquidity: f64) -> Self {
29 | // Estimate steepness based on initial and current price
30 | let curve_steepness = if initial_price > 0.0 && current_price > initial_price {
31 | (current_price / initial_price - 1.0) / liquidity
32 | } else {
33 | 0.05 // Default fallback value
34 | };
35 |
36 | Self {
37 | liquidity,
38 | curve_steepness,
39 | initial_price,
40 | current_price,
41 | liquidity_depth: liquidity,
42 | liquidity_history: vec![(
43 | std::time::SystemTime::now()
44 | .duration_since(std::time::UNIX_EPOCH)
45 | .unwrap_or_default()
46 | .as_secs(),
47 | liquidity
48 | )],
49 | }
50 | }
51 |
52 | /// Calculate price impact for a given buy amount in SOL
53 | pub fn calculate_price_impact(&self, buy_amount_sol: f64) -> f64 {
54 | if self.liquidity == 0.0 || buy_amount_sol == 0.0 {
55 | return 0.0;
56 | }
57 |
58 | // Simple bonding curve model: price impact increases with buy size relative to liquidity
59 | let relative_size = buy_amount_sol / self.liquidity;
60 | let impact = relative_size * self.curve_steepness * 100.0;
61 |
62 | // Cap at 100% to avoid unrealistic values
63 | impact.min(100.0)
64 | }
65 |
66 | /// Calculate optimal trade size to limit price impact
67 | pub fn calculate_optimal_trade_size(&self, max_price_impact_pct: f64) -> f64 {
68 | if self.curve_steepness == 0.0 || max_price_impact_pct <= 0.0 {
69 | return 0.0;
70 | }
71 |
72 | // Solve for buy_amount: max_price_impact = (buy_amount/liquidity) * curve_steepness * 100
73 | // buy_amount = (max_price_impact * liquidity) / (curve_steepness * 100)
74 | let optimal_size = (max_price_impact_pct * self.liquidity) / (self.curve_steepness * 100.0);
75 |
76 | // Return a reasonable value capped at 90% of liquidity
77 | optimal_size.min(self.liquidity * 0.9)
78 | }
79 |
80 | /// Calculate estimated price after a buy of a specific size
81 | pub fn estimate_price_after_buy(&self, buy_amount_sol: f64) -> f64 {
82 | let price_impact_pct = self.calculate_price_impact(buy_amount_sol);
83 | self.current_price * (1.0 + price_impact_pct / 100.0)
84 | }
85 |
86 | /// Update the bonding curve with new liquidity data
87 | pub fn update_liquidity(&mut self, new_liquidity: f64) {
88 | self.liquidity = new_liquidity;
89 |
90 | // Add to history
91 | self.liquidity_history.push((
92 | std::time::SystemTime::now()
93 | .duration_since(std::time::UNIX_EPOCH)
94 | .unwrap_or_default()
95 | .as_secs(),
96 | new_liquidity
97 | ));
98 |
99 | // Keep history manageable
100 | if self.liquidity_history.len() > 100 {
101 | self.liquidity_history.remove(0);
102 | }
103 |
104 | // Update liquidity depth as a simple average of recent measurements
105 | if !self.liquidity_history.is_empty() {
106 | self.liquidity_depth = self.liquidity_history
107 | .iter()
108 | .map(|(_, liquidity)| *liquidity)
109 | .sum::() / self.liquidity_history.len() as f64;
110 | }
111 | }
112 |
113 | /// Calculate liquidity growth rate over the last N data points
114 | pub fn calculate_liquidity_growth_rate(&self, lookback_points: usize) -> f64 {
115 | let history_len = self.liquidity_history.len();
116 | if history_len < 2 || lookback_points < 2 {
117 | return 0.0;
118 | }
119 |
120 | let points_to_use = lookback_points.min(history_len);
121 | let start_idx = history_len - points_to_use;
122 |
123 | let oldest_liquidity = self.liquidity_history[start_idx].1;
124 | let newest_liquidity = self.liquidity_history[history_len - 1].1;
125 |
126 | if oldest_liquidity == 0.0 {
127 | return 0.0;
128 | }
129 |
130 | ((newest_liquidity / oldest_liquidity) - 1.0) * 100.0
131 | }
132 | }
133 |
134 | /// Manager for tracking and analyzing token bonding curves
135 | pub struct BondingCurveManager {
136 | /// Bonding curves for each token mint
137 | curves: HashMap,
138 | /// Logger for events
139 | logger: Logger,
140 | }
141 |
142 | impl BondingCurveManager {
143 | /// Create a new bonding curve manager
144 | pub fn new(logger: Logger) -> Self {
145 | Self {
146 | curves: HashMap::new(),
147 | logger,
148 | }
149 | }
150 |
151 | /// Get or create bonding curve for a token
152 | pub fn get_or_create_curve(&mut self, token_mint: &str, initial_price: f64, current_price: f64, liquidity: f64) -> &mut BondingCurve {
153 | if !self.curves.contains_key(token_mint) {
154 | let curve = BondingCurve::new(initial_price, current_price, liquidity);
155 | self.curves.insert(token_mint.to_string(), curve);
156 | }
157 |
158 | self.curves.get_mut(token_mint).unwrap()
159 | }
160 |
161 | /// Update a token's bonding curve with new data
162 | pub fn update_curve(&mut self, token_mint: &str, current_price: f64, current_liquidity: f64) -> Result<()> {
163 | if let Some(curve) = self.curves.get_mut(token_mint) {
164 | // Update price
165 | curve.current_price = current_price;
166 |
167 | // Update liquidity
168 | curve.update_liquidity(current_liquidity);
169 |
170 | // Recalculate curve steepness if possible
171 | if curve.initial_price > 0.0 && current_price > curve.initial_price && current_liquidity > 0.0 {
172 | curve.curve_steepness = (current_price / curve.initial_price - 1.0) / current_liquidity;
173 | }
174 |
175 | Ok(())
176 | } else {
177 | Err(anyhow!("No curve exists for token {}", token_mint))
178 | }
179 | }
180 |
181 | /// Get price impact analysis for different trade sizes
182 | pub fn get_price_impact_analysis(&self, token_mint: &str) -> Result {
183 | let curve = self.curves.get(token_mint)
184 | .ok_or_else(|| anyhow!("No curve exists for token {}", token_mint))?;
185 |
186 | // Calculate optimal trade size for 5% price impact
187 | let optimal_trade_size = curve.calculate_optimal_trade_size(5.0);
188 |
189 | // Calculate impact for standard sizes
190 | let impact_1sol = curve.calculate_price_impact(1.0);
191 | let impact_5sol = curve.calculate_price_impact(5.0);
192 |
193 | Ok(BondingCurveAnalysis {
194 | token_mint: token_mint.to_string(),
195 | liquidity: curve.liquidity,
196 | liquidity_depth: curve.liquidity_depth,
197 | curve_steepness: curve.curve_steepness,
198 | optimal_trade_size,
199 | price_impact_1sol: impact_1sol,
200 | price_impact_5sol: impact_5sol,
201 | liquidity_growth_rate: curve.calculate_liquidity_growth_rate(5),
202 | })
203 | }
204 |
205 | /// Analyze transaction for its impact on curve and price
206 | pub async fn analyze_transaction(
207 | &mut self,
208 | token_mint: &str,
209 | transaction_amount_sol: f64,
210 | is_buy: bool,
211 | swapx: &Pump,
212 | ) -> Result {
213 | // Default values
214 | let mut current_liquidity = 0.0;
215 | let mut current_price = 0.0;
216 |
217 | // Try to get actual data from DEX
218 | if let Ok(pool_info) = swapx.get_token_pool_info(&token_mint.parse::().unwrap()).await {
219 | current_liquidity = pool_info.liquidity;
220 | current_price = pool_info.price;
221 | }
222 |
223 | // Get or create curve
224 | let curve = self.get_or_create_curve(token_mint, 0.0, current_price, current_liquidity);
225 |
226 | // Update curve with new data
227 | curve.current_price = current_price;
228 | curve.update_liquidity(current_liquidity);
229 |
230 | // Calculate expected price impact
231 | let price_impact = if is_buy {
232 | curve.calculate_price_impact(transaction_amount_sol)
233 | } else {
234 | // Sell impact is typically larger and in the opposite direction
235 | -curve.calculate_price_impact(transaction_amount_sol) * 1.2
236 | };
237 |
238 | // Calculate projected new price
239 | let projected_price = current_price * (1.0 + price_impact / 100.0);
240 |
241 | // Calculate liquidity change
242 | let liquidity_change = if is_buy {
243 | transaction_amount_sol
244 | } else {
245 | -transaction_amount_sol
246 | };
247 |
248 | let projected_liquidity = (current_liquidity + liquidity_change).max(0.0);
249 |
250 | // Log the analysis
251 | self.logger.log(format!(
252 | "Transaction impact analysis for {}: {} SOL {} - Price impact: {}%, New price: {} SOL, New liquidity: {} SOL",
253 | token_mint,
254 | transaction_amount_sol,
255 | if is_buy { "BUY".green() } else { "SELL".red() },
256 | price_impact,
257 | projected_price,
258 | projected_liquidity
259 | ).cyan().to_string());
260 |
261 | Ok(TransactionImpactAnalysis {
262 | token_mint: token_mint.to_string(),
263 | transaction_amount_sol,
264 | is_buy,
265 | current_price,
266 | projected_price,
267 | price_impact_pct: price_impact,
268 | current_liquidity,
269 | projected_liquidity,
270 | })
271 | }
272 | }
273 |
274 | /// Analysis of a bonding curve
275 | #[derive(Debug, Clone)]
276 | pub struct BondingCurveAnalysis {
277 | /// Token mint address
278 | pub token_mint: String,
279 | /// Current liquidity in SOL
280 | pub liquidity: f64,
281 | /// Liquidity depth (average of recent measurements)
282 | pub liquidity_depth: f64,
283 | /// Steepness of the bonding curve
284 | pub curve_steepness: f64,
285 | /// Optimal trade size for minimal impact (SOL)
286 | pub optimal_trade_size: f64,
287 | /// Price impact for 1 SOL buy (%)
288 | pub price_impact_1sol: f64,
289 | /// Price impact for 5 SOL buy (%)
290 | pub price_impact_5sol: f64,
291 | /// Liquidity growth rate (%)
292 | pub liquidity_growth_rate: f64,
293 | }
294 |
295 | /// Analysis of a transaction's impact on price and liquidity
296 | #[derive(Debug, Clone)]
297 | pub struct TransactionImpactAnalysis {
298 | /// Token mint address
299 | pub token_mint: String,
300 | /// Transaction amount in SOL
301 | pub transaction_amount_sol: f64,
302 | /// Whether the transaction is a buy
303 | pub is_buy: bool,
304 | /// Current token price
305 | pub current_price: f64,
306 | /// Projected price after transaction
307 | pub projected_price: f64,
308 | /// Price impact as a percentage
309 | pub price_impact_pct: f64,
310 | /// Current liquidity in SOL
311 | pub current_liquidity: f64,
312 | /// Projected liquidity after transaction
313 | pub projected_liquidity: f64,
314 | }
--------------------------------------------------------------------------------
/src/engine/enhanced_monitor.rs:
--------------------------------------------------------------------------------
1 | // Connects to a Yellowstone gRPC service to subscribe to filtered Solana blockchain transaction updates in real-time.
2 |
3 | // Tracks token activity and market metrics (price changes, volume, buy/sell counts) using a TokenTracker.
4 |
5 | // Manages token lists (whitelist/blacklist) with periodic review and persistence.
6 |
7 | // Implements buying and selling logic through BuyManager and SellManager based on configurable thresholds like take profit and stop loss percentages.
8 |
9 | // Cleans up inactive tokens and records token activity data periodically.
10 |
11 | // Integrates with Telegram for sending notifications and receiving commands.
12 |
13 | // Uses asynchronous Rust (tokio) for concurrency and efficient streaming data handling.
--------------------------------------------------------------------------------
/src/engine/enhanced_token_trader.rs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tran325/Pump-Fun-Pump-Swap-Sniper-Copy-Trading-Bot/5e26107b205ee1240305ed6300429c38d6bcd289/src/engine/enhanced_token_trader.rs
--------------------------------------------------------------------------------
/src/engine/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod monitor;
2 | pub mod swap;
3 | pub mod token_tracker;
4 | pub mod token_selling;
5 | pub mod token_buying;
6 | pub mod advanced_trading;
7 | pub mod bonding_curve;
8 | pub mod risk_management;
9 | pub mod enhanced_monitor;
10 | pub mod token_list_manager;
11 | pub mod enhanced_token_trader;
12 |
--------------------------------------------------------------------------------
/src/engine/monitor.rs:
--------------------------------------------------------------------------------
1 | // use std::hash::Hash;
2 | use anchor_lang::prelude::Pubkey;
3 | use anchor_client::solana_sdk::hash::Hash;
4 | use std::sync::{Arc, Mutex};
5 | use std::time::SystemTime;
6 | use crate::common::{
7 | blacklist::Blacklist,
8 | config::{AppState, SwapConfig},
9 | };
10 | use spl_associated_token_account::get_associated_token_address;
11 | use solana_program_pack::Pack;
12 | use yellowstone_grpc_proto::prelude::SubscribeUpdateTransaction;
13 | use anyhow::Result;
14 | use std::str::FromStr;
15 | use base64;
16 | use anchor_client::solana_client::rpc_client::RpcClient;
17 | use spl_token::state::Mint;
18 | use crate::dex::pump_fun::{get_pda, PUMP_PROGRAM};
19 | // PumpFun constants
20 | pub const PUMPFUN_CREATE_DATA_PREFIX: &str = "Program data: G3KpTd7rY3Y";
21 | pub const PUMP_FUN_BUY_OR_SELL_PROGRAM_DATA_PREFIX: &str = "Program data: vdt/007mYe";
22 | pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 1_000_000_000; // 1 SOL in lamports
23 | pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_000_000_000_000; // 1 trillion tokens
24 |
25 | // Type definition for RequestItem
26 | pub type RequestItem = String;
27 |
28 | #[derive(Clone, Debug)]
29 | pub struct BondingCurveInfo {
30 | pub bonding_curve: Pubkey,
31 | pub new_virtual_sol_reserve: u64,
32 | pub new_virtual_token_reserve: u64,
33 | }
34 |
35 | #[derive(Clone, Debug)]
36 | pub async fn new_token_trader_pumpfun(
37 | _yellowstone_grpc_http: String,
38 | _yellowstone_grpc_token: String,
39 | _yellowstone_ping_interval: u64,
40 | _yellowstone_reconnect_delay: u64,
41 | _yellowstone_max_retries: u32,
42 | _app_state: AppState,
43 | _swap_config: SwapConfig,
44 | _blacklist: Blacklist,
45 | _time_exceed: u64,
46 | _counter_limit: u64,
47 | _min_dev_buy: u64,
48 | _max_dev_buy: u64,
49 | _telegram_bot_token: String,
50 | _telegram_chat_id: String,
51 | _bundle_check: bool,
52 | _min_last_time: u64,
53 | ) -> Result<(), String> {
54 | // ... function implementation ...
55 | Ok(())
56 | }
57 |
58 | impl Default for BondingCurveInfo {
59 | fn default() -> Self {
60 | Self {
61 | bonding_curve: Pubkey::default(),
62 | new_virtual_sol_reserve: INITIAL_VIRTUAL_SOL_RESERVES,
63 | new_virtual_token_reserve: INITIAL_VIRTUAL_TOKEN_RESERVES,
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/engine/swap.rs:
--------------------------------------------------------------------------------
1 | use clap::ValueEnum;
2 | use serde::Deserialize;
3 | use std::str::FromStr;
4 | use anyhow::{Error, Result};
5 |
6 | #[derive(ValueEnum, Debug, Clone, Deserialize, PartialEq)]
7 | pub enum SwapDirection {
8 | #[serde(rename = "buy")]
9 | Buy,
10 | #[serde(rename = "sell")]
11 | Sell,
12 | }
13 | impl From for u8 {
14 | fn from(value: SwapDirection) -> Self {
15 | match value {
16 | SwapDirection::Buy => 0,
17 | SwapDirection::Sell => 1,
18 | }
19 | }
20 | }
21 |
22 | #[derive(ValueEnum, Debug, Clone, Deserialize)]
23 | pub enum SwapInType {
24 | /// Quantity
25 | #[serde(rename = "qty")]
26 | Qty,
27 | /// Percentage
28 | #[serde(rename = "pct")]
29 | Pct,
30 | }
31 |
32 | impl FromStr for SwapDirection {
33 | type Err = Error;
34 |
35 | fn from_str(s: &str) -> Result {
36 | match s.to_lowercase().as_str() {
37 | "buy" => Ok(SwapDirection::Buy),
38 | "sell" => Ok(SwapDirection::Sell),
39 | _ => Err(anyhow::anyhow!("Invalid swap direction: {}", s)),
40 | }
41 | }
42 | }
43 |
44 | impl FromStr for SwapInType {
45 | type Err = Error;
46 |
47 | fn from_str(s: &str) -> Result {
48 | match s.to_lowercase().as_str() {
49 | "qty" => Ok(SwapInType::Qty),
50 | "pct" => Ok(SwapInType::Pct),
51 | _ => Err(anyhow::anyhow!("Invalid swap in type: {}", s)),
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/engine/token_buying.rs:
--------------------------------------------------------------------------------
1 | use std::collections::{HashMap, HashSet};
2 | use std::sync::{Arc, Mutex};
3 | use std::time::Duration;
4 | use anyhow::{Result, anyhow};
5 | use colored::Colorize;
6 | use tokio::time;
7 | use tokio::time::Instant;
8 |
9 | use crate::common::logger::Logger;
10 | use crate::dex::pump_fun::Pump;
11 | use crate::engine::swap::{SwapDirection, SwapInType};
12 | use crate::common::config::{SwapConfig, Status, LiquidityPool};
13 | use crate::core::tx;
14 | use crate::services::telegram::TelegramService;
15 | use crate::engine::token_tracker::{TokenTracker, ExtendedTokenInfo};
16 | use crate::engine::advanced_trading::{RiskProfile, AdvancedTradingManager};
17 | use crate::engine::monitor::BondingCurveInfo;
18 |
19 | /// Defines a strategy for token buying with configurable parameters
20 | #[derive(Clone, Debug)]
21 | pub struct BuyStrategy {
22 | /// Mint address of the token
23 | pub token_mint: String,
24 | /// Current price of the token
25 | pub current_price: f64,
26 | /// Amount to buy in SOL
27 | pub buy_amount: f64,
28 | /// Maximum price increase tolerated for entry (%)
29 | pub max_entry_slippage: f64,
30 | /// Risk profile of the token
31 | pub risk_profile: RiskProfile,
32 | /// Minimum buy-to-sell ratio to consider purchase
33 | pub min_buy_sell_ratio: f64,
34 | /// Maximum market cap to consider (in SOL)
35 | pub max_market_cap: f64,
36 | /// Minimum launcher SOL balance
37 | pub min_launcher_sol: f64,
38 | /// Maximum launcher SOL balance
39 | pub max_launcher_sol: f64,
40 | /// Whether this is a time-sensitive opportunity
41 | pub time_sensitive: bool,
42 | /// Confidence score (0-100)
43 | pub confidence: u64,
44 | /// Timestamp when the buy strategy was created
45 | pub created_at: Instant,
46 | /// Maximum age of token to consider for buying (in seconds)
47 | pub max_token_age: u64,
48 | /// Whether buying is enabled for this token
49 | pub buying_enabled: bool,
50 | }
51 |
--------------------------------------------------------------------------------
/src/engine/token_list_manager.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 | use std::time::Instant;
3 | use tokio::time::Duration;
4 | use tokio::sync::Mutex;
5 |
6 | use crate::common::{
7 | logger::Logger,
8 | blacklist::{Blacklist, BlacklistManager},
9 | whitelist::{Whitelist, WhitelistManager},
10 | };
11 |
12 | /// Manager for handling token lists (whitelist/blacklist) with review cycles
13 | #[allow(dead_code)]
14 | pub struct TokenListManager {
15 | /// Whitelist manager
16 | whitelist_manager: WhitelistManager,
17 | /// Blacklist manager
18 | blacklist_manager: BlacklistManager,
19 | /// Logger
20 | logger: Logger,
21 | /// Last save timestamp
22 | last_save: Arc>,
23 | /// Save interval in milliseconds
24 | save_interval_ms: u64,
25 | /// Tokens seen in the current review cycle
26 | active_tokens_in_cycle: Arc>>,
27 | }
28 |
29 | impl TokenListManager {
30 | /// Create a new token list manager
31 | pub fn new(
32 | whitelist_path: &str,
33 | blacklist_path: &str,
34 | review_cycle_ms: u64,
35 | save_interval_ms: u64,
36 | logger: Logger
37 | ) -> Result {
38 | // Initialize whitelist
39 | let whitelist = Whitelist::new(whitelist_path, review_cycle_ms)?;
40 | let whitelist_manager = WhitelistManager::new(whitelist.clone(), save_interval_ms);
41 |
42 | // Initialize blacklist
43 | let blacklist = Blacklist::new(blacklist_path)?;
44 | let blacklist_manager = BlacklistManager::new(blacklist.clone(), save_interval_ms);
45 |
46 | logger.log(format!(
47 | "TokenListManager initialized - Whitelist: {} tokens, Blacklist: {} tokens",
48 | whitelist.len(),
49 | blacklist.len()
50 | ));
51 |
52 | Ok(Self {
53 | whitelist_manager,
54 | blacklist_manager,
55 | logger,
56 | last_save: Arc::new(std::sync::Mutex::new(Instant::now())),
57 | save_interval_ms,
58 | active_tokens_in_cycle: Arc::new(Mutex::new(Vec::new())),
59 | })
60 | }
61 |
62 | /// Process a token, checking blacklist/whitelist and marking as active in the current cycle
63 | pub async fn process_token(&self, token_mint: &str) -> TokenListStatus {
64 | // First check blacklist
65 | if self.blacklist_manager.is_blacklisted(token_mint).await {
66 | return TokenListStatus::Blacklisted;
67 | }
68 |
69 | // Then handle whitelist
70 | let is_whitelisted = self.whitelist_manager.is_whitelisted(token_mint).await;
71 | // Add to active tokens in this cycle
72 | let mut active_tokens = self.active_tokens_in_cycle.lock().await;
73 | if !active_tokens.contains(&token_mint.to_string()) {
74 | active_tokens.push(token_mint.to_string());
75 | }
76 |
77 | // Mark as active in whitelist if it's there
78 | if is_whitelisted {
79 | self.whitelist_manager.mark_as_active(token_mint).await;
80 | TokenListStatus::Whitelisted
81 | } else {
82 | TokenListStatus::NotListed
83 | }
84 | }
85 |
86 | /// Check if it's time for a review cycle and process it
87 | pub async fn check_review_cycle(&self) -> bool {
88 | let review_processed = self.whitelist_manager.check_review_cycle().await;
89 |
90 | if review_processed {
91 | self.logger.log("Review cycle completed - Updated whitelist with active tokens only".to_string());
92 |
93 | // Reset active tokens for the next cycle
94 | let mut active_tokens = self.active_tokens_in_cycle.lock().await;
95 | active_tokens.clear();
96 |
97 | // Force save both lists
98 | let _ = self.whitelist_manager.save().await;
99 | let _ = self.blacklist_manager.save().await;
100 | }
101 |
102 | review_processed
103 | }
104 |
105 | /// Check if a token is blacklisted
106 | pub async fn is_blacklisted(&self, token_mint: &str) -> bool {
107 | self.blacklist_manager.is_blacklisted(token_mint).await
108 | }
109 |
110 | /// Check if a token is whitelisted
111 | pub async fn is_whitelisted(&self, token_mint: &str) -> bool {
112 | self.whitelist_manager.is_whitelisted(token_mint).await
113 | }
114 |
115 | /// Add a token to the blacklist
116 | pub async fn add_to_blacklist(&self, token_mint: &str) -> bool {
117 | self.blacklist_manager.add_address(token_mint).await
118 | }
119 |
120 | /// Add a token to the whitelist
121 | pub async fn add_to_whitelist(&self, token_mint: &str) -> bool {
122 | self.whitelist_manager.add_address(token_mint).await
123 | }
124 |
125 | /// Get active tokens in the current cycle
126 | pub async fn get_active_tokens(&self) -> Vec {
127 | let active_tokens = self.active_tokens_in_cycle.lock().await;
128 | active_tokens.clone()
129 | }
130 |
131 | /// Start background task for periodic saving and review cycle checking
132 | pub fn start_background_tasks(&self) {
133 | let whitelist_manager = self.whitelist_manager.clone();
134 | let blacklist_manager = self.blacklist_manager.clone();
135 | let logger = self.logger.clone();
136 | let save_interval_ms = self.save_interval_ms;
137 |
138 | tokio::spawn(async move {
139 | let mut save_interval = tokio::time::interval(Duration::from_millis(save_interval_ms));
140 |
141 | loop {
142 | save_interval.tick().await;
143 |
144 | // Check and process review cycle
145 | if whitelist_manager.check_review_cycle().await {
146 | logger.log("Review cycle completed - Updated whitelist with active tokens only".to_string());
147 | }
148 |
149 | // Save lists
150 | if let Err(e) = whitelist_manager.save().await {
151 | logger.log(format!("Error saving whitelist: {}", e));
152 | }
153 |
154 | if let Err(e) = blacklist_manager.save().await {
155 | logger.log(format!("Error saving blacklist: {}", e));
156 | }
157 | }
158 | });
159 | }
160 |
161 | /// Mark a token as active in the current review cycle
162 | pub async fn mark_as_active(&self, token_mint: &str) {
163 | // Mark as active in the whitelist
164 | self.whitelist_manager.mark_as_active(token_mint).await;
165 |
166 | // Add to active tokens in this cycle
167 | let mut active_tokens = self.active_tokens_in_cycle.lock().await;
168 | if !active_tokens.contains(&token_mint.to_string()) {
169 | active_tokens.push(token_mint.to_string());
170 | }
171 | }
172 | }
173 |
174 | /// Status of a token in relation to the whitelist/blacklist
175 | #[derive(Debug, Clone, PartialEq)]
176 | pub enum TokenListStatus {
177 | /// Token is in the whitelist
178 | Whitelisted,
179 | /// Token is in the blacklist
180 | Blacklisted,
181 | /// Token is not in either list
182 | NotListed,
183 | }
184 |
185 | #[cfg(test)]
186 | mod tests {
187 | use super::*;
188 | use tempfile::NamedTempFile;
189 | use colored::Colorize;
190 |
191 | #[tokio::test]
192 | async fn test_token_list_manager() {
193 | let whitelist_file = NamedTempFile::new().unwrap();
194 | let blacklist_file = NamedTempFile::new().unwrap();
195 |
196 | let whitelist_path = whitelist_file.path().to_str().unwrap();
197 | let blacklist_path = blacklist_file.path().to_str().unwrap();
198 |
199 | let logger = Logger::new("[TEST] => ".blue().to_string());
200 |
201 | let manager = TokenListManager::new(
202 | whitelist_path,
203 | blacklist_path,
204 | 1000, // 1 second review cycle
205 | 5000, // 5 second save interval
206 | logger
207 | ).unwrap();
208 |
209 | // Test blacklist
210 | assert!(manager.add_to_blacklist("blacktoken").await);
211 | assert!(manager.is_blacklisted("blacktoken").await);
212 |
213 | // Test whitelist
214 | assert!(manager.add_to_whitelist("whitetoken").await);
215 | assert!(manager.is_whitelisted("whitetoken").await);
216 |
217 | // Test process token
218 | assert_eq!(manager.process_token("blacktoken").await, TokenListStatus::Blacklisted);
219 | assert_eq!(manager.process_token("whitetoken").await, TokenListStatus::Whitelisted);
220 | assert_eq!(manager.process_token("unlisted").await, TokenListStatus::NotListed);
221 |
222 | // Verify active tokens
223 | let active_tokens = manager.get_active_tokens().await;
224 | assert!(active_tokens.contains(&"blacktoken".to_string()));
225 | assert!(active_tokens.contains(&"whitetoken".to_string()));
226 | assert!(active_tokens.contains(&"unlisted".to_string()));
227 | }
228 | }
--------------------------------------------------------------------------------
/src/engine/token_selling.rs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tran325/Pump-Fun-Pump-Swap-Sniper-Copy-Trading-Bot/5e26107b205ee1240305ed6300429c38d6bcd289/src/engine/token_selling.rs
--------------------------------------------------------------------------------
/src/engine/token_tracker.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::sync::{Arc, Mutex, RwLock};
3 | use std::time::{Duration, Instant};
4 | use colored::Colorize;
5 | use tokio::task::JoinHandle;
6 | use anchor_client::solana_sdk::pubkey::Pubkey;
7 | use anchor_client::solana_client::nonblocking::rpc_client::RpcClient;
8 | use anyhow::{Result, anyhow};
9 | use spl_token::solana_program::native_token::lamports_to_sol;
10 |
11 | use crate::common::logger::Logger;
12 | use crate::engine::monitor::TransactionType;
13 |
14 | /// Represents detailed information about a token
15 | #[derive(Debug)]
16 | pub struct ExtendedTokenInfo {
17 | pub token_mint: String,
18 | pub token_name: Option,
19 | pub token_symbol: Option,
20 | pub current_token_price: f64,
21 | pub max_token_price: f64,
22 | pub initial_price: f64,
23 | pub total_supply: u64,
24 | pub buy_tx_num: u32,
25 | pub sell_tx_num: u32,
26 | pub token_mint_timestamp: Instant,
27 | pub launcher_sol_balance: Option,
28 | pub market_cap: Option,
29 | pub volume: Option,
30 | pub dev_buy_amount: Option,
31 | pub bundle_check: Option,
32 | pub active_task: Option>,
33 | pub is_monitored: bool,
34 | pub dev_wallet: Option,
35 | pub token_amount: Option,
36 | }
37 |
38 | impl ExtendedTokenInfo {
39 | pub fn new(
40 | token_mint: String,
41 | token_name: Option,
42 | token_symbol: Option,
43 | current_price: f64,
44 | total_supply: u64,
45 | dev_buy_amount: Option,
46 | launcher_sol_balance: Option,
47 | bundle_check: Option,
48 | dev_wallet: Option,
49 | ) -> Self {
50 | Self {
51 | token_mint,
52 | token_name,
53 | token_symbol,
54 | current_token_price: current_price,
55 | max_token_price: current_price,
56 | initial_price: current_price,
57 | total_supply,
58 | buy_tx_num: 1, // Start with 1 since we're detecting the initial transaction
59 | sell_tx_num: 0,
60 | token_mint_timestamp: Instant::now(),
61 | launcher_sol_balance,
62 | market_cap: Some(current_price * (total_supply as f64)),
63 | volume: dev_buy_amount,
64 | dev_buy_amount,
65 | bundle_check,
66 | active_task: None,
67 | is_monitored: false,
68 | dev_wallet,
69 | token_amount: None,
70 | }
71 | }
72 |
73 | /// Update token price and related statistics
74 | pub fn update_price(&mut self, new_price: f64, is_buy: bool) {
75 | self.current_token_price = new_price;
76 |
77 | // Update max price if current price is higher
78 | if new_price > self.max_token_price {
79 | self.max_token_price = new_price;
80 | }
81 |
82 | // Update transaction counts
83 | if is_buy {
84 | self.buy_tx_num += 1;
85 | } else {
86 | self.sell_tx_num += 1;
87 | }
88 |
89 | // Update market cap based on new price
90 | if let Some(mc) = self.market_cap.as_mut() {
91 | *mc = new_price * (self.total_supply as f64);
92 | }
93 |
94 | // Update volume metrics
95 | if let Some(vol) = self.volume.as_mut() {
96 | *vol += new_price;
97 | }
98 | }
99 |
100 | /// Calculate price delta percentage from max price
101 | pub fn price_delta_percent(&self) -> f64 {
102 | if self.max_token_price == 0.0 {
103 | return 0.0;
104 | }
105 |
106 | ((self.max_token_price - self.current_token_price) / self.max_token_price) * 100.0
107 | }
108 |
109 | /// Check if token has been active for longer than the specified duration
110 | pub fn exceeds_time_threshold(&self, threshold: Duration) -> bool {
111 | self.token_mint_timestamp.elapsed() > threshold
112 | }
113 |
114 | /// Convert to a telegram-compatible TokenInfo
115 | pub fn to_telegram_token_info(&self) -> crate::services::telegram::TokenInfo {
116 | // Calculate buy/sell ratio
117 | let total_tx = self.buy_tx_num + self.sell_tx_num;
118 | let buy_ratio = if total_tx > 0 {
119 | Some((self.buy_tx_num as f64 / total_tx as f64 * 100.0) as i32)
120 | } else {
121 | None
122 | };
123 |
124 | let sell_ratio = if total_tx > 0 {
125 | Some((self.sell_tx_num as f64 / total_tx as f64 * 100.0) as i32)
126 | } else {
127 | None
128 | };
129 |
130 | // Estimate volume buy/sell based on ratio
131 | let volume_value = self.volume.unwrap_or(0.0);
132 | let volume_buy = if let Some(ratio) = buy_ratio {
133 | Some(volume_value * (ratio as f64 / 100.0))
134 | } else {
135 | None
136 | };
137 |
138 | let volume_sell = if let Some(ratio) = sell_ratio {
139 | Some(volume_value * (ratio as f64 / 100.0))
140 | } else {
141 | None
142 | };
143 |
144 | // Token price relative to initial price (simplified as 1.0x for now)
145 | // In a real implementation, you'd compare to some base price
146 | let token_price = Some(1.0); // Placeholder, should be calculated from real data
147 |
148 | // Get SOL balance from launcher_sol_balance
149 | let sol_balance = self.launcher_sol_balance;
150 |
151 | crate::services::telegram::TokenInfo {
152 | address: self.token_mint.clone(),
153 | name: self.token_name.clone(),
154 | symbol: self.token_symbol.clone(),
155 | market_cap: self.market_cap,
156 | volume: self.volume,
157 | buy_sell_count: Some((self.buy_tx_num + self.sell_tx_num) as i32),
158 | dev_buy_amount: self.dev_buy_amount,
159 | launcher_sol_balance: self.launcher_sol_balance,
160 | bundle_check: self.bundle_check,
161 | // ATH is equivalent to max_token_price in this context
162 | ath: Some(self.max_token_price),
163 | // Add new fields from screenshot
164 | buy_ratio,
165 | sell_ratio,
166 | total_buy_sell: Some(total_tx as i32),
167 | volume_buy,
168 | volume_sell,
169 | token_price,
170 | dev_wallet: self.dev_wallet.clone(),
171 | sol_balance,
172 | }
173 | }
174 |
175 | // Manual implementation of clone since JoinHandle doesn't implement Clone
176 | pub fn clone_without_task(&self) -> Self {
177 | Self {
178 | token_mint: self.token_mint.clone(),
179 | token_name: self.token_name.clone(),
180 | token_symbol: self.token_symbol.clone(),
181 | current_token_price: self.current_token_price,
182 | max_token_price: self.max_token_price,
183 | initial_price: self.initial_price,
184 | total_supply: self.total_supply,
185 | buy_tx_num: self.buy_tx_num,
186 | sell_tx_num: self.sell_tx_num,
187 | token_mint_timestamp: self.token_mint_timestamp,
188 | launcher_sol_balance: self.launcher_sol_balance,
189 | market_cap: self.market_cap,
190 | volume: self.volume,
191 | dev_buy_amount: self.dev_buy_amount,
192 | bundle_check: self.bundle_check,
193 | active_task: None, // Don't clone the task
194 | is_monitored: self.is_monitored,
195 | dev_wallet: self.dev_wallet.clone(), // Include the dev wallet in the clone
196 | token_amount: self.token_amount,
197 | }
198 | }
199 | }
200 |
201 | #[derive(Debug, Clone)]
202 | pub struct TokenFilter {
203 | pub min_launcher_sol: f64,
204 | pub max_launcher_sol: f64,
205 | pub min_market_cap: f64,
206 | pub max_market_cap: f64,
207 | pub min_volume: f64,
208 | pub max_volume: f64,
209 | pub min_buy_sell_count: u32,
210 | pub max_buy_sell_count: u32,
211 | pub price_delta_threshold: f64,
212 | pub time_delta_threshold: u64,
213 | }
214 |
215 | impl Default for TokenFilter {
216 | fn default() -> Self {
217 | Self {
218 | min_launcher_sol: 0.1,
219 | max_launcher_sol: 100.0,
220 | min_market_cap: 0.1,
221 | max_market_cap: 1_000_000.0,
222 | min_volume: 0.1,
223 | max_volume: 1_000_000.0,
224 | min_buy_sell_count: 1,
225 | max_buy_sell_count: 1000,
226 | price_delta_threshold: 10.0,
227 | time_delta_threshold: 3600,
228 | }
229 | }
230 | }
231 |
232 | #[derive(Debug, Clone)]
233 | pub struct TrackedTokenInfo {
234 | pub token_mint: String,
235 | pub token_name: Option,
236 | pub token_symbol: Option,
237 | pub price: f64,
238 | pub market_cap: f64,
239 | pub volume: f64,
240 | pub buy_count: u32,
241 | pub sell_count: u32,
242 | pub last_updated: Instant,
243 | pub first_seen: Instant,
244 | pub price_history: Vec<(Instant, f64)>,
245 | pub highest_price: f64,
246 | pub lowest_price: f64,
247 | pub launcher_sol: f64,
248 | }
249 |
250 | impl TrackedTokenInfo {
251 | pub fn new(token_mint: String, token_name: Option, token_symbol: Option, price: f64, launcher_sol: f64) -> Self {
252 | let now = Instant::now();
253 | Self {
254 | token_mint,
255 | token_name,
256 | token_symbol,
257 | price,
258 | market_cap: price,
259 | volume: price,
260 | buy_count: 1,
261 | sell_count: 0,
262 | last_updated: now,
263 | first_seen: now,
264 | price_history: vec![(now, price)],
265 | highest_price: price,
266 | lowest_price: price,
267 | launcher_sol,
268 | }
269 | }
270 |
271 | pub fn update_price(&mut self, new_price: f64) {
272 | let now = Instant::now();
273 | self.price = new_price;
274 | self.last_updated = now;
275 | self.price_history.push((now, new_price));
276 |
277 | if new_price > self.highest_price {
278 | self.highest_price = new_price;
279 | }
280 |
281 | if new_price < self.lowest_price {
282 | self.lowest_price = new_price;
283 | }
284 |
285 | // Keep only the last 100 price points
286 | if self.price_history.len() > 100 {
287 | self.price_history.remove(0);
288 | }
289 | }
290 |
291 | pub fn record_buy(&mut self, amount: f64) {
292 | self.buy_count += 1;
293 | self.volume += amount;
294 | }
295 |
296 | pub fn record_sell(&mut self, amount: f64) {
297 | self.sell_count += 1;
298 | self.volume += amount;
299 | }
300 |
301 | pub fn calculate_price_change(&self, duration: Duration) -> Option {
302 | let now = Instant::now();
303 | let threshold_time = now.checked_sub(duration)?;
304 |
305 | // Find the oldest price point within the specified duration
306 | let oldest_price_in_duration = self.price_history.iter()
307 | .find(|(time, _)| *time >= threshold_time)
308 | .map(|(_, price)| *price);
309 |
310 | match oldest_price_in_duration {
311 | Some(old_price) => {
312 | if old_price == 0.0 {
313 | return Some(0.0);
314 | }
315 | Some((self.price - old_price) / old_price * 100.0)
316 | },
317 | None => None,
318 | }
319 | }
320 |
321 | pub fn passes_filter(&self, filter: &TokenFilter) -> bool {
322 | // Check launcher SOL bounds
323 | if self.launcher_sol < filter.min_launcher_sol || self.launcher_sol > filter.max_launcher_sol {
324 | return false;
325 | }
326 |
327 | // Check market cap bounds
328 | if self.market_cap < filter.min_market_cap || self.market_cap > filter.max_market_cap {
329 | return false;
330 | }
331 |
332 | // Check volume bounds
333 | if self.volume < filter.min_volume || self.volume > filter.max_volume {
334 | return false;
335 | }
336 |
337 | // Check transaction count bounds
338 | let total_transactions = self.buy_count + self.sell_count;
339 | if total_transactions < filter.min_buy_sell_count || total_transactions > filter.max_buy_sell_count {
340 | return false;
341 | }
342 |
343 | // Check price delta if we have enough history
344 | if let Some(price_change) = self.calculate_price_change(Duration::from_secs(filter.time_delta_threshold)) {
345 | if price_change.abs() < filter.price_delta_threshold {
346 | return false;
347 | }
348 | }
349 |
350 | true
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/src/error/mod.rs:
--------------------------------------------------------------------------------
1 | //! Error types for the Pump.fun SDK.
2 | //!
3 | //! This module defines the `ClientError` enum, which encompasses various error types that can occur when interacting with the Pump.fun program.
4 | //! It includes specific error cases for bonding curve operations, metadata uploads, Solana client errors, and more.
5 | //!
6 | //! The `ClientError` enum provides a comprehensive set of error types to help developers handle and debug issues that may arise during interactions with the Pump.fun program.
7 | //!
8 | //! # Error Types
9 | //!
10 | //! - `BondingCurveNotFound`: The bonding curve account was not found.
11 | //! - `BondingCurveError`: An error occurred while interacting with the bonding curve.
12 | //! - `BorshError`: An error occurred while serializing or deserializing data using Borsh.
13 | //! - `SolanaClientError`: An error occurred while interacting with the Solana RPC client.
14 | //! - `UploadMetadataError`: An error occurred while uploading metadata to IPFS.
15 | //! - `InvalidInput`: Invalid input parameters were provided.
16 | //! - `InsufficientFunds`: Insufficient funds for a transaction.
17 | //! - `SimulationError`: Transaction simulation failed.
18 | //! - `RateLimitExceeded`: Rate limit exceeded.
19 |
20 | use serde_json::Error;
21 | use anchor_client::solana_client::{
22 | client_error::ClientError as SolanaClientError, pubsub_client::PubsubClientError,
23 | };
24 | use anchor_client::solana_sdk::pubkey::ParsePubkeyError;
25 |
26 | // Define our own Error type alias to avoid confusion with the imported Error
27 | pub type ErrorType = ClientError;
28 |
29 | // #[derive(Debug)]
30 | // #[allow(dead_code)]
31 | // pub struct AppError(anyhow::Error);
32 |
33 | // impl From for AppError
34 | // where
35 | // E: Into,
36 | // {
37 | // fn from(err: E) -> Self {
38 | // Self(err.into())
39 | // }
40 | // }
41 |
42 | #[derive(Debug)]
43 | pub enum ClientError {
44 | /// Bonding curve account was not found
45 | BondingCurveNotFound,
46 | /// Error related to bonding curve operations
47 | BondingCurveError(&'static str),
48 | /// Error deserializing data using Borsh
49 | BorshError(std::io::Error),
50 | /// Error from Solana RPC client
51 | SolanaClientError(anchor_client::solana_client::client_error::ClientError),
52 | /// Error uploading metadata
53 | UploadMetadataError(Box),
54 | /// Invalid input parameters
55 | InvalidInput(&'static str),
56 | /// Insufficient funds for transaction
57 | InsufficientFunds,
58 | /// Transaction simulation failed
59 | SimulationError(String),
60 | /// Rate limit exceeded
61 | RateLimitExceeded,
62 |
63 | OrderLimitExceeded,
64 |
65 | ExternalService(String),
66 |
67 | Redis(String, String),
68 |
69 | Solana(String, String),
70 |
71 | Parse(String, String),
72 |
73 | Pubkey(String, String),
74 |
75 | Jito(String, String),
76 |
77 | Join(String),
78 |
79 | Subscribe(String, String),
80 |
81 | Send(String, String),
82 |
83 | Other(String),
84 |
85 | InvalidData(String),
86 |
87 | PumpFunBuy(String),
88 |
89 | PumpFunSell(String),
90 |
91 | Timeout(String, String),
92 |
93 | Duplicate(String),
94 |
95 | InvalidEventType,
96 |
97 | ChannelClosed,
98 | }
99 |
100 | impl std::fmt::Display for ClientError {
101 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 | match self {
103 | Self::BondingCurveNotFound => write!(f, "Bonding curve not found"),
104 | Self::BondingCurveError(msg) => write!(f, "Bonding curve error: {}", msg),
105 | Self::BorshError(err) => write!(f, "Borsh serialization error: {}", err),
106 | Self::SolanaClientError(err) => write!(f, "Solana client error: {}", err),
107 | Self::UploadMetadataError(err) => write!(f, "Metadata upload error: {}", err),
108 | Self::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
109 | Self::InsufficientFunds => write!(f, "Insufficient funds for transaction"),
110 | Self::SimulationError(msg) => write!(f, "Transaction simulation failed: {}", msg),
111 | Self::ExternalService(msg) => write!(f, "External service error: {}", msg),
112 | Self::RateLimitExceeded => write!(f, "Rate limit exceeded"),
113 | Self::OrderLimitExceeded => write!(f, "Order limit exceeded"),
114 | Self::Solana(msg, details) => write!(f, "Solana error: {}, details: {}", msg, details),
115 | Self::Parse(msg, details) => write!(f, "Parse error: {}, details: {}", msg, details),
116 | Self::Jito(msg, details) => write!(f, "Jito error: {}, details: {}", msg, details),
117 | Self::Redis(msg, details) => write!(f, "Redis error: {}, details: {}", msg, details),
118 | Self::Join(msg) => write!(f, "Task join error: {}", msg),
119 | Self::Pubkey(msg, details) => write!(f, "Pubkey error: {}, details: {}", msg, details),
120 | Self::Subscribe(msg, details) => {
121 | write!(f, "Subscribe error: {}, details: {}", msg, details)
122 | }
123 | Self::Send(msg, details) => write!(f, "Send error: {}, details: {}", msg, details),
124 | Self::Other(msg) => write!(f, "Other error: {}", msg),
125 | Self::PumpFunBuy(msg) => write!(f, "PumpFun buy error: {}", msg),
126 | Self::PumpFunSell(msg) => write!(f, "PumpFun sell error: {}", msg),
127 | Self::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
128 | Self::Timeout(msg, details) => {
129 | write!(f, "Operation timed out: {}, details: {}", msg, details)
130 | }
131 | Self::Duplicate(msg) => write!(f, "Duplicate event: {}", msg),
132 | Self::InvalidEventType => write!(f, "Invalid event type"),
133 | Self::ChannelClosed => write!(f, "Channel closed"),
134 | }
135 | }
136 | }
137 | impl std::error::Error for ClientError {
138 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139 | match self {
140 | Self::BorshError(err) => Some(err),
141 | Self::SolanaClientError(err) => Some(err),
142 | Self::UploadMetadataError(err) => Some(err.as_ref()),
143 | Self::ExternalService(_) => None,
144 | Self::Redis(_, _) => None,
145 | Self::Solana(_, _) => None,
146 | Self::Parse(_, _) => None,
147 | Self::Jito(_, _) => None,
148 | Self::Join(_) => None,
149 | Self::Pubkey(_, _) => None,
150 | Self::Subscribe(_, _) => None,
151 | Self::Send(_, _) => None,
152 | Self::Other(_) => None,
153 | Self::PumpFunBuy(_) => None,
154 | Self::PumpFunSell(_) => None,
155 | Self::Timeout(_, _) => None,
156 | Self::Duplicate(_) => None,
157 | Self::InvalidEventType => None,
158 | Self::ChannelClosed => None,
159 | _ => None,
160 | }
161 | }
162 | }
163 |
164 | impl From for ClientError {
165 | fn from(error: SolanaClientError) -> Self {
166 | ClientError::Solana("Solana client error".to_string(), error.to_string())
167 | }
168 | }
169 |
170 | impl From for ClientError {
171 | fn from(error: PubsubClientError) -> Self {
172 | ClientError::Solana("PubSub client error".to_string(), error.to_string())
173 | }
174 | }
175 |
176 | impl From for ClientError {
177 | fn from(error: ParsePubkeyError) -> Self {
178 | ClientError::Pubkey("Pubkey error".to_string(), error.to_string())
179 | }
180 | }
181 |
182 | impl From for ClientError {
183 | fn from(err: Error) -> Self {
184 | ClientError::Parse("JSON serialization error".to_string(), err.to_string())
185 | }
186 | }
187 |
188 | pub type ClientResult = Result;
189 |
--------------------------------------------------------------------------------
/src/fix_test.rs:
--------------------------------------------------------------------------------
1 | fn main() { println!("Hello, world!"); }
2 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Solana VNTR Sniper Crate
2 | // Add the recursion limit to handle the TokenListManager
3 | #![recursion_limit = "256"]
4 |
5 | pub mod common;
6 | pub mod core;
7 | pub mod dex;
8 | pub mod engine;
9 | pub mod error;
10 | pub mod services;
11 | // pub mod enhanced_token_trader; // Removed - consolidated into engine/enhanced_token_trader.rs
12 | pub mod tests;
13 |
14 | pub use engine::monitor::new_token_trader_pumpfun;
15 | pub use engine::enhanced_token_trader::start as start_enhanced_trading_system;
16 | pub use error::ErrorType as Error;
17 |
18 | // No duplicate module declaration needed
19 |
--------------------------------------------------------------------------------
/src/services/jito.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Result};
2 | use indicatif::{ProgressBar, ProgressStyle};
3 | use rand::{seq::IteratorRandom, thread_rng};
4 | use serde::Deserialize;
5 | use serde_json::Value;
6 | use anchor_client::solana_sdk::pubkey::Pubkey;
7 | use std::{future::Future, str::FromStr, sync::LazyLock, time::Duration};
8 | use tokio::time::{sleep, Instant};
9 |
10 | use crate::common::config::import_env_var;
11 |
12 | pub static BLOCK_ENGINE_URL: LazyLock =
13 | LazyLock::new(|| import_env_var("JITO_BLOCK_ENGINE_URL"));
14 |
15 | pub fn get_tip_account() -> Result<(Pubkey, Pubkey)> {
16 | let accounts = [
17 | "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5".to_string(),
18 | "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt".to_string(),
19 | "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL".to_string(),
20 | "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT".to_string(),
21 | "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe".to_string(),
22 | "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh".to_string(),
23 | "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49".to_string(),
24 |
25 | ];
26 | let mut rng = thread_rng();
27 | let tip_account = match accounts.iter().choose(&mut rng) {
28 | Some(acc) => Ok(Pubkey::from_str(acc).inspect_err(|err| {
29 | println!("jito: failed to parse Pubkey: {:?}", err);
30 | })?),
31 | None => Err(anyhow!("jito: no tip accounts available")),
32 | };
33 | let tip1_account = Pubkey::from_str("JitoFSvbiCrygnx4HZzau4LdeyBU6VUeyf9jt8F8bMk")
34 | .inspect_err(|err| {
35 | println!("jito: failed to parse Pubkey: {:?}", err);
36 | })?;
37 | let tip_account = tip_account?;
38 | Ok((tip_account, tip1_account))
39 | }
40 | // unit sol
41 | pub async fn get_tip_value() -> Result {
42 | // If TIP_VALUE is set, use it
43 | if let Ok(tip_value) = std::env::var("JITO_TIP_VALUE") {
44 | match f64::from_str(&tip_value) {
45 | Ok(value) => Ok(value),
46 | Err(_) => {
47 | println!(
48 | "Invalid JITO_TIP_VALUE in environment variable: '{}'. Falling back to percentile calculation.",
49 | tip_value
50 | );
51 | Err(anyhow!("Invalid TIP_VALUE in environment variable"))
52 | }
53 | }
54 | } else {
55 | Err(anyhow!("JITO_TIP_VALUE environment variable not set"))
56 | }
57 | }
58 |
59 | pub async fn get_priority_fee() -> Result {
60 | // If TIP_VALUE is set, use it
61 | if let Ok(priority_fee) = std::env::var("JITO_PRIORITY_FEE") {
62 | match f64::from_str(&priority_fee) {
63 | Ok(value) => Ok(value),
64 | Err(_) => {
65 | println!(
66 | "Invalid JITO_PRIORITY_FEE in environment variable: '{}'. Falling back to percentile calculation.",
67 | priority_fee
68 | );
69 | Err(anyhow!("Invalid JITO_PRIORITY_FEE in environment variable"))
70 | }
71 | }
72 | } else {
73 | Err(anyhow!("JITO_PRIORITY_FEE environment variable not set"))
74 | }
75 | }
76 |
77 | #[derive(Deserialize, Debug)]
78 | pub struct BundleStatus {
79 | pub bundle_id: String,
80 | pub transactions: Vec,
81 | pub slot: u64,
82 | pub confirmation_status: String,
83 | pub err: ErrorStatus,
84 | }
85 | #[derive(Deserialize, Debug)]
86 | pub struct ErrorStatus {
87 | #[serde(rename = "Ok")]
88 | pub ok: Option<()>,
89 | }
90 |
91 | pub async fn wait_for_bundle_confirmation(
92 | fetch_statuses: F,
93 | bundle_id: String,
94 | interval: Duration,
95 | timeout: Duration,
96 | ) -> Result>
97 | where
98 | F: Fn(String) -> Fut,
99 | Fut: Future