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