mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-25 23:08:05 +00:00
Merge branch 'main' into dev-work
This commit is contained in:
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "solana-streamer-sdk"
|
name = "solana-streamer-sdk"
|
||||||
version = "1.2.2"
|
version = "1.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
|
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
|
||||||
repository = "https://github.com/0xfnzero/solana-streamer"
|
repository = "https://github.com/0xfnzero/solana-streamer"
|
||||||
@@ -34,6 +34,7 @@ dashmap = "6.1.0"
|
|||||||
prost = "0.14.3"
|
prost = "0.14.3"
|
||||||
prost-types = "0.14.3"
|
prost-types = "0.14.3"
|
||||||
crossbeam-queue = "0.3.12"
|
crossbeam-queue = "0.3.12"
|
||||||
|
wide = "1.1.1"
|
||||||
spl-token = { version = "9.0.0", default-features = false, features = ["no-entrypoint"] }
|
spl-token = { version = "9.0.0", default-features = false, features = ["no-entrypoint"] }
|
||||||
spl-token-2022 = { version = "10.0.0", default-features = false, features = ["no-entrypoint"] }
|
spl-token-2022 = { version = "10.0.0", default-features = false, features = ["no-entrypoint"] }
|
||||||
solana-commitment-config = { version = "3.1.1", features = ["serde"] }
|
solana-commitment-config = { version = "3.1.1", features = ["serde"] }
|
||||||
|
|||||||
@@ -109,14 +109,14 @@ Add the dependency to your `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Add to your Cargo.toml
|
# Add to your Cargo.toml
|
||||||
solana-streamer-sdk = { path = "./solana-streamer", version = "1.2.2" }
|
solana-streamer-sdk = { path = "./solana-streamer", version = "1.3.0" }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use crates.io
|
### Use crates.io
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Add to your Cargo.toml
|
# Add to your Cargo.toml
|
||||||
solana-streamer-sdk = "1.2.2"
|
solana-streamer-sdk = "1.3.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔄 Migration Guide
|
## 🔄 Migration Guide
|
||||||
@@ -182,8 +182,19 @@ let grpc = YellowstoneGrpc::new_with_config(endpoint, token, config)?;
|
|||||||
| Monitor transaction events using Yellowstone gRPC | `cargo run --example grpc_example` | [examples/grpc_example.rs](examples/grpc_example.rs) |
|
| Monitor transaction events using Yellowstone gRPC | `cargo run --example grpc_example` | [examples/grpc_example.rs](examples/grpc_example.rs) |
|
||||||
| Monitor transaction events using ShredStream | `cargo run --example shred_example` | [examples/shred_example.rs](examples/shred_example.rs) |
|
| Monitor transaction events using ShredStream | `cargo run --example shred_example` | [examples/shred_example.rs](examples/shred_example.rs) |
|
||||||
| Parse Solana mainnet transaction data | `cargo run --example parse_tx_events` | [examples/parse_tx_events.rs](examples/parse_tx_events.rs) |
|
| Parse Solana mainnet transaction data | `cargo run --example parse_tx_events` | [examples/parse_tx_events.rs](examples/parse_tx_events.rs) |
|
||||||
|
| Parse PumpFun transaction from RPC (signature: `TX_SIGNATURE` or CLI arg) | `cargo run --example parse_pump_tx --release` | [examples/parse_pump_tx.rs](examples/parse_pump_tx.rs) |
|
||||||
|
| Parse PumpSwap transaction from RPC | `cargo run --example parse_pumpswap_tx --release` | [examples/parse_pumpswap_tx.rs](examples/parse_pumpswap_tx.rs) |
|
||||||
|
| Parse Meteora DAMM v2 transaction from RPC | `TX_SIGNATURE=<sig> cargo run --example parse_meteora_damm_tx --release` | [examples/parse_meteora_damm_tx.rs](examples/parse_meteora_damm_tx.rs) |
|
||||||
|
| Debug PumpFun transaction (fetch, print meta/logs, parse) | `TX_SIGNATURE=<sig> cargo run --example debug_pump_tx --release` | [examples/debug_pump_tx.rs](examples/debug_pump_tx.rs) |
|
||||||
|
| Debug PumpSwap transaction (fetch, print meta, parse) | `TX_SIGNATURE=<sig> cargo run --example debug_pumpswap_tx --release` | [examples/debug_pumpswap_tx.rs](examples/debug_pumpswap_tx.rs) |
|
||||||
| Update filters at runtime | `cargo run --example dynamic_subscription` | [examples/dynamic_subscription.rs](examples/dynamic_subscription.rs) |
|
| Update filters at runtime | `cargo run --example dynamic_subscription` | [examples/dynamic_subscription.rs](examples/dynamic_subscription.rs) |
|
||||||
|
| Quick test: subscribe to PumpFun, print first 10 or run 60s | `cargo run --example pumpfun_quick_test --release` | [examples/pumpfun_quick_test.rs](examples/pumpfun_quick_test.rs) |
|
||||||
|
| PumpFun trade filter: Buy/Sell/Create with latency | `cargo run --example pumpfun_trade_filter --release` | [examples/pumpfun_trade_filter.rs](examples/pumpfun_trade_filter.rs) |
|
||||||
|
| PumpFun gRPC subscription with metrics | `cargo run --example pumpfun_with_metrics --release` | [examples/pumpfun_with_metrics.rs](examples/pumpfun_with_metrics.rs) |
|
||||||
|
| PumpSwap gRPC subscription with metrics | `cargo run --example pumpswap_with_metrics --release` | [examples/pumpswap_with_metrics.rs](examples/pumpswap_with_metrics.rs) |
|
||||||
|
| Meteora DAMM v2 gRPC subscription | `cargo run --example meteora_damm_grpc --release` | [examples/meteora_damm_grpc.rs](examples/meteora_damm_grpc.rs) |
|
||||||
| Monitor specific token account balance changes | `cargo run --example token_balance_listen_example` | [examples/token_balance_listen_example.rs](examples/token_balance_listen_example.rs) |
|
| Monitor specific token account balance changes | `cargo run --example token_balance_listen_example` | [examples/token_balance_listen_example.rs](examples/token_balance_listen_example.rs) |
|
||||||
|
| Monitor token decimals via account subscription | `cargo run --example token_decimals_listen_example` | [examples/token_decimals_listen_example.rs](examples/token_decimals_listen_example.rs) |
|
||||||
| Track nonce account state changes | `cargo run --example nonce_listen_example` | [examples/nonce_listen_example.rs](examples/nonce_listen_example.rs) |
|
| Track nonce account state changes | `cargo run --example nonce_listen_example` | [examples/nonce_listen_example.rs](examples/nonce_listen_example.rs) |
|
||||||
| Monitor PumpSwap pool accounts using memcmp filters | `cargo run --example pumpswap_pool_account_listen_example` | [examples/pumpswap_pool_account_listen_example.rs](examples/pumpswap_pool_account_listen_example.rs) |
|
| Monitor PumpSwap pool accounts using memcmp filters | `cargo run --example pumpswap_pool_account_listen_example` | [examples/pumpswap_pool_account_listen_example.rs](examples/pumpswap_pool_account_listen_example.rs) |
|
||||||
| Monitor all associated token accounts for specific mints using memcmp filters | `cargo run --example mint_all_ata_account_listen_example` | [examples/mint_all_ata_account_listen_example.rs](examples/mint_all_ata_account_listen_example.rs) |
|
| Monitor all associated token accounts for specific mints using memcmp filters | `cargo run --example mint_all_ata_account_listen_example` | [examples/mint_all_ata_account_listen_example.rs](examples/mint_all_ata_account_listen_example.rs) |
|
||||||
|
|||||||
+13
-2
@@ -108,14 +108,14 @@ git clone https://github.com/0xfnzero/solana-streamer
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# 添加到您的 Cargo.toml
|
# 添加到您的 Cargo.toml
|
||||||
solana-streamer-sdk = { path = "./solana-streamer", version = "1.2.2" }
|
solana-streamer-sdk = { path = "./solana-streamer", version = "1.3.0" }
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用 crates.io
|
### 使用 crates.io
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# 添加到您的 Cargo.toml
|
# 添加到您的 Cargo.toml
|
||||||
solana-streamer-sdk = "1.2.2"
|
solana-streamer-sdk = "1.3.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔄 迁移指南
|
## 🔄 迁移指南
|
||||||
@@ -181,8 +181,19 @@ let grpc = YellowstoneGrpc::new_with_config(endpoint, token, config)?;
|
|||||||
| 使用 Yellowstone gRPC 监控交易事件 | `cargo run --example grpc_example` | [examples/grpc_example.rs](examples/grpc_example.rs) |
|
| 使用 Yellowstone gRPC 监控交易事件 | `cargo run --example grpc_example` | [examples/grpc_example.rs](examples/grpc_example.rs) |
|
||||||
| 使用 ShredStream 监控交易事件 | `cargo run --example shred_example` | [examples/shred_example.rs](examples/shred_example.rs) |
|
| 使用 ShredStream 监控交易事件 | `cargo run --example shred_example` | [examples/shred_example.rs](examples/shred_example.rs) |
|
||||||
| 解析 Solana 主网交易数据 | `cargo run --example parse_tx_events` | [examples/parse_tx_events.rs](examples/parse_tx_events.rs) |
|
| 解析 Solana 主网交易数据 | `cargo run --example parse_tx_events` | [examples/parse_tx_events.rs](examples/parse_tx_events.rs) |
|
||||||
|
| 从 RPC 解析 PumpFun 交易(签名:环境变量 `TX_SIGNATURE` 或 CLI 参数) | `cargo run --example parse_pump_tx --release` | [examples/parse_pump_tx.rs](examples/parse_pump_tx.rs) |
|
||||||
|
| 从 RPC 解析 PumpSwap 交易 | `cargo run --example parse_pumpswap_tx --release` | [examples/parse_pumpswap_tx.rs](examples/parse_pumpswap_tx.rs) |
|
||||||
|
| 从 RPC 解析 Meteora DAMM v2 交易 | `TX_SIGNATURE=<sig> cargo run --example parse_meteora_damm_tx --release` | [examples/parse_meteora_damm_tx.rs](examples/parse_meteora_damm_tx.rs) |
|
||||||
|
| 调试 PumpFun 交易(拉取、打印 meta/logs、解析) | `TX_SIGNATURE=<sig> cargo run --example debug_pump_tx --release` | [examples/debug_pump_tx.rs](examples/debug_pump_tx.rs) |
|
||||||
|
| 调试 PumpSwap 交易(拉取、打印 meta、解析) | `TX_SIGNATURE=<sig> cargo run --example debug_pumpswap_tx --release` | [examples/debug_pumpswap_tx.rs](examples/debug_pumpswap_tx.rs) |
|
||||||
| 运行时更新过滤器 | `cargo run --example dynamic_subscription` | [examples/dynamic_subscription.rs](examples/dynamic_subscription.rs) |
|
| 运行时更新过滤器 | `cargo run --example dynamic_subscription` | [examples/dynamic_subscription.rs](examples/dynamic_subscription.rs) |
|
||||||
|
| 快速测试:订阅 PumpFun,打印前 10 条或运行 60 秒 | `cargo run --example pumpfun_quick_test --release` | [examples/pumpfun_quick_test.rs](examples/pumpfun_quick_test.rs) |
|
||||||
|
| PumpFun 交易过滤:买入/卖出/创建及延迟统计 | `cargo run --example pumpfun_trade_filter --release` | [examples/pumpfun_trade_filter.rs](examples/pumpfun_trade_filter.rs) |
|
||||||
|
| PumpFun gRPC 订阅(含指标) | `cargo run --example pumpfun_with_metrics --release` | [examples/pumpfun_with_metrics.rs](examples/pumpfun_with_metrics.rs) |
|
||||||
|
| PumpSwap gRPC 订阅(含指标) | `cargo run --example pumpswap_with_metrics --release` | [examples/pumpswap_with_metrics.rs](examples/pumpswap_with_metrics.rs) |
|
||||||
|
| Meteora DAMM v2 gRPC 订阅 | `cargo run --example meteora_damm_grpc --release` | [examples/meteora_damm_grpc.rs](examples/meteora_damm_grpc.rs) |
|
||||||
| 监控特定代币账户余额变化 | `cargo run --example token_balance_listen_example` | [examples/token_balance_listen_example.rs](examples/token_balance_listen_example.rs) |
|
| 监控特定代币账户余额变化 | `cargo run --example token_balance_listen_example` | [examples/token_balance_listen_example.rs](examples/token_balance_listen_example.rs) |
|
||||||
|
| 通过账户订阅监控代币精度 | `cargo run --example token_decimals_listen_example` | [examples/token_decimals_listen_example.rs](examples/token_decimals_listen_example.rs) |
|
||||||
| 跟踪 nonce 账户状态变化 | `cargo run --example nonce_listen_example` | [examples/nonce_listen_example.rs](examples/nonce_listen_example.rs) |
|
| 跟踪 nonce 账户状态变化 | `cargo run --example nonce_listen_example` | [examples/nonce_listen_example.rs](examples/nonce_listen_example.rs) |
|
||||||
| 使用 memcmp 过滤器监控 PumpSwap 池账户 | `cargo run --example pumpswap_pool_account_listen_example` | [examples/pumpswap_pool_account_listen_example.rs](examples/pumpswap_pool_account_listen_example.rs) |
|
| 使用 memcmp 过滤器监控 PumpSwap 池账户 | `cargo run --example pumpswap_pool_account_listen_example` | [examples/pumpswap_pool_account_listen_example.rs](examples/pumpswap_pool_account_listen_example.rs) |
|
||||||
| 使用 memcmp 过滤器监控特定代币的所有关联代币账户 | `cargo run --example mint_all_ata_account_listen_example` | [examples/mint_all_ata_account_listen_example.rs](examples/mint_all_ata_account_listen_example.rs) |
|
| 使用 memcmp 过滤器监控特定代币的所有关联代币账户 | `cargo run --example mint_all_ata_account_listen_example` | [examples/mint_all_ata_account_listen_example.rs](examples/mint_all_ata_account_listen_example.rs) |
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
//! Debug PumpFun transaction: fetch from RPC, print meta/logs, then parse with EventParser.
|
||||||
|
//!
|
||||||
|
//! Usage: cargo run --example debug_pump_tx --release
|
||||||
|
//! TX_SIGNATURE=<sig> SOLANA_RPC_URL=<url> cargo run --example debug_pump_tx --release
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use solana_commitment_config::CommitmentConfig;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::core::event_parser::EventParser;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let default_sig = "5curEt85cQhAK6R9pntSJ4fmYCiPEG22NjZyGrnGSbNwAkHJMN25T9Efp1n9Tf9vGXhnDXMQYrCNpoRHQTMcZ1s9";
|
||||||
|
let tx_sig = std::env::var("TX_SIGNATURE").unwrap_or_else(|_| default_sig.to_string());
|
||||||
|
let rpc_url = std::env::var("SOLANA_RPC_URL")
|
||||||
|
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
|
|
||||||
|
println!("=== Debug PumpFun Transaction ===\n");
|
||||||
|
println!("Signature: {}\n", tx_sig);
|
||||||
|
println!("RPC: {}\n", rpc_url);
|
||||||
|
|
||||||
|
let signature = solana_sdk::signature::Signature::from_str(&tx_sig)?;
|
||||||
|
let client = solana_client::nonblocking::rpc_client::RpcClient::new(rpc_url.clone());
|
||||||
|
|
||||||
|
let transaction = match client
|
||||||
|
.get_transaction_with_config(
|
||||||
|
&signature,
|
||||||
|
solana_client::rpc_config::RpcTransactionConfig {
|
||||||
|
encoding: Some(solana_transaction_status::UiTransactionEncoding::Base64),
|
||||||
|
commitment: Some(CommitmentConfig::confirmed()),
|
||||||
|
max_supported_transaction_version: Some(0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to fetch: {}", e);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Slot: {}", transaction.slot);
|
||||||
|
println!("Block time: {:?}", transaction.block_time);
|
||||||
|
|
||||||
|
if let Some(ref meta) = transaction.transaction.meta {
|
||||||
|
println!("\n=== Transaction Meta ===");
|
||||||
|
println!("Fee: {}", meta.fee);
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(units) = &meta.compute_units_consumed {
|
||||||
|
println!("Compute units: {}", units);
|
||||||
|
}
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(logs) = &meta.log_messages {
|
||||||
|
println!("\n=== Logs ({} lines) ===", logs.len());
|
||||||
|
for (i, log) in logs.iter().enumerate() {
|
||||||
|
if log.contains("Program 6EF8") || log.contains("invoke") || log.contains("success") {
|
||||||
|
println!(" {}: {}", i, log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== Parsing with EventParser ===");
|
||||||
|
let versioned_tx = match transaction.transaction.transaction.decode() {
|
||||||
|
Some(tx) => tx,
|
||||||
|
None => {
|
||||||
|
println!("Failed to decode transaction");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
use prost_types::Timestamp;
|
||||||
|
use solana_sdk::{message::compiled_instruction::CompiledInstruction, pubkey::Pubkey};
|
||||||
|
use solana_transaction_status::{InnerInstruction, InnerInstructions, UiInstruction};
|
||||||
|
|
||||||
|
let mut inner_instructions_vec: Vec<InnerInstructions> = Vec::new();
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(ui_inner_insts) =
|
||||||
|
&meta.inner_instructions
|
||||||
|
{
|
||||||
|
for ui_inner in ui_inner_insts {
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for ui_instruction in &ui_inner.instructions {
|
||||||
|
if let UiInstruction::Compiled(ui_compiled) = ui_instruction {
|
||||||
|
if let Ok(data) = solana_sdk::bs58::decode(&ui_compiled.data).into_vec() {
|
||||||
|
converted.push(InnerInstruction {
|
||||||
|
instruction: CompiledInstruction {
|
||||||
|
program_id_index: ui_compiled.program_id_index,
|
||||||
|
accounts: ui_compiled.accounts.to_vec(),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
stack_height: ui_compiled.stack_height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner_instructions_vec.push(InnerInstructions {
|
||||||
|
index: ui_inner.index,
|
||||||
|
instructions: converted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded) =
|
||||||
|
&meta.loaded_addresses
|
||||||
|
{
|
||||||
|
for s in loaded.writable.iter().chain(loaded.readonly.iter()) {
|
||||||
|
if let Ok(p) = s.parse::<Pubkey>() {
|
||||||
|
address_table_lookups.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accounts: Vec<Pubkey> = versioned_tx.message.static_account_keys().to_vec();
|
||||||
|
accounts.extend(address_table_lookups);
|
||||||
|
|
||||||
|
let slot = transaction.slot;
|
||||||
|
let block_time = transaction
|
||||||
|
.block_time
|
||||||
|
.map(|t| Timestamp { seconds: t as i64, nanos: 0 });
|
||||||
|
let recv_us = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as i64;
|
||||||
|
|
||||||
|
let protocols = vec![
|
||||||
|
Protocol::PumpFun,
|
||||||
|
Protocol::PumpSwap,
|
||||||
|
Protocol::Bonk,
|
||||||
|
Protocol::RaydiumClmm,
|
||||||
|
Protocol::RaydiumCpmm,
|
||||||
|
Protocol::RaydiumAmmV4,
|
||||||
|
Protocol::MeteoraDammV2,
|
||||||
|
];
|
||||||
|
|
||||||
|
let callback = Arc::new(|event: DexEvent| {
|
||||||
|
println!("Parsed event: {:?}\n", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
EventParser::parse_instruction_events_from_versioned_transaction(
|
||||||
|
&protocols,
|
||||||
|
None,
|
||||||
|
&versioned_tx,
|
||||||
|
signature,
|
||||||
|
Some(slot),
|
||||||
|
block_time,
|
||||||
|
recv_us,
|
||||||
|
&accounts,
|
||||||
|
&inner_instructions_vec,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("\n✓ Done.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
//! Debug PumpSwap transaction: fetch from RPC, print meta, then parse with EventParser.
|
||||||
|
//!
|
||||||
|
//! Usage: TX_SIGNATURE=<sig> cargo run --example debug_pumpswap_tx --release
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use solana_commitment_config::CommitmentConfig;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::core::event_parser::EventParser;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let tx_sig = std::env::var("TX_SIGNATURE").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Usage: TX_SIGNATURE=<sig> cargo run --example debug_pumpswap_tx --release");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
let rpc_url = std::env::var("SOLANA_RPC_URL")
|
||||||
|
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
|
|
||||||
|
println!("=== Debug PumpSwap Transaction ===\n");
|
||||||
|
println!("Signature: {}\n", tx_sig);
|
||||||
|
|
||||||
|
let signature = solana_sdk::signature::Signature::from_str(&tx_sig)?;
|
||||||
|
let client = solana_client::nonblocking::rpc_client::RpcClient::new(rpc_url.clone());
|
||||||
|
|
||||||
|
let transaction = match client
|
||||||
|
.get_transaction_with_config(
|
||||||
|
&signature,
|
||||||
|
solana_client::rpc_config::RpcTransactionConfig {
|
||||||
|
encoding: Some(solana_transaction_status::UiTransactionEncoding::Base64),
|
||||||
|
commitment: Some(CommitmentConfig::confirmed()),
|
||||||
|
max_supported_transaction_version: Some(0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to fetch: {}", e);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Slot: {}", transaction.slot);
|
||||||
|
if let Some(ref meta) = transaction.transaction.meta {
|
||||||
|
println!("Fee: {}", meta.fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
let versioned_tx = match transaction.transaction.transaction.decode() {
|
||||||
|
Some(tx) => tx,
|
||||||
|
None => {
|
||||||
|
println!("Failed to decode");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
use prost_types::Timestamp;
|
||||||
|
use solana_sdk::{message::compiled_instruction::CompiledInstruction, pubkey::Pubkey};
|
||||||
|
use solana_transaction_status::{InnerInstruction, InnerInstructions, UiInstruction};
|
||||||
|
|
||||||
|
let mut inner_instructions_vec: Vec<InnerInstructions> = Vec::new();
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(ui_inner_insts) =
|
||||||
|
&meta.inner_instructions
|
||||||
|
{
|
||||||
|
for ui_inner in ui_inner_insts {
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for ui_instruction in &ui_inner.instructions {
|
||||||
|
if let UiInstruction::Compiled(ui_compiled) = ui_instruction {
|
||||||
|
if let Ok(data) = solana_sdk::bs58::decode(&ui_compiled.data).into_vec() {
|
||||||
|
converted.push(InnerInstruction {
|
||||||
|
instruction: CompiledInstruction {
|
||||||
|
program_id_index: ui_compiled.program_id_index,
|
||||||
|
accounts: ui_compiled.accounts.to_vec(),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
stack_height: ui_compiled.stack_height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner_instructions_vec.push(InnerInstructions {
|
||||||
|
index: ui_inner.index,
|
||||||
|
instructions: converted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded) =
|
||||||
|
&meta.loaded_addresses
|
||||||
|
{
|
||||||
|
for s in loaded.writable.iter().chain(loaded.readonly.iter()) {
|
||||||
|
if let Ok(p) = s.parse::<Pubkey>() {
|
||||||
|
address_table_lookups.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accounts: Vec<Pubkey> = versioned_tx.message.static_account_keys().to_vec();
|
||||||
|
accounts.extend(address_table_lookups);
|
||||||
|
|
||||||
|
let slot = transaction.slot;
|
||||||
|
let block_time = transaction
|
||||||
|
.block_time
|
||||||
|
.map(|t| Timestamp { seconds: t as i64, nanos: 0 });
|
||||||
|
let recv_us = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as i64;
|
||||||
|
|
||||||
|
let protocols = vec![
|
||||||
|
Protocol::PumpFun,
|
||||||
|
Protocol::PumpSwap,
|
||||||
|
Protocol::Bonk,
|
||||||
|
Protocol::RaydiumClmm,
|
||||||
|
Protocol::RaydiumCpmm,
|
||||||
|
Protocol::RaydiumAmmV4,
|
||||||
|
Protocol::MeteoraDammV2,
|
||||||
|
];
|
||||||
|
|
||||||
|
let callback = Arc::new(|event: DexEvent| {
|
||||||
|
println!("Event: {:?}\n", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
EventParser::parse_instruction_events_from_versioned_transaction(
|
||||||
|
&protocols,
|
||||||
|
None,
|
||||||
|
&versioned_tx,
|
||||||
|
signature,
|
||||||
|
Some(slot),
|
||||||
|
block_time,
|
||||||
|
recv_us,
|
||||||
|
&accounts,
|
||||||
|
&inner_instructions_vec,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("\n✓ Done.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
//! Meteora DAMM v2 subscription via gRPC.
|
||||||
|
//!
|
||||||
|
//! Usage: cargo run --example meteora_damm_grpc --release
|
||||||
|
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::protocols::meteora_damm_v2::parser::METEORA_DAMM_V2_PROGRAM_ID;
|
||||||
|
use solana_streamer_sdk::streaming::grpc::ClientConfig;
|
||||||
|
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter, YellowstoneGrpc};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
|
println!("Meteora DAMM v2 gRPC (solana-streamer)\n");
|
||||||
|
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
std::env::var("GRPC_ENDPOINT").unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()),
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
ClientConfig::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let transaction_filter = TransactionFilter {
|
||||||
|
account_include: vec![METEORA_DAMM_V2_PROGRAM_ID.to_string()],
|
||||||
|
account_exclude: vec![],
|
||||||
|
account_required: vec![],
|
||||||
|
};
|
||||||
|
let account_filter = AccountFilter {
|
||||||
|
account: vec![],
|
||||||
|
owner: vec![METEORA_DAMM_V2_PROGRAM_ID.to_string()],
|
||||||
|
filters: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let callback = |event: DexEvent| {
|
||||||
|
println!("Event: {:?}", event.metadata().event_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
grpc.subscribe_events_immediate(
|
||||||
|
vec![Protocol::MeteoraDammV2],
|
||||||
|
None,
|
||||||
|
vec![transaction_filter],
|
||||||
|
vec![account_filter],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Press Ctrl+C to stop...\n");
|
||||||
|
tokio::signal::ctrl_c().await?;
|
||||||
|
grpc.stop().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -40,10 +40,13 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let transaction_filter =
|
let transaction_filter =
|
||||||
TransactionFilter { account_include, account_exclude, account_required };
|
TransactionFilter { account_include, account_exclude, account_required };
|
||||||
|
|
||||||
let nonce_account = "use_your_nonce_account_here".to_string();
|
let nonce_account = std::env::var("NONCE_ACCOUNT").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Usage: NONCE_ACCOUNT=<pubkey> cargo run --example nonce_listen_example --release");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
// Listen to account data belonging to owner programs -> account event monitoring
|
// Listen to account data belonging to owner programs -> account event monitoring
|
||||||
let account_filter =
|
let account_filter =
|
||||||
AccountFilter { account: vec![nonce_account], owner: vec![], filters: vec![] };
|
AccountFilter { account: vec![nonce_account.clone()], owner: vec![], filters: vec![] };
|
||||||
|
|
||||||
// Event filtering
|
// Event filtering
|
||||||
let event_type_filter = Some(EventTypeFilter { include: vec![EventType::NonceAccount] });
|
let event_type_filter = Some(EventTypeFilter { include: vec![EventType::NonceAccount] });
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
//! Parse a Meteora DAMM v2 transaction from RPC using solana-streamer EventParser.
|
||||||
|
//!
|
||||||
|
//! Usage:
|
||||||
|
//! cargo run --example parse_meteora_damm_tx --release
|
||||||
|
//! TX_SIGNATURE=<sig> SOLANA_RPC_URL=<url> cargo run --example parse_meteora_damm_tx --release
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use solana_commitment_config::CommitmentConfig;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::core::event_parser::EventParser;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let tx_sig = std::env::var("TX_SIGNATURE").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Usage: TX_SIGNATURE=<sig> cargo run --example parse_meteora_damm_tx --release");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
let rpc_url = std::env::var("SOLANA_RPC_URL")
|
||||||
|
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
|
|
||||||
|
println!("=== Meteora DAMM v2 Transaction Parser (solana-streamer) ===\n");
|
||||||
|
println!("Transaction: {}\n", tx_sig);
|
||||||
|
|
||||||
|
parse_one_tx(&tx_sig, &rpc_url).await?;
|
||||||
|
println!("\n✓ Done.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse_one_tx(signature_str: &str, rpc_url: &str) -> Result<()> {
|
||||||
|
use prost_types::Timestamp;
|
||||||
|
use solana_sdk::{
|
||||||
|
message::compiled_instruction::CompiledInstruction,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::Signature,
|
||||||
|
};
|
||||||
|
use solana_transaction_status::{InnerInstruction, InnerInstructions, UiInstruction, UiTransactionEncoding};
|
||||||
|
|
||||||
|
let signature = Signature::from_str(signature_str)?;
|
||||||
|
let client = solana_client::nonblocking::rpc_client::RpcClient::new(rpc_url.to_string());
|
||||||
|
|
||||||
|
let transaction = match client
|
||||||
|
.get_transaction_with_config(
|
||||||
|
&signature,
|
||||||
|
solana_client::rpc_config::RpcTransactionConfig {
|
||||||
|
encoding: Some(UiTransactionEncoding::Base64),
|
||||||
|
commitment: Some(CommitmentConfig::confirmed()),
|
||||||
|
max_supported_transaction_version: Some(0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(e) => anyhow::bail!("Failed to fetch transaction: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let versioned_tx = match transaction.transaction.transaction.decode() {
|
||||||
|
Some(tx) => tx,
|
||||||
|
None => anyhow::bail!("Failed to decode transaction"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut inner_instructions_vec: Vec<InnerInstructions> = Vec::new();
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(ui_inner_insts) =
|
||||||
|
&meta.inner_instructions
|
||||||
|
{
|
||||||
|
for ui_inner in ui_inner_insts {
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for ui_instruction in &ui_inner.instructions {
|
||||||
|
if let UiInstruction::Compiled(ui_compiled) = ui_instruction {
|
||||||
|
if let Ok(data) = solana_sdk::bs58::decode(&ui_compiled.data).into_vec() {
|
||||||
|
converted.push(InnerInstruction {
|
||||||
|
instruction: CompiledInstruction {
|
||||||
|
program_id_index: ui_compiled.program_id_index,
|
||||||
|
accounts: ui_compiled.accounts.to_vec(),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
stack_height: ui_compiled.stack_height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner_instructions_vec.push(InnerInstructions {
|
||||||
|
index: ui_inner.index,
|
||||||
|
instructions: converted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded) =
|
||||||
|
&meta.loaded_addresses
|
||||||
|
{
|
||||||
|
for s in loaded.writable.iter().chain(loaded.readonly.iter()) {
|
||||||
|
if let Ok(p) = s.parse::<Pubkey>() {
|
||||||
|
address_table_lookups.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accounts: Vec<Pubkey> = versioned_tx.message.static_account_keys().to_vec();
|
||||||
|
accounts.extend(address_table_lookups);
|
||||||
|
|
||||||
|
let slot = transaction.slot;
|
||||||
|
let block_time = transaction
|
||||||
|
.block_time
|
||||||
|
.map(|t| Timestamp { seconds: t as i64, nanos: 0 });
|
||||||
|
let recv_us = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as i64;
|
||||||
|
|
||||||
|
let protocols = vec![
|
||||||
|
Protocol::PumpFun,
|
||||||
|
Protocol::PumpSwap,
|
||||||
|
Protocol::Bonk,
|
||||||
|
Protocol::RaydiumClmm,
|
||||||
|
Protocol::RaydiumCpmm,
|
||||||
|
Protocol::RaydiumAmmV4,
|
||||||
|
Protocol::MeteoraDammV2,
|
||||||
|
];
|
||||||
|
|
||||||
|
let callback = Arc::new(|event: DexEvent| {
|
||||||
|
println!("Event: {:?}\n", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
EventParser::parse_instruction_events_from_versioned_transaction(
|
||||||
|
&protocols,
|
||||||
|
None,
|
||||||
|
&versioned_tx,
|
||||||
|
signature,
|
||||||
|
Some(slot),
|
||||||
|
block_time,
|
||||||
|
recv_us,
|
||||||
|
&accounts,
|
||||||
|
&inner_instructions_vec,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
//! Parse a PumpFun transaction from RPC using solana-streamer EventParser.
|
||||||
|
//!
|
||||||
|
//! Signature: env `TX_SIGNATURE` or first CLI arg. RPC: env `SOLANA_RPC_URL`.
|
||||||
|
//!
|
||||||
|
//! cargo run --example parse_pump_tx --release
|
||||||
|
//! TX_SIGNATURE=your_sig cargo run --example parse_pump_tx --release
|
||||||
|
//! cargo run --example parse_pump_tx --release -- your_sig
|
||||||
|
//! SOLANA_RPC_URL=https://... cargo run --example parse_pump_tx --release
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use solana_commitment_config::CommitmentConfig;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::core::event_parser::EventParser;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let default_sig = "64srGF8CnTz9zPbdayWYmzs5aVRFBcfjDcidFVvBgAD25VMh52wr88vma7ytSbAZT3C5Giu5BPyGfNfLexLSrKhP";
|
||||||
|
let tx_sig = std::env::var("TX_SIGNATURE")
|
||||||
|
.ok()
|
||||||
|
.or_else(|| std::env::args().nth(1))
|
||||||
|
.unwrap_or_else(|| default_sig.to_string());
|
||||||
|
let rpc_url = std::env::var("SOLANA_RPC_URL")
|
||||||
|
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
|
|
||||||
|
println!("=== PumpFun Transaction Parser (solana-streamer) ===\n");
|
||||||
|
println!("Transaction: {}\n", tx_sig);
|
||||||
|
println!("RPC: {}\n", rpc_url);
|
||||||
|
|
||||||
|
parse_one_tx(&tx_sig, &rpc_url).await?;
|
||||||
|
println!("\n✓ Done.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse_one_tx(signature_str: &str, rpc_url: &str) -> Result<()> {
|
||||||
|
use prost_types::Timestamp;
|
||||||
|
use solana_sdk::{
|
||||||
|
message::compiled_instruction::CompiledInstruction,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::Signature,
|
||||||
|
};
|
||||||
|
use solana_transaction_status::{InnerInstruction, InnerInstructions, UiInstruction, UiTransactionEncoding};
|
||||||
|
|
||||||
|
let signature = Signature::from_str(signature_str)?;
|
||||||
|
let client = solana_client::nonblocking::rpc_client::RpcClient::new(rpc_url.to_string());
|
||||||
|
|
||||||
|
let transaction = match client
|
||||||
|
.get_transaction_with_config(
|
||||||
|
&signature,
|
||||||
|
solana_client::rpc_config::RpcTransactionConfig {
|
||||||
|
encoding: Some(UiTransactionEncoding::Base64),
|
||||||
|
commitment: Some(CommitmentConfig::confirmed()),
|
||||||
|
max_supported_transaction_version: Some(0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(e) => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Failed to fetch transaction: {}. If RPC returned null, try an archive RPC (SOLANA_RPC_URL).",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Slot: {}", transaction.slot);
|
||||||
|
if let Some(bt) = transaction.block_time {
|
||||||
|
println!("Block time: {}", bt);
|
||||||
|
}
|
||||||
|
|
||||||
|
let versioned_tx = match transaction.transaction.transaction.decode() {
|
||||||
|
Some(tx) => tx,
|
||||||
|
None => anyhow::bail!("Failed to decode transaction"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut inner_instructions_vec: Vec<InnerInstructions> = Vec::new();
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(ui_inner_insts) =
|
||||||
|
&meta.inner_instructions
|
||||||
|
{
|
||||||
|
for ui_inner in ui_inner_insts {
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for ui_instruction in &ui_inner.instructions {
|
||||||
|
if let UiInstruction::Compiled(ui_compiled) = ui_instruction {
|
||||||
|
if let Ok(data) = solana_sdk::bs58::decode(&ui_compiled.data).into_vec() {
|
||||||
|
converted.push(InnerInstruction {
|
||||||
|
instruction: CompiledInstruction {
|
||||||
|
program_id_index: ui_compiled.program_id_index,
|
||||||
|
accounts: ui_compiled.accounts.to_vec(),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
stack_height: ui_compiled.stack_height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner_instructions_vec.push(InnerInstructions {
|
||||||
|
index: ui_inner.index,
|
||||||
|
instructions: converted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded) =
|
||||||
|
&meta.loaded_addresses
|
||||||
|
{
|
||||||
|
address_table_lookups.extend(
|
||||||
|
loaded
|
||||||
|
.writable
|
||||||
|
.iter()
|
||||||
|
.chain(loaded.readonly.iter())
|
||||||
|
.filter_map(|s| s.parse::<Pubkey>().ok()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accounts: Vec<Pubkey> = versioned_tx.message.static_account_keys().to_vec();
|
||||||
|
accounts.extend(address_table_lookups);
|
||||||
|
|
||||||
|
let slot = transaction.slot;
|
||||||
|
let block_time = transaction
|
||||||
|
.block_time
|
||||||
|
.map(|t| Timestamp { seconds: t as i64, nanos: 0 });
|
||||||
|
let recv_us = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as i64;
|
||||||
|
|
||||||
|
let protocols = vec![
|
||||||
|
Protocol::PumpFun,
|
||||||
|
Protocol::PumpSwap,
|
||||||
|
Protocol::Bonk,
|
||||||
|
Protocol::RaydiumClmm,
|
||||||
|
Protocol::RaydiumCpmm,
|
||||||
|
Protocol::RaydiumAmmV4,
|
||||||
|
Protocol::MeteoraDammV2,
|
||||||
|
];
|
||||||
|
|
||||||
|
let callback = Arc::new(|event: DexEvent| {
|
||||||
|
println!("Event: {:?}\n", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
EventParser::parse_instruction_events_from_versioned_transaction(
|
||||||
|
&protocols,
|
||||||
|
None,
|
||||||
|
&versioned_tx,
|
||||||
|
signature,
|
||||||
|
Some(slot),
|
||||||
|
block_time,
|
||||||
|
recv_us,
|
||||||
|
&accounts,
|
||||||
|
&inner_instructions_vec,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
//! Parse a PumpSwap transaction from RPC using solana-streamer EventParser.
|
||||||
|
//!
|
||||||
|
//! Usage:
|
||||||
|
//! cargo run --example parse_pumpswap_tx --release
|
||||||
|
//! TX_SIGNATURE=<sig> SOLANA_RPC_URL=<url> cargo run --example parse_pumpswap_tx --release
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use solana_commitment_config::CommitmentConfig;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::core::event_parser::EventParser;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let default_sig = "3zsihbygW7hoKGtduAyDDFzp4E1eis8gaBzEzzNKr8ma39baffpFcphok9wHFgR3EauDe9vYYsVf4Puh5pZ6UJiS";
|
||||||
|
let tx_sig = std::env::var("TX_SIGNATURE").unwrap_or_else(|_| default_sig.to_string());
|
||||||
|
let rpc_url = std::env::var("SOLANA_RPC_URL")
|
||||||
|
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
|
||||||
|
|
||||||
|
println!("=== PumpSwap Transaction Parser (solana-streamer) ===\n");
|
||||||
|
println!("Transaction: {}\n", tx_sig);
|
||||||
|
println!("RPC: {}\n", rpc_url);
|
||||||
|
|
||||||
|
parse_one_tx(&tx_sig, &rpc_url).await?;
|
||||||
|
println!("\n✓ Done.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse_one_tx(signature_str: &str, rpc_url: &str) -> Result<()> {
|
||||||
|
use prost_types::Timestamp;
|
||||||
|
use solana_sdk::{
|
||||||
|
message::compiled_instruction::CompiledInstruction,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::Signature,
|
||||||
|
};
|
||||||
|
use solana_transaction_status::{InnerInstruction, InnerInstructions, UiInstruction, UiTransactionEncoding};
|
||||||
|
|
||||||
|
let signature = Signature::from_str(signature_str)?;
|
||||||
|
let client = solana_client::nonblocking::rpc_client::RpcClient::new(rpc_url.to_string());
|
||||||
|
|
||||||
|
let transaction = match client
|
||||||
|
.get_transaction_with_config(
|
||||||
|
&signature,
|
||||||
|
solana_client::rpc_config::RpcTransactionConfig {
|
||||||
|
encoding: Some(UiTransactionEncoding::Base64),
|
||||||
|
commitment: Some(CommitmentConfig::confirmed()),
|
||||||
|
max_supported_transaction_version: Some(0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(e) => anyhow::bail!("Failed to fetch transaction: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let versioned_tx = match transaction.transaction.transaction.decode() {
|
||||||
|
Some(tx) => tx,
|
||||||
|
None => anyhow::bail!("Failed to decode transaction"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut inner_instructions_vec: Vec<InnerInstructions> = Vec::new();
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(ui_inner_insts) =
|
||||||
|
&meta.inner_instructions
|
||||||
|
{
|
||||||
|
for ui_inner in ui_inner_insts {
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for ui_instruction in &ui_inner.instructions {
|
||||||
|
if let UiInstruction::Compiled(ui_compiled) = ui_instruction {
|
||||||
|
if let Ok(data) = solana_sdk::bs58::decode(&ui_compiled.data).into_vec() {
|
||||||
|
converted.push(InnerInstruction {
|
||||||
|
instruction: CompiledInstruction {
|
||||||
|
program_id_index: ui_compiled.program_id_index,
|
||||||
|
accounts: ui_compiled.accounts.to_vec(),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
stack_height: ui_compiled.stack_height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner_instructions_vec.push(InnerInstructions {
|
||||||
|
index: ui_inner.index,
|
||||||
|
instructions: converted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||||
|
if let Some(meta) = &transaction.transaction.meta {
|
||||||
|
if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded) =
|
||||||
|
&meta.loaded_addresses
|
||||||
|
{
|
||||||
|
for s in loaded.writable.iter().chain(loaded.readonly.iter()) {
|
||||||
|
if let Ok(p) = s.parse::<Pubkey>() {
|
||||||
|
address_table_lookups.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accounts: Vec<Pubkey> = versioned_tx.message.static_account_keys().to_vec();
|
||||||
|
accounts.extend(address_table_lookups);
|
||||||
|
|
||||||
|
let slot = transaction.slot;
|
||||||
|
let block_time = transaction
|
||||||
|
.block_time
|
||||||
|
.map(|t| Timestamp { seconds: t as i64, nanos: 0 });
|
||||||
|
let recv_us = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as i64;
|
||||||
|
|
||||||
|
let protocols = vec![
|
||||||
|
Protocol::PumpFun,
|
||||||
|
Protocol::PumpSwap,
|
||||||
|
Protocol::Bonk,
|
||||||
|
Protocol::RaydiumClmm,
|
||||||
|
Protocol::RaydiumCpmm,
|
||||||
|
Protocol::RaydiumAmmV4,
|
||||||
|
Protocol::MeteoraDammV2,
|
||||||
|
];
|
||||||
|
|
||||||
|
let callback = Arc::new(|event: DexEvent| {
|
||||||
|
println!("Event: {:?}\n", event);
|
||||||
|
});
|
||||||
|
|
||||||
|
EventParser::parse_instruction_events_from_versioned_transaction(
|
||||||
|
&protocols,
|
||||||
|
None,
|
||||||
|
&versioned_tx,
|
||||||
|
signature,
|
||||||
|
Some(slot),
|
||||||
|
block_time,
|
||||||
|
recv_us,
|
||||||
|
&accounts,
|
||||||
|
&inner_instructions_vec,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
//! Quick test: subscribe to PumpFun events and print the first 10 (or run 60s).
|
||||||
|
//!
|
||||||
|
//! Usage: cargo run --example pumpfun_quick_test --release
|
||||||
|
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::common::types::EventType;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::DexEvent;
|
||||||
|
use solana_streamer_sdk::streaming::grpc::ClientConfig;
|
||||||
|
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter, YellowstoneGrpc};
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::Protocol;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
|
println!("🚀 Quick Test - Subscribing to PumpFun events...");
|
||||||
|
|
||||||
|
let mut config = ClientConfig::default();
|
||||||
|
config.enable_metrics = true;
|
||||||
|
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
std::env::var("GRPC_ENDPOINT").unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()),
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let transaction_filter = TransactionFilter {
|
||||||
|
account_include: vec![PUMPFUN_PROGRAM_ID.to_string()],
|
||||||
|
account_exclude: vec![],
|
||||||
|
account_required: vec![],
|
||||||
|
};
|
||||||
|
let account_filter = AccountFilter {
|
||||||
|
account: vec![],
|
||||||
|
owner: vec![PUMPFUN_PROGRAM_ID.to_string()],
|
||||||
|
filters: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let event_count = std::sync::Arc::new(AtomicU64::new(0));
|
||||||
|
let count = event_count.clone();
|
||||||
|
let callback = move |event: DexEvent| {
|
||||||
|
let n = count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let event_type = match event.metadata().event_type {
|
||||||
|
EventType::PumpFunCreateToken | EventType::PumpFunCreateV2Token => "PumpFunCreate",
|
||||||
|
EventType::PumpFunBuy => "PumpFunBuy",
|
||||||
|
EventType::PumpFunSell => "PumpFunSell",
|
||||||
|
EventType::PumpFunMigrate => "PumpFunMigrate",
|
||||||
|
_ => "Other",
|
||||||
|
};
|
||||||
|
println!("✅ Event #{}: {}", n + 1, event_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
grpc.subscribe_events_immediate(
|
||||||
|
vec![Protocol::PumpFun],
|
||||||
|
None,
|
||||||
|
vec![transaction_filter],
|
||||||
|
vec![account_filter],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("🎧 Listening (up to 60s or Ctrl+C)...\n");
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
|
||||||
|
grpc.stop().await;
|
||||||
|
println!("✓ Stopped.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
//! PumpFun trade event filter: subscribe to Buy/Sell/Create and print details with latency.
|
||||||
|
//!
|
||||||
|
//! Usage: cargo run --example pumpfun_trade_filter --release
|
||||||
|
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::common::types::EventType;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::events::{PumpFunCreateTokenEvent, PumpFunCreateV2TokenEvent, PumpFunTradeEvent};
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::DexEvent;
|
||||||
|
use solana_streamer_sdk::streaming::grpc::ClientConfig;
|
||||||
|
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter, YellowstoneGrpc};
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::Protocol;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter;
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID;
|
||||||
|
|
||||||
|
fn now_micros() -> i64 {
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
|
println!("🚀 PumpFun Trade Event Filter (solana-streamer)\n");
|
||||||
|
|
||||||
|
let mut config = ClientConfig::default();
|
||||||
|
config.enable_metrics = true;
|
||||||
|
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
std::env::var("GRPC_ENDPOINT").unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()),
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let transaction_filter = TransactionFilter {
|
||||||
|
account_include: vec![PUMPFUN_PROGRAM_ID.to_string()],
|
||||||
|
account_exclude: vec![],
|
||||||
|
account_required: vec![],
|
||||||
|
};
|
||||||
|
let account_filter = AccountFilter {
|
||||||
|
account: vec![],
|
||||||
|
owner: vec![PUMPFUN_PROGRAM_ID.to_string()],
|
||||||
|
filters: vec![],
|
||||||
|
};
|
||||||
|
let event_filter = Some(EventTypeFilter {
|
||||||
|
include: vec![
|
||||||
|
EventType::PumpFunBuy,
|
||||||
|
EventType::PumpFunSell,
|
||||||
|
EventType::PumpFunCreateToken,
|
||||||
|
EventType::PumpFunCreateV2Token,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let callback = |event: DexEvent| {
|
||||||
|
let now_us = now_micros();
|
||||||
|
match &event {
|
||||||
|
DexEvent::PumpFunTradeEvent(e) => print_trade(e, now_us),
|
||||||
|
DexEvent::PumpFunCreateTokenEvent(e) => print_create_legacy(e, now_us),
|
||||||
|
DexEvent::PumpFunCreateV2TokenEvent(e) => print_create_v2(e, now_us),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grpc.subscribe_events_immediate(
|
||||||
|
vec![Protocol::PumpFun],
|
||||||
|
None,
|
||||||
|
vec![transaction_filter],
|
||||||
|
vec![account_filter],
|
||||||
|
event_filter,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("🛑 Press Ctrl+C to stop...\n");
|
||||||
|
tokio::signal::ctrl_c().await?;
|
||||||
|
grpc.stop().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_trade(e: &PumpFunTradeEvent, now_us: i64) {
|
||||||
|
let latency_us = now_us - e.metadata.recv_us;
|
||||||
|
let kind = if e.is_buy { "BUY" } else { "SELL" };
|
||||||
|
println!(
|
||||||
|
"│ {} | sig={:.8}.. | mint={:.8}.. | sol={} tok={} | user={:.8}.. | latency={} μs",
|
||||||
|
kind,
|
||||||
|
e.metadata.signature,
|
||||||
|
e.mint,
|
||||||
|
e.sol_amount,
|
||||||
|
e.token_amount,
|
||||||
|
e.user,
|
||||||
|
latency_us
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_create_legacy(e: &PumpFunCreateTokenEvent, now_us: i64) {
|
||||||
|
let latency_us = now_us - e.metadata.recv_us;
|
||||||
|
println!(
|
||||||
|
"│ CREATE | sig={:.8}.. | name={} symbol={} | mint={:.8}.. | latency={} μs",
|
||||||
|
e.metadata.signature,
|
||||||
|
e.name,
|
||||||
|
e.symbol,
|
||||||
|
e.mint,
|
||||||
|
latency_us
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_create_v2(e: &PumpFunCreateV2TokenEvent, now_us: i64) {
|
||||||
|
let latency_us = now_us - e.metadata.recv_us;
|
||||||
|
println!(
|
||||||
|
"│ CREATE_V2 | sig={:.8}.. | name={} symbol={} | mint={:.8}.. | latency={} μs",
|
||||||
|
e.metadata.signature,
|
||||||
|
e.name,
|
||||||
|
e.symbol,
|
||||||
|
e.mint,
|
||||||
|
latency_us
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//! PumpFun subscription with metrics enabled.
|
||||||
|
//!
|
||||||
|
//! Usage: cargo run --example pumpfun_with_metrics --release
|
||||||
|
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID;
|
||||||
|
use solana_streamer_sdk::streaming::grpc::ClientConfig;
|
||||||
|
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter, YellowstoneGrpc};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
|
println!("PumpFun with metrics (solana-streamer)\n");
|
||||||
|
|
||||||
|
let mut config = ClientConfig::default();
|
||||||
|
config.enable_metrics = true;
|
||||||
|
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
std::env::var("GRPC_ENDPOINT").unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()),
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let transaction_filter = TransactionFilter {
|
||||||
|
account_include: vec![PUMPFUN_PROGRAM_ID.to_string()],
|
||||||
|
account_exclude: vec![],
|
||||||
|
account_required: vec![],
|
||||||
|
};
|
||||||
|
let account_filter = AccountFilter {
|
||||||
|
account: vec![],
|
||||||
|
owner: vec![PUMPFUN_PROGRAM_ID.to_string()],
|
||||||
|
filters: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let callback = |event: DexEvent| {
|
||||||
|
println!("Event: {:?}", event.metadata().event_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
grpc.subscribe_events_immediate(
|
||||||
|
vec![Protocol::PumpFun],
|
||||||
|
None,
|
||||||
|
vec![transaction_filter],
|
||||||
|
vec![account_filter],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Press Ctrl+C to stop (metrics printed periodically)...\n");
|
||||||
|
tokio::signal::ctrl_c().await?;
|
||||||
|
grpc.stop().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//! PumpSwap subscription with metrics enabled.
|
||||||
|
//!
|
||||||
|
//! Usage: cargo run --example pumpswap_with_metrics --release
|
||||||
|
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::{DexEvent, Protocol};
|
||||||
|
use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::parser::PUMPSWAP_PROGRAM_ID;
|
||||||
|
use solana_streamer_sdk::streaming::grpc::ClientConfig;
|
||||||
|
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter, YellowstoneGrpc};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
|
println!("PumpSwap with metrics (solana-streamer)\n");
|
||||||
|
|
||||||
|
let mut config = ClientConfig::default();
|
||||||
|
config.enable_metrics = true;
|
||||||
|
|
||||||
|
let grpc = YellowstoneGrpc::new_with_config(
|
||||||
|
std::env::var("GRPC_ENDPOINT").unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()),
|
||||||
|
std::env::var("GRPC_AUTH_TOKEN").ok(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let transaction_filter = TransactionFilter {
|
||||||
|
account_include: vec![PUMPSWAP_PROGRAM_ID.to_string()],
|
||||||
|
account_exclude: vec![],
|
||||||
|
account_required: vec![],
|
||||||
|
};
|
||||||
|
let account_filter = AccountFilter {
|
||||||
|
account: vec![],
|
||||||
|
owner: vec![PUMPSWAP_PROGRAM_ID.to_string()],
|
||||||
|
filters: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let callback = |event: DexEvent| {
|
||||||
|
println!("Event: {:?}", event.metadata().event_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
grpc.subscribe_events_immediate(
|
||||||
|
vec![Protocol::PumpSwap],
|
||||||
|
None,
|
||||||
|
vec![transaction_filter],
|
||||||
|
vec![account_filter],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Press Ctrl+C to stop...\n");
|
||||||
|
tokio::signal::ctrl_c().await?;
|
||||||
|
grpc.stop().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -40,7 +40,10 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let transaction_filter =
|
let transaction_filter =
|
||||||
TransactionFilter { account_include, account_exclude, account_required };
|
TransactionFilter { account_include, account_exclude, account_required };
|
||||||
|
|
||||||
let account_to_listen = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string();
|
let account_to_listen = std::env::var("MINT_ACCOUNT").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Usage: MINT_ACCOUNT=<pubkey> cargo run --example token_decimals_listen_example --release");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
// Listen to account data belonging to owner programs -> account event monitoring
|
// Listen to account data belonging to owner programs -> account event monitoring
|
||||||
let account_filter =
|
let account_filter =
|
||||||
@@ -79,7 +82,7 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
fn create_event_callback() -> impl Fn(DexEvent) {
|
fn create_event_callback() -> impl Fn(DexEvent) {
|
||||||
|event: DexEvent| match event {
|
|event: DexEvent| match event {
|
||||||
DexEvent::TokenInfoEvent(e) => {
|
DexEvent::TokenInfoEvent(e) => {
|
||||||
println!("TokenInfoEvent: {:?}", e.decimals);
|
println!("TokenInfo pubkey={} decimals={} supply={}", e.pubkey, e.decimals, e.supply);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,18 +268,13 @@ impl InnerInstructionLike for solana_sdk::message::compiled_instruction::Compile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adapter for gRPC inner instructions (yellowstone)
|
static SOL_MINT: std::sync::LazyLock<Pubkey> =
|
||||||
impl InnerInstructionLike for yellowstone_grpc_proto::prelude::InnerInstruction {
|
std::sync::LazyLock::new(|| Pubkey::from_str("So11111111111111111111111111111111111111111").unwrap());
|
||||||
fn program_id_index(&self) -> usize {
|
static SYSTEM_PROGRAMS: std::sync::LazyLock<[Pubkey; 3]> = std::sync::LazyLock::new(|| [
|
||||||
self.program_id_index as usize
|
Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(),
|
||||||
}
|
Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap(),
|
||||||
fn accounts(&self) -> &[u8] {
|
Pubkey::from_str("11111111111111111111111111111111").unwrap(),
|
||||||
&self.accounts
|
]);
|
||||||
}
|
|
||||||
fn data(&self) -> &[u8] {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract event context (mint/token account/vault info) from a DexEvent
|
/// Extract event context (mint/token account/vault info) from a DexEvent
|
||||||
fn extract_swap_context(event: &DexEvent) -> (
|
fn extract_swap_context(event: &DexEvent) -> (
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ pub struct PumpFunCreateTokenEvent {
|
|||||||
pub program: Pubkey,
|
pub program: Pubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CreateV2 事件:与 create_v2 指令 16 个账户一致(见 parser 注释)。
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
|
||||||
pub struct PumpFunCreateV2TokenEvent {
|
pub struct PumpFunCreateV2TokenEvent {
|
||||||
#[borsh(skip)]
|
#[borsh(skip)]
|
||||||
|
|||||||
@@ -188,10 +188,11 @@ fn parse_create_token_instruction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 解析创建 V2 代币指令事件 (SPL-22 Token, Mayhem Mode)
|
/// 解析创建 V2 代币指令事件 (SPL-22 Token, Mayhem Mode)
|
||||||
/// 账户: 0: mint, 1: mint_authority, 2: bonding_curve, 3: associated_bonding_curve, 4: global,
|
/// 与 IDL create_v2 及区块浏览器一致,共 16 个固定账户:
|
||||||
/// 5: user, 6: system_program, 7: token_program, 8: associated_token_program, 9: mayhem_program_id,
|
/// 0: mint, 1: mint_authority, 2: bonding_curve, 3: associated_bonding_curve, 4: global,
|
||||||
/// 10: global_params, 11: sol_vault, 12: mayhem_state, 13: mayhem_token_vault, 14: event_authority, 15: program.
|
/// 5: user, 6: system_program, 7: token_program, 8: associated_token_program, 9: mayhem_program_id,
|
||||||
/// 共 16 个固定账户,不足时返回 None 避免越界。
|
/// 10: global_params, 11: sol_vault, 12: mayhem_state, 13: mayhem_token_vault, 14: event_authority, 15: program.
|
||||||
|
/// 不足 16 个账户时返回 None 避免越界。
|
||||||
/// 注意:shredstream 路径仅传入 static_account_keys,若交易使用 Address Lookup Tables,
|
/// 注意:shredstream 路径仅传入 static_account_keys,若交易使用 Address Lookup Tables,
|
||||||
/// 无法解析 loaded_addresses,部分账户会以 default 填充,导致 token_program/global 等错误。
|
/// 无法解析 loaded_addresses,部分账户会以 default 填充,导致 token_program/global 等错误。
|
||||||
fn parse_create_v2_token_instruction(
|
fn parse_create_v2_token_instruction(
|
||||||
@@ -199,12 +200,13 @@ fn parse_create_v2_token_instruction(
|
|||||||
accounts: &[Pubkey],
|
accounts: &[Pubkey],
|
||||||
mut metadata: EventMetadata,
|
mut metadata: EventMetadata,
|
||||||
) -> Option<DexEvent> {
|
) -> Option<DexEvent> {
|
||||||
metadata.event_type = EventType::PumpFunCreateV2Token;
|
|
||||||
|
|
||||||
const CREATE_V2_MIN_ACCOUNTS: usize = 16;
|
const CREATE_V2_MIN_ACCOUNTS: usize = 16;
|
||||||
if data.len() < 16 || accounts.len() < CREATE_V2_MIN_ACCOUNTS {
|
// Guard: avoid index out of bounds (e.g. ALT-loaded tx with fewer static accounts). See issue #63.
|
||||||
|
if accounts.len() < CREATE_V2_MIN_ACCOUNTS || data.len() < 16 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
metadata.event_type = EventType::PumpFunCreateV2Token;
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
if offset + 4 > data.len() {
|
if offset + 4 > data.len() {
|
||||||
return None;
|
return None;
|
||||||
@@ -242,28 +244,30 @@ fn parse_create_v2_token_instruction(
|
|||||||
Pubkey::default()
|
Pubkey::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Safe slice: already guaranteed accounts.len() >= 16 above; avoid any index panic (issue #63).
|
||||||
|
let acc = &accounts[0..CREATE_V2_MIN_ACCOUNTS];
|
||||||
Some(DexEvent::PumpFunCreateV2TokenEvent(PumpFunCreateV2TokenEvent {
|
Some(DexEvent::PumpFunCreateV2TokenEvent(PumpFunCreateV2TokenEvent {
|
||||||
metadata,
|
metadata,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
creator,
|
creator,
|
||||||
mint: accounts[0],
|
mint: acc[0],
|
||||||
mint_authority: accounts[1],
|
mint_authority: acc[1],
|
||||||
bonding_curve: accounts[2],
|
bonding_curve: acc[2],
|
||||||
associated_bonding_curve: accounts[3],
|
associated_bonding_curve: acc[3],
|
||||||
global: accounts[4],
|
global: acc[4],
|
||||||
user: accounts[5],
|
user: acc[5],
|
||||||
system_program: accounts[6],
|
system_program: acc[6],
|
||||||
token_program: accounts[7],
|
token_program: acc[7],
|
||||||
associated_token_program: accounts[8],
|
associated_token_program: acc[8],
|
||||||
mayhem_program_id: accounts[9],
|
mayhem_program_id: acc[9],
|
||||||
global_params: accounts[10],
|
global_params: acc[10],
|
||||||
sol_vault: accounts[11],
|
sol_vault: acc[11],
|
||||||
mayhem_state: accounts[12],
|
mayhem_state: acc[12],
|
||||||
mayhem_token_vault: accounts[13],
|
mayhem_token_vault: acc[13],
|
||||||
event_authority: accounts[14],
|
event_authority: acc[14],
|
||||||
program: accounts[15],
|
program: acc[15],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user