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