mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-16 10:28:05 +00:00
feat(shred): tx_index, Migrate without CPI, merge fix, doc limits
- Add tx_index to TransactionWithSlot (entry index), pass to parser - Emit PumpFun Migrate even when no CPI (shred gets instruction-only) - Merge: only overwrite Create/CreateV2 with CPI when value non-default - Rename transaction_index -> tx_index across codebase - Add docs/SHREDSTREAM_LIMITATIONS.md (ALT/CPI limits, field completeness) - Comment shred path: static_account_keys only, no inner_instructions Made-with: Cursor
This commit is contained in:
@@ -301,7 +301,7 @@ pub struct SwapData {
|
||||
pub struct EventMetadata {
|
||||
pub signature: Signature,
|
||||
pub slot: u64,
|
||||
pub transaction_index: Option<u64>, // 新增:交易在slot中的索引
|
||||
pub tx_index: Option<u64>, // 新增:交易在slot中的索引
|
||||
pub block_time: i64,
|
||||
pub block_time_ms: i64,
|
||||
pub recv_us: i64,
|
||||
@@ -327,7 +327,7 @@ impl EventMetadata {
|
||||
outer_index: i64,
|
||||
inner_index: Option<i64>,
|
||||
recv_us: i64,
|
||||
transaction_index: Option<u64>,
|
||||
tx_index: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
signature,
|
||||
@@ -342,7 +342,7 @@ impl EventMetadata {
|
||||
swap_data: None,
|
||||
outer_index,
|
||||
inner_index,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ impl EventParser {
|
||||
block_time: Option<Timestamp>,
|
||||
recv_us: i64,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
transaction_index: Option<u64>,
|
||||
tx_index: Option<u64>,
|
||||
callback: Arc<dyn Fn(DexEvent) + Send + Sync>,
|
||||
) -> anyhow::Result<()> {
|
||||
// 创建适配器回调,将所有权回调转换为引用回调
|
||||
@@ -93,7 +93,7 @@ impl EventParser {
|
||||
&accounts,
|
||||
&inner_instructions,
|
||||
bot_wallet,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
adapter_callback,
|
||||
)
|
||||
.await?;
|
||||
@@ -119,7 +119,7 @@ impl EventParser {
|
||||
accounts: &[Pubkey],
|
||||
inner_instructions: &[InnerInstructions],
|
||||
bot_wallet: Option<Pubkey>,
|
||||
transaction_index: Option<u64>,
|
||||
tx_index: Option<u64>,
|
||||
callback: Arc<dyn Fn(DexEvent) + Send + Sync>,
|
||||
) -> anyhow::Result<()> {
|
||||
// 创建适配器回调,将所有权回调转换为引用回调
|
||||
@@ -159,7 +159,7 @@ impl EventParser {
|
||||
index as i64,
|
||||
None,
|
||||
bot_wallet,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
inner_instructions,
|
||||
adapter_callback.clone(),
|
||||
)?;
|
||||
@@ -181,7 +181,7 @@ impl EventParser {
|
||||
index as i64,
|
||||
Some(inner_index as i64),
|
||||
bot_wallet,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
Some(&inner_instructions),
|
||||
adapter_callback.clone(),
|
||||
)?;
|
||||
@@ -213,7 +213,7 @@ impl EventParser {
|
||||
accounts: &[Pubkey],
|
||||
inner_instructions: &[yellowstone_grpc_proto::prelude::InnerInstructions],
|
||||
bot_wallet: Option<Pubkey>,
|
||||
transaction_index: Option<u64>,
|
||||
tx_index: Option<u64>,
|
||||
callback: Arc<dyn for<'a> Fn(&'a DexEvent) + Send + Sync>,
|
||||
) -> anyhow::Result<()> {
|
||||
// 获取交易的指令和账户
|
||||
@@ -248,7 +248,7 @@ impl EventParser {
|
||||
index as i64,
|
||||
None,
|
||||
bot_wallet,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
inner_instructions,
|
||||
callback.clone(),
|
||||
)?;
|
||||
@@ -278,7 +278,7 @@ impl EventParser {
|
||||
inner_instructions.index as i64,
|
||||
Some(inner_index as i64),
|
||||
bot_wallet,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
Some(&inner_instructions),
|
||||
callback.clone(),
|
||||
)?;
|
||||
@@ -307,7 +307,7 @@ impl EventParser {
|
||||
outer_index: i64,
|
||||
inner_index: Option<i64>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
transaction_index: Option<u64>,
|
||||
tx_index: Option<u64>,
|
||||
inner_instructions: Option<&yellowstone_grpc_proto::prelude::InnerInstructions>,
|
||||
callback: Arc<dyn for<'a> Fn(&'a DexEvent) + Send + Sync>,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -346,7 +346,7 @@ impl EventParser {
|
||||
outer_index,
|
||||
inner_index,
|
||||
recv_us,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
);
|
||||
|
||||
if is_cu_program {
|
||||
@@ -428,14 +428,7 @@ impl EventParser {
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊处理: PumpFun MIGRATE 指令需要 inner instruction data
|
||||
if matches!(protocol, Protocol::PumpFun) {
|
||||
const PUMPFUN_MIGRATE_IX: &[u8] = &[155, 234, 231, 146, 236, 158, 162, 30];
|
||||
if instruction_discriminator == PUMPFUN_MIGRATE_IX && inner_instruction_event.is_none()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// PumpFun MIGRATE: 有 CPI 时合并 log;无 CPI 时仍发出仅含指令数据的事件。
|
||||
|
||||
// 合并事件
|
||||
if let Some(inner_instruction_event) = inner_instruction_event {
|
||||
@@ -471,7 +464,7 @@ impl EventParser {
|
||||
outer_index: i64,
|
||||
inner_index: Option<i64>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
transaction_index: Option<u64>,
|
||||
tx_index: Option<u64>,
|
||||
inner_instructions: Option<&InnerInstructions>,
|
||||
callback: Arc<dyn for<'a> Fn(&'a DexEvent) + Send + Sync>,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -511,7 +504,7 @@ impl EventParser {
|
||||
outer_index,
|
||||
inner_index,
|
||||
recv_us,
|
||||
transaction_index,
|
||||
tx_index,
|
||||
);
|
||||
|
||||
if is_cu_program {
|
||||
@@ -611,14 +604,7 @@ impl EventParser {
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊处理: PumpFun MIGRATE 指令需要 inner instruction data
|
||||
if matches!(protocol, Protocol::PumpFun) {
|
||||
const PUMPFUN_MIGRATE_IX: &[u8] = &[155, 234, 231, 146, 236, 158, 162, 30];
|
||||
if instruction_discriminator == PUMPFUN_MIGRATE_IX && inner_instruction_event.is_none()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// PumpFun MIGRATE: 有 CPI 时合并 log;无 CPI(如 shred)仍发出仅含指令数据的事件。
|
||||
|
||||
// 合并事件
|
||||
if let Some(inner_instruction_event) = inner_instruction_event {
|
||||
|
||||
@@ -36,16 +36,36 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) {
|
||||
},
|
||||
DexEvent::PumpFunCreateTokenEvent(e) => match cpi_log_event {
|
||||
DexEvent::PumpFunCreateV2TokenEvent(cpie) => {
|
||||
e.mint = cpie.mint;
|
||||
e.bonding_curve = cpie.bonding_curve;
|
||||
e.user = cpie.user;
|
||||
e.creator = cpie.creator;
|
||||
e.timestamp = cpie.timestamp;
|
||||
e.virtual_token_reserves = cpie.virtual_token_reserves;
|
||||
e.virtual_sol_reserves = cpie.virtual_sol_reserves;
|
||||
e.real_token_reserves = cpie.real_token_reserves;
|
||||
e.token_total_supply = cpie.token_total_supply;
|
||||
e.token_program = cpie.token_program;
|
||||
if cpie.mint != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.mint = cpie.mint;
|
||||
}
|
||||
if cpie.bonding_curve != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.bonding_curve = cpie.bonding_curve;
|
||||
}
|
||||
if cpie.user != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.user = cpie.user;
|
||||
}
|
||||
if cpie.creator != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.creator = cpie.creator;
|
||||
}
|
||||
if cpie.timestamp != 0 {
|
||||
e.timestamp = cpie.timestamp;
|
||||
}
|
||||
if cpie.virtual_token_reserves != 0 {
|
||||
e.virtual_token_reserves = cpie.virtual_token_reserves;
|
||||
}
|
||||
if cpie.virtual_sol_reserves != 0 {
|
||||
e.virtual_sol_reserves = cpie.virtual_sol_reserves;
|
||||
}
|
||||
if cpie.real_token_reserves != 0 {
|
||||
e.real_token_reserves = cpie.real_token_reserves;
|
||||
}
|
||||
if cpie.token_total_supply != 0 {
|
||||
e.token_total_supply = cpie.token_total_supply;
|
||||
}
|
||||
if cpie.token_program != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.token_program = cpie.token_program;
|
||||
}
|
||||
e.is_mayhem_mode = cpie.is_mayhem_mode;
|
||||
e.is_cashback_enabled = cpie.is_cashback_enabled;
|
||||
}
|
||||
@@ -53,16 +73,36 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) {
|
||||
},
|
||||
DexEvent::PumpFunCreateV2TokenEvent(e) => match cpi_log_event {
|
||||
DexEvent::PumpFunCreateV2TokenEvent(cpie) => {
|
||||
e.mint = cpie.mint;
|
||||
e.bonding_curve = cpie.bonding_curve;
|
||||
e.user = cpie.user;
|
||||
e.creator = cpie.creator;
|
||||
e.timestamp = cpie.timestamp;
|
||||
e.virtual_token_reserves = cpie.virtual_token_reserves;
|
||||
e.virtual_sol_reserves = cpie.virtual_sol_reserves;
|
||||
e.real_token_reserves = cpie.real_token_reserves;
|
||||
e.token_total_supply = cpie.token_total_supply;
|
||||
e.token_program = cpie.token_program;
|
||||
if cpie.mint != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.mint = cpie.mint;
|
||||
}
|
||||
if cpie.bonding_curve != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.bonding_curve = cpie.bonding_curve;
|
||||
}
|
||||
if cpie.user != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.user = cpie.user;
|
||||
}
|
||||
if cpie.creator != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.creator = cpie.creator;
|
||||
}
|
||||
if cpie.timestamp != 0 {
|
||||
e.timestamp = cpie.timestamp;
|
||||
}
|
||||
if cpie.virtual_token_reserves != 0 {
|
||||
e.virtual_token_reserves = cpie.virtual_token_reserves;
|
||||
}
|
||||
if cpie.virtual_sol_reserves != 0 {
|
||||
e.virtual_sol_reserves = cpie.virtual_sol_reserves;
|
||||
}
|
||||
if cpie.real_token_reserves != 0 {
|
||||
e.real_token_reserves = cpie.real_token_reserves;
|
||||
}
|
||||
if cpie.token_total_supply != 0 {
|
||||
e.token_total_supply = cpie.token_total_supply;
|
||||
}
|
||||
if cpie.token_program != solana_sdk::pubkey::Pubkey::default() {
|
||||
e.token_program = cpie.token_program;
|
||||
}
|
||||
e.is_mayhem_mode = cpie.is_mayhem_mode;
|
||||
e.is_cashback_enabled = cpie.is_cashback_enabled;
|
||||
}
|
||||
|
||||
@@ -192,6 +192,8 @@ fn parse_create_token_instruction(
|
||||
/// 5: user, 6: system_program, 7: token_program, 8: associated_token_program, 9: mayhem_program_id,
|
||||
/// 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,
|
||||
/// 无法解析 loaded_addresses,部分账户会以 default 填充,导致 token_program/global 等错误。
|
||||
fn parse_create_v2_token_instruction(
|
||||
data: &[u8],
|
||||
accounts: &[Pubkey],
|
||||
|
||||
Reference in New Issue
Block a user