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 use DexEvent::*;
43
44 match (base, inner) {
45 (PumpFunTrade(b), PumpFunTrade(i))
47 | (PumpFunTrade(b), PumpFunBuy(i))
48 | (PumpFunTrade(b), PumpFunSell(i))
49 | (PumpFunTrade(b), PumpFunBuyExactSolIn(i))
50 | (PumpFunBuy(b), PumpFunTrade(i))
51 | (PumpFunBuy(b), PumpFunBuy(i))
52 | (PumpFunSell(b), PumpFunTrade(i))
53 | (PumpFunSell(b), PumpFunSell(i))
54 | (PumpFunBuyExactSolIn(b), PumpFunTrade(i))
55 | (PumpFunBuyExactSolIn(b), PumpFunBuyExactSolIn(i)) => merge_pumpfun_trade(b, i),
56
57 (PumpFunCreate(b), PumpFunCreate(i)) => merge_pumpfun_create(b, i),
58 (PumpFunCreateV2(b), PumpFunCreateV2(i)) => merge_pumpfun_create_v2(b, i),
59 (PumpFunMigrate(b), PumpFunMigrate(i)) => merge_pumpfun_migrate(b, i),
60 (PumpFunMigrateBondingCurveCreator(b), PumpFunMigrateBondingCurveCreator(i)) => {
61 merge_generic(b, i)
62 }
63
64 (PumpFeesCreateFeeSharingConfig(b), PumpFeesCreateFeeSharingConfig(i)) => {
66 merge_generic(b, i)
67 }
68 (PumpFeesInitializeFeeConfig(b), PumpFeesInitializeFeeConfig(i)) => merge_generic(b, i),
69 (PumpFeesResetFeeSharingConfig(b), PumpFeesResetFeeSharingConfig(i)) => merge_generic(b, i),
70 (PumpFeesRevokeFeeSharingAuthority(b), PumpFeesRevokeFeeSharingAuthority(i)) => {
71 merge_generic(b, i)
72 }
73 (PumpFeesTransferFeeSharingAuthority(b), PumpFeesTransferFeeSharingAuthority(i)) => {
74 merge_generic(b, i)
75 }
76 (PumpFeesUpdateAdmin(b), PumpFeesUpdateAdmin(i)) => merge_generic(b, i),
77 (PumpFeesUpdateFeeConfig(b), PumpFeesUpdateFeeConfig(i)) => merge_generic(b, i),
78 (PumpFeesUpdateFeeShares(b), PumpFeesUpdateFeeShares(i)) => merge_generic(b, i),
79 (PumpFeesUpsertFeeTiers(b), PumpFeesUpsertFeeTiers(i)) => merge_generic(b, i),
80
81 (PumpSwapTrade(b), PumpSwapTrade(i)) => merge_generic(b, i),
83 (PumpSwapBuy(b), PumpSwapBuy(i)) => merge_pumpswap_buy(b, i),
84 (PumpSwapSell(b), PumpSwapSell(i)) => merge_pumpswap_sell(b, i),
85 (PumpSwapCreatePool(b), PumpSwapCreatePool(i)) => merge_generic(b, i),
86 (PumpSwapLiquidityAdded(b), PumpSwapLiquidityAdded(i)) => merge_generic(b, i),
87 (PumpSwapLiquidityRemoved(b), PumpSwapLiquidityRemoved(i)) => merge_generic(b, i),
88
89 (RaydiumClmmSwap(b), RaydiumClmmSwap(i)) => merge_generic(b, i),
91 (RaydiumClmmIncreaseLiquidity(b), RaydiumClmmIncreaseLiquidity(i)) => merge_generic(b, i),
92 (RaydiumClmmDecreaseLiquidity(b), RaydiumClmmDecreaseLiquidity(i)) => merge_generic(b, i),
93 (RaydiumClmmLiquidityChange(b), RaydiumClmmLiquidityChange(i)) => merge_generic(b, i),
94 (RaydiumClmmConfigChange(b), RaydiumClmmConfigChange(i)) => merge_generic(b, i),
95 (RaydiumClmmCreatePersonalPosition(b), RaydiumClmmCreatePersonalPosition(i)) => {
96 merge_generic(b, i)
97 }
98 (RaydiumClmmLiquidityCalculate(b), RaydiumClmmLiquidityCalculate(i)) => merge_generic(b, i),
99 (RaydiumClmmOpenLimitOrder(b), RaydiumClmmOpenLimitOrder(i)) => merge_generic(b, i),
100 (RaydiumClmmIncreaseLimitOrder(b), RaydiumClmmIncreaseLimitOrder(i)) => merge_generic(b, i),
101 (RaydiumClmmDecreaseLimitOrder(b), RaydiumClmmDecreaseLimitOrder(i)) => merge_generic(b, i),
102 (RaydiumClmmSettleLimitOrder(b), RaydiumClmmSettleLimitOrder(i)) => merge_generic(b, i),
103 (RaydiumClmmUpdateRewardInfos(b), RaydiumClmmUpdateRewardInfos(i)) => merge_generic(b, i),
104 (RaydiumClmmCreatePool(b), RaydiumClmmCreatePool(i)) => merge_generic(b, i),
105 (RaydiumClmmOpenPosition(b), RaydiumClmmOpenPosition(i)) => merge_generic(b, i),
106 (RaydiumClmmClosePosition(b), RaydiumClmmClosePosition(i)) => merge_generic(b, i),
107 (RaydiumClmmOpenPositionWithTokenExtNft(b), RaydiumClmmOpenPositionWithTokenExtNft(i)) => {
108 merge_generic(b, i)
109 }
110 (RaydiumClmmCollectFee(b), RaydiumClmmCollectFee(i)) => merge_generic(b, i),
111
112 (RaydiumCpmmSwap(b), RaydiumCpmmSwap(i)) => merge_generic(b, i),
114 (RaydiumCpmmDeposit(b), RaydiumCpmmDeposit(i)) => merge_generic(b, i),
115 (RaydiumCpmmWithdraw(b), RaydiumCpmmWithdraw(i)) => merge_generic(b, i),
116 (RaydiumCpmmInitialize(b), RaydiumCpmmInitialize(i)) => merge_generic(b, i),
117
118 (RaydiumAmmV4Swap(b), RaydiumAmmV4Swap(i)) => merge_generic(b, i),
120 (RaydiumAmmV4Deposit(b), RaydiumAmmV4Deposit(i)) => merge_generic(b, i),
121 (RaydiumAmmV4Withdraw(b), RaydiumAmmV4Withdraw(i)) => merge_generic(b, i),
122 (RaydiumAmmV4Initialize2(b), RaydiumAmmV4Initialize2(i)) => merge_generic(b, i),
123 (RaydiumAmmV4WithdrawPnl(b), RaydiumAmmV4WithdrawPnl(i)) => merge_generic(b, i),
124
125 (OrcaWhirlpoolSwap(b), OrcaWhirlpoolSwap(i)) => merge_generic(b, i),
127 (OrcaWhirlpoolLiquidityIncreased(b), OrcaWhirlpoolLiquidityIncreased(i)) => {
128 merge_generic(b, i)
129 }
130 (OrcaWhirlpoolLiquidityDecreased(b), OrcaWhirlpoolLiquidityDecreased(i)) => {
131 merge_generic(b, i)
132 }
133 (OrcaWhirlpoolPoolInitialized(b), OrcaWhirlpoolPoolInitialized(i)) => merge_generic(b, i),
134
135 (MeteoraPoolsSwap(b), MeteoraPoolsSwap(i)) => merge_generic(b, i),
137 (MeteoraPoolsAddLiquidity(b), MeteoraPoolsAddLiquidity(i)) => merge_generic(b, i),
138 (MeteoraPoolsRemoveLiquidity(b), MeteoraPoolsRemoveLiquidity(i)) => merge_generic(b, i),
139 (MeteoraPoolsBootstrapLiquidity(b), MeteoraPoolsBootstrapLiquidity(i)) => {
140 merge_generic(b, i)
141 }
142 (MeteoraPoolsPoolCreated(b), MeteoraPoolsPoolCreated(i)) => merge_generic(b, i),
143 (MeteoraPoolsSetPoolFees(b), MeteoraPoolsSetPoolFees(i)) => merge_generic(b, i),
144
145 (MeteoraDammV2Swap(b), MeteoraDammV2Swap(i)) => merge_generic(b, i),
147 (MeteoraDammV2AddLiquidity(b), MeteoraDammV2AddLiquidity(i)) => merge_generic(b, i),
148 (MeteoraDammV2RemoveLiquidity(b), MeteoraDammV2RemoveLiquidity(i)) => merge_generic(b, i),
149 (MeteoraDammV2InitializePool(b), MeteoraDammV2InitializePool(i)) => merge_generic(b, i),
150 (MeteoraDammV2CreatePosition(b), MeteoraDammV2CreatePosition(i)) => merge_generic(b, i),
151 (MeteoraDammV2ClosePosition(b), MeteoraDammV2ClosePosition(i)) => merge_generic(b, i),
152
153 (MeteoraDlmmSwap(b), MeteoraDlmmSwap(i)) => merge_dlmm_swap(b, i),
155 (MeteoraDlmmAddLiquidity(b), MeteoraDlmmAddLiquidity(i)) => merge_generic(b, i),
156 (MeteoraDlmmRemoveLiquidity(b), MeteoraDlmmRemoveLiquidity(i)) => merge_generic(b, i),
157 (MeteoraDlmmInitializePool(b), MeteoraDlmmInitializePool(i)) => {
158 merge_dlmm_initialize_pool(b, i)
159 }
160 (MeteoraDlmmInitializeBinArray(b), MeteoraDlmmInitializeBinArray(i)) => merge_generic(b, i),
161 (MeteoraDlmmCreatePosition(b), MeteoraDlmmCreatePosition(i)) => {
162 merge_dlmm_create_position(b, i)
163 }
164 (MeteoraDlmmClosePosition(b), MeteoraDlmmClosePosition(i)) => {
165 merge_dlmm_close_position(b, i)
166 }
167 (MeteoraDlmmClaimFee(b), MeteoraDlmmClaimFee(i)) => merge_generic(b, i),
168
169 (RaydiumLaunchlabTrade(b), RaydiumLaunchlabTrade(i)) => merge_generic(b, i),
171 (RaydiumLaunchlabPoolCreate(b), RaydiumLaunchlabPoolCreate(i)) => merge_generic(b, i),
172 (RaydiumLaunchlabMigrateAmm(b), RaydiumLaunchlabMigrateAmm(i)) => merge_generic(b, i),
173
174 (_, event) => {
176 *unmerged = Some(event);
177 return false;
178 }
179 }
180
181 true
182}
183
184#[inline(always)]
191fn merge_generic<T>(base: &mut T, inner: T) {
192 *base = inner;
193}
194
195#[inline(always)]
196fn merge_dlmm_swap(base: &mut MeteoraDlmmSwapEvent, inner: MeteoraDlmmSwapEvent) {
197 let min_amount_out =
198 if inner.min_amount_out != 0 { inner.min_amount_out } else { base.min_amount_out };
199 let user_token_in = if inner.user_token_in != Pubkey::default() {
200 inner.user_token_in
201 } else {
202 base.user_token_in
203 };
204 let user_token_out = if inner.user_token_out != Pubkey::default() {
205 inner.user_token_out
206 } else {
207 base.user_token_out
208 };
209 *base = inner;
210 base.min_amount_out = min_amount_out;
211 base.user_token_in = user_token_in;
212 base.user_token_out = user_token_out;
213}
214
215#[inline(always)]
216fn merge_dlmm_initialize_pool(
217 base: &mut MeteoraDlmmInitializePoolEvent,
218 inner: MeteoraDlmmInitializePoolEvent,
219) {
220 let creator = base.creator;
221 let active_bin_id = base.active_bin_id;
222 *base = inner;
223 base.creator = creator;
224 base.active_bin_id = active_bin_id;
225}
226
227#[inline(always)]
228fn merge_dlmm_create_position(
229 base: &mut MeteoraDlmmCreatePositionEvent,
230 inner: MeteoraDlmmCreatePositionEvent,
231) {
232 let lower_bin_id = base.lower_bin_id;
233 let width = base.width;
234 *base = inner;
235 base.lower_bin_id = lower_bin_id;
236 base.width = width;
237}
238
239#[inline(always)]
240fn merge_dlmm_close_position(
241 base: &mut MeteoraDlmmClosePositionEvent,
242 inner: MeteoraDlmmClosePositionEvent,
243) {
244 let pool = base.pool;
245 *base = inner;
246 base.pool = pool;
247}
248
249#[inline(always)]
254fn put_pk_if_set(to: &mut Pubkey, from: Pubkey) {
255 if from != Pubkey::default() {
256 *to = from;
257 }
258}
259
260#[inline(always)]
261fn put_pumpfun_quote_mint_if_set(to: &mut Pubkey, from: Pubkey) {
262 let from = normalize_pumpfun_quote_mint(from);
263 if from != Pubkey::default()
264 && (*to == Pubkey::default()
265 || is_pumpfun_solscan_sol_quote_mint(*to)
266 || !is_pumpfun_solscan_sol_quote_mint(from))
267 {
268 *to = from;
269 }
270}
271
272#[inline(always)]
273fn put_u64_if_nonzero(to: &mut u64, from: u64) {
274 if from != 0 {
275 *to = from;
276 }
277}
278
279#[inline(always)]
280fn put_i64_if_nonzero(to: &mut i64, from: i64) {
281 if from != 0 {
282 *to = from;
283 }
284}
285
286#[inline(always)]
296fn merge_pumpfun_trade(base: &mut PumpFunTradeEvent, inner: PumpFunTradeEvent) {
297 let leg = inner.sol_amount != 0 || inner.token_amount != 0;
298
299 put_pk_if_set(&mut base.mint, inner.mint);
300 put_pk_if_set(&mut base.user, inner.user);
301 put_pk_if_set(&mut base.fee_recipient, inner.fee_recipient);
302 put_pk_if_set(&mut base.creator, inner.creator);
303
304 if leg {
305 base.sol_amount = inner.sol_amount;
306 base.token_amount = inner.token_amount;
307 base.is_buy = inner.is_buy;
308 base.timestamp = inner.timestamp;
309 base.virtual_sol_reserves = inner.virtual_sol_reserves;
310 base.virtual_token_reserves = inner.virtual_token_reserves;
311 base.real_sol_reserves = inner.real_sol_reserves;
312 base.real_token_reserves = inner.real_token_reserves;
313 base.fee_basis_points = inner.fee_basis_points;
314 base.fee = inner.fee;
315 base.creator_fee_basis_points = inner.creator_fee_basis_points;
316 base.creator_fee = inner.creator_fee;
317 base.track_volume |= inner.track_volume;
318 base.total_unclaimed_tokens = inner.total_unclaimed_tokens;
319 base.total_claimed_tokens = inner.total_claimed_tokens;
320 base.current_sol_volume = inner.current_sol_volume;
321 base.last_update_timestamp = inner.last_update_timestamp;
322 if !inner.ix_name.is_empty() {
323 base.ix_name = inner.ix_name;
324 }
325 base.mayhem_mode |= inner.mayhem_mode;
326 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
327 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
328 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
329 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
330 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
331 base.shareholders = inner.shareholders;
332 }
333 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
334 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
335 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
336 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
337 base.is_cashback_coin |= inner.is_cashback_coin;
338 } else {
339 put_u64_if_nonzero(&mut base.fee, inner.fee);
340 put_u64_if_nonzero(&mut base.creator_fee, inner.creator_fee);
341 put_u64_if_nonzero(&mut base.fee_basis_points, inner.fee_basis_points);
342 put_u64_if_nonzero(&mut base.creator_fee_basis_points, inner.creator_fee_basis_points);
343 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
344 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
345 put_u64_if_nonzero(&mut base.real_sol_reserves, inner.real_sol_reserves);
346 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
347 put_u64_if_nonzero(&mut base.total_unclaimed_tokens, inner.total_unclaimed_tokens);
348 put_u64_if_nonzero(&mut base.total_claimed_tokens, inner.total_claimed_tokens);
349 put_u64_if_nonzero(&mut base.current_sol_volume, inner.current_sol_volume);
350 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
351 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
352 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
353 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
354 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
355 base.shareholders = inner.shareholders;
356 }
357 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
358 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
359 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
360 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
361 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
362 put_i64_if_nonzero(&mut base.last_update_timestamp, inner.last_update_timestamp);
363 if !inner.ix_name.is_empty() {
364 base.ix_name = inner.ix_name;
365 }
366 base.track_volume |= inner.track_volume;
367 base.mayhem_mode |= inner.mayhem_mode;
368 base.is_cashback_coin |= inner.is_cashback_coin;
369 }
370 put_u64_if_nonzero(&mut base.amount, inner.amount);
371 put_u64_if_nonzero(&mut base.max_sol_cost, inner.max_sol_cost);
372 put_u64_if_nonzero(&mut base.min_sol_output, inner.min_sol_output);
373 put_u64_if_nonzero(&mut base.spendable_sol_in, inner.spendable_sol_in);
374 put_u64_if_nonzero(&mut base.spendable_quote_in, inner.spendable_quote_in);
375 put_u64_if_nonzero(&mut base.min_tokens_out, inner.min_tokens_out);
376 put_pk_if_set(&mut base.global, inner.global);
377 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
378 put_pk_if_set(&mut base.bonding_curve_v2, inner.bonding_curve_v2);
379 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
380 put_pk_if_set(&mut base.associated_user, inner.associated_user);
381 put_pk_if_set(&mut base.system_program, inner.system_program);
382 put_pk_if_set(&mut base.token_program, inner.token_program);
383 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
384 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
385 put_pk_if_set(&mut base.creator_vault, inner.creator_vault);
386 put_pk_if_set(&mut base.associated_quote_fee_recipient, inner.associated_quote_fee_recipient);
387 put_pk_if_set(&mut base.buyback_fee_recipient, inner.buyback_fee_recipient);
388 put_pk_if_set(
389 &mut base.associated_quote_buyback_fee_recipient,
390 inner.associated_quote_buyback_fee_recipient,
391 );
392 put_pk_if_set(&mut base.associated_quote_bonding_curve, inner.associated_quote_bonding_curve);
393 put_pk_if_set(&mut base.associated_quote_user, inner.associated_quote_user);
394 put_pk_if_set(&mut base.associated_creator_vault, inner.associated_creator_vault);
395 put_pk_if_set(&mut base.sharing_config, inner.sharing_config);
396 put_pk_if_set(&mut base.event_authority, inner.event_authority);
397 put_pk_if_set(&mut base.program, inner.program);
398 put_pk_if_set(&mut base.global_volume_accumulator, inner.global_volume_accumulator);
399 put_pk_if_set(&mut base.user_volume_accumulator, inner.user_volume_accumulator);
400 put_pk_if_set(
401 &mut base.associated_user_volume_accumulator,
402 inner.associated_user_volume_accumulator,
403 );
404 put_pk_if_set(&mut base.fee_config, inner.fee_config);
405 put_pk_if_set(&mut base.fee_program, inner.fee_program);
406 if base.account.is_none() {
407 base.account = inner.account;
408 }
409
410 base.is_created_buy |= inner.is_created_buy;
411 }
413
414#[inline(always)]
416fn merge_pumpfun_create(base: &mut PumpFunCreateTokenEvent, inner: PumpFunCreateTokenEvent) {
417 base.name = inner.name;
419 base.symbol = inner.symbol;
420 base.uri = inner.uri;
421 base.mint = inner.mint;
422 base.bonding_curve = inner.bonding_curve;
423 base.user = inner.user;
424 base.creator = inner.creator;
425 base.timestamp = inner.timestamp;
426 base.virtual_token_reserves = inner.virtual_token_reserves;
427 base.virtual_sol_reserves = inner.virtual_sol_reserves;
428 base.real_token_reserves = inner.real_token_reserves;
429 base.token_total_supply = inner.token_total_supply;
430 base.token_program = inner.token_program;
431 base.is_mayhem_mode = inner.is_mayhem_mode;
432 base.is_cashback_enabled = inner.is_cashback_enabled;
433 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
434 put_pk_if_set(&mut base.quote_vault, inner.quote_vault);
435 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
436 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
437}
438
439#[inline(always)]
441fn merge_pumpfun_create_v2(base: &mut PumpFunCreateV2TokenEvent, inner: PumpFunCreateV2TokenEvent) {
442 fill_str_if_empty(&mut base.name, &inner.name);
443 fill_str_if_empty(&mut base.symbol, &inner.symbol);
444 fill_str_if_empty(&mut base.uri, &inner.uri);
445 put_pk_if_set(&mut base.mint, inner.mint);
446 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
447 put_pk_if_set(&mut base.user, inner.user);
448 put_pk_if_set(&mut base.creator, inner.creator);
449 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
450 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
451 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
452 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
453 put_u64_if_nonzero(&mut base.token_total_supply, inner.token_total_supply);
454 put_pk_if_set(&mut base.token_program, inner.token_program);
455 base.is_mayhem_mode |= inner.is_mayhem_mode;
456 base.is_cashback_enabled |= inner.is_cashback_enabled;
457 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
458 put_pk_if_set(&mut base.quote_vault, inner.quote_vault);
459 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
460 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
461 put_pk_if_set(&mut base.mint_authority, inner.mint_authority);
462 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
463 put_pk_if_set(&mut base.global, inner.global);
464 put_pk_if_set(&mut base.system_program, inner.system_program);
465 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
466 put_pk_if_set(&mut base.mayhem_program_id, inner.mayhem_program_id);
467 put_pk_if_set(&mut base.global_params, inner.global_params);
468 put_pk_if_set(&mut base.sol_vault, inner.sol_vault);
469 put_pk_if_set(&mut base.mayhem_state, inner.mayhem_state);
470 put_pk_if_set(&mut base.mayhem_token_vault, inner.mayhem_token_vault);
471 put_pk_if_set(&mut base.event_authority, inner.event_authority);
472 put_pk_if_set(&mut base.program, inner.program);
473 put_pk_if_set(&mut base.observed_fee_recipient, inner.observed_fee_recipient);
474}
475
476#[inline(always)]
478fn merge_pumpfun_migrate(base: &mut PumpFunMigrateEvent, inner: PumpFunMigrateEvent) {
479 base.user = inner.user;
481 base.mint = inner.mint;
482 base.mint_amount = inner.mint_amount;
483 base.sol_amount = inner.sol_amount;
484 base.pool_migration_fee = inner.pool_migration_fee;
485 base.bonding_curve = inner.bonding_curve;
486 base.timestamp = inner.timestamp;
487 base.pool = inner.pool;
488}
489
490#[inline(always)]
491fn merge_pumpswap_buy(base: &mut PumpSwapBuyEvent, inner: PumpSwapBuyEvent) {
492 let ix = std::mem::take(base);
493 *base = inner;
494 merge_pumpswap_buy_log_preferred(base, ix);
495}
496
497#[inline(always)]
498fn merge_pumpswap_sell(base: &mut PumpSwapSellEvent, inner: PumpSwapSellEvent) {
499 let ix = std::mem::take(base);
500 *base = inner;
501 merge_pumpswap_sell_log_preferred(base, ix);
502}
503
504#[inline(always)]
515pub fn can_merge(base: &DexEvent, inner: &DexEvent) -> bool {
516 if base.metadata().signature != inner.metadata().signature {
518 return false;
519 }
520
521 match (base, inner) {
523 (DexEvent::PumpFunTrade(_), DexEvent::PumpFunTrade(_))
525 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuy(_))
526 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunSell(_))
527 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuyExactSolIn(_))
528 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunTrade(_))
529 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunBuy(_))
530 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunTrade(_))
531 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunSell(_))
532 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunTrade(_))
533 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunBuyExactSolIn(_)) => true,
534
535 (DexEvent::PumpFunCreate(_), DexEvent::PumpFunCreate(_)) => true,
537 (DexEvent::PumpFunCreateV2(_), DexEvent::PumpFunCreateV2(_)) => true,
538
539 (DexEvent::PumpFunMigrate(_), DexEvent::PumpFunMigrate(_)) => true,
541
542 _ => false,
544 }
545}
546
547#[inline(always)]
552fn fill_pk(to: &mut Pubkey, from: Pubkey) {
553 if *to == Pubkey::default() && from != Pubkey::default() {
554 *to = from;
555 }
556}
557
558#[inline(always)]
559fn fill_pumpfun_quote_mint(to: &mut Pubkey, from: Pubkey) {
560 let from = normalize_pumpfun_quote_mint(from);
561 if (*to == Pubkey::default() || is_pumpfun_solscan_sol_quote_mint(*to))
562 && from != Pubkey::default()
563 {
564 *to = from;
565 }
566}
567
568#[inline(always)]
569fn fill_str_if_empty(to: &mut String, from: &str) {
570 if to.is_empty() && !from.is_empty() {
571 to.push_str(from);
572 }
573}
574
575#[inline]
578fn merge_pumpfun_trade_log_preferred(log: &mut PumpFunTradeEvent, ix: PumpFunTradeEvent) {
579 fill_pk(&mut log.global, ix.global);
580 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
581 fill_pk(&mut log.bonding_curve_v2, ix.bonding_curve_v2);
582 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
583 fill_pk(&mut log.associated_user, ix.associated_user);
584 fill_pk(&mut log.system_program, ix.system_program);
585 fill_pk(&mut log.token_program, ix.token_program);
586 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
587 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
588 fill_pk(&mut log.creator_vault, ix.creator_vault);
589 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
590 fill_pk(&mut log.creator, ix.creator);
591 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
592 fill_pk(&mut log.associated_quote_fee_recipient, ix.associated_quote_fee_recipient);
593 fill_pk(&mut log.buyback_fee_recipient, ix.buyback_fee_recipient);
594 fill_pk(
595 &mut log.associated_quote_buyback_fee_recipient,
596 ix.associated_quote_buyback_fee_recipient,
597 );
598 fill_pk(&mut log.associated_quote_bonding_curve, ix.associated_quote_bonding_curve);
599 fill_pk(&mut log.associated_quote_user, ix.associated_quote_user);
600 fill_pk(&mut log.associated_creator_vault, ix.associated_creator_vault);
601 fill_pk(&mut log.sharing_config, ix.sharing_config);
602 fill_pk(&mut log.event_authority, ix.event_authority);
603 fill_pk(&mut log.program, ix.program);
604 fill_pk(&mut log.global_volume_accumulator, ix.global_volume_accumulator);
605 fill_pk(&mut log.user_volume_accumulator, ix.user_volume_accumulator);
606 fill_pk(&mut log.associated_user_volume_accumulator, ix.associated_user_volume_accumulator);
607 fill_pk(&mut log.fee_config, ix.fee_config);
608 fill_pk(&mut log.fee_program, ix.fee_program);
609 if log.account.is_none() {
610 log.account = ix.account;
611 }
612 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
613 log.ix_name = ix.ix_name;
614 }
615 put_u64_if_nonzero(&mut log.amount, ix.amount);
616 put_u64_if_nonzero(&mut log.max_sol_cost, ix.max_sol_cost);
617 put_u64_if_nonzero(&mut log.min_sol_output, ix.min_sol_output);
618 put_u64_if_nonzero(&mut log.spendable_sol_in, ix.spendable_sol_in);
619 put_u64_if_nonzero(&mut log.spendable_quote_in, ix.spendable_quote_in);
620 put_u64_if_nonzero(&mut log.min_tokens_out, ix.min_tokens_out);
621 put_u64_if_nonzero(&mut log.quote_amount, ix.quote_amount);
622 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
623 put_u64_if_nonzero(&mut log.real_quote_reserves, ix.real_quote_reserves);
624 if !log.is_created_buy && ix.is_created_buy {
625 log.is_created_buy = true;
626 }
627}
628
629#[inline]
630fn merge_pumpfun_create_log_preferred(
631 log: &mut PumpFunCreateTokenEvent,
632 ix: PumpFunCreateTokenEvent,
633) {
634 fill_str_if_empty(&mut log.name, &ix.name);
635 fill_str_if_empty(&mut log.symbol, &ix.symbol);
636 fill_str_if_empty(&mut log.uri, &ix.uri);
637 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
638 fill_pk(&mut log.user, ix.user);
639 fill_pk(&mut log.creator, ix.creator);
640 fill_pk(&mut log.token_program, ix.token_program);
641 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
642 fill_pk(&mut log.quote_vault, ix.quote_vault);
643 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
644 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
645 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
646 log.ix_name = ix.ix_name;
647 }
648 log.is_mayhem_mode |= ix.is_mayhem_mode;
649 log.is_cashback_enabled |= ix.is_cashback_enabled;
650}
651
652#[inline]
653fn merge_pumpfun_create_v2_into_create_log_preferred(
654 log: &mut PumpFunCreateTokenEvent,
655 ix: PumpFunCreateV2TokenEvent,
656) {
657 fill_str_if_empty(&mut log.name, &ix.name);
658 fill_str_if_empty(&mut log.symbol, &ix.symbol);
659 fill_str_if_empty(&mut log.uri, &ix.uri);
660 fill_pk(&mut log.mint, ix.mint);
661 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
662 fill_pk(&mut log.user, ix.user);
663 fill_pk(&mut log.creator, ix.creator);
664 if log.timestamp == 0 && ix.timestamp != 0 {
665 log.timestamp = ix.timestamp;
666 }
667 put_u64_if_nonzero(&mut log.virtual_token_reserves, ix.virtual_token_reserves);
668 put_u64_if_nonzero(&mut log.virtual_sol_reserves, ix.virtual_sol_reserves);
669 put_u64_if_nonzero(&mut log.real_token_reserves, ix.real_token_reserves);
670 put_u64_if_nonzero(&mut log.token_total_supply, ix.token_total_supply);
671 fill_pk(&mut log.token_program, ix.token_program);
672 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
673 fill_pk(&mut log.quote_vault, ix.quote_vault);
674 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
675 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
676 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
677 log.ix_name = ix.ix_name;
678 }
679 log.is_mayhem_mode |= ix.is_mayhem_mode;
680 log.is_cashback_enabled |= ix.is_cashback_enabled;
681}
682
683#[inline]
684fn merge_pumpfun_create_into_create_v2_log_preferred(
685 log: &mut PumpFunCreateV2TokenEvent,
686 ix: PumpFunCreateTokenEvent,
687) {
688 fill_str_if_empty(&mut log.name, &ix.name);
689 fill_str_if_empty(&mut log.symbol, &ix.symbol);
690 fill_str_if_empty(&mut log.uri, &ix.uri);
691 fill_pk(&mut log.mint, ix.mint);
692 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
693 fill_pk(&mut log.user, ix.user);
694 fill_pk(&mut log.creator, ix.creator);
695 if log.timestamp == 0 && ix.timestamp != 0 {
696 log.timestamp = ix.timestamp;
697 }
698 put_u64_if_nonzero(&mut log.virtual_token_reserves, ix.virtual_token_reserves);
699 put_u64_if_nonzero(&mut log.virtual_sol_reserves, ix.virtual_sol_reserves);
700 put_u64_if_nonzero(&mut log.real_token_reserves, ix.real_token_reserves);
701 put_u64_if_nonzero(&mut log.token_total_supply, ix.token_total_supply);
702 fill_pk(&mut log.token_program, ix.token_program);
703 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
704 fill_pk(&mut log.quote_vault, ix.quote_vault);
705 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
706 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
707 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
708 log.ix_name = ix.ix_name;
709 }
710 log.is_mayhem_mode |= ix.is_mayhem_mode;
711 log.is_cashback_enabled |= ix.is_cashback_enabled;
712}
713
714#[inline]
715fn merge_pumpfun_create_v2_log_preferred(
716 log: &mut PumpFunCreateV2TokenEvent,
717 ix: PumpFunCreateV2TokenEvent,
718) {
719 fill_str_if_empty(&mut log.name, &ix.name);
720 fill_str_if_empty(&mut log.symbol, &ix.symbol);
721 fill_str_if_empty(&mut log.uri, &ix.uri);
722 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
723 fill_pk(&mut log.user, ix.user);
724 fill_pk(&mut log.creator, ix.creator);
725 fill_pk(&mut log.token_program, ix.token_program);
726 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
727 fill_pk(&mut log.quote_vault, ix.quote_vault);
728 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
729 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
730 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
731 log.ix_name = ix.ix_name;
732 }
733 fill_pk(&mut log.mint_authority, ix.mint_authority);
734 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
735 fill_pk(&mut log.global, ix.global);
736 fill_pk(&mut log.system_program, ix.system_program);
737 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
738 fill_pk(&mut log.mayhem_program_id, ix.mayhem_program_id);
739 fill_pk(&mut log.global_params, ix.global_params);
740 fill_pk(&mut log.sol_vault, ix.sol_vault);
741 fill_pk(&mut log.mayhem_state, ix.mayhem_state);
742 fill_pk(&mut log.mayhem_token_vault, ix.mayhem_token_vault);
743 fill_pk(&mut log.event_authority, ix.event_authority);
744 fill_pk(&mut log.program, ix.program);
745 fill_pk(&mut log.observed_fee_recipient, ix.observed_fee_recipient);
746}
747
748#[inline]
749fn merge_pumpfun_migrate_log_preferred(log: &mut PumpFunMigrateEvent, ix: PumpFunMigrateEvent) {
750 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
751 fill_pk(&mut log.pool, ix.pool);
752 fill_pk(&mut log.user, ix.user);
753}
754
755#[inline]
756fn merge_pumpswap_trade_log_preferred(log: &mut PumpSwapTradeEvent, ix: PumpSwapTradeEvent) {
757 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
758 log.ix_name = ix.ix_name;
759 }
760}
761
762#[inline]
763fn merge_pumpswap_buy_log_preferred(log: &mut PumpSwapBuyEvent, ix: PumpSwapBuyEvent) {
764 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
765 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
766 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
767 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
768 fill_pk(&mut log.coin_creator, ix.coin_creator);
769 fill_pk(&mut log.base_mint, ix.base_mint);
770 fill_pk(&mut log.quote_mint, ix.quote_mint);
771 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
772 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
773 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
774 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
775 fill_pk(&mut log.base_token_program, ix.base_token_program);
776 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
777 fill_pk(&mut log.pool_v2, ix.pool_v2);
778 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
779 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
780 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
781 log.ix_name = ix.ix_name;
782 }
783}
784
785#[inline]
786fn merge_pumpswap_sell_log_preferred(log: &mut PumpSwapSellEvent, ix: PumpSwapSellEvent) {
787 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
788 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
789 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
790 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
791 fill_pk(&mut log.coin_creator, ix.coin_creator);
792 fill_pk(&mut log.base_mint, ix.base_mint);
793 fill_pk(&mut log.quote_mint, ix.quote_mint);
794 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
795 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
796 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
797 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
798 fill_pk(&mut log.base_token_program, ix.base_token_program);
799 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
800 fill_pk(&mut log.pool_v2, ix.pool_v2);
801 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
802 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
803}
804
805#[inline]
806fn merge_raydium_clmm_swap_log_preferred(log: &mut RaydiumClmmSwapEvent, ix: RaydiumClmmSwapEvent) {
807 fill_pk(&mut log.token_account_0, ix.token_account_0);
808 fill_pk(&mut log.token_account_1, ix.token_account_1);
809 fill_pk(&mut log.sender, ix.sender);
810}
811
812#[inline]
813fn merge_raydium_amm_v4_swap_log_preferred(
814 log: &mut RaydiumAmmV4SwapEvent,
815 ix: RaydiumAmmV4SwapEvent,
816) {
817 fill_pk(&mut log.token_program, ix.token_program);
818 fill_pk(&mut log.amm_authority, ix.amm_authority);
819 fill_pk(&mut log.amm_open_orders, ix.amm_open_orders);
820 if let Some(ref o) = ix.amm_target_orders {
821 if log.amm_target_orders.is_none() {
822 log.amm_target_orders = Some(*o);
823 }
824 }
825 fill_pk(&mut log.pool_coin_token_account, ix.pool_coin_token_account);
826 fill_pk(&mut log.pool_pc_token_account, ix.pool_pc_token_account);
827 fill_pk(&mut log.serum_program, ix.serum_program);
828 fill_pk(&mut log.serum_market, ix.serum_market);
829 fill_pk(&mut log.serum_bids, ix.serum_bids);
830 fill_pk(&mut log.serum_asks, ix.serum_asks);
831 fill_pk(&mut log.serum_event_queue, ix.serum_event_queue);
832 fill_pk(&mut log.serum_coin_vault_account, ix.serum_coin_vault_account);
833 fill_pk(&mut log.serum_pc_vault_account, ix.serum_pc_vault_account);
834 fill_pk(&mut log.serum_vault_signer, ix.serum_vault_signer);
835 fill_pk(&mut log.user_source_token_account, ix.user_source_token_account);
836 fill_pk(&mut log.user_destination_token_account, ix.user_destination_token_account);
837 fill_pk(&mut log.user_source_owner, ix.user_source_owner);
838 fill_pk(&mut log.amm, ix.amm);
839}
840
841#[inline]
842fn merge_pumpswap_create_pool_log_preferred(
843 log: &mut PumpSwapCreatePoolEvent,
844 ix: PumpSwapCreatePoolEvent,
845) {
846 fill_pk(&mut log.creator, ix.creator);
847 fill_pk(&mut log.pool, ix.pool);
848 fill_pk(&mut log.lp_mint, ix.lp_mint);
849 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
850 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
851 fill_pk(&mut log.coin_creator, ix.coin_creator);
852 log.is_mayhem_mode |= ix.is_mayhem_mode;
853 log.is_cashback_coin |= ix.is_cashback_coin;
854}
855
856#[inline]
857fn merge_pumpswap_liquidity_added_log_preferred(
858 log: &mut PumpSwapLiquidityAdded,
859 ix: PumpSwapLiquidityAdded,
860) {
861 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
862 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
863 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
864}
865
866#[inline]
867fn merge_pumpswap_liquidity_removed_log_preferred(
868 log: &mut PumpSwapLiquidityRemoved,
869 ix: PumpSwapLiquidityRemoved,
870) {
871 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
872 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
873 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
874}
875
876#[inline]
877fn merge_raydium_launchlab_pool_create_log_preferred(
878 log: &mut RaydiumLaunchlabPoolCreateEvent,
879 ix: RaydiumLaunchlabPoolCreateEvent,
880) {
881 fill_pk(&mut log.payer, ix.payer);
882 fill_pk(&mut log.creator, ix.creator);
883 fill_pk(&mut log.global_config, ix.global_config);
884 fill_pk(&mut log.platform_config, ix.platform_config);
885 fill_pk(&mut log.base_mint, ix.base_mint);
886 fill_pk(&mut log.quote_mint, ix.quote_mint);
887 fill_pk(&mut log.base_vault, ix.base_vault);
888 fill_pk(&mut log.quote_vault, ix.quote_vault);
889 fill_pk(&mut log.base_token_program, ix.base_token_program);
890 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
891 fill_str_if_empty(&mut log.base_mint_param.name, &ix.base_mint_param.name);
892 fill_str_if_empty(&mut log.base_mint_param.symbol, &ix.base_mint_param.symbol);
893 fill_str_if_empty(&mut log.base_mint_param.uri, &ix.base_mint_param.uri);
894}
895
896#[inline]
897fn merge_raydium_launchlab_migrate_amm_log_preferred(
898 log: &mut RaydiumLaunchlabMigrateAmmEvent,
899 ix: RaydiumLaunchlabMigrateAmmEvent,
900) {
901 fill_pk(&mut log.old_pool, ix.old_pool);
902 fill_pk(&mut log.new_pool, ix.new_pool);
903 fill_pk(&mut log.user, ix.user);
904}
905
906#[inline]
907fn merge_raydium_launchlab_trade_log_preferred(
908 log: &mut RaydiumLaunchlabTradeEvent,
909 ix: RaydiumLaunchlabTradeEvent,
910) {
911 fill_pk(&mut log.user, ix.user);
912 fill_pk(&mut log.global_config, ix.global_config);
913 fill_pk(&mut log.platform_config, ix.platform_config);
914 fill_pk(&mut log.user_base_token, ix.user_base_token);
915 fill_pk(&mut log.user_quote_token, ix.user_quote_token);
916 fill_pk(&mut log.base_vault, ix.base_vault);
917 fill_pk(&mut log.quote_vault, ix.quote_vault);
918 fill_pk(&mut log.base_mint, ix.base_mint);
919 fill_pk(&mut log.quote_mint, ix.quote_mint);
920 fill_pk(&mut log.base_token_program, ix.base_token_program);
921 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
922}
923
924#[inline]
925fn merge_meteora_dlmm_swap_log_preferred(log: &mut MeteoraDlmmSwapEvent, ix: MeteoraDlmmSwapEvent) {
926 fill_pk(&mut log.user_token_in, ix.user_token_in);
927 fill_pk(&mut log.user_token_out, ix.user_token_out);
928 if log.min_amount_out == 0 {
929 log.min_amount_out = ix.min_amount_out;
930 }
931}
932
933pub fn merge_grpc_instruction_into_log(log: &mut DexEvent, ix: DexEvent) {
939 use DexEvent::*;
940 match log {
941 PumpFunTrade(l) => {
942 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
943 merge_pumpfun_trade_log_preferred(l, i);
944 }
945 }
946 PumpFunBuy(l) => {
947 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
948 merge_pumpfun_trade_log_preferred(l, i);
949 }
950 }
951 PumpFunSell(l) => {
952 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
953 merge_pumpfun_trade_log_preferred(l, i);
954 }
955 }
956 PumpFunBuyExactSolIn(l) => {
957 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
958 merge_pumpfun_trade_log_preferred(l, i);
959 }
960 }
961 PumpFunCreate(l) => match ix {
962 DexEvent::PumpFunCreate(i) => merge_pumpfun_create_log_preferred(l, i),
963 DexEvent::PumpFunCreateV2(i) => merge_pumpfun_create_v2_into_create_log_preferred(l, i),
964 _ => {}
965 },
966 PumpFunCreateV2(l) => match ix {
967 DexEvent::PumpFunCreate(i) => merge_pumpfun_create_into_create_v2_log_preferred(l, i),
968 DexEvent::PumpFunCreateV2(i) => merge_pumpfun_create_v2_log_preferred(l, i),
969 _ => {}
970 },
971 PumpFunMigrate(l) => {
972 if let DexEvent::PumpFunMigrate(i) = ix {
973 merge_pumpfun_migrate_log_preferred(l, i);
974 }
975 }
976 PumpSwapTrade(l) => {
977 if let PumpSwapTrade(i) = ix {
978 merge_pumpswap_trade_log_preferred(l, i);
979 }
980 }
981 PumpSwapBuy(l) => {
982 if let PumpSwapBuy(i) = ix {
983 merge_pumpswap_buy_log_preferred(l, i);
984 }
985 }
986 PumpSwapSell(l) => {
987 if let PumpSwapSell(i) = ix {
988 merge_pumpswap_sell_log_preferred(l, i);
989 }
990 }
991 RaydiumClmmSwap(l) => {
992 if let RaydiumClmmSwap(i) = ix {
993 merge_raydium_clmm_swap_log_preferred(l, i);
994 }
995 }
996 RaydiumAmmV4Swap(l) => {
997 if let RaydiumAmmV4Swap(i) = ix {
998 merge_raydium_amm_v4_swap_log_preferred(l, i);
999 }
1000 }
1001 RaydiumLaunchlabTrade(l) => {
1002 if let RaydiumLaunchlabTrade(i) = ix {
1003 merge_raydium_launchlab_trade_log_preferred(l, i);
1004 }
1005 }
1006 RaydiumLaunchlabPoolCreate(l) => {
1007 if let RaydiumLaunchlabPoolCreate(i) = ix {
1008 merge_raydium_launchlab_pool_create_log_preferred(l, i);
1009 }
1010 }
1011 RaydiumLaunchlabMigrateAmm(l) => {
1012 if let RaydiumLaunchlabMigrateAmm(i) = ix {
1013 merge_raydium_launchlab_migrate_amm_log_preferred(l, i);
1014 }
1015 }
1016 PumpSwapCreatePool(l) => {
1017 if let PumpSwapCreatePool(i) = ix {
1018 merge_pumpswap_create_pool_log_preferred(l, i);
1019 }
1020 }
1021 PumpSwapLiquidityAdded(l) => {
1022 if let PumpSwapLiquidityAdded(i) = ix {
1023 merge_pumpswap_liquidity_added_log_preferred(l, i);
1024 }
1025 }
1026 PumpSwapLiquidityRemoved(l) => {
1027 if let PumpSwapLiquidityRemoved(i) = ix {
1028 merge_pumpswap_liquidity_removed_log_preferred(l, i);
1029 }
1030 }
1031 MeteoraDlmmSwap(l) => {
1032 if let MeteoraDlmmSwap(i) = ix {
1033 merge_meteora_dlmm_swap_log_preferred(l, i);
1034 }
1035 }
1036 _ => {}
1037 }
1038}
1039
1040#[inline]
1041fn pumpfun_trade_from_ix_variant(ix: DexEvent) -> Option<PumpFunTradeEvent> {
1042 match ix {
1043 DexEvent::PumpFunTrade(t)
1044 | DexEvent::PumpFunBuy(t)
1045 | DexEvent::PumpFunSell(t)
1046 | DexEvent::PumpFunBuyExactSolIn(t) => Some(t),
1047 _ => None,
1048 }
1049}
1050
1051#[cfg(test)]
1052mod tests {
1053 use super::*;
1054
1055 #[test]
1056 fn incompatible_merge_leaves_both_events_unchanged() {
1057 let mut base = DexEvent::Error("base".to_string());
1058 let inner = DexEvent::Error("inner".to_string());
1059 let mut unmerged = None;
1060
1061 assert!(!try_merge_events(&mut base, inner, &mut unmerged));
1062 assert!(matches!(base, DexEvent::Error(ref message) if message == "base"));
1063 assert!(matches!(unmerged, Some(DexEvent::Error(ref message)) if message == "inner"));
1064 }
1065 use solana_sdk::{pubkey::Pubkey, signature::Signature};
1066
1067 fn dlmm_swap(min_amount_out: u64, amount_out: u64) -> MeteoraDlmmSwapEvent {
1068 MeteoraDlmmSwapEvent {
1069 metadata: EventMetadata::default(),
1070 token_x_mint: Pubkey::default(),
1071 token_y_mint: Pubkey::default(),
1072 user_token_in: Pubkey::new_unique(),
1073 user_token_out: Pubkey::new_unique(),
1074 min_amount_out,
1075 pool: Pubkey::new_unique(),
1076 from: Pubkey::new_unique(),
1077 start_bin_id: 0,
1078 end_bin_id: 0,
1079 amount_in: 200,
1080 amount_out,
1081 swap_for_y: true,
1082 fee: 1,
1083 protocol_fee: 0,
1084 fee_bps: 25,
1085 host_fee: 0,
1086 }
1087 }
1088
1089 #[test]
1090 fn dlmm_event_merge_keeps_instruction_threshold_and_executed_output() {
1091 let base_event = dlmm_swap(100, 0);
1092 let expected_user_token_in = base_event.user_token_in;
1093 let expected_user_token_out = base_event.user_token_out;
1094 let mut inner_event = dlmm_swap(0, 125);
1095 inner_event.user_token_in = Pubkey::default();
1096 inner_event.user_token_out = Pubkey::default();
1097 let mut base = DexEvent::MeteoraDlmmSwap(base_event);
1098 let inner = DexEvent::MeteoraDlmmSwap(inner_event);
1099
1100 assert!(try_merge_events(&mut base, inner, &mut None));
1101 let DexEvent::MeteoraDlmmSwap(event) = base else { panic!("swap") };
1102 assert_eq!(event.min_amount_out, 100);
1103 assert_eq!(event.amount_out, 125);
1104 assert_eq!(event.user_token_in, expected_user_token_in);
1105 assert_eq!(event.user_token_out, expected_user_token_out);
1106 }
1107
1108 #[test]
1109 fn grpc_dlmm_merge_keeps_log_output_and_adds_instruction_threshold() {
1110 let mut log_event = dlmm_swap(0, 125);
1111 log_event.user_token_in = Pubkey::default();
1112 log_event.user_token_out = Pubkey::default();
1113 let instruction_event = dlmm_swap(100, 0);
1114 let expected_user_token_in = instruction_event.user_token_in;
1115 let expected_user_token_out = instruction_event.user_token_out;
1116 let mut log = DexEvent::MeteoraDlmmSwap(log_event);
1117 let instruction = DexEvent::MeteoraDlmmSwap(instruction_event);
1118
1119 merge_grpc_instruction_into_log(&mut log, instruction);
1120 let DexEvent::MeteoraDlmmSwap(event) = log else { panic!("swap") };
1121 assert_eq!(event.min_amount_out, 100);
1122 assert_eq!(event.amount_out, 125);
1123 assert_eq!(event.user_token_in, expected_user_token_in);
1124 assert_eq!(event.user_token_out, expected_user_token_out);
1125 }
1126
1127 #[test]
1128 fn test_merge_pumpfun_trade() {
1129 let metadata = EventMetadata {
1130 signature: Signature::default(),
1131 slot: 100,
1132 tx_index: 1,
1133 block_time_us: 1000,
1134 grpc_recv_us: 2000,
1135 recent_blockhash: None,
1136 };
1137
1138 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1140 metadata: metadata.clone(),
1141 bonding_curve: Pubkey::new_unique(),
1142 associated_bonding_curve: Pubkey::new_unique(),
1143 ..Default::default()
1144 });
1145
1146 let inner = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1148 metadata: metadata.clone(),
1149 mint: Pubkey::new_unique(),
1150 sol_amount: 1000,
1151 token_amount: 2000,
1152 is_buy: true,
1153 user: Pubkey::new_unique(),
1154 ..Default::default()
1155 });
1156
1157 merge_events(&mut base, inner);
1159
1160 if let DexEvent::PumpFunTrade(trade) = base {
1162 assert_eq!(trade.sol_amount, 1000);
1163 assert_eq!(trade.token_amount, 2000);
1164 assert!(trade.is_buy);
1165 assert_ne!(trade.bonding_curve, Pubkey::default());
1167 assert_ne!(trade.associated_bonding_curve, Pubkey::default());
1168 } else {
1169 panic!("Expected PumpFunTrade event");
1170 }
1171 }
1172
1173 #[test]
1174 fn merge_preserves_instruction_context_when_log_tail_is_absent() {
1175 let metadata = EventMetadata {
1176 signature: Signature::default(),
1177 slot: 100,
1178 tx_index: 1,
1179 block_time_us: 1000,
1180 grpc_recv_us: 2000,
1181 recent_blockhash: None,
1182 };
1183 let quote_mint = Pubkey::new_unique();
1184 let associated_quote_user = Pubkey::new_unique();
1185
1186 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1187 metadata: metadata.clone(),
1188 ix_name: "buy_exact_quote_in".to_string(),
1189 quote_mint,
1190 spendable_quote_in: 1_000,
1191 min_tokens_out: 2_000,
1192 associated_quote_user,
1193 ..Default::default()
1194 });
1195
1196 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1197 metadata,
1198 sol_amount: 1_000,
1199 token_amount: 2_000,
1200 is_buy: true,
1201 ..Default::default()
1202 });
1203
1204 merge_events(&mut base, inner);
1205
1206 if let DexEvent::PumpFunTrade(t) = base {
1207 assert_eq!(t.sol_amount, 1_000);
1208 assert_eq!(t.token_amount, 2_000);
1209 assert_eq!(t.ix_name, "buy_exact_quote_in");
1210 assert_eq!(t.quote_mint, quote_mint);
1211 assert_eq!(t.spendable_quote_in, 1_000);
1212 assert_eq!(t.min_tokens_out, 2_000);
1213 assert_eq!(t.associated_quote_user, associated_quote_user);
1214 } else {
1215 panic!("Expected PumpFunTrade event");
1216 }
1217 }
1218
1219 #[test]
1220 fn merge_replaces_sol_quote_sentinel_with_real_quote_mint() {
1221 let quote_mint = Pubkey::new_unique();
1222 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1223 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1224 ..Default::default()
1225 });
1226
1227 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent { quote_mint, ..Default::default() });
1228
1229 merge_events(&mut base, inner);
1230
1231 if let DexEvent::PumpFunTrade(t) = base {
1232 assert_eq!(t.quote_mint, quote_mint);
1233 } else {
1234 panic!("Expected PumpFunTrade event");
1235 }
1236 }
1237
1238 #[test]
1239 fn merge_replaces_sol_quote_sentinel_with_wsol_mint() {
1240 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1241 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1242 ..Default::default()
1243 });
1244
1245 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1246 quote_mint: PUMPFUN_WSOL_QUOTE_MINT,
1247 ..Default::default()
1248 });
1249
1250 merge_events(&mut base, inner);
1251
1252 if let DexEvent::PumpFunTrade(t) = base {
1253 assert_eq!(t.quote_mint, PUMPFUN_WSOL_QUOTE_MINT);
1254 } else {
1255 panic!("Expected PumpFunTrade event");
1256 }
1257 }
1258
1259 #[test]
1260 fn test_can_merge() {
1261 let metadata = EventMetadata {
1262 signature: Signature::default(),
1263 slot: 100,
1264 tx_index: 1,
1265 block_time_us: 1000,
1266 grpc_recv_us: 2000,
1267 recent_blockhash: None,
1268 };
1269
1270 let base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1271 metadata: metadata.clone(),
1272 ..Default::default()
1273 });
1274
1275 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1276 metadata: metadata.clone(),
1277 ..Default::default()
1278 });
1279
1280 assert!(can_merge(&base, &inner));
1282
1283 let different_sig = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1285 metadata: EventMetadata { signature: Signature::new_unique(), ..metadata },
1286 ..Default::default()
1287 });
1288
1289 assert!(!can_merge(&base, &different_sig));
1290 }
1291
1292 #[test]
1293 fn dlmm_position_event_keeps_instruction_only_fields() {
1294 let pool = Pubkey::new_unique();
1295 let position = Pubkey::new_unique();
1296 let owner = Pubkey::new_unique();
1297 let mut base = DexEvent::MeteoraDlmmCreatePosition(MeteoraDlmmCreatePositionEvent {
1298 metadata: EventMetadata::default(),
1299 pool,
1300 position,
1301 owner,
1302 lower_bin_id: -42,
1303 width: 70,
1304 });
1305 let inner = DexEvent::MeteoraDlmmCreatePosition(MeteoraDlmmCreatePositionEvent {
1306 metadata: EventMetadata::default(),
1307 pool,
1308 position,
1309 owner,
1310 lower_bin_id: 0,
1311 width: 0,
1312 });
1313
1314 assert!(try_merge_events(&mut base, inner, &mut None));
1315 let DexEvent::MeteoraDlmmCreatePosition(event) = base else { panic!("position") };
1316 assert_eq!(event.lower_bin_id, -42);
1317 assert_eq!(event.width, 70);
1318 }
1319
1320 #[test]
1321 fn grpc_merge_fills_fee_recipient_from_ix_when_log_default() {
1322 let metadata = EventMetadata {
1323 signature: Signature::default(),
1324 slot: 1,
1325 tx_index: 0,
1326 block_time_us: 0,
1327 grpc_recv_us: 0,
1328 recent_blockhash: None,
1329 };
1330 let fr = Pubkey::new_unique();
1331 let log_t =
1332 PumpFunTradeEvent { metadata: metadata.clone(), sol_amount: 50, ..Default::default() };
1333 let mut ix_t = log_t.clone();
1334 ix_t.fee_recipient = fr;
1335 ix_t.sol_amount = 777;
1336 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1337 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1338 match log_ev {
1339 DexEvent::PumpFunTrade(t) => {
1340 assert_eq!(t.fee_recipient, fr);
1341 assert_eq!(t.sol_amount, 50);
1342 }
1343 _ => panic!("expected trade"),
1344 }
1345 }
1346
1347 #[test]
1348 fn grpc_merge_keeps_log_trade_fields() {
1349 let metadata = EventMetadata {
1350 signature: Signature::default(),
1351 slot: 1,
1352 tx_index: 0,
1353 block_time_us: 0,
1354 grpc_recv_us: 0,
1355 recent_blockhash: None,
1356 };
1357 let log_t = PumpFunTradeEvent {
1358 metadata: metadata.clone(),
1359 mayhem_mode: true,
1360 sol_amount: 100,
1361 ..Default::default()
1362 };
1363 let mut ix_t = log_t.clone();
1364 ix_t.mayhem_mode = false;
1365 ix_t.sol_amount = 999;
1366
1367 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1368 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1369 match log_ev {
1370 DexEvent::PumpFunTrade(t) => {
1371 assert!(t.mayhem_mode);
1372 assert_eq!(t.sol_amount, 100);
1373 }
1374 _ => panic!("variant preserved"),
1375 }
1376 }
1377}