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 (MeteoraDammV2UpdateDelegatePermission(b), MeteoraDammV2UpdateDelegatePermission(i)) => {
153 merge_generic(b, i)
154 }
155 (
156 MeteoraDammV2WithdrawDeadLiquidityReward(b),
157 MeteoraDammV2WithdrawDeadLiquidityReward(i),
158 ) => merge_generic(b, i),
159 (MeteoraDammV2CreateConfig(b), MeteoraDammV2CreateConfig(i)) => merge_generic(b, i),
160 (MeteoraDammV2CreateDynamicConfig(b), MeteoraDammV2CreateDynamicConfig(i)) => {
161 merge_generic(b, i)
162 }
163
164 (MeteoraDlmmSwap(b), MeteoraDlmmSwap(i)) => merge_dlmm_swap(b, i),
166 (MeteoraDlmmAddLiquidity(b), MeteoraDlmmAddLiquidity(i)) => merge_generic(b, i),
167 (MeteoraDlmmRemoveLiquidity(b), MeteoraDlmmRemoveLiquidity(i)) => merge_generic(b, i),
168 (MeteoraDlmmInitializePool(b), MeteoraDlmmInitializePool(i)) => {
169 merge_dlmm_initialize_pool(b, i)
170 }
171 (MeteoraDlmmInitializeBinArray(b), MeteoraDlmmInitializeBinArray(i)) => merge_generic(b, i),
172 (MeteoraDlmmCreatePosition(b), MeteoraDlmmCreatePosition(i)) => {
173 merge_dlmm_create_position(b, i)
174 }
175 (MeteoraDlmmClosePosition(b), MeteoraDlmmClosePosition(i)) => {
176 merge_dlmm_close_position(b, i)
177 }
178 (MeteoraDlmmClaimFee(b), MeteoraDlmmClaimFee(i)) => merge_generic(b, i),
179
180 (RaydiumLaunchlabTrade(b), RaydiumLaunchlabTrade(i)) => merge_generic(b, i),
182 (RaydiumLaunchlabPoolCreate(b), RaydiumLaunchlabPoolCreate(i)) => merge_generic(b, i),
183 (RaydiumLaunchlabMigrateAmm(b), RaydiumLaunchlabMigrateAmm(i)) => merge_generic(b, i),
184
185 (_, event) => {
187 *unmerged = Some(event);
188 return false;
189 }
190 }
191
192 true
193}
194
195#[inline(always)]
202fn merge_generic<T>(base: &mut T, inner: T) {
203 *base = inner;
204}
205
206#[inline(always)]
207fn merge_dlmm_swap(base: &mut MeteoraDlmmSwapEvent, inner: MeteoraDlmmSwapEvent) {
208 let min_amount_out =
209 if inner.min_amount_out != 0 { inner.min_amount_out } else { base.min_amount_out };
210 let user_token_in = if inner.user_token_in != Pubkey::default() {
211 inner.user_token_in
212 } else {
213 base.user_token_in
214 };
215 let user_token_out = if inner.user_token_out != Pubkey::default() {
216 inner.user_token_out
217 } else {
218 base.user_token_out
219 };
220 *base = inner;
221 base.min_amount_out = min_amount_out;
222 base.user_token_in = user_token_in;
223 base.user_token_out = user_token_out;
224}
225
226#[inline(always)]
227fn merge_dlmm_initialize_pool(
228 base: &mut MeteoraDlmmInitializePoolEvent,
229 inner: MeteoraDlmmInitializePoolEvent,
230) {
231 let creator = base.creator;
232 let active_bin_id = base.active_bin_id;
233 *base = inner;
234 base.creator = creator;
235 base.active_bin_id = active_bin_id;
236}
237
238#[inline(always)]
239fn merge_dlmm_create_position(
240 base: &mut MeteoraDlmmCreatePositionEvent,
241 inner: MeteoraDlmmCreatePositionEvent,
242) {
243 let lower_bin_id = base.lower_bin_id;
244 let width = base.width;
245 *base = inner;
246 base.lower_bin_id = lower_bin_id;
247 base.width = width;
248}
249
250#[inline(always)]
251fn merge_dlmm_close_position(
252 base: &mut MeteoraDlmmClosePositionEvent,
253 inner: MeteoraDlmmClosePositionEvent,
254) {
255 let pool = base.pool;
256 *base = inner;
257 base.pool = pool;
258}
259
260#[inline(always)]
265fn put_pk_if_set(to: &mut Pubkey, from: Pubkey) {
266 if from != Pubkey::default() {
267 *to = from;
268 }
269}
270
271#[inline(always)]
272fn put_pumpfun_quote_mint_if_set(to: &mut Pubkey, from: Pubkey) {
273 let from = normalize_pumpfun_quote_mint(from);
274 if from != Pubkey::default()
275 && (*to == Pubkey::default()
276 || is_pumpfun_solscan_sol_quote_mint(*to)
277 || !is_pumpfun_solscan_sol_quote_mint(from))
278 {
279 *to = from;
280 }
281}
282
283#[inline(always)]
284fn put_u64_if_nonzero(to: &mut u64, from: u64) {
285 if from != 0 {
286 *to = from;
287 }
288}
289
290#[inline(always)]
291fn put_i64_if_nonzero(to: &mut i64, from: i64) {
292 if from != 0 {
293 *to = from;
294 }
295}
296
297#[inline(always)]
307fn merge_pumpfun_trade(base: &mut PumpFunTradeEvent, inner: PumpFunTradeEvent) {
308 let leg = inner.sol_amount != 0 || inner.token_amount != 0;
309
310 put_pk_if_set(&mut base.mint, inner.mint);
311 put_pk_if_set(&mut base.user, inner.user);
312 put_pk_if_set(&mut base.fee_recipient, inner.fee_recipient);
313 put_pk_if_set(&mut base.creator, inner.creator);
314
315 if leg {
316 base.sol_amount = inner.sol_amount;
317 base.token_amount = inner.token_amount;
318 base.is_buy = inner.is_buy;
319 base.timestamp = inner.timestamp;
320 base.virtual_sol_reserves = inner.virtual_sol_reserves;
321 base.virtual_token_reserves = inner.virtual_token_reserves;
322 base.real_sol_reserves = inner.real_sol_reserves;
323 base.real_token_reserves = inner.real_token_reserves;
324 base.fee_basis_points = inner.fee_basis_points;
325 base.fee = inner.fee;
326 base.creator_fee_basis_points = inner.creator_fee_basis_points;
327 base.creator_fee = inner.creator_fee;
328 base.track_volume |= inner.track_volume;
329 base.total_unclaimed_tokens = inner.total_unclaimed_tokens;
330 base.total_claimed_tokens = inner.total_claimed_tokens;
331 base.current_sol_volume = inner.current_sol_volume;
332 base.last_update_timestamp = inner.last_update_timestamp;
333 if !inner.ix_name.is_empty() {
334 base.ix_name = inner.ix_name;
335 }
336 base.mayhem_mode |= inner.mayhem_mode;
337 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
338 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
339 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
340 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
341 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
342 base.shareholders = inner.shareholders;
343 }
344 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
345 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
346 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
347 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
348 base.is_cashback_coin |= inner.is_cashback_coin;
349 base.holder_rewards_bps = inner.holder_rewards_bps;
350 base.holder_rewards = inner.holder_rewards;
351 } else {
352 put_u64_if_nonzero(&mut base.fee, inner.fee);
353 put_u64_if_nonzero(&mut base.creator_fee, inner.creator_fee);
354 put_u64_if_nonzero(&mut base.fee_basis_points, inner.fee_basis_points);
355 put_u64_if_nonzero(&mut base.creator_fee_basis_points, inner.creator_fee_basis_points);
356 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
357 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
358 put_u64_if_nonzero(&mut base.real_sol_reserves, inner.real_sol_reserves);
359 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
360 put_u64_if_nonzero(&mut base.total_unclaimed_tokens, inner.total_unclaimed_tokens);
361 put_u64_if_nonzero(&mut base.total_claimed_tokens, inner.total_claimed_tokens);
362 put_u64_if_nonzero(&mut base.current_sol_volume, inner.current_sol_volume);
363 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
364 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
365 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
366 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
367 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
368 base.shareholders = inner.shareholders;
369 }
370 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
371 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
372 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
373 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
374 put_u64_if_nonzero(&mut base.holder_rewards_bps, inner.holder_rewards_bps);
375 put_u64_if_nonzero(&mut base.holder_rewards, inner.holder_rewards);
376 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
377 put_i64_if_nonzero(&mut base.last_update_timestamp, inner.last_update_timestamp);
378 if !inner.ix_name.is_empty() {
379 base.ix_name = inner.ix_name;
380 }
381 base.track_volume |= inner.track_volume;
382 base.mayhem_mode |= inner.mayhem_mode;
383 base.is_cashback_coin |= inner.is_cashback_coin;
384 }
385 put_u64_if_nonzero(&mut base.amount, inner.amount);
386 put_u64_if_nonzero(&mut base.max_sol_cost, inner.max_sol_cost);
387 put_u64_if_nonzero(&mut base.min_sol_output, inner.min_sol_output);
388 put_u64_if_nonzero(&mut base.spendable_sol_in, inner.spendable_sol_in);
389 put_u64_if_nonzero(&mut base.spendable_quote_in, inner.spendable_quote_in);
390 put_u64_if_nonzero(&mut base.min_tokens_out, inner.min_tokens_out);
391 put_pk_if_set(&mut base.global, inner.global);
392 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
393 put_pk_if_set(&mut base.bonding_curve_v2, inner.bonding_curve_v2);
394 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
395 put_pk_if_set(&mut base.associated_user, inner.associated_user);
396 put_pk_if_set(&mut base.system_program, inner.system_program);
397 put_pk_if_set(&mut base.token_program, inner.token_program);
398 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
399 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
400 put_pk_if_set(&mut base.creator_vault, inner.creator_vault);
401 put_pk_if_set(&mut base.associated_quote_fee_recipient, inner.associated_quote_fee_recipient);
402 put_pk_if_set(&mut base.buyback_fee_recipient, inner.buyback_fee_recipient);
403 put_pk_if_set(
404 &mut base.associated_quote_buyback_fee_recipient,
405 inner.associated_quote_buyback_fee_recipient,
406 );
407 put_pk_if_set(&mut base.associated_quote_bonding_curve, inner.associated_quote_bonding_curve);
408 put_pk_if_set(&mut base.associated_quote_user, inner.associated_quote_user);
409 put_pk_if_set(&mut base.associated_creator_vault, inner.associated_creator_vault);
410 put_pk_if_set(&mut base.sharing_config, inner.sharing_config);
411 put_pk_if_set(&mut base.event_authority, inner.event_authority);
412 put_pk_if_set(&mut base.program, inner.program);
413 put_pk_if_set(&mut base.global_volume_accumulator, inner.global_volume_accumulator);
414 put_pk_if_set(&mut base.user_volume_accumulator, inner.user_volume_accumulator);
415 put_pk_if_set(
416 &mut base.associated_user_volume_accumulator,
417 inner.associated_user_volume_accumulator,
418 );
419 put_pk_if_set(&mut base.fee_config, inner.fee_config);
420 put_pk_if_set(&mut base.fee_program, inner.fee_program);
421 if base.account.is_none() {
422 base.account = inner.account;
423 }
424
425 base.is_created_buy |= inner.is_created_buy;
426 }
428
429#[inline(always)]
431fn merge_pumpfun_create(base: &mut PumpFunCreateTokenEvent, inner: PumpFunCreateTokenEvent) {
432 base.name = inner.name;
434 base.symbol = inner.symbol;
435 base.uri = inner.uri;
436 base.mint = inner.mint;
437 base.bonding_curve = inner.bonding_curve;
438 base.user = inner.user;
439 base.creator = inner.creator;
440 base.timestamp = inner.timestamp;
441 base.virtual_token_reserves = inner.virtual_token_reserves;
442 base.virtual_sol_reserves = inner.virtual_sol_reserves;
443 base.real_token_reserves = inner.real_token_reserves;
444 base.token_total_supply = inner.token_total_supply;
445 base.token_program = inner.token_program;
446 base.is_mayhem_mode = inner.is_mayhem_mode;
447 base.is_cashback_enabled = inner.is_cashback_enabled;
448 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
449 put_pk_if_set(&mut base.quote_vault, inner.quote_vault);
450 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
451 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
452 put_u64_if_nonzero(&mut base.creator_fee_bps, inner.creator_fee_bps);
453 base.is_holder_reward |= inner.is_holder_reward;
454}
455
456#[inline(always)]
458fn merge_pumpfun_create_v2(base: &mut PumpFunCreateV2TokenEvent, inner: PumpFunCreateV2TokenEvent) {
459 fill_str_if_empty(&mut base.name, &inner.name);
460 fill_str_if_empty(&mut base.symbol, &inner.symbol);
461 fill_str_if_empty(&mut base.uri, &inner.uri);
462 put_pk_if_set(&mut base.mint, inner.mint);
463 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
464 put_pk_if_set(&mut base.user, inner.user);
465 put_pk_if_set(&mut base.creator, inner.creator);
466 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
467 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
468 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
469 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
470 put_u64_if_nonzero(&mut base.token_total_supply, inner.token_total_supply);
471 put_pk_if_set(&mut base.token_program, inner.token_program);
472 base.is_mayhem_mode |= inner.is_mayhem_mode;
473 base.is_cashback_enabled |= inner.is_cashback_enabled;
474 put_pumpfun_quote_mint_if_set(&mut base.quote_mint, inner.quote_mint);
475 put_pk_if_set(&mut base.quote_vault, inner.quote_vault);
476 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
477 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
478 put_pk_if_set(&mut base.mint_authority, inner.mint_authority);
479 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
480 put_pk_if_set(&mut base.global, inner.global);
481 put_pk_if_set(&mut base.system_program, inner.system_program);
482 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
483 put_pk_if_set(&mut base.mayhem_program_id, inner.mayhem_program_id);
484 put_pk_if_set(&mut base.global_params, inner.global_params);
485 put_pk_if_set(&mut base.sol_vault, inner.sol_vault);
486 put_pk_if_set(&mut base.mayhem_state, inner.mayhem_state);
487 put_pk_if_set(&mut base.mayhem_token_vault, inner.mayhem_token_vault);
488 put_pk_if_set(&mut base.event_authority, inner.event_authority);
489 put_pk_if_set(&mut base.program, inner.program);
490 put_pk_if_set(&mut base.observed_fee_recipient, inner.observed_fee_recipient);
491 put_u64_if_nonzero(&mut base.creator_fee_bps, inner.creator_fee_bps);
492 base.is_holder_reward |= inner.is_holder_reward;
493}
494
495#[inline(always)]
497fn merge_pumpfun_migrate(base: &mut PumpFunMigrateEvent, inner: PumpFunMigrateEvent) {
498 base.user = inner.user;
500 base.mint = inner.mint;
501 base.mint_amount = inner.mint_amount;
502 base.sol_amount = inner.sol_amount;
503 base.pool_migration_fee = inner.pool_migration_fee;
504 base.bonding_curve = inner.bonding_curve;
505 base.timestamp = inner.timestamp;
506 base.pool = inner.pool;
507}
508
509#[inline(always)]
510fn merge_pumpswap_buy(base: &mut PumpSwapBuyEvent, inner: PumpSwapBuyEvent) {
511 let ix = std::mem::take(base);
512 *base = inner;
513 merge_pumpswap_buy_log_preferred(base, ix);
514}
515
516#[inline(always)]
517fn merge_pumpswap_sell(base: &mut PumpSwapSellEvent, inner: PumpSwapSellEvent) {
518 let ix = std::mem::take(base);
519 *base = inner;
520 merge_pumpswap_sell_log_preferred(base, ix);
521}
522
523#[inline(always)]
534pub fn can_merge(base: &DexEvent, inner: &DexEvent) -> bool {
535 if base.metadata().signature != inner.metadata().signature {
537 return false;
538 }
539
540 match (base, inner) {
542 (DexEvent::PumpFunTrade(_), DexEvent::PumpFunTrade(_))
544 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuy(_))
545 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunSell(_))
546 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuyExactSolIn(_))
547 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunTrade(_))
548 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunBuy(_))
549 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunTrade(_))
550 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunSell(_))
551 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunTrade(_))
552 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunBuyExactSolIn(_)) => true,
553
554 (DexEvent::PumpFunCreate(_), DexEvent::PumpFunCreate(_)) => true,
556 (DexEvent::PumpFunCreateV2(_), DexEvent::PumpFunCreateV2(_)) => true,
557
558 (DexEvent::PumpFunMigrate(_), DexEvent::PumpFunMigrate(_)) => true,
560
561 _ => false,
563 }
564}
565
566#[inline(always)]
571fn fill_pk(to: &mut Pubkey, from: Pubkey) {
572 if *to == Pubkey::default() && from != Pubkey::default() {
573 *to = from;
574 }
575}
576
577#[inline(always)]
578fn fill_pumpfun_quote_mint(to: &mut Pubkey, from: Pubkey) {
579 let from = normalize_pumpfun_quote_mint(from);
580 if (*to == Pubkey::default() || is_pumpfun_solscan_sol_quote_mint(*to))
581 && from != Pubkey::default()
582 {
583 *to = from;
584 }
585}
586
587#[inline(always)]
588fn fill_str_if_empty(to: &mut String, from: &str) {
589 if to.is_empty() && !from.is_empty() {
590 to.push_str(from);
591 }
592}
593
594#[inline]
597fn merge_pumpfun_trade_log_preferred(log: &mut PumpFunTradeEvent, ix: PumpFunTradeEvent) {
598 fill_pk(&mut log.global, ix.global);
599 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
600 fill_pk(&mut log.bonding_curve_v2, ix.bonding_curve_v2);
601 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
602 fill_pk(&mut log.associated_user, ix.associated_user);
603 fill_pk(&mut log.system_program, ix.system_program);
604 fill_pk(&mut log.token_program, ix.token_program);
605 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
606 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
607 fill_pk(&mut log.creator_vault, ix.creator_vault);
608 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
609 fill_pk(&mut log.creator, ix.creator);
610 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
611 fill_pk(&mut log.associated_quote_fee_recipient, ix.associated_quote_fee_recipient);
612 fill_pk(&mut log.buyback_fee_recipient, ix.buyback_fee_recipient);
613 fill_pk(
614 &mut log.associated_quote_buyback_fee_recipient,
615 ix.associated_quote_buyback_fee_recipient,
616 );
617 fill_pk(&mut log.associated_quote_bonding_curve, ix.associated_quote_bonding_curve);
618 fill_pk(&mut log.associated_quote_user, ix.associated_quote_user);
619 fill_pk(&mut log.associated_creator_vault, ix.associated_creator_vault);
620 fill_pk(&mut log.sharing_config, ix.sharing_config);
621 fill_pk(&mut log.event_authority, ix.event_authority);
622 fill_pk(&mut log.program, ix.program);
623 fill_pk(&mut log.global_volume_accumulator, ix.global_volume_accumulator);
624 fill_pk(&mut log.user_volume_accumulator, ix.user_volume_accumulator);
625 fill_pk(&mut log.associated_user_volume_accumulator, ix.associated_user_volume_accumulator);
626 fill_pk(&mut log.fee_config, ix.fee_config);
627 fill_pk(&mut log.fee_program, ix.fee_program);
628 if log.account.is_none() {
629 log.account = ix.account;
630 }
631 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
632 log.ix_name = ix.ix_name;
633 }
634 put_u64_if_nonzero(&mut log.amount, ix.amount);
635 put_u64_if_nonzero(&mut log.max_sol_cost, ix.max_sol_cost);
636 put_u64_if_nonzero(&mut log.min_sol_output, ix.min_sol_output);
637 put_u64_if_nonzero(&mut log.spendable_sol_in, ix.spendable_sol_in);
638 put_u64_if_nonzero(&mut log.spendable_quote_in, ix.spendable_quote_in);
639 put_u64_if_nonzero(&mut log.min_tokens_out, ix.min_tokens_out);
640 put_u64_if_nonzero(&mut log.quote_amount, ix.quote_amount);
641 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
642 put_u64_if_nonzero(&mut log.real_quote_reserves, ix.real_quote_reserves);
643 if !log.is_created_buy && ix.is_created_buy {
644 log.is_created_buy = true;
645 }
646}
647
648#[inline]
649fn merge_pumpfun_create_log_preferred(
650 log: &mut PumpFunCreateTokenEvent,
651 ix: PumpFunCreateTokenEvent,
652) {
653 fill_str_if_empty(&mut log.name, &ix.name);
654 fill_str_if_empty(&mut log.symbol, &ix.symbol);
655 fill_str_if_empty(&mut log.uri, &ix.uri);
656 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
657 fill_pk(&mut log.user, ix.user);
658 fill_pk(&mut log.creator, ix.creator);
659 fill_pk(&mut log.token_program, ix.token_program);
660 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
661 fill_pk(&mut log.quote_vault, ix.quote_vault);
662 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
663 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
664 if log.creator_fee_bps == 0 {
665 log.creator_fee_bps = ix.creator_fee_bps;
666 }
667 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
668 log.ix_name = ix.ix_name;
669 }
670 log.is_mayhem_mode |= ix.is_mayhem_mode;
671 log.is_cashback_enabled |= ix.is_cashback_enabled;
672 log.is_holder_reward |= ix.is_holder_reward;
673}
674
675#[inline]
676fn merge_pumpfun_create_v2_into_create_log_preferred(
677 log: &mut PumpFunCreateTokenEvent,
678 ix: PumpFunCreateV2TokenEvent,
679) {
680 fill_str_if_empty(&mut log.name, &ix.name);
681 fill_str_if_empty(&mut log.symbol, &ix.symbol);
682 fill_str_if_empty(&mut log.uri, &ix.uri);
683 fill_pk(&mut log.mint, ix.mint);
684 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
685 fill_pk(&mut log.user, ix.user);
686 fill_pk(&mut log.creator, ix.creator);
687 if log.timestamp == 0 && ix.timestamp != 0 {
688 log.timestamp = ix.timestamp;
689 }
690 put_u64_if_nonzero(&mut log.virtual_token_reserves, ix.virtual_token_reserves);
691 put_u64_if_nonzero(&mut log.virtual_sol_reserves, ix.virtual_sol_reserves);
692 put_u64_if_nonzero(&mut log.real_token_reserves, ix.real_token_reserves);
693 put_u64_if_nonzero(&mut log.token_total_supply, ix.token_total_supply);
694 fill_pk(&mut log.token_program, ix.token_program);
695 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
696 fill_pk(&mut log.quote_vault, ix.quote_vault);
697 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
698 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
699 if log.creator_fee_bps == 0 {
700 log.creator_fee_bps = ix.creator_fee_bps;
701 }
702 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
703 log.ix_name = ix.ix_name;
704 }
705 log.is_mayhem_mode |= ix.is_mayhem_mode;
706 log.is_cashback_enabled |= ix.is_cashback_enabled;
707 log.is_holder_reward |= ix.is_holder_reward;
708}
709
710#[inline]
711fn merge_pumpfun_create_into_create_v2_log_preferred(
712 log: &mut PumpFunCreateV2TokenEvent,
713 ix: PumpFunCreateTokenEvent,
714) {
715 fill_str_if_empty(&mut log.name, &ix.name);
716 fill_str_if_empty(&mut log.symbol, &ix.symbol);
717 fill_str_if_empty(&mut log.uri, &ix.uri);
718 fill_pk(&mut log.mint, ix.mint);
719 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
720 fill_pk(&mut log.user, ix.user);
721 fill_pk(&mut log.creator, ix.creator);
722 if log.timestamp == 0 && ix.timestamp != 0 {
723 log.timestamp = ix.timestamp;
724 }
725 put_u64_if_nonzero(&mut log.virtual_token_reserves, ix.virtual_token_reserves);
726 put_u64_if_nonzero(&mut log.virtual_sol_reserves, ix.virtual_sol_reserves);
727 put_u64_if_nonzero(&mut log.real_token_reserves, ix.real_token_reserves);
728 put_u64_if_nonzero(&mut log.token_total_supply, ix.token_total_supply);
729 fill_pk(&mut log.token_program, ix.token_program);
730 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
731 fill_pk(&mut log.quote_vault, ix.quote_vault);
732 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
733 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
734 if log.creator_fee_bps == 0 {
735 log.creator_fee_bps = ix.creator_fee_bps;
736 }
737 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
738 log.ix_name = ix.ix_name;
739 }
740 log.is_mayhem_mode |= ix.is_mayhem_mode;
741 log.is_cashback_enabled |= ix.is_cashback_enabled;
742 log.is_holder_reward |= ix.is_holder_reward;
743}
744
745#[inline]
746fn merge_pumpfun_create_v2_log_preferred(
747 log: &mut PumpFunCreateV2TokenEvent,
748 ix: PumpFunCreateV2TokenEvent,
749) {
750 fill_str_if_empty(&mut log.name, &ix.name);
751 fill_str_if_empty(&mut log.symbol, &ix.symbol);
752 fill_str_if_empty(&mut log.uri, &ix.uri);
753 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
754 fill_pk(&mut log.user, ix.user);
755 fill_pk(&mut log.creator, ix.creator);
756 fill_pk(&mut log.token_program, ix.token_program);
757 fill_pumpfun_quote_mint(&mut log.quote_mint, ix.quote_mint);
758 fill_pk(&mut log.quote_vault, ix.quote_vault);
759 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
760 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
761 if log.creator_fee_bps == 0 {
762 log.creator_fee_bps = ix.creator_fee_bps;
763 }
764 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
765 log.ix_name = ix.ix_name;
766 }
767 fill_pk(&mut log.mint_authority, ix.mint_authority);
768 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
769 fill_pk(&mut log.global, ix.global);
770 fill_pk(&mut log.system_program, ix.system_program);
771 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
772 fill_pk(&mut log.mayhem_program_id, ix.mayhem_program_id);
773 fill_pk(&mut log.global_params, ix.global_params);
774 fill_pk(&mut log.sol_vault, ix.sol_vault);
775 fill_pk(&mut log.mayhem_state, ix.mayhem_state);
776 fill_pk(&mut log.mayhem_token_vault, ix.mayhem_token_vault);
777 fill_pk(&mut log.event_authority, ix.event_authority);
778 fill_pk(&mut log.program, ix.program);
779 fill_pk(&mut log.observed_fee_recipient, ix.observed_fee_recipient);
780 log.is_holder_reward |= ix.is_holder_reward;
781}
782
783#[inline]
784fn merge_pumpfun_migrate_log_preferred(log: &mut PumpFunMigrateEvent, ix: PumpFunMigrateEvent) {
785 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
786 fill_pk(&mut log.pool, ix.pool);
787 fill_pk(&mut log.user, ix.user);
788}
789
790#[inline]
791fn merge_pumpswap_trade_log_preferred(log: &mut PumpSwapTradeEvent, ix: PumpSwapTradeEvent) {
792 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
793 log.ix_name = ix.ix_name;
794 }
795}
796
797#[inline]
798fn merge_pumpswap_buy_log_preferred(log: &mut PumpSwapBuyEvent, ix: PumpSwapBuyEvent) {
799 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
800 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
801 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
802 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
803 fill_pk(&mut log.coin_creator, ix.coin_creator);
804 fill_pk(&mut log.base_mint, ix.base_mint);
805 fill_pk(&mut log.quote_mint, ix.quote_mint);
806 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
807 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
808 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
809 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
810 fill_pk(&mut log.base_token_program, ix.base_token_program);
811 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
812 fill_pk(&mut log.pool_v2, ix.pool_v2);
813 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
814 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
815 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
816 log.ix_name = ix.ix_name;
817 }
818}
819
820#[inline]
821fn merge_pumpswap_sell_log_preferred(log: &mut PumpSwapSellEvent, ix: PumpSwapSellEvent) {
822 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
823 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
824 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
825 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
826 fill_pk(&mut log.coin_creator, ix.coin_creator);
827 fill_pk(&mut log.base_mint, ix.base_mint);
828 fill_pk(&mut log.quote_mint, ix.quote_mint);
829 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
830 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
831 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
832 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
833 fill_pk(&mut log.base_token_program, ix.base_token_program);
834 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
835 fill_pk(&mut log.pool_v2, ix.pool_v2);
836 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
837 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
838}
839
840#[inline]
841fn merge_raydium_clmm_swap_log_preferred(log: &mut RaydiumClmmSwapEvent, ix: RaydiumClmmSwapEvent) {
842 fill_pk(&mut log.token_account_0, ix.token_account_0);
843 fill_pk(&mut log.token_account_1, ix.token_account_1);
844 fill_pk(&mut log.sender, ix.sender);
845 fill_pk(&mut log.amm_config, ix.amm_config);
846 fill_pk(&mut log.input_vault, ix.input_vault);
847 fill_pk(&mut log.output_vault, ix.output_vault);
848 fill_pk(&mut log.observation_state, ix.observation_state);
849 fill_pk(&mut log.input_mint, ix.input_mint);
850 fill_pk(&mut log.output_mint, ix.output_mint);
851 if log.tick_array_bitmap_extension.is_none() {
852 log.tick_array_bitmap_extension = ix.tick_array_bitmap_extension;
853 }
854 if log.tick_arrays.is_empty() && !ix.tick_arrays.is_empty() {
855 log.tick_arrays = ix.tick_arrays;
856 }
857}
858
859#[inline]
860fn merge_raydium_amm_v4_swap_log_preferred(
861 log: &mut RaydiumAmmV4SwapEvent,
862 ix: RaydiumAmmV4SwapEvent,
863) {
864 fill_pk(&mut log.token_program, ix.token_program);
865 fill_pk(&mut log.amm_authority, ix.amm_authority);
866 fill_pk(&mut log.amm_open_orders, ix.amm_open_orders);
867 if let Some(ref o) = ix.amm_target_orders {
868 if log.amm_target_orders.is_none() {
869 log.amm_target_orders = Some(*o);
870 }
871 }
872 fill_pk(&mut log.pool_coin_token_account, ix.pool_coin_token_account);
873 fill_pk(&mut log.pool_pc_token_account, ix.pool_pc_token_account);
874 fill_pk(&mut log.serum_program, ix.serum_program);
875 fill_pk(&mut log.serum_market, ix.serum_market);
876 fill_pk(&mut log.serum_bids, ix.serum_bids);
877 fill_pk(&mut log.serum_asks, ix.serum_asks);
878 fill_pk(&mut log.serum_event_queue, ix.serum_event_queue);
879 fill_pk(&mut log.serum_coin_vault_account, ix.serum_coin_vault_account);
880 fill_pk(&mut log.serum_pc_vault_account, ix.serum_pc_vault_account);
881 fill_pk(&mut log.serum_vault_signer, ix.serum_vault_signer);
882 fill_pk(&mut log.user_source_token_account, ix.user_source_token_account);
883 fill_pk(&mut log.user_destination_token_account, ix.user_destination_token_account);
884 fill_pk(&mut log.user_source_owner, ix.user_source_owner);
885 fill_pk(&mut log.amm, ix.amm);
886}
887
888#[inline]
889fn merge_pumpswap_create_pool_log_preferred(
890 log: &mut PumpSwapCreatePoolEvent,
891 ix: PumpSwapCreatePoolEvent,
892) {
893 fill_pk(&mut log.creator, ix.creator);
894 fill_pk(&mut log.pool, ix.pool);
895 fill_pk(&mut log.lp_mint, ix.lp_mint);
896 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
897 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
898 fill_pk(&mut log.coin_creator, ix.coin_creator);
899 log.is_mayhem_mode |= ix.is_mayhem_mode;
900 log.is_cashback_coin |= ix.is_cashback_coin;
901 if log.creator_fee_bps == 0 {
902 log.creator_fee_bps = ix.creator_fee_bps;
903 }
904 log.can_edit_creator_fee |= ix.can_edit_creator_fee;
905 log.is_holder_reward |= ix.is_holder_reward;
906}
907
908#[inline]
909fn merge_pumpswap_liquidity_added_log_preferred(
910 log: &mut PumpSwapLiquidityAdded,
911 ix: PumpSwapLiquidityAdded,
912) {
913 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
914 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
915 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
916}
917
918#[inline]
919fn merge_pumpswap_liquidity_removed_log_preferred(
920 log: &mut PumpSwapLiquidityRemoved,
921 ix: PumpSwapLiquidityRemoved,
922) {
923 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
924 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
925 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
926}
927
928#[inline]
929fn merge_raydium_launchlab_pool_create_log_preferred(
930 log: &mut RaydiumLaunchlabPoolCreateEvent,
931 ix: RaydiumLaunchlabPoolCreateEvent,
932) {
933 fill_pk(&mut log.payer, ix.payer);
934 fill_pk(&mut log.creator, ix.creator);
935 fill_pk(&mut log.global_config, ix.global_config);
936 fill_pk(&mut log.platform_config, ix.platform_config);
937 fill_pk(&mut log.base_mint, ix.base_mint);
938 fill_pk(&mut log.quote_mint, ix.quote_mint);
939 fill_pk(&mut log.base_vault, ix.base_vault);
940 fill_pk(&mut log.quote_vault, ix.quote_vault);
941 fill_pk(&mut log.base_token_program, ix.base_token_program);
942 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
943 fill_str_if_empty(&mut log.base_mint_param.name, &ix.base_mint_param.name);
944 fill_str_if_empty(&mut log.base_mint_param.symbol, &ix.base_mint_param.symbol);
945 fill_str_if_empty(&mut log.base_mint_param.uri, &ix.base_mint_param.uri);
946}
947
948#[inline]
949fn merge_raydium_launchlab_migrate_amm_log_preferred(
950 log: &mut RaydiumLaunchlabMigrateAmmEvent,
951 ix: RaydiumLaunchlabMigrateAmmEvent,
952) {
953 fill_pk(&mut log.old_pool, ix.old_pool);
954 fill_pk(&mut log.new_pool, ix.new_pool);
955 fill_pk(&mut log.user, ix.user);
956}
957
958#[inline]
959fn merge_raydium_launchlab_trade_log_preferred(
960 log: &mut RaydiumLaunchlabTradeEvent,
961 ix: RaydiumLaunchlabTradeEvent,
962) {
963 fill_pk(&mut log.user, ix.user);
964 fill_pk(&mut log.global_config, ix.global_config);
965 fill_pk(&mut log.platform_config, ix.platform_config);
966 fill_pk(&mut log.user_base_token, ix.user_base_token);
967 fill_pk(&mut log.user_quote_token, ix.user_quote_token);
968 fill_pk(&mut log.base_vault, ix.base_vault);
969 fill_pk(&mut log.quote_vault, ix.quote_vault);
970 fill_pk(&mut log.base_mint, ix.base_mint);
971 fill_pk(&mut log.quote_mint, ix.quote_mint);
972 fill_pk(&mut log.base_token_program, ix.base_token_program);
973 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
974 fill_pk(&mut log.system_program, ix.system_program);
975 fill_pk(&mut log.platform_associated_account, ix.platform_associated_account);
976 fill_pk(&mut log.creator_associated_account, ix.creator_associated_account);
977}
978
979#[inline]
980fn merge_meteora_dlmm_swap_log_preferred(log: &mut MeteoraDlmmSwapEvent, ix: MeteoraDlmmSwapEvent) {
981 fill_pk(&mut log.user_token_in, ix.user_token_in);
982 fill_pk(&mut log.user_token_out, ix.user_token_out);
983 if log.min_amount_out == 0 {
984 log.min_amount_out = ix.min_amount_out;
985 }
986}
987
988pub fn merge_grpc_instruction_into_log(log: &mut DexEvent, ix: DexEvent) {
994 use DexEvent::*;
995 match log {
996 PumpFunTrade(l) => {
997 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
998 merge_pumpfun_trade_log_preferred(l, i);
999 }
1000 }
1001 PumpFunBuy(l) => {
1002 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1003 merge_pumpfun_trade_log_preferred(l, i);
1004 }
1005 }
1006 PumpFunSell(l) => {
1007 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1008 merge_pumpfun_trade_log_preferred(l, i);
1009 }
1010 }
1011 PumpFunBuyExactSolIn(l) => {
1012 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
1013 merge_pumpfun_trade_log_preferred(l, i);
1014 }
1015 }
1016 PumpFunCreate(l) => match ix {
1017 DexEvent::PumpFunCreate(i) => merge_pumpfun_create_log_preferred(l, i),
1018 DexEvent::PumpFunCreateV2(i) => merge_pumpfun_create_v2_into_create_log_preferred(l, i),
1019 _ => {}
1020 },
1021 PumpFunCreateV2(l) => match ix {
1022 DexEvent::PumpFunCreate(i) => merge_pumpfun_create_into_create_v2_log_preferred(l, i),
1023 DexEvent::PumpFunCreateV2(i) => merge_pumpfun_create_v2_log_preferred(l, i),
1024 _ => {}
1025 },
1026 PumpFunMigrate(l) => {
1027 if let DexEvent::PumpFunMigrate(i) = ix {
1028 merge_pumpfun_migrate_log_preferred(l, i);
1029 }
1030 }
1031 PumpSwapTrade(l) => {
1032 if let PumpSwapTrade(i) = ix {
1033 merge_pumpswap_trade_log_preferred(l, i);
1034 }
1035 }
1036 PumpSwapBuy(l) => {
1037 if let PumpSwapBuy(i) = ix {
1038 merge_pumpswap_buy_log_preferred(l, i);
1039 }
1040 }
1041 PumpSwapSell(l) => {
1042 if let PumpSwapSell(i) = ix {
1043 merge_pumpswap_sell_log_preferred(l, i);
1044 }
1045 }
1046 RaydiumClmmSwap(l) => {
1047 if let RaydiumClmmSwap(i) = ix {
1048 merge_raydium_clmm_swap_log_preferred(l, i);
1049 }
1050 }
1051 RaydiumAmmV4Swap(l) => {
1052 if let RaydiumAmmV4Swap(i) = ix {
1053 merge_raydium_amm_v4_swap_log_preferred(l, i);
1054 }
1055 }
1056 RaydiumLaunchlabTrade(l) => {
1057 if let RaydiumLaunchlabTrade(i) = ix {
1058 merge_raydium_launchlab_trade_log_preferred(l, i);
1059 }
1060 }
1061 RaydiumLaunchlabPoolCreate(l) => {
1062 if let RaydiumLaunchlabPoolCreate(i) = ix {
1063 merge_raydium_launchlab_pool_create_log_preferred(l, i);
1064 }
1065 }
1066 RaydiumLaunchlabMigrateAmm(l) => {
1067 if let RaydiumLaunchlabMigrateAmm(i) = ix {
1068 merge_raydium_launchlab_migrate_amm_log_preferred(l, i);
1069 }
1070 }
1071 PumpSwapCreatePool(l) => {
1072 if let PumpSwapCreatePool(i) = ix {
1073 merge_pumpswap_create_pool_log_preferred(l, i);
1074 }
1075 }
1076 PumpSwapLiquidityAdded(l) => {
1077 if let PumpSwapLiquidityAdded(i) = ix {
1078 merge_pumpswap_liquidity_added_log_preferred(l, i);
1079 }
1080 }
1081 PumpSwapLiquidityRemoved(l) => {
1082 if let PumpSwapLiquidityRemoved(i) = ix {
1083 merge_pumpswap_liquidity_removed_log_preferred(l, i);
1084 }
1085 }
1086 MeteoraDlmmSwap(l) => {
1087 if let MeteoraDlmmSwap(i) = ix {
1088 merge_meteora_dlmm_swap_log_preferred(l, i);
1089 }
1090 }
1091 _ => {}
1092 }
1093}
1094
1095#[inline]
1096fn pumpfun_trade_from_ix_variant(ix: DexEvent) -> Option<PumpFunTradeEvent> {
1097 match ix {
1098 DexEvent::PumpFunTrade(t)
1099 | DexEvent::PumpFunBuy(t)
1100 | DexEvent::PumpFunSell(t)
1101 | DexEvent::PumpFunBuyExactSolIn(t) => Some(t),
1102 _ => None,
1103 }
1104}
1105
1106#[cfg(test)]
1107mod tests {
1108 use super::*;
1109
1110 #[test]
1111 fn incompatible_merge_leaves_both_events_unchanged() {
1112 let mut base = DexEvent::Error("base".to_string());
1113 let inner = DexEvent::Error("inner".to_string());
1114 let mut unmerged = None;
1115
1116 assert!(!try_merge_events(&mut base, inner, &mut unmerged));
1117 assert!(matches!(base, DexEvent::Error(ref message) if message == "base"));
1118 assert!(matches!(unmerged, Some(DexEvent::Error(ref message)) if message == "inner"));
1119 }
1120 use solana_sdk::{pubkey::Pubkey, signature::Signature};
1121
1122 fn dlmm_swap(min_amount_out: u64, amount_out: u64) -> MeteoraDlmmSwapEvent {
1123 MeteoraDlmmSwapEvent {
1124 metadata: EventMetadata::default(),
1125 token_x_mint: Pubkey::default(),
1126 token_y_mint: Pubkey::default(),
1127 user_token_in: Pubkey::new_unique(),
1128 user_token_out: Pubkey::new_unique(),
1129 min_amount_out,
1130 pool: Pubkey::new_unique(),
1131 from: Pubkey::new_unique(),
1132 start_bin_id: 0,
1133 end_bin_id: 0,
1134 amount_in: 200,
1135 amount_out,
1136 swap_for_y: true,
1137 fee: 1,
1138 protocol_fee: 0,
1139 fee_bps: 25,
1140 host_fee: 0,
1141 ..Default::default()
1142 }
1143 }
1144
1145 #[test]
1146 fn dlmm_event_merge_keeps_instruction_threshold_and_executed_output() {
1147 let base_event = dlmm_swap(100, 0);
1148 let expected_user_token_in = base_event.user_token_in;
1149 let expected_user_token_out = base_event.user_token_out;
1150 let mut inner_event = dlmm_swap(0, 125);
1151 inner_event.user_token_in = Pubkey::default();
1152 inner_event.user_token_out = Pubkey::default();
1153 let mut base = DexEvent::MeteoraDlmmSwap(base_event);
1154 let inner = DexEvent::MeteoraDlmmSwap(inner_event);
1155
1156 assert!(try_merge_events(&mut base, inner, &mut None));
1157 let DexEvent::MeteoraDlmmSwap(event) = base else { panic!("swap") };
1158 assert_eq!(event.min_amount_out, 100);
1159 assert_eq!(event.amount_out, 125);
1160 assert_eq!(event.user_token_in, expected_user_token_in);
1161 assert_eq!(event.user_token_out, expected_user_token_out);
1162 }
1163
1164 #[test]
1165 fn grpc_dlmm_merge_keeps_log_output_and_adds_instruction_threshold() {
1166 let mut log_event = dlmm_swap(0, 125);
1167 log_event.user_token_in = Pubkey::default();
1168 log_event.user_token_out = Pubkey::default();
1169 let instruction_event = dlmm_swap(100, 0);
1170 let expected_user_token_in = instruction_event.user_token_in;
1171 let expected_user_token_out = instruction_event.user_token_out;
1172 let mut log = DexEvent::MeteoraDlmmSwap(log_event);
1173 let instruction = DexEvent::MeteoraDlmmSwap(instruction_event);
1174
1175 merge_grpc_instruction_into_log(&mut log, instruction);
1176 let DexEvent::MeteoraDlmmSwap(event) = log else { panic!("swap") };
1177 assert_eq!(event.min_amount_out, 100);
1178 assert_eq!(event.amount_out, 125);
1179 assert_eq!(event.user_token_in, expected_user_token_in);
1180 assert_eq!(event.user_token_out, expected_user_token_out);
1181 }
1182
1183 #[test]
1184 fn test_merge_pumpfun_trade() {
1185 let metadata = EventMetadata {
1186 signature: Signature::default(),
1187 slot: 100,
1188 tx_index: 1,
1189 block_time_us: 1000,
1190 grpc_recv_us: 2000,
1191 recent_blockhash: None,
1192 };
1193
1194 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1196 metadata: metadata.clone(),
1197 bonding_curve: Pubkey::new_unique(),
1198 associated_bonding_curve: Pubkey::new_unique(),
1199 ..Default::default()
1200 });
1201
1202 let inner = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1204 metadata: metadata.clone(),
1205 mint: Pubkey::new_unique(),
1206 sol_amount: 1000,
1207 token_amount: 2000,
1208 is_buy: true,
1209 user: Pubkey::new_unique(),
1210 holder_rewards_bps: 300,
1211 holder_rewards: 400,
1212 ..Default::default()
1213 });
1214
1215 merge_events(&mut base, inner);
1217
1218 if let DexEvent::PumpFunTrade(trade) = base {
1220 assert_eq!(trade.sol_amount, 1000);
1221 assert_eq!(trade.token_amount, 2000);
1222 assert!(trade.is_buy);
1223 assert_eq!(trade.holder_rewards_bps, 300);
1224 assert_eq!(trade.holder_rewards, 400);
1225 assert_ne!(trade.bonding_curve, Pubkey::default());
1227 assert_ne!(trade.associated_bonding_curve, Pubkey::default());
1228 } else {
1229 panic!("Expected PumpFunTrade event");
1230 }
1231 }
1232
1233 #[test]
1234 fn merge_pumpfun_trade_non_leg_propagates_holder_rewards() {
1235 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent::default());
1236 let inner = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1237 holder_rewards_bps: 300,
1238 holder_rewards: 400,
1239 ..Default::default()
1240 });
1241
1242 merge_events(&mut base, inner);
1243
1244 let DexEvent::PumpFunTrade(trade) = base else {
1245 panic!("expected PumpFunTrade event");
1246 };
1247 assert_eq!(trade.holder_rewards_bps, 300);
1248 assert_eq!(trade.holder_rewards, 400);
1249 }
1250
1251 #[test]
1252 fn merge_preserves_instruction_context_when_log_tail_is_absent() {
1253 let metadata = EventMetadata {
1254 signature: Signature::default(),
1255 slot: 100,
1256 tx_index: 1,
1257 block_time_us: 1000,
1258 grpc_recv_us: 2000,
1259 recent_blockhash: None,
1260 };
1261 let quote_mint = Pubkey::new_unique();
1262 let associated_quote_user = Pubkey::new_unique();
1263
1264 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1265 metadata: metadata.clone(),
1266 ix_name: "buy_exact_quote_in".to_string(),
1267 quote_mint,
1268 spendable_quote_in: 1_000,
1269 min_tokens_out: 2_000,
1270 associated_quote_user,
1271 ..Default::default()
1272 });
1273
1274 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1275 metadata,
1276 sol_amount: 1_000,
1277 token_amount: 2_000,
1278 is_buy: true,
1279 ..Default::default()
1280 });
1281
1282 merge_events(&mut base, inner);
1283
1284 if let DexEvent::PumpFunTrade(t) = base {
1285 assert_eq!(t.sol_amount, 1_000);
1286 assert_eq!(t.token_amount, 2_000);
1287 assert_eq!(t.ix_name, "buy_exact_quote_in");
1288 assert_eq!(t.quote_mint, quote_mint);
1289 assert_eq!(t.spendable_quote_in, 1_000);
1290 assert_eq!(t.min_tokens_out, 2_000);
1291 assert_eq!(t.associated_quote_user, associated_quote_user);
1292 } else {
1293 panic!("Expected PumpFunTrade event");
1294 }
1295 }
1296
1297 #[test]
1298 fn merge_replaces_sol_quote_sentinel_with_real_quote_mint() {
1299 let quote_mint = Pubkey::new_unique();
1300 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1301 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1302 ..Default::default()
1303 });
1304
1305 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent { quote_mint, ..Default::default() });
1306
1307 merge_events(&mut base, inner);
1308
1309 if let DexEvent::PumpFunTrade(t) = base {
1310 assert_eq!(t.quote_mint, quote_mint);
1311 } else {
1312 panic!("Expected PumpFunTrade event");
1313 }
1314 }
1315
1316 #[test]
1317 fn merge_replaces_sol_quote_sentinel_with_wsol_mint() {
1318 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1319 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1320 ..Default::default()
1321 });
1322
1323 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1324 quote_mint: PUMPFUN_WSOL_QUOTE_MINT,
1325 ..Default::default()
1326 });
1327
1328 merge_events(&mut base, inner);
1329
1330 if let DexEvent::PumpFunTrade(t) = base {
1331 assert_eq!(t.quote_mint, PUMPFUN_WSOL_QUOTE_MINT);
1332 } else {
1333 panic!("Expected PumpFunTrade event");
1334 }
1335 }
1336
1337 #[test]
1338 fn test_can_merge() {
1339 let metadata = EventMetadata {
1340 signature: Signature::default(),
1341 slot: 100,
1342 tx_index: 1,
1343 block_time_us: 1000,
1344 grpc_recv_us: 2000,
1345 recent_blockhash: None,
1346 };
1347
1348 let base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1349 metadata: metadata.clone(),
1350 ..Default::default()
1351 });
1352
1353 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1354 metadata: metadata.clone(),
1355 ..Default::default()
1356 });
1357
1358 assert!(can_merge(&base, &inner));
1360
1361 let different_sig = DexEvent::PumpFunTrade(PumpFunTradeEvent {
1363 metadata: EventMetadata { signature: Signature::new_unique(), ..metadata },
1364 ..Default::default()
1365 });
1366
1367 assert!(!can_merge(&base, &different_sig));
1368 }
1369
1370 #[test]
1371 fn dlmm_position_event_keeps_instruction_only_fields() {
1372 let pool = Pubkey::new_unique();
1373 let position = Pubkey::new_unique();
1374 let owner = Pubkey::new_unique();
1375 let mut base = DexEvent::MeteoraDlmmCreatePosition(MeteoraDlmmCreatePositionEvent {
1376 metadata: EventMetadata::default(),
1377 pool,
1378 position,
1379 owner,
1380 lower_bin_id: -42,
1381 width: 70,
1382 });
1383 let inner = DexEvent::MeteoraDlmmCreatePosition(MeteoraDlmmCreatePositionEvent {
1384 metadata: EventMetadata::default(),
1385 pool,
1386 position,
1387 owner,
1388 lower_bin_id: 0,
1389 width: 0,
1390 });
1391
1392 assert!(try_merge_events(&mut base, inner, &mut None));
1393 let DexEvent::MeteoraDlmmCreatePosition(event) = base else { panic!("position") };
1394 assert_eq!(event.lower_bin_id, -42);
1395 assert_eq!(event.width, 70);
1396 }
1397
1398 #[test]
1399 fn grpc_merge_fills_fee_recipient_from_ix_when_log_default() {
1400 let metadata = EventMetadata {
1401 signature: Signature::default(),
1402 slot: 1,
1403 tx_index: 0,
1404 block_time_us: 0,
1405 grpc_recv_us: 0,
1406 recent_blockhash: None,
1407 };
1408 let fr = Pubkey::new_unique();
1409 let log_t =
1410 PumpFunTradeEvent { metadata: metadata.clone(), sol_amount: 50, ..Default::default() };
1411 let mut ix_t = log_t.clone();
1412 ix_t.fee_recipient = fr;
1413 ix_t.sol_amount = 777;
1414 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1415 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1416 match log_ev {
1417 DexEvent::PumpFunTrade(t) => {
1418 assert_eq!(t.fee_recipient, fr);
1419 assert_eq!(t.sol_amount, 50);
1420 }
1421 _ => panic!("expected trade"),
1422 }
1423 }
1424
1425 #[test]
1426 fn grpc_merge_keeps_log_trade_fields() {
1427 let metadata = EventMetadata {
1428 signature: Signature::default(),
1429 slot: 1,
1430 tx_index: 0,
1431 block_time_us: 0,
1432 grpc_recv_us: 0,
1433 recent_blockhash: None,
1434 };
1435 let log_t = PumpFunTradeEvent {
1436 metadata: metadata.clone(),
1437 mayhem_mode: true,
1438 sol_amount: 100,
1439 ..Default::default()
1440 };
1441 let mut ix_t = log_t.clone();
1442 ix_t.mayhem_mode = false;
1443 ix_t.sol_amount = 999;
1444
1445 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1446 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1447 match log_ev {
1448 DexEvent::PumpFunTrade(t) => {
1449 assert!(t.mayhem_mode);
1450 assert_eq!(t.sol_amount, 100);
1451 }
1452 _ => panic!("variant preserved"),
1453 }
1454 }
1455
1456 #[test]
1457 fn pumpfun_create_merge_propagates_holder_rewards_fields() {
1458 let mut base = DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1459 creator_fee_bps: 125,
1460 ..Default::default()
1461 });
1462 let inner = DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1463 creator_fee_bps: 300,
1464 is_holder_reward: true,
1465 ..Default::default()
1466 });
1467
1468 merge_events(&mut base, inner);
1469
1470 let DexEvent::PumpFunCreate(event) = base else { panic!("create") };
1471 assert_eq!(event.creator_fee_bps, 300);
1472 assert!(event.is_holder_reward);
1473 }
1474
1475 #[test]
1476 fn grpc_create_merge_keeps_log_fee_and_adds_instruction_holder_reward() {
1477 let mut log = DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1478 creator_fee_bps: 300,
1479 ..Default::default()
1480 });
1481 let instruction = DexEvent::PumpFunCreateV2(PumpFunCreateV2TokenEvent {
1482 creator_fee_bps: 500,
1483 is_holder_reward: true,
1484 ..Default::default()
1485 });
1486
1487 merge_grpc_instruction_into_log(&mut log, instruction);
1488
1489 let DexEvent::PumpFunCreate(event) = log else { panic!("create") };
1490 assert_eq!(event.creator_fee_bps, 300);
1491 assert!(event.is_holder_reward);
1492 }
1493}