diff --git a/Cargo.toml b/Cargo.toml index d893295..fac42c4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "0.4.2" +version = "0.4.3" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" diff --git a/README.md b/README.md index 65473f9..54ee142 100755 --- a/README.md +++ b/README.md @@ -46,14 +46,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.2" } +solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.3" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -solana-streamer-sdk = "0.4.2" +solana-streamer-sdk = "0.4.3" ``` ## Configuration System diff --git a/README_CN.md b/README_CN.md index 1c56cf8..88413c7 100644 --- a/README_CN.md +++ b/README_CN.md @@ -46,14 +46,14 @@ git clone https://github.com/0xfnzero/solana-streamer ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.2" } +solana-streamer-sdk = { path = "./solana-streamer", version = "0.4.3" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -solana-streamer-sdk = "0.4.2" +solana-streamer-sdk = "0.4.3" ``` ## 配置系统 diff --git a/examples/grpc_example.rs b/examples/grpc_example.rs index 5a96e51..ae0a466 100644 --- a/examples/grpc_example.rs +++ b/examples/grpc_example.rs @@ -3,7 +3,7 @@ use solana_streamer_sdk::{ streaming::{ event_parser::{ common::EventType, - core::account_event_parser::CommonAccountEvent, + core::account_event_parser::{TokenInfoEvent, NonceAccountEvent, TokenAccountEvent}, protocols::{ bonk::{ parser::BONK_PROGRAM_ID, BonkGlobalConfigAccountEvent, BonkMigrateToAmmEvent, @@ -289,9 +289,15 @@ fn create_event_callback() -> impl Fn(Box) { RaydiumCpmmPoolStateAccountEvent => |e: RaydiumCpmmPoolStateAccountEvent| { println!("RaydiumCpmmPoolStateAccountEvent: {e:?}"); }, - CommonAccountEvent => |e: CommonAccountEvent| { - println!("CommonAccountEvent: {e:?}"); + TokenAccountEvent => |e: TokenAccountEvent| { + println!("TokenAccountEvent: {e:?}"); + }, + NonceAccountEvent => |e: NonceAccountEvent| { + println!("NonceAccountEvent: {e:?}"); + }, + TokenInfoEvent => |e: TokenInfoEvent| { + println!("TokenInfoEvent: {e:?}"); }, }); } -} \ No newline at end of file +} diff --git a/examples/nonce_listen_example.rs b/examples/nonce_listen_example.rs index baa540b..bb3321f 100644 --- a/examples/nonce_listen_example.rs +++ b/examples/nonce_listen_example.rs @@ -45,7 +45,7 @@ async fn test_grpc() -> Result<(), Box> { let account_filter = AccountFilter { account: vec![nonce_account], owner: vec![] }; // Event filtering - let event_type_filter = Some(EventTypeFilter { include: vec![EventType::AccountNonce] }); + let event_type_filter = Some(EventTypeFilter { include: vec![EventType::NonceAccount] }); println!("Starting to listen for events, press Ctrl+C to stop..."); println!("Starting subscription..."); diff --git a/examples/shred_example.rs b/examples/shred_example.rs index a1a9e90..bc7bb34 100644 --- a/examples/shred_example.rs +++ b/examples/shred_example.rs @@ -3,7 +3,7 @@ use solana_streamer_sdk::{ streaming::{ event_parser::{ common::EventType, - core::account_event_parser::CommonAccountEvent, + core::account_event_parser::TokenAccountEvent, protocols::{ bonk::{ BonkGlobalConfigAccountEvent, BonkMigrateToAmmEvent, @@ -245,8 +245,8 @@ fn create_event_callback() -> impl Fn(Box) { RaydiumCpmmPoolStateAccountEvent => |e: RaydiumCpmmPoolStateAccountEvent| { println!("RaydiumCpmmPoolStateAccountEvent: {e:?}"); }, - CommonAccountEvent => |e: CommonAccountEvent| { - println!("CommonAccountEvent: {e:?}"); + TokenAccountEvent => |e: TokenAccountEvent| { + println!("TokenAccountEvent: {e:?}"); }, }); } diff --git a/examples/token_balance_listen_example.rs b/examples/token_balance_listen_example.rs index bcca67c..ee47c5e 100644 --- a/examples/token_balance_listen_example.rs +++ b/examples/token_balance_listen_example.rs @@ -46,7 +46,7 @@ async fn test_grpc() -> Result<(), Box> { let account_filter = AccountFilter { account: vec![account_to_listen], owner: vec![] }; // Event filtering - let event_type_filter = Some(EventTypeFilter { include: vec![EventType::AccountCommon] }); + let event_type_filter = Some(EventTypeFilter { include: vec![EventType::TokenAccount] }); println!("Starting to listen for events, press Ctrl+C to stop..."); println!("Starting subscription..."); diff --git a/examples/token_decimals_listen_example.rs b/examples/token_decimals_listen_example.rs new file mode 100644 index 0000000..38bc9e3 --- /dev/null +++ b/examples/token_decimals_listen_example.rs @@ -0,0 +1,90 @@ +use solana_streamer_sdk::{ + match_event, + streaming::{ + event_parser::{ + common::{filter::EventTypeFilter, EventType}, + core::account_event_parser::TokenInfoEvent, + UnifiedEvent, + }, + grpc::ClientConfig, + yellowstone_grpc::{AccountFilter, TransactionFilter}, + YellowstoneGrpc, + }, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Starting Yellowstone gRPC Streamer..."); + test_grpc().await?; + Ok(()) +} + +async fn test_grpc() -> Result<(), Box> { + println!("Subscribing to Yellowstone gRPC events..."); + // Create low-latency configuration + let mut config: ClientConfig = ClientConfig::low_latency(); + // Enable performance monitoring, has performance overhead, disabled by default + config.enable_metrics = true; + let grpc = YellowstoneGrpc::new_with_config( + "https://solana-yellowstone-grpc.publicnode.com:443".to_string(), + None, + config, + )?; + println!("GRPC client created successfully"); + let callback = create_event_callback(); + // Will try to parse corresponding protocol events from transactions + let protocols = vec![]; + println!("Protocols to monitor: {:?}", protocols); + // Filter accounts + let account_include = vec![]; + let account_exclude = vec![]; + let account_required = vec![]; + + // Listen to transaction data + let transaction_filter = + TransactionFilter { account_include, account_exclude, account_required }; + + let account_to_listen = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(); + + // Listen to account data belonging to owner programs -> account event monitoring + let account_filter = AccountFilter { account: vec![account_to_listen], owner: vec![] }; + + // Event filtering + let event_type_filter = Some(EventTypeFilter { include: vec![EventType::TokenAccount] }); + + println!("Starting to listen for events, press Ctrl+C to stop..."); + println!("Starting subscription..."); + + grpc.subscribe_events_immediate( + protocols.clone(), + None, + transaction_filter.clone(), + account_filter.clone(), + event_type_filter.clone(), + None, + callback, + ) + .await?; + + // 支持 stop 方法,测试代码 - 异步1000秒之后停止 + let grpc_clone = grpc.clone(); + tokio::spawn(async move { + tokio::time::sleep(std::time::Duration::from_secs(1000)).await; + grpc_clone.stop().await; + }); + + println!("Waiting for Ctrl+C to stop..."); + tokio::signal::ctrl_c().await?; + + Ok(()) +} + +fn create_event_callback() -> impl Fn(Box) { + |event: Box| { + match_event!(event, { + TokenInfoEvent => |e: TokenInfoEvent| { + println!("TokenInfoEvent: {:?}", e.decimals); + }, + }); + } +} diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 0c04cf4..a5328d4 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -141,8 +141,8 @@ pub enum EventType { AccountRaydiumCpmmAmmConfig, AccountRaydiumCpmmPoolState, - AccountNonce, - AccountCommon, + NonceAccount, + TokenAccount, // Common events BlockMeta, @@ -164,8 +164,8 @@ pub const ACCOUNT_EVENT_TYPES: &[EventType] = &[ EventType::AccountRaydiumClmmTickArrayState, EventType::AccountRaydiumCpmmAmmConfig, EventType::AccountRaydiumCpmmPoolState, - EventType::AccountCommon, - EventType::AccountNonce, + EventType::TokenAccount, + EventType::NonceAccount, ]; pub const BLOCK_EVENT_TYPES: &[EventType] = &[EventType::BlockMeta]; @@ -230,8 +230,8 @@ impl fmt::Display for EventType { } EventType::AccountRaydiumCpmmAmmConfig => write!(f, "AccountRaydiumCpmmAmmConfig"), EventType::AccountRaydiumCpmmPoolState => write!(f, "AccountRaydiumCpmmPoolState"), - EventType::AccountCommon => write!(f, "AccountCommon"), - EventType::AccountNonce => write!(f, "AccountNonce"), + EventType::TokenAccount => write!(f, "TokenAccount"), + EventType::NonceAccount => write!(f, "NonceAccount"), EventType::BlockMeta => write!(f, "BlockMeta"), EventType::Unknown => write!(f, "Unknown"), } diff --git a/src/streaming/event_parser/core/account_event_parser.rs b/src/streaming/event_parser/core/account_event_parser.rs index cf10061..87cad9d 100644 --- a/src/streaming/event_parser/core/account_event_parser.rs +++ b/src/streaming/event_parser/core/account_event_parser.rs @@ -33,7 +33,7 @@ pub struct AccountEventParseConfig { /// 通用账户事件 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct CommonAccountEvent { +pub struct TokenAccountEvent { pub metadata: EventMetadata, pub pubkey: Pubkey, pub executable: bool, @@ -42,7 +42,7 @@ pub struct CommonAccountEvent { pub rent_epoch: u64, pub amount: Option, } -impl_unified_event!(CommonAccountEvent,); +impl_unified_event!(TokenAccountEvent,); /// Nonce account event #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -57,6 +57,20 @@ pub struct NonceAccountEvent { } impl_unified_event!(NonceAccountEvent,); +/// Nonce account event +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct TokenInfoEvent { + pub metadata: EventMetadata, + pub pubkey: Pubkey, + pub executable: bool, + pub lamports: u64, + pub owner: Pubkey, + pub rent_epoch: u64, + pub supply: u64, + pub decimals: u8, +} +impl_unified_event!(TokenInfoEvent,); + /// 账户事件解析器 pub type AccountEventParserFn = fn(account: &AccountPretty, metadata: EventMetadata) -> Option>; @@ -209,12 +223,12 @@ impl AccountEventParser { } if event_type_filter.is_none() - || event_type_filter.unwrap().include.contains(&EventType::AccountNonce) + || event_type_filter.unwrap().include.contains(&EventType::NonceAccount) { let nonce_config = NONCE_CONFIG.get_or_init(|| AccountEventParseConfig { program_id: Pubkey::default(), protocol_type: ProtocolType::Common, - event_type: EventType::AccountNonce, + event_type: EventType::NonceAccount, account_discriminator: &[1, 0, 0, 0, 1, 0, 0, 0], account_parser: Self::parse_nonce_account_event, }); @@ -224,7 +238,7 @@ impl AccountEventParser { let common_config = COMMON_CONFIG.get_or_init(|| AccountEventParseConfig { program_id: Pubkey::default(), protocol_type: ProtocolType::Common, - event_type: EventType::AccountCommon, + event_type: EventType::TokenAccount, account_discriminator: &[], account_parser: Self::parse_token_account_event, }); @@ -272,8 +286,24 @@ impl AccountEventParser { account: &AccountPretty, metadata: EventMetadata, ) -> Option> { + use solana_program::program_pack::Pack; + use spl_token::state::Mint; + if let Ok(mint) = Mint::unpack_from_slice(&account.data) { + let mut event = TokenInfoEvent { + metadata, + pubkey: account.pubkey, + executable: account.executable, + lamports: account.lamports, + owner: account.owner, + rent_epoch: account.rent_epoch, + supply: mint.supply, + decimals: mint.decimals, + }; + event.set_handle_us(elapsed_micros_since(account.recv_us)); + return Some(Box::new(event)); + } let info = Account::unpack(&account.data); - let mut event = CommonAccountEvent { + let mut event = TokenAccountEvent { metadata, pubkey: account.pubkey, executable: account.executable,