refactor: remove trade parse status and preserve wrapper provenance

This commit is contained in:
0xfnzero
2026-10-28 01:13:47 +08:00
parent 9e471c36a2
commit 9ec0f2cc2c
19 changed files with 9 additions and 101 deletions
+2 -2
View File
@@ -305,9 +305,9 @@ account value is also available from
## Trade provenance and parsing boundaries
gRPC/RPC use supplied execution metadata and actual CPI instructions; Shred sees outer instructions and locally resolved accounts. An unknown third-party instruction referencing Pump.fun in its own accounts can produce a `PumpFunTrade` candidate with the original input in `third_program` and `Unresolved` status. This does not establish execution, direction or amounts. Buy/sell-only filters do not treat an unknown direction as a fill.
gRPC/RPC use supplied execution metadata and actual CPI instructions; Shred sees outer instructions and locally resolved accounts. An unknown third-party instruction referencing Pump.fun in its own accounts can produce a `PumpFunTrade` candidate with the original input in `third_program`. This does not establish execution, direction or amounts. Buy/sell-only filters do not treat an unknown direction as a fill.
The parsing hot path performs no RPC. Check `parse_status` before interpreting defaults as amounts. Original account order and instruction bytes remain in `third_program`; transaction position and receive time remain in event metadata. A local Shred index is not confirmed slot-wide execution order. See [wrapped transactions](docs/wrapped-pumpfun.md).
The parsing hot path performs no RPC. Missing fields retain defaults and must not be interpreted as actual zero fills. Original account order and instruction bytes remain in `third_program`; transaction position and receive time remain in event metadata. A local Shred index is not confirmed slot-wide execution order. See [wrapped transactions](docs/wrapped-pumpfun.md).
Known Pump.fun create/trade instructions keep their callbacks with unresolved ALT accounts; unknown addresses stay unknown. The bridge preserves `create_v2`, V2/V3 method names, original creation accounts and official `multi_hop_swap` provenance. Run `cargo run --example rpc_transaction_replay` to compare the reported signed transactions through the static Shred and execution facades offline. Shred route budgets are not per-hop fill amounts; execution metadata supplies actual quote/token amounts.
+2 -2
View File
@@ -285,9 +285,9 @@ log-only 的 `CreatePoolEvent` payload 因为 IDL 不包含该字段,会保持
## 交易来源与解析边界
gRPC/RPC 使用提供的执行元数据和实际 CPI 指令;Shred 只能看到外层指令与本地已解析的账户。未知第三方指令在自身账户中引用 Pump.fun 时,可回调 `PumpFunTrade` 候选,携带 `third_program` 原始输入与 `Unresolved` 状态;这不证明交易已执行,也不确定方向或金额。买入/卖出专用过滤器不会把未知方向当作成交。
gRPC/RPC 使用提供的执行元数据和实际 CPI 指令;Shred 只能看到外层指令与本地已解析的账户。未知第三方指令在自身账户中引用 Pump.fun 时,可回调 `PumpFunTrade` 候选,携带 `third_program` 原始输入;这不证明交易已执行,也不确定方向或金额。买入/卖出专用过滤器不会把未知方向当作成交。
解析热路径不调用 RPC。按 `parse_status` 判断证据,缺失字段的默认值不代表实际零成交。账户顺序与原始指令数据保留在 `third_program`;交易序号和接收时间仍在事件元数据中,本地 Shred 序号不等于全 slot 执行顺序。详见 [第三方包装交易](docs/wrapped-pumpfun.md)。
解析热路径不调用 RPC。缺失字段的默认值不代表实际零成交。账户顺序与原始指令数据保留在 `third_program`;交易序号和接收时间仍在事件元数据中,本地 Shred 序号不等于全 slot 执行顺序。详见 [第三方包装交易](docs/wrapped-pumpfun.md)。
已知 Pump.fun 创建/交易指令在 ALT 未展开时仍保留回调,未知账户保持未知。适配层保留 `create_v2`、V2/V3 方法名、创建账户和官方 `multi_hop_swap` 来源。运行 `cargo run --example rpc_transaction_replay`,可用已报告的真实签名交易离线比较纯 Shred 与执行解析;Shred 路由预算不是各跳成交金额,实际报价币/代币金额来自执行元数据。
+1 -2
View File
@@ -1,7 +1,6 @@
# Wrapped Pump.fun transactions
The streamer preserves `PumpFunTradeEvent.third_program`, `parse_status` and
transaction metadata from sol-parser-sdk. Subscribe to the PumpFunTrade family
The streamer preserves `PumpFunTradeEvent.third_program` and transaction metadata from sol-parser-sdk. Subscribe to the PumpFunTrade family
to receive unresolved Shred candidates as well as gRPC trades.
Shred candidates only establish that an unknown outer instruction references
+1 -1
View File
@@ -10,7 +10,7 @@ use solana_streamer_sdk::streaming::event_parser::common::{filter::EventTypeFilt
fn main() {
let streamer_filter = EventTypeFilter::include_only(vec![
// Include unresolved Shred candidates as well as decoded buys/sells.
// Inspect parse_status and third_program before using amounts.
// Inspect third_program; missing amounts retain defaults.
EventType::PumpFunTrade,
EventType::TokenAccount,
]);
-4
View File
@@ -482,10 +482,6 @@ mod tests {
let DexEvent::PumpFunTradeEvent(candidate) = &events[0] else {
panic!("missing wrapper candidate")
};
assert_eq!(
candidate.parse_status,
sol_parser_sdk::core::events::PumpFunParseStatus::Unresolved
);
let raw = candidate.third_program.as_ref().unwrap();
let ix = &tx.message.instructions()[raw.instruction_index];
assert_eq!(raw.instruction_data, ix.data);
@@ -1,6 +1,6 @@
//! Consistent provenance access across streamer trade events.
use super::DexEvent;
use sol_parser_sdk::core::events::{RawInstructionPayload, TradeParseStatus};
use sol_parser_sdk::core::events::RawInstructionPayload;
impl DexEvent {
pub fn third_program(&self) -> Option<&RawInstructionPayload> {
@@ -22,24 +22,4 @@ impl DexEvent {
_ => None,
}
}
pub fn trade_parse_status(&self) -> Option<TradeParseStatus> {
match self {
Self::PumpFunTradeEvent(t) => Some(t.parse_status),
Self::PumpSwapBuyEvent(t) => Some(t.parse_status),
Self::PumpSwapSellEvent(t) => Some(t.parse_status),
Self::RaydiumCpmmSwapEvent(t) => Some(t.parse_status),
Self::RaydiumAmmV4SwapEvent(t) => Some(t.parse_status),
Self::RaydiumClmmSwapEvent(t) => Some(t.parse_status),
Self::RaydiumClmmSwapV2Event(t) => Some(t.parse_status),
Self::BonkTradeEvent(t) => Some(t.parse_status),
Self::MeteoraDammV2SwapEvent(t) => Some(t.parse_status),
Self::MeteoraDammV2Swap2Event(t) => Some(t.parse_status),
Self::OrcaWhirlpoolSwapEvent(t) => Some(t.parse_status),
Self::MeteoraDbcSwapEvent(t) => Some(t.parse_status),
Self::MeteoraPoolsSwapEvent(t) => Some(t.parse_status),
Self::MeteoraDlmmSwapEvent(t) => Some(t.parse_status),
_ => None,
}
}
}
@@ -16,9 +16,6 @@ pub struct BonkTradeEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[borsh(skip)]
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
#[borsh(skip)]
pub metadata: EventMetadata,
pub pool_state: Pubkey,
pub total_base_sell: u64,
@@ -39,8 +39,6 @@ pub struct PoolFeeParameters {
pub struct MeteoraDammV2SwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
@@ -100,8 +98,6 @@ pub struct MeteoraDammV2SwapEvent {
pub struct MeteoraDammV2Swap2Event {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
@@ -152,9 +152,6 @@ pub struct PumpFunTradeEvent {
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[borsh(skip)]
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::PumpFunParseStatus,
#[borsh(skip)]
#[serde(default)]
pub creator_fee_unclaimed: u64,
#[borsh(skip)]
pub metadata: EventMetadata,
@@ -13,9 +13,6 @@ pub struct PumpSwapBuyEvent {
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[borsh(skip)]
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
#[borsh(skip)]
#[serde(default)]
pub creator_fee_unclaimed: u64,
#[borsh(skip)]
pub metadata: EventMetadata,
@@ -106,9 +103,6 @@ pub struct PumpSwapSellEvent {
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[borsh(skip)]
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
#[borsh(skip)]
#[serde(default)]
pub creator_fee_unclaimed: u64,
#[borsh(skip)]
pub metadata: EventMetadata,
@@ -11,9 +11,6 @@ pub struct RaydiumAmmV4SwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[borsh(skip)]
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
#[borsh(skip)]
pub metadata: EventMetadata,
// base in
pub amount_in: u64,
@@ -9,8 +9,6 @@ use solana_sdk::pubkey::Pubkey;
pub struct RaydiumClmmSwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
pub amount: u64,
pub other_amount_threshold: u64,
@@ -59,8 +57,6 @@ pub struct RaydiumClmmSwapEvent {
pub struct RaydiumClmmSwapV2Event {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
pub amount: u64,
pub other_amount_threshold: u64,
@@ -12,9 +12,6 @@ pub struct RaydiumCpmmSwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[borsh(skip)]
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
#[borsh(skip)]
pub metadata: EventMetadata,
pub amount_in: u64,
pub minimum_amount_out: u64,
@@ -15,8 +15,6 @@ pub struct ParserSdkErrorEvent {
pub struct OrcaWhirlpoolSwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
pub whirlpool: Pubkey,
pub input_amount: u64,
@@ -255,8 +253,6 @@ pub struct OrcaWhirlpoolsConfigAccount {
pub struct MeteoraDbcSwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
pub pool: Pubkey,
pub config: Pubkey,
@@ -319,8 +315,6 @@ pub struct MeteoraDbcCurveCompleteEvent {
pub struct MeteoraPoolsSwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
pub in_amount: u64,
pub out_amount: u64,
@@ -380,8 +374,6 @@ pub struct MeteoraPoolsSetPoolFeesEvent {
pub struct MeteoraDlmmSwapEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub third_program: Option<sol_parser_sdk::core::events::RawInstructionPayload>,
#[serde(default)]
pub parse_status: sol_parser_sdk::core::events::TradeParseStatus,
pub metadata: EventMetadata,
#[serde(default)]
pub token_x_mint: Pubkey,
@@ -45,7 +45,6 @@ pub(crate) fn bonk_trade_from_parser(
) -> BonkTradeEvent {
BonkTradeEvent {
third_program: b.third_program,
parse_status: b.parse_status,
metadata: meta,
pool_state: b.pool_state,
total_base_sell: b.total_base_sell,
@@ -21,7 +21,6 @@ pub(crate) fn orca_swap_from_pb(
) -> OrcaWhirlpoolSwapEvent {
OrcaWhirlpoolSwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
whirlpool: e.whirlpool,
input_amount: e.input_amount,
@@ -251,7 +250,6 @@ pub(crate) fn meteora_dbc_swap_from_pb(
) -> MeteoraDbcSwapEvent {
MeteoraDbcSwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
event_version: e.event_version,
swap_mode: e.swap_mode,
@@ -314,7 +312,6 @@ pub(crate) fn meteora_pools_swap_from_pb(
) -> MeteoraPoolsSwapEvent {
MeteoraPoolsSwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
in_amount: e.in_amount,
out_amount: e.out_amount,
@@ -395,7 +392,6 @@ pub(crate) fn meteora_dlmm_swap_from_pb(
) -> MeteoraDlmmSwapEvent {
MeteoraDlmmSwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
event_version: e.event_version,
amount_left: e.amount_left,
+2 -21
View File
@@ -27,13 +27,11 @@ mod raydium_and_damm;
#[cfg(test)]
mod provenance_tests {
use super::convert_parser_event;
use sol_parser_sdk::core::events::{
DexEvent as ParserEvent, RawInstructionPayload, TradeParseStatus,
};
use sol_parser_sdk::core::events::{DexEvent as ParserEvent, RawInstructionPayload};
use solana_sdk::pubkey::Pubkey;
#[test]
fn every_supported_trade_bridge_preserves_source_and_status() {
fn every_supported_trade_bridge_preserves_source() {
let source = RawInstructionPayload {
program_id: Pubkey::new_unique(),
instruction_index: 4,
@@ -44,90 +42,73 @@ mod provenance_tests {
for event in [
ParserEvent::PumpFunTrade(sol_parser_sdk::core::events::PumpFunTradeEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::PumpFunBuy(sol_parser_sdk::core::events::PumpFunTradeEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::PumpFunSell(sol_parser_sdk::core::events::PumpFunTradeEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::PumpFunBuyExactSolIn(sol_parser_sdk::core::events::PumpFunTradeEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::PumpSwapTrade(sol_parser_sdk::core::events::PumpSwapTradeEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::PumpSwapBuy(sol_parser_sdk::core::events::PumpSwapBuyEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::PumpSwapSell(sol_parser_sdk::core::events::PumpSwapSellEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::RaydiumLaunchlabTrade(
sol_parser_sdk::core::events::RaydiumLaunchlabTradeEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
},
),
ParserEvent::RaydiumCpmmSwap(sol_parser_sdk::core::events::RaydiumCpmmSwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::RaydiumClmmSwap(sol_parser_sdk::core::events::RaydiumClmmSwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::RaydiumAmmV4Swap(sol_parser_sdk::core::events::RaydiumAmmV4SwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::OrcaWhirlpoolSwap(sol_parser_sdk::core::events::OrcaWhirlpoolSwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::MeteoraPoolsSwap(sol_parser_sdk::core::events::MeteoraPoolsSwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::MeteoraDammV2Swap(sol_parser_sdk::core::events::MeteoraDammV2SwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::MeteoraDlmmSwap(sol_parser_sdk::core::events::MeteoraDlmmSwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
ParserEvent::MeteoraDbcSwap(sol_parser_sdk::core::events::MeteoraDbcSwapEvent {
third_program: Some(source.clone()),
parse_status: TradeParseStatus::ExecutionConfirmed,
..Default::default()
}),
] {
let converted = convert_parser_event(event, None, 123).expect("supported trade");
assert_eq!(converted.third_program(), Some(&source));
assert_eq!(converted.trade_parse_status(), Some(TradeParseStatus::ExecutionConfirmed));
}
}
}
@@ -525,7 +525,6 @@ pub(crate) fn pumpswap_buy_full_from_parser(
) -> PumpSwapBuyEvent {
PumpSwapBuyEvent {
third_program: b.third_program,
parse_status: b.parse_status,
metadata: meta,
timestamp: b.timestamp,
base_amount_out: b.base_amount_out,
@@ -588,7 +587,6 @@ pub(crate) fn pumpswap_sell_full_from_parser(
) -> PumpSwapSellEvent {
PumpSwapSellEvent {
third_program: s.third_program,
parse_status: s.parse_status,
metadata: meta,
timestamp: s.timestamp,
base_amount_in: s.base_amount_in,
@@ -734,7 +732,6 @@ pub(crate) fn pumpfun_trade_from_parser_with_event_type(
let meta = adapt_pm(pm, bt, recv_wall_us, ProtocolType::PumpFun, event_type, pump_program());
let st = PumpFunTradeEvent {
third_program: t.third_program,
parse_status: t.parse_status,
metadata: meta,
mint: t.mint,
sol_amount: t.sol_amount,
@@ -829,7 +826,6 @@ pub(crate) fn pumpswap_trade_from_parser(
if t.is_buy {
Some(DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent {
third_program: t.third_program,
parse_status: t.parse_status,
metadata: meta,
timestamp: t.timestamp,
base_amount_out: t.token_amount,
@@ -870,7 +866,6 @@ pub(crate) fn pumpswap_trade_from_parser(
} else {
Some(DexEvent::PumpSwapSellEvent(PumpSwapSellEvent {
third_program: t.third_program,
parse_status: t.parse_status,
metadata: meta,
timestamp: t.timestamp,
base_amount_in: t.token_amount,
@@ -41,7 +41,6 @@ pub(crate) fn meteora_damm_v2_swap_from_parser(
) -> MeteoraDammV2SwapEvent {
MeteoraDammV2SwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
pool: e.pool,
trade_direction: e.trade_direction,
@@ -121,7 +120,6 @@ pub(crate) fn raydium_cpmm_swap_from_parser(
) -> RaydiumCpmmSwapEvent {
RaydiumCpmmSwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
input_vault_before: e.input_vault_before,
output_vault_before: e.output_vault_before,
@@ -309,7 +307,6 @@ pub(crate) fn raydium_amm_v4_swap_from_parser(
) -> RaydiumAmmV4SwapEvent {
RaydiumAmmV4SwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
amount_in: e.amount_in,
minimum_amount_out: e.minimum_amount_out,
@@ -460,7 +457,6 @@ pub(crate) fn raydium_clmm_swap_from_parser(
) -> RaydiumClmmSwapEvent {
RaydiumClmmSwapEvent {
third_program: e.third_program,
parse_status: e.parse_status,
metadata: meta,
amount: e.amount,
other_amount_threshold: e.other_amount_threshold,