1use solana_sdk::pubkey::Pubkey;
13
14use crate::core::events::*;
15
16#[inline(always)]
28pub fn merge_events(base: &mut DexEvent, inner: DexEvent) {
29 let mut unmerged = None;
30 let _ = try_merge_events(base, inner, &mut unmerged);
31}
32
33#[inline(always)]
37pub fn try_merge_events(
38 base: &mut DexEvent,
39 mut inner: DexEvent,
40 unmerged: &mut Option<DexEvent>,
41) -> bool {
42 if pump_trade_identity_conflicts(base, &inner) {
43 *unmerged = Some(inner);
44 return false;
45 }
46 if wrapper_sources_conflict(base, &inner) {
47 *unmerged = Some(inner);
48 return false;
49 }
50 if std::mem::discriminant(base) == std::mem::discriminant(&inner) {
51 reconcile_trade_source(base, &mut inner);
52 }
53 use DexEvent::*;
54
55 match (base, inner) {
56 (PumpFunTrade(b), PumpFunTrade(i))
58 | (PumpFunTrade(b), PumpFunBuy(i))
59 | (PumpFunTrade(b), PumpFunSell(i))
60 | (PumpFunTrade(b), PumpFunBuyExactSolIn(i))
61 | (PumpFunBuy(b), PumpFunTrade(i))
62 | (PumpFunBuy(b), PumpFunBuy(i))
63 | (PumpFunSell(b), PumpFunTrade(i))
64 | (PumpFunSell(b), PumpFunSell(i))
65 | (PumpFunBuyExactSolIn(b), PumpFunTrade(i))
66 | (PumpFunBuyExactSolIn(b), PumpFunBuyExactSolIn(i)) => merge_pumpfun_trade(b, i),
67
68 (PumpFunCreate(b), PumpFunCreate(i)) => merge_pumpfun_create(b, i),
69 (PumpFunCreateV2(b), PumpFunCreateV2(i)) => merge_pumpfun_create_v2(b, i),
70 (PumpFunMigrate(b), PumpFunMigrate(i)) => merge_pumpfun_migrate(b, i),
71 (PumpFunMigrateBondingCurveCreator(b), PumpFunMigrateBondingCurveCreator(i)) => {
72 merge_generic(b, i)
73 }
74
75 (PumpFeesCreateFeeSharingConfig(b), PumpFeesCreateFeeSharingConfig(i)) => {
77 merge_generic(b, i)
78 }
79 (PumpFeesInitializeFeeConfig(b), PumpFeesInitializeFeeConfig(i)) => merge_generic(b, i),
80 (PumpFeesResetFeeSharingConfig(b), PumpFeesResetFeeSharingConfig(i)) => merge_generic(b, i),
81 (PumpFeesRevokeFeeSharingAuthority(b), PumpFeesRevokeFeeSharingAuthority(i)) => {
82 merge_generic(b, i)
83 }
84 (PumpFeesTransferFeeSharingAuthority(b), PumpFeesTransferFeeSharingAuthority(i)) => {
85 merge_generic(b, i)
86 }
87 (PumpFeesUpdateAdmin(b), PumpFeesUpdateAdmin(i)) => merge_generic(b, i),
88 (PumpFeesUpdateFeeConfig(b), PumpFeesUpdateFeeConfig(i)) => merge_generic(b, i),
89 (PumpFeesUpdateFeeShares(b), PumpFeesUpdateFeeShares(i)) => merge_generic(b, i),
90 (PumpFeesUpsertFeeTiers(b), PumpFeesUpsertFeeTiers(i)) => merge_generic(b, i),
91
92 (PumpSwapTrade(b), PumpSwapTrade(i)) => merge_generic(b, i),
94 (PumpSwapBuy(b), PumpSwapBuy(i)) => merge_pumpswap_buy(b, i),
95 (PumpSwapSell(b), PumpSwapSell(i)) => merge_pumpswap_sell(b, i),
96 (PumpSwapCreatePool(b), PumpSwapCreatePool(i)) => merge_generic(b, i),
97 (PumpSwapLiquidityAdded(b), PumpSwapLiquidityAdded(i)) => merge_generic(b, i),
98 (PumpSwapLiquidityRemoved(b), PumpSwapLiquidityRemoved(i)) => merge_generic(b, i),
99
100 (RaydiumClmmSwap(b), RaydiumClmmSwap(i)) => merge_clmm_swap(b, i),
102 (RaydiumClmmIncreaseLiquidity(b), RaydiumClmmIncreaseLiquidity(i)) => merge_generic(b, i),
103 (RaydiumClmmDecreaseLiquidity(b), RaydiumClmmDecreaseLiquidity(i)) => merge_generic(b, i),
104 (RaydiumClmmLiquidityChange(b), RaydiumClmmLiquidityChange(i)) => merge_generic(b, i),
105 (RaydiumClmmConfigChange(b), RaydiumClmmConfigChange(i)) => merge_generic(b, i),
106 (RaydiumClmmCreatePersonalPosition(b), RaydiumClmmCreatePersonalPosition(i)) => {
107 merge_generic(b, i)
108 }
109 (RaydiumClmmLiquidityCalculate(b), RaydiumClmmLiquidityCalculate(i)) => merge_generic(b, i),
110 (RaydiumClmmOpenLimitOrder(b), RaydiumClmmOpenLimitOrder(i)) => merge_generic(b, i),
111 (RaydiumClmmIncreaseLimitOrder(b), RaydiumClmmIncreaseLimitOrder(i)) => merge_generic(b, i),
112 (RaydiumClmmDecreaseLimitOrder(b), RaydiumClmmDecreaseLimitOrder(i)) => merge_generic(b, i),
113 (RaydiumClmmSettleLimitOrder(b), RaydiumClmmSettleLimitOrder(i)) => merge_generic(b, i),
114 (RaydiumClmmUpdateRewardInfos(b), RaydiumClmmUpdateRewardInfos(i)) => merge_generic(b, i),
115 (RaydiumClmmCreatePool(b), RaydiumClmmCreatePool(i)) => merge_generic(b, i),
116 (RaydiumClmmOpenPosition(b), RaydiumClmmOpenPosition(i)) => merge_generic(b, i),
117 (RaydiumClmmClosePosition(b), RaydiumClmmClosePosition(i)) => merge_generic(b, i),
118 (RaydiumClmmOpenPositionWithTokenExtNft(b), RaydiumClmmOpenPositionWithTokenExtNft(i)) => {
119 merge_generic(b, i)
120 }
121 (RaydiumClmmCollectFee(b), RaydiumClmmCollectFee(i)) => merge_generic(b, i),
122
123 (RaydiumCpmmSwap(b), RaydiumCpmmSwap(i)) => merge_cpmm_swap(b, i),
125 (RaydiumCpmmDeposit(b), RaydiumCpmmDeposit(i)) => merge_generic(b, i),
126 (RaydiumCpmmWithdraw(b), RaydiumCpmmWithdraw(i)) => merge_generic(b, i),
127 (RaydiumCpmmInitialize(b), RaydiumCpmmInitialize(i)) => merge_generic(b, i),
128
129 (RaydiumAmmV4Swap(b), RaydiumAmmV4Swap(i)) => {
131 let instruction = std::mem::replace(b, i);
132 merge_raydium_amm_v4_swap_log_preferred(b, instruction);
133 }
134 (RaydiumAmmV4Deposit(b), RaydiumAmmV4Deposit(i)) => merge_generic(b, i),
135 (RaydiumAmmV4Withdraw(b), RaydiumAmmV4Withdraw(i)) => merge_generic(b, i),
136 (RaydiumAmmV4Initialize2(b), RaydiumAmmV4Initialize2(i)) => merge_generic(b, i),
137 (RaydiumAmmV4WithdrawPnl(b), RaydiumAmmV4WithdrawPnl(i)) => merge_generic(b, i),
138
139 (OrcaWhirlpoolSwap(b), OrcaWhirlpoolSwap(i)) => {
141 let instruction = std::mem::replace(b, i);
142 merge_orca_swap_context(b, instruction);
143 }
144 (OrcaWhirlpoolLiquidityIncreased(b), OrcaWhirlpoolLiquidityIncreased(i)) => {
145 merge_generic(b, i)
146 }
147 (OrcaWhirlpoolLiquidityDecreased(b), OrcaWhirlpoolLiquidityDecreased(i)) => {
148 merge_generic(b, i)
149 }
150 (OrcaWhirlpoolPoolInitialized(b), OrcaWhirlpoolPoolInitialized(i)) => merge_generic(b, i),
151
152 (MeteoraPoolsSwap(b), MeteoraPoolsSwap(i)) => {
154 let instruction = std::mem::replace(b, i);
155 merge_meteora_pools_swap_context(b, instruction);
156 }
157 (MeteoraPoolsAddLiquidity(b), MeteoraPoolsAddLiquidity(i)) => {
158 let instruction = std::mem::replace(b, i);
159 merge_meteora_pools_add_context(b, instruction);
160 }
161 (MeteoraPoolsRemoveLiquidity(b), MeteoraPoolsRemoveLiquidity(i)) => {
162 let instruction = std::mem::replace(b, i);
163 merge_meteora_pools_remove_context(b, instruction);
164 }
165 (MeteoraPoolsBootstrapLiquidity(b), MeteoraPoolsBootstrapLiquidity(i)) => {
166 let instruction = std::mem::replace(b, i);
167 merge_meteora_pools_bootstrap_context(b, instruction);
168 }
169 (MeteoraPoolsPoolCreated(b), MeteoraPoolsPoolCreated(i)) => {
170 let instruction = std::mem::replace(b, i);
171 merge_meteora_pools_poolcreated_context(b, instruction);
172 }
173 (MeteoraPoolsSetPoolFees(b), MeteoraPoolsSetPoolFees(i)) => {
174 let instruction = std::mem::replace(b, i);
175 merge_meteora_pools_setpoolfees_context(b, instruction);
176 }
177
178 (MeteoraDammV2Swap(b), MeteoraDammV2Swap(i)) => merge_generic(b, i),
180 (MeteoraDammV2AddLiquidity(b), MeteoraDammV2AddLiquidity(i)) => merge_generic(b, i),
181 (MeteoraDammV2RemoveLiquidity(b), MeteoraDammV2RemoveLiquidity(i)) => merge_generic(b, i),
182 (MeteoraDammV2InitializePool(b), MeteoraDammV2InitializePool(i)) => merge_generic(b, i),
183 (MeteoraDammV2ClaimPositionFee(b), MeteoraDammV2ClaimPositionFee(i)) => merge_generic(b, i),
184 (MeteoraDammV2ClaimReward(b), MeteoraDammV2ClaimReward(i)) => merge_generic(b, i),
185 (MeteoraDlmmClaimReward(b), MeteoraDlmmClaimReward(i)) => merge_generic(b, i),
186 (MeteoraDammV2CreatePosition(b), MeteoraDammV2CreatePosition(i)) => merge_generic(b, i),
187 (MeteoraDammV2ClosePosition(b), MeteoraDammV2ClosePosition(i)) => merge_generic(b, i),
188 (MeteoraDammV2UpdateDelegatePermission(b), MeteoraDammV2UpdateDelegatePermission(i)) => {
189 merge_generic(b, i)
190 }
191 (
192 MeteoraDammV2WithdrawDeadLiquidityReward(b),
193 MeteoraDammV2WithdrawDeadLiquidityReward(i),
194 ) => merge_generic(b, i),
195 (MeteoraDammV2WithdrawIneligibleReward(b), MeteoraDammV2WithdrawIneligibleReward(i)) => {
196 merge_generic(b, i)
197 }
198 (MeteoraDammV2UpdateRewardFunder(b), MeteoraDammV2UpdateRewardFunder(i)) => {
199 merge_generic(b, i)
200 }
201 (MeteoraDammV2UpdateRewardDuration(b), MeteoraDammV2UpdateRewardDuration(i)) => {
202 merge_generic(b, i)
203 }
204 (MeteoraDammV2InitializeReward(b), MeteoraDammV2InitializeReward(i)) => merge_generic(b, i),
205 (MeteoraDammV2FundReward(b), MeteoraDammV2FundReward(i)) => merge_generic(b, i),
206 (MeteoraDammV2CreateConfig(b), MeteoraDammV2CreateConfig(i)) => merge_generic(b, i),
207 (MeteoraDammV2CreateDynamicConfig(b), MeteoraDammV2CreateDynamicConfig(i)) => {
208 merge_generic(b, i)
209 }
210
211 (MeteoraDlmmSwap(b), MeteoraDlmmSwap(i)) => merge_dlmm_swap(b, i),
213 (MeteoraDlmmAddLiquidity(b), MeteoraDlmmAddLiquidity(i)) => merge_generic(b, i),
214 (MeteoraDlmmRemoveLiquidity(b), MeteoraDlmmRemoveLiquidity(i)) => merge_generic(b, i),
215 (MeteoraDlmmInitializePool(b), MeteoraDlmmInitializePool(i)) => {
216 merge_dlmm_initialize_pool(b, i)
217 }
218 (MeteoraDlmmInitializeBinArray(b), MeteoraDlmmInitializeBinArray(i)) => merge_generic(b, i),
219 (MeteoraDlmmCreatePosition(b), MeteoraDlmmCreatePosition(i)) => {
220 merge_dlmm_create_position(b, i)
221 }
222 (MeteoraDlmmClosePosition(b), MeteoraDlmmClosePosition(i)) => {
223 merge_dlmm_close_position(b, i)
224 }
225 (MeteoraDlmmClaimFee(b), MeteoraDlmmClaimFee(i)) => merge_generic(b, i),
226
227 (RaydiumLaunchlabTrade(b), RaydiumLaunchlabTrade(i)) => merge_generic(b, i),
229 (RaydiumLaunchlabPoolCreate(b), RaydiumLaunchlabPoolCreate(i)) => merge_generic(b, i),
230 (RaydiumLaunchlabMigrateAmm(b), RaydiumLaunchlabMigrateAmm(i)) => merge_generic(b, i),
231
232 (_, event) => {
234 *unmerged = Some(event);
235 return false;
236 }
237 }
238
239 true
240}
241
242#[inline(always)]
249fn merge_cpmm_swap(base: &mut RaydiumCpmmSwapEvent, inner: RaydiumCpmmSwapEvent) {
250 let instruction = std::mem::replace(base, inner);
251 merge_raydium_cpmm_swap_log_preferred(base, instruction);
252}
253
254#[inline]
255fn merge_raydium_cpmm_swap_log_preferred(log: &mut RaydiumCpmmSwapEvent, ix: RaydiumCpmmSwapEvent) {
256 if !ix.ix_name.is_empty() {
257 log.ix_name = ix.ix_name;
258 log.amount_in = ix.amount_in;
259 log.minimum_amount_out = ix.minimum_amount_out;
260 log.max_amount_in = ix.max_amount_in;
261 log.amount_out = ix.amount_out;
262 }
263 fill_pk(&mut log.payer, ix.payer);
264 fill_pk(&mut log.authority, ix.authority);
265 fill_pk(&mut log.input_token_account, ix.input_token_account);
266 fill_pk(&mut log.output_token_account, ix.output_token_account);
267 fill_pk(&mut log.amm_config, ix.amm_config);
268 fill_pk(&mut log.input_vault, ix.input_vault);
269 fill_pk(&mut log.output_vault, ix.output_vault);
270 fill_pk(&mut log.input_token_program, ix.input_token_program);
271 fill_pk(&mut log.output_token_program, ix.output_token_program);
272 fill_pk(&mut log.input_token_mint, ix.input_token_mint);
273 fill_pk(&mut log.output_token_mint, ix.output_token_mint);
274 fill_pk(&mut log.observation_state, ix.observation_state);
275}
276
277#[inline(always)]
278fn merge_clmm_swap(base: &mut RaydiumClmmSwapEvent, inner: RaydiumClmmSwapEvent) {
279 let instruction = std::mem::replace(base, inner);
280 merge_raydium_clmm_swap_log_preferred(base, instruction);
281}
282
283#[inline(always)]
284fn merge_generic<T>(base: &mut T, inner: T) {
285 *base = inner;
286}
287
288#[inline(always)]
289fn merge_dlmm_swap(base: &mut MeteoraDlmmSwapEvent, inner: MeteoraDlmmSwapEvent) {
290 let min_amount_out =
291 if inner.min_amount_out != 0 { inner.min_amount_out } else { base.min_amount_out };
292 let user_token_in = if inner.user_token_in != Pubkey::default() {
293 inner.user_token_in
294 } else {
295 base.user_token_in
296 };
297 let user_token_out = if inner.user_token_out != Pubkey::default() {
298 inner.user_token_out
299 } else {
300 base.user_token_out
301 };
302 *base = inner;
303 base.min_amount_out = min_amount_out;
304 base.user_token_in = user_token_in;
305 base.user_token_out = user_token_out;
306}
307
308#[inline(always)]
309fn merge_dlmm_initialize_pool(
310 base: &mut MeteoraDlmmInitializePoolEvent,
311 inner: MeteoraDlmmInitializePoolEvent,
312) {
313 let creator = base.creator;
314 let active_bin_id = base.active_bin_id;
315 *base = inner;
316 base.creator = creator;
317 base.active_bin_id = active_bin_id;
318}
319
320#[inline(always)]
321fn merge_dlmm_create_position(
322 base: &mut MeteoraDlmmCreatePositionEvent,
323 inner: MeteoraDlmmCreatePositionEvent,
324) {
325 let lower_bin_id = base.lower_bin_id;
326 let width = base.width;
327 *base = inner;
328 base.lower_bin_id = lower_bin_id;
329 base.width = width;
330}
331
332#[inline(always)]
333fn merge_dlmm_close_position(
334 base: &mut MeteoraDlmmClosePositionEvent,
335 inner: MeteoraDlmmClosePositionEvent,
336) {
337 let pool = base.pool;
338 *base = inner;
339 base.pool = pool;
340}
341
342#[inline(always)]
347fn put_pk_if_set(to: &mut Pubkey, from: Pubkey) {
348 if from != Pubkey::default() {
349 *to = from;
350 }
351}
352
353#[inline(always)]
354fn put_pumpfun_quote_mint_if_set(to: &mut Pubkey, from: Pubkey) {
355 let from = normalize_pumpfun_quote_mint(from);
356 if from != Pubkey::default()
357 && (*to == Pubkey::default()
358 || is_pumpfun_solscan_sol_quote_mint(*to)
359 || !is_pumpfun_solscan_sol_quote_mint(from))
360 {
361 *to = from;
362 }
363}
364
365#[inline(always)]
366fn put_u64_if_nonzero(to: &mut u64, from: u64) {
367 if from != 0 {
368 *to = from;
369 }
370}
371
372#[inline(always)]
373fn put_i64_if_nonzero(to: &mut i64, from: i64) {
374 if from != 0 {
375 *to = from;
376 }
377}
378
379#[inline(always)]
389fn merge_pumpfun_trade(base: &mut PumpFunTradeEvent, inner: PumpFunTradeEvent) {
390 if base.third_program.is_none() {
391 base.third_program = inner.third_program.clone();
392 }
393 if inner.parse_status == PumpFunParseStatus::ExecutionConfirmed {
394 base.parse_status = inner.parse_status;
395 }
396 let leg = inner.sol_amount != 0 || inner.token_amount != 0;
397
398 put_pk_if_set(&mut base.mint, inner.mint);
399 put_pk_if_set(&mut base.user, inner.user);
400 put_pk_if_set(&mut base.fee_recipient, inner.fee_recipient);
401 put_pk_if_set(&mut base.creator, inner.creator);
402
403 if leg {
404 base.sol_amount = inner.sol_amount;
405 base.token_amount = inner.token_amount;
406 base.is_buy = inner.is_buy;
407 base.timestamp = inner.timestamp;
408 base.virtual_sol_reserves = inner.virtual_sol_reserves;
409 base.virtual_token_reserves = inner.virtual_token_reserves;
410 base.real_sol_reserves = inner.real_sol_reserves;
411 base.real_token_reserves = inner.real_token_reserves;
412 base.fee_basis_points = inner.fee_basis_points;
413 base.fee = inner.fee;
414 base.creator_fee_basis_points = inner.creator_fee_basis_points;
415 base.creator_fee = inner.creator_fee;
416 base.track_volume |= inner.track_volume;
417 base.total_unclaimed_tokens = inner.total_unclaimed_tokens;
418 base.total_claimed_tokens = inner.total_claimed_tokens;
419 base.current_sol_volume = inner.current_sol_volume;
420 base.last_update_timestamp = inner.last_update_timestamp;
421 if prefer_pump_instruction_name(&base.ix_name, &inner.ix_name) {
422 base.ix_name = inner.ix_name;
423 }
424 base.mayhem_mode |= inner.mayhem_mode;
425 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
426 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
427 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
428 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
429 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
430 base.shareholders = inner.shareholders;
431 }
432 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
433 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
434 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
435 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
436 base.is_cashback_coin |= inner.is_cashback_coin;
437 base.holder_rewards_bps = inner.holder_rewards_bps;
438 base.holder_rewards = inner.holder_rewards;
439 base.creator_fee_unclaimed = inner.creator_fee_unclaimed;
440 } else {
441 put_u64_if_nonzero(&mut base.fee, inner.fee);
442 put_u64_if_nonzero(&mut base.creator_fee, inner.creator_fee);
443 put_u64_if_nonzero(&mut base.fee_basis_points, inner.fee_basis_points);
444 put_u64_if_nonzero(&mut base.creator_fee_basis_points, inner.creator_fee_basis_points);
445 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
446 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
447 put_u64_if_nonzero(&mut base.real_sol_reserves, inner.real_sol_reserves);
448 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
449 put_u64_if_nonzero(&mut base.total_unclaimed_tokens, inner.total_unclaimed_tokens);
450 put_u64_if_nonzero(&mut base.total_claimed_tokens, inner.total_claimed_tokens);
451 put_u64_if_nonzero(&mut base.current_sol_volume, inner.current_sol_volume);
452 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
453 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
454 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
455 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
456 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
457 base.shareholders = inner.shareholders;
458 }
459 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
460 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
461 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
462 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
463 put_u64_if_nonzero(&mut base.holder_rewards_bps, inner.holder_rewards_bps);
464 put_u64_if_nonzero(&mut base.holder_rewards, inner.holder_rewards);
465 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
466 put_i64_if_nonzero(&mut base.last_update_timestamp, inner.last_update_timestamp);
467 if prefer_pump_instruction_name(&base.ix_name, &inner.ix_name) {
468 base.ix_name = inner.ix_name;
469 }
470 base.track_volume |= inner.track_volume;
471 base.mayhem_mode |= inner.mayhem_mode;
472 base.is_cashback_coin |= inner.is_cashback_coin;
473 }
474 put_u64_if_nonzero(&mut base.amount, inner.amount);
475 put_u64_if_nonzero(&mut base.max_sol_cost, inner.max_sol_cost);
476 put_u64_if_nonzero(&mut base.min_sol_output, inner.min_sol_output);
477 put_u64_if_nonzero(&mut base.spendable_sol_in, inner.spendable_sol_in);
478 put_u64_if_nonzero(&mut base.spendable_quote_in, inner.spendable_quote_in);
479 put_u64_if_nonzero(&mut base.min_tokens_out, inner.min_tokens_out);
480 put_pk_if_set(&mut base.global, inner.global);
481 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
482 put_pk_if_set(&mut base.bonding_curve_v2, inner.bonding_curve_v2);
483 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
484 put_pk_if_set(&mut base.associated_user, inner.associated_user);
485 put_pk_if_set(&mut base.system_program, inner.system_program);
486 put_pk_if_set(&mut base.token_program, inner.token_program);
487 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
488 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
489 put_pk_if_set(&mut base.creator_vault, inner.creator_vault);
490 put_pk_if_set(&mut base.associated_quote_fee_recipient, inner.associated_quote_fee_recipient);
491 put_pk_if_set(&mut base.buyback_fee_recipient, inner.buyback_fee_recipient);
492 put_pk_if_set(
493 &mut base.associated_quote_buyback_fee_recipient,
494 inner.associated_quote_buyback_fee_recipient,
495 );
496 put_pk_if_set(&mut base.associated_quote_bonding_curve, inner.associated_quote_bonding_curve);
497 put_pk_if_set(&mut base.associated_quote_user, inner.associated_quote_user);
498 put_pk_if_set(&mut base.associated_creator_vault, inner.associated_creator_vault);
499 put_pk_if_set(&mut base.sharing_config, inner.sharing_config);
500 put_pk_if_set(&mut base.event_authority, inner.event_authority);
501 put_pk_if_set(&mut base.program, inner.program);
502 put_pk_if_set(&mut base.global_volume_accumulator, inner.global_volume_accumulator);
503 put_pk_if_set(&mut base.user_volume_accumulator, inner.user_volume_accumulator);
504 put_pk_if_set(
505 &mut base.associated_user_volume_accumulator,
506 inner.associated_user_volume_accumulator,
507 );
508 put_pk_if_set(&mut base.fee_config, inner.fee_config);
509 put_pk_if_set(&mut base.fee_program, inner.fee_program);
510 if base.account.is_none() {
511 base.account = inner.account;
512 }
513
514 base.is_created_buy |= inner.is_created_buy;
515 }
517
518fn prefer_pump_instruction_name(current: &str, incoming: &str) -> bool {
521 let versioned = |s: &str| s.ends_with("_v2") || s.ends_with("_v3");
522 !incoming.is_empty() && (!versioned(current) || versioned(incoming))
523}
524
525#[inline(always)]
527fn merge_pumpfun_create(base: &mut PumpFunCreateTokenEvent, inner: PumpFunCreateTokenEvent) {
528 base.name = inner.name;
530 base.symbol = inner.symbol;
531 base.uri = inner.uri;
532 base.mint = inner.mint;
533 base.bonding_curve = inner.bonding_curve;
534 base.user = inner.user;
535 base.creator = inner.creator;
536 base.timestamp = inner.timestamp;
537 base.virtual_token_reserves = inner.virtual_token_reserves;
538 base.virtual_sol_reserves = inner.virtual_sol_reserves;
539 base.real_token_reserves = inner.real_token_reserves;
540 base.token_total_supply = inner.token_total_supply;
541 base.token_program = inner.token_program;
542 base.is_mayhem_mode = inner.is_mayhem_mode;
543 base.is_cashback_enabled = inner.is_cashback_enabled;
544 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
545 put_pk_if_set(&mut base.quote_vault, inner.quote_vault);
546 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
547 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
548 put_u64_if_nonzero(&mut base.creator_fee_bps, inner.creator_fee_bps);
549 base.is_holder_reward |= inner.is_holder_reward;
550}
551
552#[inline(always)]
554fn merge_pumpfun_create_v2(base: &mut PumpFunCreateV2TokenEvent, inner: PumpFunCreateV2TokenEvent) {
555 fill_str_if_empty(&mut base.name, &inner.name);
556 fill_str_if_empty(&mut base.symbol, &inner.symbol);
557 fill_str_if_empty(&mut base.uri, &inner.uri);
558 put_pk_if_set(&mut base.mint, inner.mint);
559 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
560 put_pk_if_set(&mut base.user, inner.user);
561 put_pk_if_set(&mut base.creator, inner.creator);
562 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
563 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
564 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
565 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
566 put_u64_if_nonzero(&mut base.token_total_supply, inner.token_total_supply);
567 put_pk_if_set(&mut base.token_program, inner.token_program);
568 base.is_mayhem_mode |= inner.is_mayhem_mode;
569 base.is_cashback_enabled |= inner.is_cashback_enabled;
570 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
571 put_pk_if_set(&mut base.quote_vault, inner.quote_vault);
572 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
573 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
574 put_pk_if_set(&mut base.mint_authority, inner.mint_authority);
575 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
576 put_pk_if_set(&mut base.global, inner.global);
577 put_pk_if_set(&mut base.system_program, inner.system_program);
578 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
579 put_pk_if_set(&mut base.mayhem_program_id, inner.mayhem_program_id);
580 put_pk_if_set(&mut base.global_params, inner.global_params);
581 put_pk_if_set(&mut base.sol_vault, inner.sol_vault);
582 put_pk_if_set(&mut base.mayhem_state, inner.mayhem_state);
583 put_pk_if_set(&mut base.mayhem_token_vault, inner.mayhem_token_vault);
584 put_pk_if_set(&mut base.event_authority, inner.event_authority);
585 put_pk_if_set(&mut base.program, inner.program);
586 put_pk_if_set(&mut base.observed_fee_recipient, inner.observed_fee_recipient);
587 put_u64_if_nonzero(&mut base.creator_fee_bps, inner.creator_fee_bps);
588 base.is_holder_reward |= inner.is_holder_reward;
589}
590
591#[inline(always)]
593fn merge_pumpfun_migrate(base: &mut PumpFunMigrateEvent, inner: PumpFunMigrateEvent) {
594 base.user = inner.user;
596 base.mint = inner.mint;
597 base.mint_amount = inner.mint_amount;
598 base.sol_amount = inner.sol_amount;
599 base.pool_migration_fee = inner.pool_migration_fee;
600 base.bonding_curve = inner.bonding_curve;
601 base.timestamp = inner.timestamp;
602 base.pool = inner.pool;
603}
604
605#[inline(always)]
606fn merge_pumpswap_buy(base: &mut PumpSwapBuyEvent, inner: PumpSwapBuyEvent) {
607 let ix = std::mem::take(base);
608 *base = inner;
609 merge_pumpswap_buy_log_preferred(base, ix);
610}
611
612#[inline(always)]
613fn merge_pumpswap_sell(base: &mut PumpSwapSellEvent, inner: PumpSwapSellEvent) {
614 let ix = std::mem::take(base);
615 *base = inner;
616 merge_pumpswap_sell_log_preferred(base, ix);
617}
618
619#[inline(always)]
630pub fn can_merge(base: &DexEvent, inner: &DexEvent) -> bool {
631 if pump_trade_identity_conflicts(base, inner) {
632 return false;
633 }
634 if base.metadata().signature != inner.metadata().signature {
636 return false;
637 }
638
639 match (base, inner) {
641 (DexEvent::PumpFunTrade(_), DexEvent::PumpFunTrade(_))
643 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuy(_))
644 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunSell(_))
645 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuyExactSolIn(_))
646 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunTrade(_))
647 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunBuy(_))
648 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunTrade(_))
649 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunSell(_))
650 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunTrade(_))
651 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunBuyExactSolIn(_)) => true,
652
653 (DexEvent::PumpFunCreate(_), DexEvent::PumpFunCreate(_)) => true,
655 (DexEvent::PumpFunCreateV2(_), DexEvent::PumpFunCreateV2(_)) => true,
656
657 (DexEvent::PumpFunMigrate(_), DexEvent::PumpFunMigrate(_)) => true,
659
660 (DexEvent::PumpSwapBuy(_), DexEvent::PumpSwapBuy(_))
661 | (DexEvent::PumpSwapSell(_), DexEvent::PumpSwapSell(_)) => true,
662
663 _ => false,
665 }
666}
667
668#[inline(always)]
673fn fill_pk(to: &mut Pubkey, from: Pubkey) {
674 if *to == Pubkey::default() && from != Pubkey::default() {
675 *to = from;
676 }
677}
678
679#[inline(always)]
680fn fill_pumpfun_quote_mint(to: &mut Pubkey, from: Pubkey) {
681 let from = normalize_pumpfun_quote_mint(from);
682 if (*to == Pubkey::default() || is_pumpfun_solscan_sol_quote_mint(*to))
683 && from != Pubkey::default()
684 {
685 *to = from;
686 }
687}
688
689#[inline(always)]
690fn fill_str_if_empty(to: &mut String, from: &str) {
691 if to.is_empty() && !from.is_empty() {
692 to.push_str(from);
693 }
694}
695
696#[inline]
699fn merge_pumpfun_trade_log_preferred(log: &mut PumpFunTradeEvent, ix: PumpFunTradeEvent) {
700 if log.third_program.is_none() {
701 log.third_program = ix.third_program.clone();
702 }
703 if ix.parse_status == PumpFunParseStatus::ExecutionConfirmed {
704 log.parse_status = ix.parse_status;
705 }
706 fill_pk(&mut log.global, ix.global);
707 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
708 fill_pk(&mut log.bonding_curve_v2, ix.bonding_curve_v2);
709 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
710 fill_pk(&mut log.associated_user, ix.associated_user);
711 fill_pk(&mut log.system_program, ix.system_program);
712 fill_pk(&mut log.token_program, ix.token_program);
713 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
714 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
715 fill_pk(&mut log.creator_vault, ix.creator_vault);
716 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
717 fill_pk(&mut log.creator, ix.creator);
718 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
719 fill_pk(&mut log.associated_quote_fee_recipient, ix.associated_quote_fee_recipient);
720 fill_pk(&mut log.buyback_fee_recipient, ix.buyback_fee_recipient);
721 fill_pk(
722 &mut log.associated_quote_buyback_fee_recipient,
723 ix.associated_quote_buyback_fee_recipient,
724 );
725 fill_pk(&mut log.associated_quote_bonding_curve, ix.associated_quote_bonding_curve);
726 fill_pk(&mut log.associated_quote_user, ix.associated_quote_user);
727 fill_pk(&mut log.associated_creator_vault, ix.associated_creator_vault);
728 fill_pk(&mut log.sharing_config, ix.sharing_config);
729 fill_pk(&mut log.event_authority, ix.event_authority);
730 fill_pk(&mut log.program, ix.program);
731 fill_pk(&mut log.global_volume_accumulator, ix.global_volume_accumulator);
732 fill_pk(&mut log.user_volume_accumulator, ix.user_volume_accumulator);
733 fill_pk(&mut log.associated_user_volume_accumulator, ix.associated_user_volume_accumulator);
734 fill_pk(&mut log.fee_config, ix.fee_config);
735 fill_pk(&mut log.fee_program, ix.fee_program);
736 if log.account.is_none() {
737 log.account = ix.account;
738 }
739 if prefer_pump_instruction_name(&log.ix_name, &ix.ix_name) {
740 log.ix_name = ix.ix_name;
741 }
742 put_u64_if_nonzero(&mut log.amount, ix.amount);
743 put_u64_if_nonzero(&mut log.max_sol_cost, ix.max_sol_cost);
744 put_u64_if_nonzero(&mut log.min_sol_output, ix.min_sol_output);
745 put_u64_if_nonzero(&mut log.spendable_sol_in, ix.spendable_sol_in);
746 put_u64_if_nonzero(&mut log.spendable_quote_in, ix.spendable_quote_in);
747 put_u64_if_nonzero(&mut log.min_tokens_out, ix.min_tokens_out);
748 if log.quote_amount == 0 {
749 put_u64_if_nonzero(&mut log.quote_amount, ix.quote_amount);
750 }
751 if log.virtual_quote_reserves == 0 {
752 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
753 }
754 if log.real_quote_reserves == 0 {
755 put_u64_if_nonzero(&mut log.real_quote_reserves, ix.real_quote_reserves);
756 }
757 if !log.is_created_buy && ix.is_created_buy {
758 log.is_created_buy = true;
759 }
760}
761
762#[inline]
763fn merge_pumpfun_create_log_preferred(
764 log: &mut PumpFunCreateTokenEvent,
765 ix: PumpFunCreateTokenEvent,
766) {
767 fill_str_if_empty(&mut log.name, &ix.name);
768 fill_str_if_empty(&mut log.symbol, &ix.symbol);
769 fill_str_if_empty(&mut log.uri, &ix.uri);
770 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
771 fill_pk(&mut log.user, ix.user);
772 fill_pk(&mut log.creator, ix.creator);
773 fill_pk(&mut log.token_program, ix.token_program);
774 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
775 fill_pk(&mut log.quote_vault, ix.quote_vault);
776 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
777 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
778 if log.creator_fee_bps == 0 {
779 log.creator_fee_bps = ix.creator_fee_bps;
780 }
781 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
782 log.ix_name = ix.ix_name;
783 }
784 log.is_mayhem_mode |= ix.is_mayhem_mode;
785 log.is_cashback_enabled |= ix.is_cashback_enabled;
786 log.is_holder_reward |= ix.is_holder_reward;
787}
788
789#[inline]
790fn merge_pumpfun_create_v2_into_create_log_preferred(
791 log: &mut PumpFunCreateTokenEvent,
792 ix: PumpFunCreateV2TokenEvent,
793) {
794 fill_str_if_empty(&mut log.name, &ix.name);
795 fill_str_if_empty(&mut log.symbol, &ix.symbol);
796 fill_str_if_empty(&mut log.uri, &ix.uri);
797 fill_pk(&mut log.mint, ix.mint);
798 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
799 fill_pk(&mut log.user, ix.user);
800 fill_pk(&mut log.creator, ix.creator);
801 if log.timestamp == 0 && ix.timestamp != 0 {
802 log.timestamp = ix.timestamp;
803 }
804 put_u64_if_nonzero(&mut log.virtual_token_reserves, ix.virtual_token_reserves);
805 put_u64_if_nonzero(&mut log.virtual_sol_reserves, ix.virtual_sol_reserves);
806 put_u64_if_nonzero(&mut log.real_token_reserves, ix.real_token_reserves);
807 put_u64_if_nonzero(&mut log.token_total_supply, ix.token_total_supply);
808 fill_pk(&mut log.token_program, ix.token_program);
809 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
810 fill_pk(&mut log.quote_vault, ix.quote_vault);
811 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
812 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
813 if log.creator_fee_bps == 0 {
814 log.creator_fee_bps = ix.creator_fee_bps;
815 }
816 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
817 log.ix_name = ix.ix_name;
818 }
819 log.is_mayhem_mode |= ix.is_mayhem_mode;
820 log.is_cashback_enabled |= ix.is_cashback_enabled;
821 log.is_holder_reward |= ix.is_holder_reward;
822}
823
824#[inline]
825fn merge_pumpfun_create_into_create_v2_log_preferred(
826 log: &mut PumpFunCreateV2TokenEvent,
827 ix: PumpFunCreateTokenEvent,
828) {
829 fill_str_if_empty(&mut log.name, &ix.name);
830 fill_str_if_empty(&mut log.symbol, &ix.symbol);
831 fill_str_if_empty(&mut log.uri, &ix.uri);
832 fill_pk(&mut log.mint, ix.mint);
833 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
834 fill_pk(&mut log.user, ix.user);
835 fill_pk(&mut log.creator, ix.creator);
836 if log.timestamp == 0 && ix.timestamp != 0 {
837 log.timestamp = ix.timestamp;
838 }
839 put_u64_if_nonzero(&mut log.virtual_token_reserves, ix.virtual_token_reserves);
840 put_u64_if_nonzero(&mut log.virtual_sol_reserves, ix.virtual_sol_reserves);
841 put_u64_if_nonzero(&mut log.real_token_reserves, ix.real_token_reserves);
842 put_u64_if_nonzero(&mut log.token_total_supply, ix.token_total_supply);
843 fill_pk(&mut log.token_program, ix.token_program);
844 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
845 fill_pk(&mut log.quote_vault, ix.quote_vault);
846 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
847 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
848 if log.creator_fee_bps == 0 {
849 log.creator_fee_bps = ix.creator_fee_bps;
850 }
851 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
852 log.ix_name = ix.ix_name;
853 }
854 log.is_mayhem_mode |= ix.is_mayhem_mode;
855 log.is_cashback_enabled |= ix.is_cashback_enabled;
856 log.is_holder_reward |= ix.is_holder_reward;
857}
858
859#[inline]
860fn merge_pumpfun_create_v2_log_preferred(
861 log: &mut PumpFunCreateV2TokenEvent,
862 ix: PumpFunCreateV2TokenEvent,
863) {
864 fill_str_if_empty(&mut log.name, &ix.name);
865 fill_str_if_empty(&mut log.symbol, &ix.symbol);
866 fill_str_if_empty(&mut log.uri, &ix.uri);
867 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
868 fill_pk(&mut log.user, ix.user);
869 fill_pk(&mut log.creator, ix.creator);
870 fill_pk(&mut log.token_program, ix.token_program);
871 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
872 fill_pk(&mut log.quote_vault, ix.quote_vault);
873 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
874 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
875 if log.creator_fee_bps == 0 {
876 log.creator_fee_bps = ix.creator_fee_bps;
877 }
878 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
879 log.ix_name = ix.ix_name;
880 }
881 fill_pk(&mut log.mint_authority, ix.mint_authority);
882 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
883 fill_pk(&mut log.global, ix.global);
884 fill_pk(&mut log.system_program, ix.system_program);
885 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
886 fill_pk(&mut log.mayhem_program_id, ix.mayhem_program_id);
887 fill_pk(&mut log.global_params, ix.global_params);
888 fill_pk(&mut log.sol_vault, ix.sol_vault);
889 fill_pk(&mut log.mayhem_state, ix.mayhem_state);
890 fill_pk(&mut log.mayhem_token_vault, ix.mayhem_token_vault);
891 fill_pk(&mut log.event_authority, ix.event_authority);
892 fill_pk(&mut log.program, ix.program);
893 fill_pk(&mut log.observed_fee_recipient, ix.observed_fee_recipient);
894 log.is_holder_reward |= ix.is_holder_reward;
895}
896
897#[inline]
898fn merge_pumpfun_migrate_log_preferred(log: &mut PumpFunMigrateEvent, ix: PumpFunMigrateEvent) {
899 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
900 fill_pk(&mut log.pool, ix.pool);
901 fill_pk(&mut log.user, ix.user);
902}
903
904#[inline]
905fn merge_pumpswap_trade_log_preferred(log: &mut PumpSwapTradeEvent, ix: PumpSwapTradeEvent) {
906 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
907 log.ix_name = ix.ix_name;
908 }
909}
910
911#[inline]
912fn merge_pumpswap_buy_log_preferred(log: &mut PumpSwapBuyEvent, ix: PumpSwapBuyEvent) {
913 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
914 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
915 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
916 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
917 fill_pk(&mut log.coin_creator, ix.coin_creator);
918 fill_pk(&mut log.base_mint, ix.base_mint);
919 fill_pk(&mut log.quote_mint, ix.quote_mint);
920 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
921 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
922 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
923 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
924 fill_pk(&mut log.base_token_program, ix.base_token_program);
925 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
926 fill_pk(&mut log.pool_v2, ix.pool_v2);
927 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
928 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
929 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
930 log.ix_name = ix.ix_name;
931 }
932}
933
934#[inline]
935fn merge_pumpswap_sell_log_preferred(log: &mut PumpSwapSellEvent, ix: PumpSwapSellEvent) {
936 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
937 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
938 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
939 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
940 fill_pk(&mut log.coin_creator, ix.coin_creator);
941 fill_pk(&mut log.base_mint, ix.base_mint);
942 fill_pk(&mut log.quote_mint, ix.quote_mint);
943 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
944 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
945 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
946 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
947 fill_pk(&mut log.base_token_program, ix.base_token_program);
948 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
949 fill_pk(&mut log.pool_v2, ix.pool_v2);
950 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
951 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
952}
953
954#[inline]
955fn merge_raydium_clmm_swap_log_preferred(log: &mut RaydiumClmmSwapEvent, ix: RaydiumClmmSwapEvent) {
956 if !ix.ix_name.is_empty() {
957 log.ix_name = ix.ix_name;
958 log.amount = ix.amount;
959 log.other_amount_threshold = ix.other_amount_threshold;
960 log.sqrt_price_limit_x64 = ix.sqrt_price_limit_x64;
961 log.is_base_input = ix.is_base_input;
962 }
963 fill_pk(&mut log.token_account_0, ix.token_account_0);
964 fill_pk(&mut log.token_account_1, ix.token_account_1);
965 fill_pk(&mut log.input_token_account, ix.input_token_account);
966 fill_pk(&mut log.output_token_account, ix.output_token_account);
967 fill_pk(&mut log.sender, ix.sender);
968 fill_pk(&mut log.amm_config, ix.amm_config);
969 fill_pk(&mut log.input_vault, ix.input_vault);
970 fill_pk(&mut log.output_vault, ix.output_vault);
971 fill_pk(&mut log.observation_state, ix.observation_state);
972 fill_pk(&mut log.input_mint, ix.input_mint);
973 fill_pk(&mut log.output_mint, ix.output_mint);
974 if log.tick_array_bitmap_extension.is_none() {
975 log.tick_array_bitmap_extension = ix.tick_array_bitmap_extension;
976 }
977 if log.tick_arrays.is_empty() && !ix.tick_arrays.is_empty() {
978 log.tick_arrays = ix.tick_arrays;
979 }
980}
981
982#[inline]
983fn merge_raydium_amm_v4_swap_log_preferred(
984 log: &mut RaydiumAmmV4SwapEvent,
985 ix: RaydiumAmmV4SwapEvent,
986) {
987 if !ix.ix_name.is_empty() {
988 log.ix_name = ix.ix_name;
989 log.instruction_amount_in = ix.instruction_amount_in;
990 log.instruction_amount_out = ix.instruction_amount_out;
991 log.minimum_amount_out = ix.minimum_amount_out;
992 log.max_amount_in = ix.max_amount_in;
993 }
994 fill_pk(&mut log.token_program, ix.token_program);
995 fill_pk(&mut log.amm_authority, ix.amm_authority);
996 fill_pk(&mut log.amm_open_orders, ix.amm_open_orders);
997 if let Some(ref o) = ix.amm_target_orders {
998 if log.amm_target_orders.is_none() {
999 log.amm_target_orders = Some(*o);
1000 }
1001 }
1002 fill_pk(&mut log.pool_coin_token_account, ix.pool_coin_token_account);
1003 fill_pk(&mut log.pool_pc_token_account, ix.pool_pc_token_account);
1004 fill_pk(&mut log.serum_program, ix.serum_program);
1005 fill_pk(&mut log.serum_market, ix.serum_market);
1006 fill_pk(&mut log.serum_bids, ix.serum_bids);
1007 fill_pk(&mut log.serum_asks, ix.serum_asks);
1008 fill_pk(&mut log.serum_event_queue, ix.serum_event_queue);
1009 fill_pk(&mut log.serum_coin_vault_account, ix.serum_coin_vault_account);
1010 fill_pk(&mut log.serum_pc_vault_account, ix.serum_pc_vault_account);
1011 fill_pk(&mut log.serum_vault_signer, ix.serum_vault_signer);
1012 fill_pk(&mut log.user_source_token_account, ix.user_source_token_account);
1013 fill_pk(&mut log.user_destination_token_account, ix.user_destination_token_account);
1014 fill_pk(&mut log.user_source_owner, ix.user_source_owner);
1015 fill_pk(&mut log.amm, ix.amm);
1016}
1017
1018#[inline]
1019fn merge_pumpswap_create_pool_log_preferred(
1020 log: &mut PumpSwapCreatePoolEvent,
1021 ix: PumpSwapCreatePoolEvent,
1022) {
1023 fill_pk(&mut log.creator, ix.creator);
1024 fill_pk(&mut log.pool, ix.pool);
1025 fill_pk(&mut log.lp_mint, ix.lp_mint);
1026 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
1027 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
1028 fill_pk(&mut log.coin_creator, ix.coin_creator);
1029 log.is_mayhem_mode |= ix.is_mayhem_mode;
1030 log.is_cashback_coin |= ix.is_cashback_coin;
1031 if log.creator_fee_bps == 0 {
1032 log.creator_fee_bps = ix.creator_fee_bps;
1033 }
1034 log.can_edit_creator_fee |= ix.can_edit_creator_fee;
1035 log.is_holder_reward |= ix.is_holder_reward;
1036}
1037
1038#[inline]
1039fn merge_pumpswap_liquidity_added_log_preferred(
1040 log: &mut PumpSwapLiquidityAdded,
1041 ix: PumpSwapLiquidityAdded,
1042) {
1043 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
1044 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
1045 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
1046}
1047
1048#[inline]
1049fn merge_pumpswap_liquidity_removed_log_preferred(
1050 log: &mut PumpSwapLiquidityRemoved,
1051 ix: PumpSwapLiquidityRemoved,
1052) {
1053 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
1054 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
1055 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
1056}
1057
1058#[inline]
1059fn merge_raydium_launchlab_pool_create_log_preferred(
1060 log: &mut RaydiumLaunchlabPoolCreateEvent,
1061 ix: RaydiumLaunchlabPoolCreateEvent,
1062) {
1063 fill_pk(&mut log.payer, ix.payer);
1064 fill_pk(&mut log.creator, ix.creator);
1065 fill_pk(&mut log.global_config, ix.global_config);
1066 fill_pk(&mut log.platform_config, ix.platform_config);
1067 fill_pk(&mut log.base_mint, ix.base_mint);
1068 fill_pk(&mut log.quote_mint, ix.quote_mint);
1069 fill_pk(&mut log.base_vault, ix.base_vault);
1070 fill_pk(&mut log.quote_vault, ix.quote_vault);
1071 fill_pk(&mut log.base_token_program, ix.base_token_program);
1072 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
1073 fill_str_if_empty(&mut log.base_mint_param.name, &ix.base_mint_param.name);
1074 fill_str_if_empty(&mut log.base_mint_param.symbol, &ix.base_mint_param.symbol);
1075 fill_str_if_empty(&mut log.base_mint_param.uri, &ix.base_mint_param.uri);
1076}
1077
1078#[inline]
1079fn merge_raydium_launchlab_migrate_amm_log_preferred(
1080 log: &mut RaydiumLaunchlabMigrateAmmEvent,
1081 ix: RaydiumLaunchlabMigrateAmmEvent,
1082) {
1083 fill_pk(&mut log.old_pool, ix.old_pool);
1084 fill_pk(&mut log.new_pool, ix.new_pool);
1085 fill_pk(&mut log.user, ix.user);
1086}
1087
1088#[inline]
1089fn merge_raydium_launchlab_trade_log_preferred(
1090 log: &mut RaydiumLaunchlabTradeEvent,
1091 ix: RaydiumLaunchlabTradeEvent,
1092) {
1093 fill_pk(&mut log.user, ix.user);
1094 fill_pk(&mut log.global_config, ix.global_config);
1095 fill_pk(&mut log.platform_config, ix.platform_config);
1096 fill_pk(&mut log.user_base_token, ix.user_base_token);
1097 fill_pk(&mut log.user_quote_token, ix.user_quote_token);
1098 fill_pk(&mut log.base_vault, ix.base_vault);
1099 fill_pk(&mut log.quote_vault, ix.quote_vault);
1100 fill_pk(&mut log.base_mint, ix.base_mint);
1101 fill_pk(&mut log.quote_mint, ix.quote_mint);
1102 fill_pk(&mut log.base_token_program, ix.base_token_program);
1103 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
1104 fill_pk(&mut log.system_program, ix.system_program);
1105 fill_pk(&mut log.platform_associated_account, ix.platform_associated_account);
1106 fill_pk(&mut log.creator_associated_account, ix.creator_associated_account);
1107}
1108
1109#[inline]
1110fn merge_meteora_dlmm_swap_log_preferred(log: &mut MeteoraDlmmSwapEvent, ix: MeteoraDlmmSwapEvent) {
1111 fill_pk(&mut log.user_token_in, ix.user_token_in);
1112 fill_pk(&mut log.user_token_out, ix.user_token_out);
1113 if log.min_amount_out == 0 {
1114 log.min_amount_out = ix.min_amount_out;
1115 }
1116}
1117
1118fn merge_meteora_pools_add_context(
1124 log: &mut MeteoraPoolsAddLiquidityEvent,
1125 ix: MeteoraPoolsAddLiquidityEvent,
1126) {
1127 if !ix.ix_name.is_empty() {
1128 log.ix_name = ix.ix_name;
1129 log.pool_token_amount = ix.pool_token_amount;
1130 log.maximum_token_a_amount = ix.maximum_token_a_amount;
1131 log.maximum_token_b_amount = ix.maximum_token_b_amount;
1132 log.minimum_pool_token_amount = ix.minimum_pool_token_amount;
1133 log.token_a_in_amount = ix.token_a_in_amount;
1134 log.token_b_in_amount = ix.token_b_in_amount;
1135 }
1136 fill_pk(&mut log.pool, ix.pool);
1137 fill_pk(&mut log.lp_mint, ix.lp_mint);
1138 fill_pk(&mut log.user_pool_lp, ix.user_pool_lp);
1139 fill_pk(&mut log.a_vault_lp, ix.a_vault_lp);
1140 fill_pk(&mut log.b_vault_lp, ix.b_vault_lp);
1141 fill_pk(&mut log.a_vault, ix.a_vault);
1142 fill_pk(&mut log.b_vault, ix.b_vault);
1143 fill_pk(&mut log.a_vault_lp_mint, ix.a_vault_lp_mint);
1144 fill_pk(&mut log.b_vault_lp_mint, ix.b_vault_lp_mint);
1145 fill_pk(&mut log.a_token_vault, ix.a_token_vault);
1146 fill_pk(&mut log.b_token_vault, ix.b_token_vault);
1147 fill_pk(&mut log.user_a_token, ix.user_a_token);
1148 fill_pk(&mut log.user_b_token, ix.user_b_token);
1149 fill_pk(&mut log.user, ix.user);
1150 fill_pk(&mut log.vault_program, ix.vault_program);
1151 fill_pk(&mut log.token_program, ix.token_program);
1152}
1153
1154fn merge_meteora_pools_bootstrap_context(
1155 log: &mut MeteoraPoolsBootstrapLiquidityEvent,
1156 ix: MeteoraPoolsBootstrapLiquidityEvent,
1157) {
1158 if !ix.ix_name.is_empty() {
1159 log.ix_name = ix.ix_name;
1160 log.token_a_in_amount = ix.token_a_in_amount;
1161 log.token_b_in_amount = ix.token_b_in_amount;
1162 }
1163 fill_pk(&mut log.pool, ix.pool);
1164 fill_pk(&mut log.lp_mint, ix.lp_mint);
1165 fill_pk(&mut log.user_pool_lp, ix.user_pool_lp);
1166 fill_pk(&mut log.a_vault_lp, ix.a_vault_lp);
1167 fill_pk(&mut log.b_vault_lp, ix.b_vault_lp);
1168 fill_pk(&mut log.a_vault, ix.a_vault);
1169 fill_pk(&mut log.b_vault, ix.b_vault);
1170 fill_pk(&mut log.a_vault_lp_mint, ix.a_vault_lp_mint);
1171 fill_pk(&mut log.b_vault_lp_mint, ix.b_vault_lp_mint);
1172 fill_pk(&mut log.a_token_vault, ix.a_token_vault);
1173 fill_pk(&mut log.b_token_vault, ix.b_token_vault);
1174 fill_pk(&mut log.user_a_token, ix.user_a_token);
1175 fill_pk(&mut log.user_b_token, ix.user_b_token);
1176 fill_pk(&mut log.user, ix.user);
1177 fill_pk(&mut log.vault_program, ix.vault_program);
1178 fill_pk(&mut log.token_program, ix.token_program);
1179}
1180
1181fn merge_meteora_pools_poolcreated_context(
1182 log: &mut MeteoraPoolsPoolCreatedEvent,
1183 ix: MeteoraPoolsPoolCreatedEvent,
1184) {
1185 if !ix.ix_name.is_empty() {
1186 log.ix_name = ix.ix_name;
1187 log.token_a_in_amount = ix.token_a_in_amount;
1188 log.token_b_in_amount = ix.token_b_in_amount;
1189 log.activation_point = ix.activation_point;
1190 log.stable_curve = ix.stable_curve;
1191 log.trade_fee_bps = ix.trade_fee_bps;
1192 log.customizable_params = ix.customizable_params;
1193 }
1194 fill_pk(&mut log.pool, ix.pool);
1195 fill_pk(&mut log.config, ix.config);
1196 fill_pk(&mut log.lp_mint, ix.lp_mint);
1197 fill_pk(&mut log.token_a_mint, ix.token_a_mint);
1198 fill_pk(&mut log.token_b_mint, ix.token_b_mint);
1199 fill_pk(&mut log.a_vault, ix.a_vault);
1200 fill_pk(&mut log.b_vault, ix.b_vault);
1201 fill_pk(&mut log.a_token_vault, ix.a_token_vault);
1202 fill_pk(&mut log.b_token_vault, ix.b_token_vault);
1203 fill_pk(&mut log.a_vault_lp_mint, ix.a_vault_lp_mint);
1204 fill_pk(&mut log.b_vault_lp_mint, ix.b_vault_lp_mint);
1205 fill_pk(&mut log.a_vault_lp, ix.a_vault_lp);
1206 fill_pk(&mut log.b_vault_lp, ix.b_vault_lp);
1207 fill_pk(&mut log.payer_token_a, ix.payer_token_a);
1208 fill_pk(&mut log.payer_token_b, ix.payer_token_b);
1209 fill_pk(&mut log.payer_pool_lp, ix.payer_pool_lp);
1210 fill_pk(&mut log.protocol_token_a_fee, ix.protocol_token_a_fee);
1211 fill_pk(&mut log.protocol_token_b_fee, ix.protocol_token_b_fee);
1212 fill_pk(&mut log.payer, ix.payer);
1213 fill_pk(&mut log.rent, ix.rent);
1214 fill_pk(&mut log.mint_metadata, ix.mint_metadata);
1215 fill_pk(&mut log.metadata_program, ix.metadata_program);
1216 fill_pk(&mut log.vault_program, ix.vault_program);
1217 fill_pk(&mut log.token_program, ix.token_program);
1218 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
1219 fill_pk(&mut log.system_program, ix.system_program);
1220 fill_pk(&mut log.admin_token_a, ix.admin_token_a);
1221 fill_pk(&mut log.admin_token_b, ix.admin_token_b);
1222 fill_pk(&mut log.admin_pool_lp, ix.admin_pool_lp);
1223 fill_pk(&mut log.admin, ix.admin);
1224 fill_pk(&mut log.fee_owner, ix.fee_owner);
1225}
1226
1227fn merge_meteora_pools_setpoolfees_context(
1228 log: &mut MeteoraPoolsSetPoolFeesEvent,
1229 ix: MeteoraPoolsSetPoolFeesEvent,
1230) {
1231 if !ix.ix_name.is_empty() {
1232 log.ix_name = ix.ix_name;
1233 log.new_partner_fee_numerator = ix.new_partner_fee_numerator;
1234 }
1235 fill_pk(&mut log.pool, ix.pool);
1236 fill_pk(&mut log.fee_operator, ix.fee_operator);
1237}
1238
1239fn merge_meteora_pools_remove_context(
1240 log: &mut MeteoraPoolsRemoveLiquidityEvent,
1241 ix: MeteoraPoolsRemoveLiquidityEvent,
1242) {
1243 if !ix.ix_name.is_empty() {
1244 log.ix_name = ix.ix_name;
1245 log.pool_token_amount = ix.pool_token_amount;
1246 log.minimum_out_amount = ix.minimum_out_amount;
1247 log.minimum_a_token_out = ix.minimum_a_token_out;
1248 log.minimum_b_token_out = ix.minimum_b_token_out;
1249 }
1250 fill_pk(&mut log.pool, ix.pool);
1251 fill_pk(&mut log.lp_mint, ix.lp_mint);
1252 fill_pk(&mut log.user_pool_lp, ix.user_pool_lp);
1253 fill_pk(&mut log.a_vault_lp, ix.a_vault_lp);
1254 fill_pk(&mut log.b_vault_lp, ix.b_vault_lp);
1255 fill_pk(&mut log.a_vault, ix.a_vault);
1256 fill_pk(&mut log.b_vault, ix.b_vault);
1257 fill_pk(&mut log.a_vault_lp_mint, ix.a_vault_lp_mint);
1258 fill_pk(&mut log.b_vault_lp_mint, ix.b_vault_lp_mint);
1259 fill_pk(&mut log.a_token_vault, ix.a_token_vault);
1260 fill_pk(&mut log.b_token_vault, ix.b_token_vault);
1261 fill_pk(&mut log.user_a_token, ix.user_a_token);
1262 fill_pk(&mut log.user_b_token, ix.user_b_token);
1263 fill_pk(&mut log.user, ix.user);
1264 fill_pk(&mut log.vault_program, ix.vault_program);
1265 fill_pk(&mut log.token_program, ix.token_program);
1266 fill_pk(&mut log.user_destination_token, ix.user_destination_token);
1267}
1268
1269fn merge_meteora_pools_swap_context(log: &mut MeteoraPoolsSwapEvent, ix: MeteoraPoolsSwapEvent) {
1270 if !ix.ix_name.is_empty() {
1271 log.ix_name = ix.ix_name;
1272 log.amount_in = ix.amount_in;
1273 log.minimum_out_amount = ix.minimum_out_amount;
1274 }
1275 fill_pk(&mut log.pool, ix.pool);
1276 fill_pk(&mut log.user_source_token, ix.user_source_token);
1277 fill_pk(&mut log.user_destination_token, ix.user_destination_token);
1278 fill_pk(&mut log.a_vault, ix.a_vault);
1279 fill_pk(&mut log.b_vault, ix.b_vault);
1280 fill_pk(&mut log.a_token_vault, ix.a_token_vault);
1281 fill_pk(&mut log.b_token_vault, ix.b_token_vault);
1282 fill_pk(&mut log.a_vault_lp_mint, ix.a_vault_lp_mint);
1283 fill_pk(&mut log.b_vault_lp_mint, ix.b_vault_lp_mint);
1284 fill_pk(&mut log.a_vault_lp, ix.a_vault_lp);
1285 fill_pk(&mut log.b_vault_lp, ix.b_vault_lp);
1286 fill_pk(&mut log.protocol_token_fee, ix.protocol_token_fee);
1287 fill_pk(&mut log.user, ix.user);
1288 fill_pk(&mut log.vault_program, ix.vault_program);
1289 fill_pk(&mut log.token_program, ix.token_program);
1290}
1291
1292fn merge_orca_swap_context(log: &mut OrcaWhirlpoolSwapEvent, ix: OrcaWhirlpoolSwapEvent) {
1293 if !ix.ix_name.is_empty() {
1294 log.ix_name = ix.ix_name;
1295 log.amount = ix.amount;
1296 log.other_amount_threshold = ix.other_amount_threshold;
1297 log.sqrt_price_limit = ix.sqrt_price_limit;
1298 log.amount_specified_is_input = ix.amount_specified_is_input;
1299 }
1300 fill_pk(&mut log.token_authority, ix.token_authority);
1301 fill_pk(&mut log.token_owner_account_a, ix.token_owner_account_a);
1302 fill_pk(&mut log.token_owner_account_b, ix.token_owner_account_b);
1303 fill_pk(&mut log.token_program_a, ix.token_program_a);
1304 fill_pk(&mut log.token_program_b, ix.token_program_b);
1305 fill_pk(&mut log.token_mint_a, ix.token_mint_a);
1306 fill_pk(&mut log.token_mint_b, ix.token_mint_b);
1307 fill_pk(&mut log.token_vault_a, ix.token_vault_a);
1308 fill_pk(&mut log.token_vault_b, ix.token_vault_b);
1309 fill_pk(&mut log.tick_array_0, ix.tick_array_0);
1310 fill_pk(&mut log.tick_array_1, ix.tick_array_1);
1311 fill_pk(&mut log.tick_array_2, ix.tick_array_2);
1312 fill_pk(&mut log.oracle, ix.oracle);
1313}
1314
1315fn wrapper_sources_conflict(a: &DexEvent, b: &DexEvent) -> bool {
1316 a.third_program().zip(b.third_program()).is_some_and(|(a, b)| {
1317 a.instruction_index != b.instruction_index || a.program_id != b.program_id
1318 })
1319}
1320
1321fn reconcile_trade_source(a: &mut DexEvent, b: &mut DexEvent) {
1322 let Some((a_source, a_status, a_program)) = a.trade_source_mut() else { return };
1323 let Some((b_source, b_status, b_program)) = b.trade_source_mut() else { return };
1324 if a_program != b_program {
1325 return;
1326 }
1327 if a_source.is_none() {
1328 *a_source = b_source.clone();
1329 }
1330 if b_source.is_none() {
1331 *b_source = a_source.clone();
1332 }
1333 if *a_status == TradeParseStatus::ExecutionConfirmed
1334 || *b_status == TradeParseStatus::ExecutionConfirmed
1335 {
1336 *a_status = TradeParseStatus::ExecutionConfirmed;
1337 *b_status = TradeParseStatus::ExecutionConfirmed;
1338 }
1339}
1340
1341pub fn merge_grpc_instruction_into_log(log: &mut DexEvent, mut ix: DexEvent) {
1342 if wrapper_sources_conflict(log, &ix) {
1343 return;
1344 }
1345 if std::mem::discriminant(log) == std::mem::discriminant(&ix) {
1346 reconcile_trade_source(log, &mut ix);
1347 }
1348 use DexEvent::*;
1349 match log {
1350 PumpFunTrade(l) => {
1351 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1352 merge_pumpfun_trade_log_preferred(l, i);
1353 }
1354 }
1355 PumpFunBuy(l) => {
1356 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1357 merge_pumpfun_trade_log_preferred(l, i);
1358 }
1359 }
1360 PumpFunSell(l) => {
1361 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1362 merge_pumpfun_trade_log_preferred(l, i);
1363 }
1364 }
1365 PumpFunBuyExactSolIn(l) => {
1366 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1367 merge_pumpfun_trade_log_preferred(l, i);
1368 }
1369 }
1370 PumpFunCreate(l) => match ix {
1371 DexEvent::PumpFunCreate(i) => merge_pumpfun_create_log_preferred(l, i),
1372 DexEvent::PumpFunCreateV2(i) => merge_pumpfun_create_v2_into_create_log_preferred(l, i),
1373 _ => {}
1374 },
1375 PumpFunCreateV2(l) => match ix {
1376 DexEvent::PumpFunCreate(i) => merge_pumpfun_create_into_create_v2_log_preferred(l, i),
1377 DexEvent::PumpFunCreateV2(i) => merge_pumpfun_create_v2_log_preferred(l, i),
1378 _ => {}
1379 },
1380 PumpFunMigrate(l) => {
1381 if let DexEvent::PumpFunMigrate(i) = ix {
1382 merge_pumpfun_migrate_log_preferred(l, i);
1383 }
1384 }
1385 PumpSwapTrade(l) => {
1386 if let PumpSwapTrade(i) = ix {
1387 merge_pumpswap_trade_log_preferred(l, i);
1388 }
1389 }
1390 PumpSwapBuy(l) => {
1391 if let PumpSwapBuy(i) = ix {
1392 merge_pumpswap_buy_log_preferred(l, i);
1393 }
1394 }
1395 PumpSwapSell(l) => {
1396 if let PumpSwapSell(i) = ix {
1397 merge_pumpswap_sell_log_preferred(l, i);
1398 }
1399 }
1400 RaydiumCpmmSwap(l) => {
1401 if let RaydiumCpmmSwap(i) = ix {
1402 merge_raydium_cpmm_swap_log_preferred(l, i);
1403 }
1404 }
1405 MeteoraPoolsAddLiquidity(l) => {
1406 if let MeteoraPoolsAddLiquidity(i) = ix {
1407 merge_meteora_pools_add_context(l, i);
1408 }
1409 }
1410 MeteoraPoolsRemoveLiquidity(l) => {
1411 if let MeteoraPoolsRemoveLiquidity(i) = ix {
1412 merge_meteora_pools_remove_context(l, i);
1413 }
1414 }
1415 MeteoraPoolsBootstrapLiquidity(l) => {
1416 if let MeteoraPoolsBootstrapLiquidity(i) = ix {
1417 merge_meteora_pools_bootstrap_context(l, i);
1418 }
1419 }
1420 MeteoraPoolsPoolCreated(l) => {
1421 if let MeteoraPoolsPoolCreated(i) = ix {
1422 merge_meteora_pools_poolcreated_context(l, i);
1423 }
1424 }
1425 MeteoraPoolsSetPoolFees(l) => {
1426 if let MeteoraPoolsSetPoolFees(i) = ix {
1427 merge_meteora_pools_setpoolfees_context(l, i);
1428 }
1429 }
1430 MeteoraPoolsSwap(l) => {
1431 if let MeteoraPoolsSwap(i) = ix {
1432 merge_meteora_pools_swap_context(l, i);
1433 }
1434 }
1435 OrcaWhirlpoolSwap(l) => {
1436 if let OrcaWhirlpoolSwap(i) = ix {
1437 merge_orca_swap_context(l, i);
1438 }
1439 }
1440 RaydiumClmmIncreaseLiquidity(l) => {
1441 if let RaydiumClmmIncreaseLiquidity(i) = ix {
1442 fill_pk(&mut l.pool, i.pool);
1443 fill_pk(&mut l.user, i.user);
1444 fill_pk(&mut l.personal_position, i.personal_position);
1445 l.amount0_max = i.amount0_max;
1446 l.amount1_max = i.amount1_max;
1447 }
1448 }
1449 RaydiumClmmDecreaseLiquidity(l) => {
1450 if let RaydiumClmmDecreaseLiquidity(i) = ix {
1451 fill_pk(&mut l.pool, i.pool);
1452 fill_pk(&mut l.user, i.user);
1453 fill_pk(&mut l.personal_position, i.personal_position);
1454 l.amount0_min = i.amount0_min;
1455 l.amount1_min = i.amount1_min;
1456 }
1457 }
1458 RaydiumClmmSwap(l) => {
1459 if let RaydiumClmmSwap(i) = ix {
1460 merge_raydium_clmm_swap_log_preferred(l, i);
1461 }
1462 }
1463 RaydiumAmmV4Swap(l) => {
1464 if let RaydiumAmmV4Swap(i) = ix {
1465 merge_raydium_amm_v4_swap_log_preferred(l, i);
1466 }
1467 }
1468 RaydiumLaunchlabTrade(l) => {
1469 if let RaydiumLaunchlabTrade(i) = ix {
1470 merge_raydium_launchlab_trade_log_preferred(l, i);
1471 }
1472 }
1473 RaydiumLaunchlabPoolCreate(l) => {
1474 if let RaydiumLaunchlabPoolCreate(i) = ix {
1475 merge_raydium_launchlab_pool_create_log_preferred(l, i);
1476 }
1477 }
1478 RaydiumLaunchlabMigrateAmm(l) => {
1479 if let RaydiumLaunchlabMigrateAmm(i) = ix {
1480 merge_raydium_launchlab_migrate_amm_log_preferred(l, i);
1481 }
1482 }
1483 PumpSwapCreatePool(l) => {
1484 if let PumpSwapCreatePool(i) = ix {
1485 merge_pumpswap_create_pool_log_preferred(l, i);
1486 }
1487 }
1488 PumpSwapLiquidityAdded(l) => {
1489 if let PumpSwapLiquidityAdded(i) = ix {
1490 merge_pumpswap_liquidity_added_log_preferred(l, i);
1491 }
1492 }
1493 PumpSwapLiquidityRemoved(l) => {
1494 if let PumpSwapLiquidityRemoved(i) = ix {
1495 merge_pumpswap_liquidity_removed_log_preferred(l, i);
1496 }
1497 }
1498 MeteoraDlmmSwap(l) => {
1499 if let MeteoraDlmmSwap(i) = ix {
1500 merge_meteora_dlmm_swap_log_preferred(l, i);
1501 }
1502 }
1503 _ => {}
1504 }
1505}
1506
1507#[inline]
1508fn pumpfun_trade_from_ix_variant(ix: DexEvent) -> Option<PumpFunTradeEvent> {
1509 match ix {
1510 DexEvent::PumpFunTrade(t)
1511 | DexEvent::PumpFunBuy(t)
1512 | DexEvent::PumpFunSell(t)
1513 | DexEvent::PumpFunBuyExactSolIn(t) => Some(t),
1514 _ => None,
1515 }
1516}
1517
1518#[cfg(test)]
1519mod tests {
1520
1521 #[test]
1522 fn clmm_merge_retains_limits_and_mode_without_overwriting_executed_price_or_direction() {
1523 use crate::core::events::{DexEvent, RaydiumClmmSwapEvent};
1524 for input_mode in [false, true] {
1525 let instruction = RaydiumClmmSwapEvent {
1526 ix_name: "swap_v2".to_string(),
1527 amount: 0,
1528 other_amount_threshold: u64::MAX,
1529 sqrt_price_limit_x64: 0,
1530 is_base_input: input_mode,
1531 input_mint: solana_sdk::pubkey::Pubkey::new_unique(),
1532 tick_arrays: vec![solana_sdk::pubkey::Pubkey::new_unique()],
1533 ..Default::default()
1534 };
1535 let log = RaydiumClmmSwapEvent {
1536 amount_0: 456,
1537 amount_1: 123,
1538 sqrt_price_x64: 789,
1539 zero_for_one: !input_mode,
1540 tick: -7,
1541 liquidity: 1011,
1542 amount: 99,
1543 sqrt_price_limit_x64: 99,
1544 ..Default::default()
1545 };
1546 for log_first in [false, true] {
1547 let mut merged = DexEvent::RaydiumClmmSwap(if log_first {
1548 log.clone()
1549 } else {
1550 instruction.clone()
1551 });
1552 if log_first {
1553 super::merge_grpc_instruction_into_log(
1554 &mut merged,
1555 DexEvent::RaydiumClmmSwap(instruction.clone()),
1556 );
1557 } else {
1558 super::merge_events(&mut merged, DexEvent::RaydiumClmmSwap(log.clone()));
1559 }
1560 let DexEvent::RaydiumClmmSwap(e) = merged else { panic!("swap") };
1561 assert_eq!(
1562 (e.amount_0, e.amount_1, e.sqrt_price_x64, e.tick, e.liquidity),
1563 (456, 123, 789, -7, 1011)
1564 );
1565 assert_eq!(e.zero_for_one, !input_mode);
1566 assert_eq!(
1567 (e.amount, e.other_amount_threshold, e.sqrt_price_limit_x64),
1568 (0, u64::MAX, 0)
1569 );
1570 assert_eq!(e.is_base_input, input_mode);
1571 assert_eq!(e.ix_name, "swap_v2");
1572 assert_eq!(e.input_mint, instruction.input_mint);
1573 assert_eq!(e.tick_arrays, instruction.tick_arrays);
1574 }
1575 }
1576 }
1577
1578 #[test]
1579 fn cpmm_merge_preserves_zero_limits_and_execution_values_in_both_paths() {
1580 use crate::core::events::{DexEvent, RaydiumCpmmSwapEvent};
1581 for exact_input in [false, true] {
1582 let payer = solana_sdk::pubkey::Pubkey::new_unique();
1583 let input_account = solana_sdk::pubkey::Pubkey::new_unique();
1584 let instruction = RaydiumCpmmSwapEvent {
1585 ix_name: if exact_input { "swap_base_input" } else { "swap_base_output" }
1586 .to_string(),
1587 base_input: exact_input,
1588 amount_in: 0,
1589 minimum_amount_out: if exact_input { u64::MAX } else { 0 },
1590 max_amount_in: if exact_input { 0 } else { u64::MAX },
1591 amount_out: 0,
1592 payer,
1593 input_token_account: input_account,
1594 ..Default::default()
1595 };
1596 let log = RaydiumCpmmSwapEvent {
1597 base_input: exact_input,
1598 input_amount: 456,
1599 output_amount: 123,
1600 input_transfer_fee: 7,
1601 amount_in: 99,
1602 amount_out: 99,
1603 ..Default::default()
1604 };
1605 for log_first in [false, true] {
1606 let mut merged = if log_first {
1607 DexEvent::RaydiumCpmmSwap(log.clone())
1608 } else {
1609 DexEvent::RaydiumCpmmSwap(instruction.clone())
1610 };
1611 if log_first {
1612 super::merge_grpc_instruction_into_log(
1613 &mut merged,
1614 DexEvent::RaydiumCpmmSwap(instruction.clone()),
1615 );
1616 } else {
1617 super::merge_events(&mut merged, DexEvent::RaydiumCpmmSwap(log.clone()));
1618 }
1619 let DexEvent::RaydiumCpmmSwap(e) = merged else { panic!("swap") };
1620 assert_eq!((e.input_amount, e.output_amount, e.input_transfer_fee), (456, 123, 7));
1621 assert_eq!(e.ix_name, instruction.ix_name);
1622 assert_eq!(
1623 (e.amount_in, e.minimum_amount_out, e.max_amount_in, e.amount_out),
1624 (
1625 instruction.amount_in,
1626 instruction.minimum_amount_out,
1627 instruction.max_amount_in,
1628 0
1629 )
1630 );
1631 assert_eq!(e.payer, payer);
1632 assert_eq!(e.input_token_account, input_account);
1633 assert_eq!(e.base_input, exact_input);
1634 }
1635 }
1636 }
1637 use super::*;
1638
1639 #[test]
1640 fn incompatible_merge_leaves_both_events_unchanged() {
1641 let mut base = DexEvent::Error("base".to_string());
1642 let inner = DexEvent::Error("inner".to_string());
1643 let mut unmerged = None;
1644
1645 assert!(!try_merge_events(&mut base, inner, &mut unmerged));
1646 assert!(matches!(base, DexEvent::Error(ref message) if message == "base"));
1647 assert!(matches!(unmerged, Some(DexEvent::Error(ref message)) if message == "inner"));
1648 }
1649 use solana_sdk::{pubkey::Pubkey, signature::Signature};
1650
1651 fn dlmm_swap(min_amount_out: u64, amount_out: u64) -> MeteoraDlmmSwapEvent {
1652 MeteoraDlmmSwapEvent {
1653 metadata: EventMetadata::default(),
1654 token_x_mint: Pubkey::default(),
1655 token_y_mint: Pubkey::default(),
1656 user_token_in: Pubkey::new_unique(),
1657 user_token_out: Pubkey::new_unique(),
1658 min_amount_out,
1659 pool: Pubkey::new_unique(),
1660 from: Pubkey::new_unique(),
1661 start_bin_id: 0,
1662 end_bin_id: 0,
1663 amount_in: 200,
1664 amount_out,
1665 swap_for_y: true,
1666 fee: 1,
1667 protocol_fee: 0,
1668 fee_bps: 25,
1669 host_fee: 0,
1670 ..Default::default()
1671 }
1672 }
1673
1674 #[test]
1675 fn dlmm_event_merge_keeps_instruction_threshold_and_executed_output() {
1676 let base_event = dlmm_swap(100, 0);
1677 let expected_user_token_in = base_event.user_token_in;
1678 let expected_user_token_out = base_event.user_token_out;
1679 let mut inner_event = dlmm_swap(0, 125);
1680 inner_event.user_token_in = Pubkey::default();
1681 inner_event.user_token_out = Pubkey::default();
1682 let mut base = DexEvent::MeteoraDlmmSwap(base_event);
1683 let inner = DexEvent::MeteoraDlmmSwap(inner_event);
1684
1685 assert!(try_merge_events(&mut base, inner, &mut None));
1686 let DexEvent::MeteoraDlmmSwap(event) = base else { panic!("swap") };
1687 assert_eq!(event.min_amount_out, 100);
1688 assert_eq!(event.amount_out, 125);
1689 assert_eq!(event.user_token_in, expected_user_token_in);
1690 assert_eq!(event.user_token_out, expected_user_token_out);
1691 }
1692
1693 #[test]
1694 fn grpc_dlmm_merge_keeps_log_output_and_adds_instruction_threshold() {
1695 let mut log_event = dlmm_swap(0, 125);
1696 log_event.user_token_in = Pubkey::default();
1697 log_event.user_token_out = Pubkey::default();
1698 let instruction_event = dlmm_swap(100, 0);
1699 let expected_user_token_in = instruction_event.user_token_in;
1700 let expected_user_token_out = instruction_event.user_token_out;
1701 let mut log = DexEvent::MeteoraDlmmSwap(log_event);
1702 let instruction = DexEvent::MeteoraDlmmSwap(instruction_event);
1703
1704 merge_grpc_instruction_into_log(&mut log, instruction);
1705 let DexEvent::MeteoraDlmmSwap(event) = log else { panic!("swap") };
1706 assert_eq!(event.min_amount_out, 100);
1707 assert_eq!(event.amount_out, 125);
1708 assert_eq!(event.user_token_in, expected_user_token_in);
1709 assert_eq!(event.user_token_out, expected_user_token_out);
1710 }
1711
1712 #[test]
1713 fn test_merge_pumpfun_trade() {
1714 let metadata = EventMetadata {
1715 signature: Signature::default(),
1716 slot: 100,
1717 tx_index: 1,
1718 block_time_us: 1000,
1719 grpc_recv_us: 2000,
1720 recent_blockhash: None,
1721 };
1722
1723 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1725 metadata: metadata.clone(),
1726 bonding_curve: Pubkey::new_unique(),
1727 associated_bonding_curve: Pubkey::new_unique(),
1728 ..Default::default()
1729 });
1730
1731 let inner = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1733 metadata: metadata.clone(),
1734 mint: Pubkey::new_unique(),
1735 sol_amount: 1000,
1736 token_amount: 2000,
1737 is_buy: true,
1738 user: Pubkey::new_unique(),
1739 holder_rewards_bps: 300,
1740 holder_rewards: 400,
1741 ..Default::default()
1742 });
1743
1744 merge_events(&mut base, inner);
1746
1747 if let DexEvent::PumpFunTrade(trade) = base {
1749 assert_eq!(trade.sol_amount, 1000);
1750 assert_eq!(trade.token_amount, 2000);
1751 assert!(trade.is_buy);
1752 assert_eq!(trade.holder_rewards_bps, 300);
1753 assert_eq!(trade.holder_rewards, 400);
1754 assert_ne!(trade.bonding_curve, Pubkey::default());
1756 assert_ne!(trade.associated_bonding_curve, Pubkey::default());
1757 } else {
1758 panic!("Expected PumpFunTrade event");
1759 }
1760 }
1761
1762 #[test]
1763 fn merge_pumpfun_trade_non_leg_propagates_holder_rewards() {
1764 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent::default());
1765 let inner = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1766 holder_rewards_bps: 300,
1767 holder_rewards: 400,
1768 ..Default::default()
1769 });
1770
1771 merge_events(&mut base, inner);
1772
1773 let DexEvent::PumpFunTrade(trade) = base else {
1774 panic!("expected PumpFunTrade event");
1775 };
1776 assert_eq!(trade.holder_rewards_bps, 300);
1777 assert_eq!(trade.holder_rewards, 400);
1778 }
1779
1780 #[test]
1781 fn merge_preserves_instruction_context_when_log_tail_is_absent() {
1782 let metadata = EventMetadata {
1783 signature: Signature::default(),
1784 slot: 100,
1785 tx_index: 1,
1786 block_time_us: 1000,
1787 grpc_recv_us: 2000,
1788 recent_blockhash: None,
1789 };
1790 let quote_mint = Pubkey::new_unique();
1791 let associated_quote_user = Pubkey::new_unique();
1792
1793 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1794 metadata: metadata.clone(),
1795 ix_name: "buy_exact_quote_in".to_string(),
1796 is_buy: true,
1797 quote_mint,
1798 spendable_quote_in: 1_000,
1799 min_tokens_out: 2_000,
1800 associated_quote_user,
1801 ..Default::default()
1802 });
1803
1804 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1805 metadata,
1806 sol_amount: 1_000,
1807 token_amount: 2_000,
1808 is_buy: true,
1809 ..Default::default()
1810 });
1811
1812 merge_events(&mut base, inner);
1813
1814 if let DexEvent::PumpFunTrade(t) = base {
1815 assert_eq!(t.sol_amount, 1_000);
1816 assert_eq!(t.token_amount, 2_000);
1817 assert_eq!(t.ix_name, "buy_exact_quote_in");
1818 assert_eq!(t.quote_mint, quote_mint);
1819 assert_eq!(t.spendable_quote_in, 1_000);
1820 assert_eq!(t.min_tokens_out, 2_000);
1821 assert_eq!(t.associated_quote_user, associated_quote_user);
1822 } else {
1823 panic!("Expected PumpFunTrade event");
1824 }
1825 }
1826
1827 #[test]
1828 fn merge_replaces_sol_quote_sentinel_with_real_quote_mint() {
1829 let quote_mint = Pubkey::new_unique();
1830 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1831 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1832 ..Default::default()
1833 });
1834
1835 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent { quote_mint, ..Default::default() });
1836
1837 merge_events(&mut base, inner);
1838
1839 if let DexEvent::PumpFunTrade(t) = base {
1840 assert_eq!(t.quote_mint, quote_mint);
1841 } else {
1842 panic!("Expected PumpFunTrade event");
1843 }
1844 }
1845
1846 #[test]
1847 fn merge_replaces_sol_quote_sentinel_with_wsol_mint() {
1848 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1849 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1850 ..Default::default()
1851 });
1852
1853 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1854 quote_mint: PUMPFUN_WSOL_QUOTE_MINT,
1855 ..Default::default()
1856 });
1857
1858 merge_events(&mut base, inner);
1859
1860 if let DexEvent::PumpFunTrade(t) = base {
1861 assert_eq!(t.quote_mint, PUMPFUN_WSOL_QUOTE_MINT);
1862 } else {
1863 panic!("Expected PumpFunTrade event");
1864 }
1865 }
1866
1867 #[test]
1868 fn test_can_merge() {
1869 let metadata = EventMetadata {
1870 signature: Signature::default(),
1871 slot: 100,
1872 tx_index: 1,
1873 block_time_us: 1000,
1874 grpc_recv_us: 2000,
1875 recent_blockhash: None,
1876 };
1877
1878 let base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1879 metadata: metadata.clone(),
1880 ..Default::default()
1881 });
1882
1883 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1884 metadata: metadata.clone(),
1885 ..Default::default()
1886 });
1887
1888 assert!(can_merge(&base, &inner));
1890
1891 let different_sig = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1893 metadata: EventMetadata { signature: Signature::new_unique(), ..metadata },
1894 ..Default::default()
1895 });
1896
1897 assert!(!can_merge(&base, &different_sig));
1898 }
1899
1900 #[test]
1901 fn dlmm_position_event_keeps_instruction_only_fields() {
1902 let pool = Pubkey::new_unique();
1903 let position = Pubkey::new_unique();
1904 let owner = Pubkey::new_unique();
1905 let mut base = DexEvent::MeteoraDlmmCreatePosition(MeteoraDlmmCreatePositionEvent {
1906 metadata: EventMetadata::default(),
1907 pool,
1908 position,
1909 owner,
1910 lower_bin_id: -42,
1911 width: 70,
1912 });
1913 let inner = DexEvent::MeteoraDlmmCreatePosition(MeteoraDlmmCreatePositionEvent {
1914 metadata: EventMetadata::default(),
1915 pool,
1916 position,
1917 owner,
1918 lower_bin_id: 0,
1919 width: 0,
1920 });
1921
1922 assert!(try_merge_events(&mut base, inner, &mut None));
1923 let DexEvent::MeteoraDlmmCreatePosition(event) = base else { panic!("position") };
1924 assert_eq!(event.lower_bin_id, -42);
1925 assert_eq!(event.width, 70);
1926 }
1927
1928 #[test]
1929 fn grpc_merge_fills_fee_recipient_from_ix_when_log_default() {
1930 let metadata = EventMetadata {
1931 signature: Signature::default(),
1932 slot: 1,
1933 tx_index: 0,
1934 block_time_us: 0,
1935 grpc_recv_us: 0,
1936 recent_blockhash: None,
1937 };
1938 let fr = Pubkey::new_unique();
1939 let log_t =
1940 PumpFunTradeEvent { metadata: metadata.clone(), sol_amount: 50, ..Default::default() };
1941 let mut ix_t = log_t.clone();
1942 ix_t.fee_recipient = fr;
1943 ix_t.sol_amount = 777;
1944 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1945 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1946 match log_ev {
1947 DexEvent::PumpFunTrade(t) => {
1948 assert_eq!(t.fee_recipient, fr);
1949 assert_eq!(t.sol_amount, 50);
1950 }
1951 _ => panic!("expected trade"),
1952 }
1953 }
1954
1955 #[test]
1956 fn grpc_merge_keeps_log_trade_fields() {
1957 let metadata = EventMetadata {
1958 signature: Signature::default(),
1959 slot: 1,
1960 tx_index: 0,
1961 block_time_us: 0,
1962 grpc_recv_us: 0,
1963 recent_blockhash: None,
1964 };
1965 let log_t = PumpFunTradeEvent {
1966 metadata: metadata.clone(),
1967 mayhem_mode: true,
1968 sol_amount: 100,
1969 ..Default::default()
1970 };
1971 let mut ix_t = log_t.clone();
1972 ix_t.mayhem_mode = false;
1973 ix_t.sol_amount = 999;
1974
1975 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1976 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1977 match log_ev {
1978 DexEvent::PumpFunTrade(t) => {
1979 assert!(t.mayhem_mode);
1980 assert_eq!(t.sol_amount, 100);
1981 }
1982 _ => panic!("variant preserved"),
1983 }
1984 }
1985
1986 #[test]
1987 fn pumpfun_create_merge_propagates_holder_rewards_fields() {
1988 let mut base = DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1989 creator_fee_bps: 125,
1990 ..Default::default()
1991 });
1992 let inner = DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1993 creator_fee_bps: 300,
1994 is_holder_reward: true,
1995 ..Default::default()
1996 });
1997
1998 merge_events(&mut base, inner);
1999
2000 let DexEvent::PumpFunCreate(event) = base else { panic!("create") };
2001 assert_eq!(event.creator_fee_bps, 300);
2002 assert!(event.is_holder_reward);
2003 }
2004
2005 #[test]
2006 fn grpc_create_merge_keeps_log_fee_and_adds_instruction_holder_reward() {
2007 let mut log = DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
2008 creator_fee_bps: 300,
2009 ..Default::default()
2010 });
2011 let instruction = DexEvent::PumpFunCreateV2(PumpFunCreateV2TokenEvent {
2012 creator_fee_bps: 500,
2013 is_holder_reward: true,
2014 ..Default::default()
2015 });
2016
2017 merge_grpc_instruction_into_log(&mut log, instruction);
2018
2019 let DexEvent::PumpFunCreate(event) = log else { panic!("create") };
2020 assert_eq!(event.creator_fee_bps, 300);
2021 assert!(event.is_holder_reward);
2022 }
2023}
2024
2025#[cfg(test)]
2026mod orca_swap_context_tests {
2027 use super::*;
2028
2029 #[test]
2030 fn both_merge_paths_preserve_zero_wire_values_and_authoritative_execution() {
2031 for input_mode in [false, true] {
2032 let ix = OrcaWhirlpoolSwapEvent {
2033 ix_name: "swap_v2".into(),
2034 amount: 0,
2035 other_amount_threshold: u64::MAX,
2036 sqrt_price_limit: 0,
2037 amount_specified_is_input: input_mode,
2038 token_authority: Pubkey::new_unique(),
2039 token_owner_account_a: Pubkey::new_unique(),
2040 token_owner_account_b: Pubkey::new_unique(),
2041 ..Default::default()
2042 };
2043 let log = OrcaWhirlpoolSwapEvent {
2044 amount: 99,
2045 sqrt_price_limit: 99,
2046 input_amount: 123,
2047 output_amount: 456,
2048 pre_sqrt_price: 789,
2049 post_sqrt_price: 987,
2050 input_transfer_fee: 1,
2051 output_transfer_fee: 2,
2052 lp_fee: 3,
2053 protocol_fee: 4,
2054 a_to_b: true,
2055 ..Default::default()
2056 };
2057 for log_first in [false, true] {
2058 let mut result = if log_first {
2059 DexEvent::OrcaWhirlpoolSwap(log.clone())
2060 } else {
2061 DexEvent::OrcaWhirlpoolSwap(ix.clone())
2062 };
2063 if log_first {
2064 merge_grpc_instruction_into_log(
2065 &mut result,
2066 DexEvent::OrcaWhirlpoolSwap(ix.clone()),
2067 );
2068 } else {
2069 merge_events(&mut result, DexEvent::OrcaWhirlpoolSwap(log.clone()));
2070 }
2071 let DexEvent::OrcaWhirlpoolSwap(e) = result else { panic!("swap") };
2072 assert_eq!(
2073 (e.amount, e.other_amount_threshold, e.sqrt_price_limit),
2074 (0, u64::MAX, 0)
2075 );
2076 assert_eq!(e.amount_specified_is_input, input_mode);
2077 assert_eq!(e.ix_name, "swap_v2");
2078 assert_eq!(
2079 (e.input_amount, e.output_amount, e.pre_sqrt_price, e.post_sqrt_price),
2080 (123, 456, 789, 987)
2081 );
2082 assert_eq!(
2083 (e.input_transfer_fee, e.output_transfer_fee, e.lp_fee, e.protocol_fee),
2084 (1, 2, 3, 4)
2085 );
2086 assert!(e.a_to_b);
2087 assert_eq!(
2088 (e.token_authority, e.token_owner_account_a, e.token_owner_account_b),
2089 (ix.token_authority, ix.token_owner_account_a, ix.token_owner_account_b)
2090 );
2091 }
2092 }
2093 }
2094}
2095
2096#[cfg(test)]
2097mod pools_swap_merge_tests {
2098 use super::*;
2099
2100 #[test]
2101 fn both_paths_keep_instruction_zero_values_and_log_execution_values() {
2102 let ix = MeteoraPoolsSwapEvent {
2103 ix_name: "swap".into(),
2104 amount_in: 0,
2105 minimum_out_amount: u64::MAX,
2106 pool: Pubkey::new_unique(),
2107 user_source_token: Pubkey::new_unique(),
2108 user_destination_token: Pubkey::new_unique(),
2109 a_vault: Pubkey::new_unique(),
2110 b_vault: Pubkey::new_unique(),
2111 a_token_vault: Pubkey::new_unique(),
2112 b_token_vault: Pubkey::new_unique(),
2113 a_vault_lp_mint: Pubkey::new_unique(),
2114 b_vault_lp_mint: Pubkey::new_unique(),
2115 a_vault_lp: Pubkey::new_unique(),
2116 b_vault_lp: Pubkey::new_unique(),
2117 protocol_token_fee: Pubkey::new_unique(),
2118 user: Pubkey::new_unique(),
2119 vault_program: Pubkey::new_unique(),
2120 token_program: Pubkey::new_unique(),
2121 ..Default::default()
2122 };
2123 let log = MeteoraPoolsSwapEvent {
2124 amount_in: 99,
2125 minimum_out_amount: 99,
2126 in_amount: 123,
2127 out_amount: 456,
2128 trade_fee: 1,
2129 admin_fee: 2,
2130 host_fee: 3,
2131 token_program: Pubkey::new_unique(),
2132 ..Default::default()
2133 };
2134 for log_first in [false, true] {
2135 let mut result =
2136 DexEvent::MeteoraPoolsSwap(if log_first { log.clone() } else { ix.clone() });
2137 if log_first {
2138 merge_grpc_instruction_into_log(
2139 &mut result,
2140 DexEvent::MeteoraPoolsSwap(ix.clone()),
2141 );
2142 } else {
2143 merge_events(&mut result, DexEvent::MeteoraPoolsSwap(log.clone()));
2144 }
2145 let DexEvent::MeteoraPoolsSwap(e) = result else { panic!("swap") };
2146 assert_eq!(e.ix_name, "swap");
2147 assert_eq!((e.amount_in, e.minimum_out_amount), (0, u64::MAX));
2148 assert_eq!(
2149 (e.in_amount, e.out_amount, e.trade_fee, e.admin_fee, e.host_fee),
2150 (123, 456, 1, 2, 3)
2151 );
2152 assert_eq!(e.pool, ix.pool);
2153 assert_eq!(e.user_source_token, ix.user_source_token);
2154 assert_eq!(e.user_destination_token, ix.user_destination_token);
2155 assert_eq!(e.a_vault, ix.a_vault);
2156 assert_eq!(e.b_vault, ix.b_vault);
2157 assert_eq!(e.a_token_vault, ix.a_token_vault);
2158 assert_eq!(e.b_token_vault, ix.b_token_vault);
2159 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2160 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2161 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2162 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2163 assert_eq!(e.protocol_token_fee, ix.protocol_token_fee);
2164 assert_eq!(e.user, ix.user);
2165 assert_eq!(e.vault_program, ix.vault_program);
2166 assert_eq!(e.token_program, log.token_program);
2167 }
2168 }
2169}
2170
2171#[cfg(test)]
2172mod pools_liquidity_merge_tests {
2173 use super::*;
2174 #[test]
2175 fn add_balance_liquidity_both_merge_paths_preserve_limits_and_execution() {
2176 let ix = MeteoraPoolsAddLiquidityEvent {
2177 ix_name: "add_balance_liquidity".into(),
2178 pool_token_amount: 0,
2179 maximum_token_a_amount: u64::MAX,
2180 maximum_token_b_amount: 0,
2181 pool: Pubkey::new_unique(),
2182 lp_mint: Pubkey::new_unique(),
2183 user_pool_lp: Pubkey::new_unique(),
2184 a_vault_lp: Pubkey::new_unique(),
2185 b_vault_lp: Pubkey::new_unique(),
2186 a_vault: Pubkey::new_unique(),
2187 b_vault: Pubkey::new_unique(),
2188 a_vault_lp_mint: Pubkey::new_unique(),
2189 b_vault_lp_mint: Pubkey::new_unique(),
2190 a_token_vault: Pubkey::new_unique(),
2191 b_token_vault: Pubkey::new_unique(),
2192 user_a_token: Pubkey::new_unique(),
2193 user_b_token: Pubkey::new_unique(),
2194 user: Pubkey::new_unique(),
2195 vault_program: Pubkey::new_unique(),
2196 token_program: Pubkey::new_unique(),
2197 ..Default::default()
2198 };
2199 let log = MeteoraPoolsAddLiquidityEvent {
2200 pool_token_amount: 99,
2201 maximum_token_a_amount: 99,
2202 maximum_token_b_amount: 99,
2203 lp_mint_amount: 123,
2204 token_a_amount: 124,
2205 token_b_amount: 125,
2206 token_program: Pubkey::new_unique(),
2207 ..Default::default()
2208 };
2209 for log_first in [false, true] {
2210 let mut result = DexEvent::MeteoraPoolsAddLiquidity(if log_first {
2211 log.clone()
2212 } else {
2213 ix.clone()
2214 });
2215 if log_first {
2216 merge_grpc_instruction_into_log(
2217 &mut result,
2218 DexEvent::MeteoraPoolsAddLiquidity(ix.clone()),
2219 );
2220 } else {
2221 merge_events(&mut result, DexEvent::MeteoraPoolsAddLiquidity(log.clone()));
2222 }
2223 let DexEvent::MeteoraPoolsAddLiquidity(e) = result else { panic!("liquidity") };
2224 assert_eq!(e.ix_name, "add_balance_liquidity");
2225 assert_eq!(e.pool_token_amount, ix.pool_token_amount);
2226 assert_eq!(e.maximum_token_a_amount, ix.maximum_token_a_amount);
2227 assert_eq!(e.maximum_token_b_amount, ix.maximum_token_b_amount);
2228 assert_eq!(e.pool, ix.pool);
2229 assert_eq!(e.lp_mint, ix.lp_mint);
2230 assert_eq!(e.user_pool_lp, ix.user_pool_lp);
2231 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2232 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2233 assert_eq!(e.a_vault, ix.a_vault);
2234 assert_eq!(e.b_vault, ix.b_vault);
2235 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2236 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2237 assert_eq!(e.a_token_vault, ix.a_token_vault);
2238 assert_eq!(e.b_token_vault, ix.b_token_vault);
2239 assert_eq!(e.user_a_token, ix.user_a_token);
2240 assert_eq!(e.user_b_token, ix.user_b_token);
2241 assert_eq!(e.user, ix.user);
2242 assert_eq!(e.vault_program, ix.vault_program);
2243 assert_eq!(e.lp_mint_amount, log.lp_mint_amount);
2244 assert_eq!(e.token_a_amount, log.token_a_amount);
2245 assert_eq!(e.token_b_amount, log.token_b_amount);
2246 assert_eq!(e.token_program, log.token_program);
2247 }
2248 }
2249 #[test]
2250 fn add_imbalance_liquidity_both_merge_paths_preserve_limits_and_execution() {
2251 let ix = MeteoraPoolsAddLiquidityEvent {
2252 ix_name: "add_imbalance_liquidity".into(),
2253 minimum_pool_token_amount: 0,
2254 token_a_in_amount: u64::MAX,
2255 token_b_in_amount: 0,
2256 pool: Pubkey::new_unique(),
2257 lp_mint: Pubkey::new_unique(),
2258 user_pool_lp: Pubkey::new_unique(),
2259 a_vault_lp: Pubkey::new_unique(),
2260 b_vault_lp: Pubkey::new_unique(),
2261 a_vault: Pubkey::new_unique(),
2262 b_vault: Pubkey::new_unique(),
2263 a_vault_lp_mint: Pubkey::new_unique(),
2264 b_vault_lp_mint: Pubkey::new_unique(),
2265 a_token_vault: Pubkey::new_unique(),
2266 b_token_vault: Pubkey::new_unique(),
2267 user_a_token: Pubkey::new_unique(),
2268 user_b_token: Pubkey::new_unique(),
2269 user: Pubkey::new_unique(),
2270 vault_program: Pubkey::new_unique(),
2271 token_program: Pubkey::new_unique(),
2272 ..Default::default()
2273 };
2274 let log = MeteoraPoolsAddLiquidityEvent {
2275 minimum_pool_token_amount: 99,
2276 token_a_in_amount: 99,
2277 token_b_in_amount: 99,
2278 lp_mint_amount: 123,
2279 token_a_amount: 124,
2280 token_b_amount: 125,
2281 token_program: Pubkey::new_unique(),
2282 ..Default::default()
2283 };
2284 for log_first in [false, true] {
2285 let mut result = DexEvent::MeteoraPoolsAddLiquidity(if log_first {
2286 log.clone()
2287 } else {
2288 ix.clone()
2289 });
2290 if log_first {
2291 merge_grpc_instruction_into_log(
2292 &mut result,
2293 DexEvent::MeteoraPoolsAddLiquidity(ix.clone()),
2294 );
2295 } else {
2296 merge_events(&mut result, DexEvent::MeteoraPoolsAddLiquidity(log.clone()));
2297 }
2298 let DexEvent::MeteoraPoolsAddLiquidity(e) = result else { panic!("liquidity") };
2299 assert_eq!(e.ix_name, "add_imbalance_liquidity");
2300 assert_eq!(e.minimum_pool_token_amount, ix.minimum_pool_token_amount);
2301 assert_eq!(e.token_a_in_amount, ix.token_a_in_amount);
2302 assert_eq!(e.token_b_in_amount, ix.token_b_in_amount);
2303 assert_eq!(e.pool, ix.pool);
2304 assert_eq!(e.lp_mint, ix.lp_mint);
2305 assert_eq!(e.user_pool_lp, ix.user_pool_lp);
2306 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2307 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2308 assert_eq!(e.a_vault, ix.a_vault);
2309 assert_eq!(e.b_vault, ix.b_vault);
2310 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2311 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2312 assert_eq!(e.a_token_vault, ix.a_token_vault);
2313 assert_eq!(e.b_token_vault, ix.b_token_vault);
2314 assert_eq!(e.user_a_token, ix.user_a_token);
2315 assert_eq!(e.user_b_token, ix.user_b_token);
2316 assert_eq!(e.user, ix.user);
2317 assert_eq!(e.vault_program, ix.vault_program);
2318 assert_eq!(e.lp_mint_amount, log.lp_mint_amount);
2319 assert_eq!(e.token_a_amount, log.token_a_amount);
2320 assert_eq!(e.token_b_amount, log.token_b_amount);
2321 assert_eq!(e.token_program, log.token_program);
2322 }
2323 }
2324 #[test]
2325 fn remove_balance_liquidity_both_merge_paths_preserve_limits_and_execution() {
2326 let ix = MeteoraPoolsRemoveLiquidityEvent {
2327 ix_name: "remove_balance_liquidity".into(),
2328 pool_token_amount: 0,
2329 minimum_a_token_out: u64::MAX,
2330 minimum_b_token_out: 0,
2331 pool: Pubkey::new_unique(),
2332 lp_mint: Pubkey::new_unique(),
2333 user_pool_lp: Pubkey::new_unique(),
2334 a_vault_lp: Pubkey::new_unique(),
2335 b_vault_lp: Pubkey::new_unique(),
2336 a_vault: Pubkey::new_unique(),
2337 b_vault: Pubkey::new_unique(),
2338 a_vault_lp_mint: Pubkey::new_unique(),
2339 b_vault_lp_mint: Pubkey::new_unique(),
2340 a_token_vault: Pubkey::new_unique(),
2341 b_token_vault: Pubkey::new_unique(),
2342 user_a_token: Pubkey::new_unique(),
2343 user_b_token: Pubkey::new_unique(),
2344 user: Pubkey::new_unique(),
2345 vault_program: Pubkey::new_unique(),
2346 token_program: Pubkey::new_unique(),
2347 ..Default::default()
2348 };
2349 let log = MeteoraPoolsRemoveLiquidityEvent {
2350 pool_token_amount: 99,
2351 minimum_a_token_out: 99,
2352 minimum_b_token_out: 99,
2353 lp_unmint_amount: 123,
2354 token_a_out_amount: 124,
2355 token_b_out_amount: 125,
2356 token_program: Pubkey::new_unique(),
2357 ..Default::default()
2358 };
2359 for log_first in [false, true] {
2360 let mut result = DexEvent::MeteoraPoolsRemoveLiquidity(if log_first {
2361 log.clone()
2362 } else {
2363 ix.clone()
2364 });
2365 if log_first {
2366 merge_grpc_instruction_into_log(
2367 &mut result,
2368 DexEvent::MeteoraPoolsRemoveLiquidity(ix.clone()),
2369 );
2370 } else {
2371 merge_events(&mut result, DexEvent::MeteoraPoolsRemoveLiquidity(log.clone()));
2372 }
2373 let DexEvent::MeteoraPoolsRemoveLiquidity(e) = result else { panic!("liquidity") };
2374 assert_eq!(e.ix_name, "remove_balance_liquidity");
2375 assert_eq!(e.pool_token_amount, ix.pool_token_amount);
2376 assert_eq!(e.minimum_a_token_out, ix.minimum_a_token_out);
2377 assert_eq!(e.minimum_b_token_out, ix.minimum_b_token_out);
2378 assert_eq!(e.pool, ix.pool);
2379 assert_eq!(e.lp_mint, ix.lp_mint);
2380 assert_eq!(e.user_pool_lp, ix.user_pool_lp);
2381 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2382 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2383 assert_eq!(e.a_vault, ix.a_vault);
2384 assert_eq!(e.b_vault, ix.b_vault);
2385 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2386 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2387 assert_eq!(e.a_token_vault, ix.a_token_vault);
2388 assert_eq!(e.b_token_vault, ix.b_token_vault);
2389 assert_eq!(e.user_a_token, ix.user_a_token);
2390 assert_eq!(e.user_b_token, ix.user_b_token);
2391 assert_eq!(e.user, ix.user);
2392 assert_eq!(e.vault_program, ix.vault_program);
2393 assert_eq!(e.lp_unmint_amount, log.lp_unmint_amount);
2394 assert_eq!(e.token_a_out_amount, log.token_a_out_amount);
2395 assert_eq!(e.token_b_out_amount, log.token_b_out_amount);
2396 assert_eq!(e.token_program, log.token_program);
2397 }
2398 }
2399}
2400
2401#[cfg(test)]
2402mod remaining_liquidity_merge_tests {
2403 use super::*;
2404 #[test]
2405 fn remove_liquidity_single_side_both_mergers_keep_zero_inputs_and_log_execution() {
2406 let ix = MeteoraPoolsRemoveLiquidityEvent {
2407 ix_name: "remove_liquidity_single_side".into(),
2408 pool_token_amount: 0,
2409 minimum_out_amount: u64::MAX,
2410 pool: Pubkey::new_unique(),
2411 lp_mint: Pubkey::new_unique(),
2412 user_pool_lp: Pubkey::new_unique(),
2413 a_vault_lp: Pubkey::new_unique(),
2414 b_vault_lp: Pubkey::new_unique(),
2415 a_vault: Pubkey::new_unique(),
2416 b_vault: Pubkey::new_unique(),
2417 a_vault_lp_mint: Pubkey::new_unique(),
2418 b_vault_lp_mint: Pubkey::new_unique(),
2419 a_token_vault: Pubkey::new_unique(),
2420 b_token_vault: Pubkey::new_unique(),
2421 user_destination_token: Pubkey::new_unique(),
2422 user: Pubkey::new_unique(),
2423 vault_program: Pubkey::new_unique(),
2424 token_program: Pubkey::new_unique(),
2425 ..Default::default()
2426 };
2427 let log = MeteoraPoolsRemoveLiquidityEvent {
2428 pool_token_amount: 99,
2429 minimum_out_amount: 99,
2430 lp_unmint_amount: 123,
2431 token_a_out_amount: 124,
2432 token_b_out_amount: 125,
2433 token_program: Pubkey::new_unique(),
2434 ..Default::default()
2435 };
2436 for log_first in [false, true] {
2437 let mut result = DexEvent::MeteoraPoolsRemoveLiquidity(if log_first {
2438 log.clone()
2439 } else {
2440 ix.clone()
2441 });
2442 if log_first {
2443 merge_grpc_instruction_into_log(
2444 &mut result,
2445 DexEvent::MeteoraPoolsRemoveLiquidity(ix.clone()),
2446 );
2447 } else {
2448 merge_events(&mut result, DexEvent::MeteoraPoolsRemoveLiquidity(log.clone()));
2449 }
2450 let DexEvent::MeteoraPoolsRemoveLiquidity(e) = result else { panic!("liquidity") };
2451 assert_eq!(e.ix_name, "remove_liquidity_single_side");
2452 assert_eq!(e.pool_token_amount, ix.pool_token_amount);
2453 assert_eq!(e.minimum_out_amount, ix.minimum_out_amount);
2454 assert_eq!(e.pool, ix.pool);
2455 assert_eq!(e.lp_mint, ix.lp_mint);
2456 assert_eq!(e.user_pool_lp, ix.user_pool_lp);
2457 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2458 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2459 assert_eq!(e.a_vault, ix.a_vault);
2460 assert_eq!(e.b_vault, ix.b_vault);
2461 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2462 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2463 assert_eq!(e.a_token_vault, ix.a_token_vault);
2464 assert_eq!(e.b_token_vault, ix.b_token_vault);
2465 assert_eq!(e.user_destination_token, ix.user_destination_token);
2466 assert_eq!(e.user, ix.user);
2467 assert_eq!(e.vault_program, ix.vault_program);
2468 assert_eq!(e.lp_unmint_amount, log.lp_unmint_amount);
2469 assert_eq!(e.token_a_out_amount, log.token_a_out_amount);
2470 assert_eq!(e.token_b_out_amount, log.token_b_out_amount);
2471 assert_eq!(e.token_program, log.token_program);
2472 }
2473 }
2474 #[test]
2475 fn bootstrap_liquidity_both_mergers_keep_zero_inputs_and_log_execution() {
2476 let ix = MeteoraPoolsBootstrapLiquidityEvent {
2477 ix_name: "bootstrap_liquidity".into(),
2478 token_a_in_amount: 0,
2479 token_b_in_amount: u64::MAX,
2480 pool: Pubkey::new_unique(),
2481 lp_mint: Pubkey::new_unique(),
2482 user_pool_lp: Pubkey::new_unique(),
2483 a_vault_lp: Pubkey::new_unique(),
2484 b_vault_lp: Pubkey::new_unique(),
2485 a_vault: Pubkey::new_unique(),
2486 b_vault: Pubkey::new_unique(),
2487 a_vault_lp_mint: Pubkey::new_unique(),
2488 b_vault_lp_mint: Pubkey::new_unique(),
2489 a_token_vault: Pubkey::new_unique(),
2490 b_token_vault: Pubkey::new_unique(),
2491 user_a_token: Pubkey::new_unique(),
2492 user_b_token: Pubkey::new_unique(),
2493 user: Pubkey::new_unique(),
2494 vault_program: Pubkey::new_unique(),
2495 token_program: Pubkey::new_unique(),
2496 ..Default::default()
2497 };
2498 let log = MeteoraPoolsBootstrapLiquidityEvent {
2499 token_a_in_amount: 99,
2500 token_b_in_amount: 99,
2501 lp_mint_amount: 123,
2502 token_a_amount: 124,
2503 token_b_amount: 125,
2504 token_program: Pubkey::new_unique(),
2505 ..Default::default()
2506 };
2507 for log_first in [false, true] {
2508 let mut result = DexEvent::MeteoraPoolsBootstrapLiquidity(if log_first {
2509 log.clone()
2510 } else {
2511 ix.clone()
2512 });
2513 if log_first {
2514 merge_grpc_instruction_into_log(
2515 &mut result,
2516 DexEvent::MeteoraPoolsBootstrapLiquidity(ix.clone()),
2517 );
2518 } else {
2519 merge_events(&mut result, DexEvent::MeteoraPoolsBootstrapLiquidity(log.clone()));
2520 }
2521 let DexEvent::MeteoraPoolsBootstrapLiquidity(e) = result else { panic!("liquidity") };
2522 assert_eq!(e.ix_name, "bootstrap_liquidity");
2523 assert_eq!(e.token_a_in_amount, ix.token_a_in_amount);
2524 assert_eq!(e.token_b_in_amount, ix.token_b_in_amount);
2525 assert_eq!(e.pool, ix.pool);
2526 assert_eq!(e.lp_mint, ix.lp_mint);
2527 assert_eq!(e.user_pool_lp, ix.user_pool_lp);
2528 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2529 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2530 assert_eq!(e.a_vault, ix.a_vault);
2531 assert_eq!(e.b_vault, ix.b_vault);
2532 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2533 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2534 assert_eq!(e.a_token_vault, ix.a_token_vault);
2535 assert_eq!(e.b_token_vault, ix.b_token_vault);
2536 assert_eq!(e.user_a_token, ix.user_a_token);
2537 assert_eq!(e.user_b_token, ix.user_b_token);
2538 assert_eq!(e.user, ix.user);
2539 assert_eq!(e.vault_program, ix.vault_program);
2540 assert_eq!(e.lp_mint_amount, log.lp_mint_amount);
2541 assert_eq!(e.token_a_amount, log.token_a_amount);
2542 assert_eq!(e.token_b_amount, log.token_b_amount);
2543 assert_eq!(e.token_program, log.token_program);
2544 }
2545 }
2546}
2547
2548#[cfg(test)]
2549mod pools_management_merge_tests {
2550 use super::*;
2551 #[test]
2552 fn creation_both_paths_keep_zero_inputs_optional_activation_and_accounts() {
2553 for activation_point in [None, Some(0), Some(u64::MAX)] {
2554 let ix = MeteoraPoolsPoolCreatedEvent {
2555 ix_name: "initialize_permissionless_constant_product_pool_with_config2".into(),
2556 token_a_in_amount: 0,
2557 token_b_in_amount: u64::MAX,
2558 activation_point,
2559 pool: Pubkey::new_unique(),
2560 config: Pubkey::new_unique(),
2561 lp_mint: Pubkey::new_unique(),
2562 token_a_mint: Pubkey::new_unique(),
2563 token_b_mint: Pubkey::new_unique(),
2564 a_vault: Pubkey::new_unique(),
2565 b_vault: Pubkey::new_unique(),
2566 a_token_vault: Pubkey::new_unique(),
2567 b_token_vault: Pubkey::new_unique(),
2568 a_vault_lp_mint: Pubkey::new_unique(),
2569 b_vault_lp_mint: Pubkey::new_unique(),
2570 a_vault_lp: Pubkey::new_unique(),
2571 b_vault_lp: Pubkey::new_unique(),
2572 payer_token_a: Pubkey::new_unique(),
2573 payer_token_b: Pubkey::new_unique(),
2574 payer_pool_lp: Pubkey::new_unique(),
2575 protocol_token_a_fee: Pubkey::new_unique(),
2576 protocol_token_b_fee: Pubkey::new_unique(),
2577 payer: Pubkey::new_unique(),
2578 rent: Pubkey::new_unique(),
2579 mint_metadata: Pubkey::new_unique(),
2580 metadata_program: Pubkey::new_unique(),
2581 vault_program: Pubkey::new_unique(),
2582 token_program: Pubkey::new_unique(),
2583 associated_token_program: Pubkey::new_unique(),
2584 system_program: Pubkey::new_unique(),
2585 ..Default::default()
2586 };
2587 let log = MeteoraPoolsPoolCreatedEvent {
2588 token_a_in_amount: 99,
2589 activation_point: Some(99),
2590 pool_type: 1,
2591 token_program: Pubkey::new_unique(),
2592 ..Default::default()
2593 };
2594 for log_first in [false, true] {
2595 let mut result = DexEvent::MeteoraPoolsPoolCreated(if log_first {
2596 log.clone()
2597 } else {
2598 ix.clone()
2599 });
2600 if log_first {
2601 merge_grpc_instruction_into_log(
2602 &mut result,
2603 DexEvent::MeteoraPoolsPoolCreated(ix.clone()),
2604 );
2605 } else {
2606 merge_events(&mut result, DexEvent::MeteoraPoolsPoolCreated(log.clone()));
2607 }
2608 let DexEvent::MeteoraPoolsPoolCreated(e) = result else { panic!("create") };
2609 assert_eq!(
2610 (e.token_a_in_amount, e.token_b_in_amount, e.activation_point),
2611 (0, u64::MAX, activation_point)
2612 );
2613 assert_eq!(e.pool_type, 1);
2614 assert_eq!(e.pool, ix.pool);
2615 assert_eq!(e.config, ix.config);
2616 assert_eq!(e.lp_mint, ix.lp_mint);
2617 assert_eq!(e.token_a_mint, ix.token_a_mint);
2618 assert_eq!(e.token_b_mint, ix.token_b_mint);
2619 assert_eq!(e.a_vault, ix.a_vault);
2620 assert_eq!(e.b_vault, ix.b_vault);
2621 assert_eq!(e.a_token_vault, ix.a_token_vault);
2622 assert_eq!(e.b_token_vault, ix.b_token_vault);
2623 assert_eq!(e.a_vault_lp_mint, ix.a_vault_lp_mint);
2624 assert_eq!(e.b_vault_lp_mint, ix.b_vault_lp_mint);
2625 assert_eq!(e.a_vault_lp, ix.a_vault_lp);
2626 assert_eq!(e.b_vault_lp, ix.b_vault_lp);
2627 assert_eq!(e.payer_token_a, ix.payer_token_a);
2628 assert_eq!(e.payer_token_b, ix.payer_token_b);
2629 assert_eq!(e.payer_pool_lp, ix.payer_pool_lp);
2630 assert_eq!(e.protocol_token_a_fee, ix.protocol_token_a_fee);
2631 assert_eq!(e.protocol_token_b_fee, ix.protocol_token_b_fee);
2632 assert_eq!(e.payer, ix.payer);
2633 assert_eq!(e.rent, ix.rent);
2634 assert_eq!(e.mint_metadata, ix.mint_metadata);
2635 assert_eq!(e.metadata_program, ix.metadata_program);
2636 assert_eq!(e.vault_program, ix.vault_program);
2637 assert_eq!(e.associated_token_program, ix.associated_token_program);
2638 assert_eq!(e.system_program, ix.system_program);
2639 assert_eq!(e.token_program, log.token_program);
2640 }
2641 }
2642 }
2643 #[test]
2644 fn fees_both_paths_keep_partner_parameter_and_log_fee_values() {
2645 let ix = MeteoraPoolsSetPoolFeesEvent {
2646 ix_name: "set_pool_fees".into(),
2647 new_partner_fee_numerator: 0,
2648 fee_operator: Pubkey::new_unique(),
2649 pool: Pubkey::new_unique(),
2650 ..Default::default()
2651 };
2652 let log = MeteoraPoolsSetPoolFeesEvent {
2653 new_partner_fee_numerator: 99,
2654 trade_fee_numerator: 1,
2655 trade_fee_denominator: 2,
2656 protocol_trade_fee_numerator: 3,
2657 protocol_trade_fee_denominator: 4,
2658 owner_trade_fee_numerator: 3,
2659 owner_trade_fee_denominator: 4,
2660 ..Default::default()
2661 };
2662 for log_first in [false, true] {
2663 let mut result =
2664 DexEvent::MeteoraPoolsSetPoolFees(if log_first { log.clone() } else { ix.clone() });
2665 if log_first {
2666 merge_grpc_instruction_into_log(
2667 &mut result,
2668 DexEvent::MeteoraPoolsSetPoolFees(ix.clone()),
2669 );
2670 } else {
2671 merge_events(&mut result, DexEvent::MeteoraPoolsSetPoolFees(log.clone()));
2672 }
2673 let DexEvent::MeteoraPoolsSetPoolFees(e) = result else { panic!("fees") };
2674 assert_eq!(e.new_partner_fee_numerator, 0);
2675 assert_eq!((e.pool, e.fee_operator), (ix.pool, ix.fee_operator));
2676 assert_eq!(
2677 (
2678 e.trade_fee_numerator,
2679 e.trade_fee_denominator,
2680 e.protocol_trade_fee_numerator,
2681 e.protocol_trade_fee_denominator
2682 ),
2683 (1, 2, 3, 4)
2684 );
2685 }
2686 }
2687}
2688
2689#[cfg(test)]
2690mod remaining_creation_merge_tests {
2691 use super::*;
2692 #[test]
2693 fn both_paths_preserve_curve_custom_parameters_and_clear_absent_options() {
2694 for populated in [false, true] {
2695 let ix = MeteoraPoolsPoolCreatedEvent {
2696 ix_name: "initialize_customizable_permissionless_constant_product_pool".into(),
2697 stable_curve: populated
2698 .then(|| MeteoraPoolsStableCurveParams { amp: u64::MAX, ..Default::default() }),
2699 trade_fee_bps: populated.then_some(0),
2700 customizable_params: populated.then(|| MeteoraPoolsCustomizableParams {
2701 trade_fee_numerator: u32::MAX,
2702 padding: vec![255; 90],
2703 ..Default::default()
2704 }),
2705 admin: Pubkey::new_unique(),
2706 admin_token_a: Pubkey::new_unique(),
2707 admin_token_b: Pubkey::new_unique(),
2708 admin_pool_lp: Pubkey::new_unique(),
2709 fee_owner: Pubkey::new_unique(),
2710 ..Default::default()
2711 };
2712 let log = MeteoraPoolsPoolCreatedEvent {
2713 pool_type: 1,
2714 stable_curve: Some(MeteoraPoolsStableCurveParams::default()),
2715 trade_fee_bps: Some(99),
2716 customizable_params: Some(MeteoraPoolsCustomizableParams::default()),
2717 ..Default::default()
2718 };
2719 for log_first in [false, true] {
2720 let mut merged = DexEvent::MeteoraPoolsPoolCreated(if log_first {
2721 log.clone()
2722 } else {
2723 ix.clone()
2724 });
2725 if log_first {
2726 merge_grpc_instruction_into_log(
2727 &mut merged,
2728 DexEvent::MeteoraPoolsPoolCreated(ix.clone()),
2729 );
2730 } else {
2731 merge_events(&mut merged, DexEvent::MeteoraPoolsPoolCreated(log.clone()));
2732 }
2733 let DexEvent::MeteoraPoolsPoolCreated(e) = merged else { panic!("create") };
2734 assert_eq!(e.stable_curve, ix.stable_curve);
2735 assert_eq!(e.trade_fee_bps, ix.trade_fee_bps);
2736 assert_eq!(e.customizable_params, ix.customizable_params);
2737 assert_eq!(
2738 (e.admin, e.admin_token_a, e.admin_token_b, e.admin_pool_lp, e.fee_owner),
2739 (ix.admin, ix.admin_token_a, ix.admin_token_b, ix.admin_pool_lp, ix.fee_owner)
2740 );
2741 assert_eq!(e.pool_type, 1);
2742 }
2743 }
2744 }
2745}
2746
2747#[cfg(test)]
2748mod amm_swap_parameter_merge_tests {
2749 use super::*;
2750 #[test]
2751 fn both_paths_preserve_zero_parameters_execution_and_account_context() {
2752 for name in ["swap_base_in", "swap_base_out", "swap_base_in_v2", "swap_base_out_v2"] {
2753 let input = name.starts_with("swap_base_in");
2754 let ix = RaydiumAmmV4SwapEvent {
2755 ix_name: name.into(),
2756 instruction_amount_in: 0,
2757 instruction_amount_out: 0,
2758 minimum_amount_out: if input { u64::MAX } else { 0 },
2759 max_amount_in: if input { 0 } else { u64::MAX },
2760 user_source_owner: Pubkey::new_unique(),
2761 user_source_token_account: Pubkey::new_unique(),
2762 user_destination_token_account: Pubkey::new_unique(),
2763 amm_target_orders: Some(Pubkey::new_unique()),
2764 ..Default::default()
2765 };
2766 let log = RaydiumAmmV4SwapEvent {
2767 amount_in: 123,
2768 amount_out: 456,
2769 instruction_amount_in: 99,
2770 instruction_amount_out: 99,
2771 minimum_amount_out: 99,
2772 max_amount_in: 99,
2773 token_program: Pubkey::new_unique(),
2774 ..Default::default()
2775 };
2776 for log_first in [false, true] {
2777 let mut merged =
2778 DexEvent::RaydiumAmmV4Swap(if log_first { log.clone() } else { ix.clone() });
2779 if log_first {
2780 merge_grpc_instruction_into_log(
2781 &mut merged,
2782 DexEvent::RaydiumAmmV4Swap(ix.clone()),
2783 );
2784 } else {
2785 merge_events(&mut merged, DexEvent::RaydiumAmmV4Swap(log.clone()));
2786 }
2787 let DexEvent::RaydiumAmmV4Swap(e) = merged else { panic!("swap") };
2788 assert_eq!(e.ix_name, name);
2789 assert_eq!((e.amount_in, e.amount_out), (123, 456));
2790 assert_eq!((e.instruction_amount_in, e.instruction_amount_out), (0, 0));
2791 assert_eq!(
2792 (e.minimum_amount_out, e.max_amount_in),
2793 (ix.minimum_amount_out, ix.max_amount_in)
2794 );
2795 assert_eq!(
2796 (
2797 e.user_source_owner,
2798 e.user_source_token_account,
2799 e.user_destination_token_account
2800 ),
2801 (
2802 ix.user_source_owner,
2803 ix.user_source_token_account,
2804 ix.user_destination_token_account
2805 )
2806 );
2807 assert_eq!(e.amm_target_orders, ix.amm_target_orders);
2808 assert_eq!(e.token_program, log.token_program);
2809 }
2810 }
2811 }
2812}
2813
2814fn pump_trade_identity(event: &DexEvent) -> Option<(u8, Pubkey, Pubkey, Option<bool>)> {
2816 use DexEvent::*;
2817 match event {
2818 PumpFunTrade(e) | PumpFunBuy(e) | PumpFunSell(e) | PumpFunBuyExactSolIn(e) => Some((
2819 0,
2820 e.mint,
2821 e.user,
2822 (e.mint != Pubkey::default()
2823 || !e.ix_name.is_empty()
2824 || e.sol_amount != 0
2825 || e.token_amount != 0)
2826 .then_some(e.is_buy),
2827 )),
2828 PumpSwapBuy(e) => Some((1, e.pool, e.user, Some(true))),
2829 PumpSwapSell(e) => Some((1, e.pool, e.user, Some(false))),
2830 _ => None,
2831 }
2832}
2833
2834fn pump_trade_identity_conflicts(base: &DexEvent, inner: &DexEvent) -> bool {
2835 let (Some((kind, venue, user, buy)), Some((other, other_venue, other_user, other_buy))) =
2836 (pump_trade_identity(base), pump_trade_identity(inner))
2837 else {
2838 return false;
2839 };
2840 kind == other
2841 && ((buy.is_some() && other_buy.is_some() && buy != other_buy)
2842 || (venue != Pubkey::default()
2843 && other_venue != Pubkey::default()
2844 && venue != other_venue)
2845 || (user != Pubkey::default() && other_user != Pubkey::default() && user != other_user))
2846}