1use solana_sdk::pubkey::Pubkey;
13
14use crate::core::events::*;
15
16#[inline(always)]
28pub fn merge_events(base: &mut DexEvent, inner: DexEvent) {
29 use DexEvent::*;
30
31 match (base, inner) {
32 (PumpFunTrade(b), PumpFunTrade(i))
34 | (PumpFunTrade(b), PumpFunBuy(i))
35 | (PumpFunTrade(b), PumpFunSell(i))
36 | (PumpFunTrade(b), PumpFunBuyExactSolIn(i))
37 | (PumpFunBuy(b), PumpFunTrade(i))
38 | (PumpFunBuy(b), PumpFunBuy(i))
39 | (PumpFunSell(b), PumpFunTrade(i))
40 | (PumpFunSell(b), PumpFunSell(i))
41 | (PumpFunBuyExactSolIn(b), PumpFunTrade(i))
42 | (PumpFunBuyExactSolIn(b), PumpFunBuyExactSolIn(i)) => merge_pumpfun_trade(b, i),
43
44 (PumpFunCreate(b), PumpFunCreate(i)) => merge_pumpfun_create(b, i),
45 (PumpFunCreateV2(b), PumpFunCreateV2(i)) => merge_pumpfun_create_v2(b, i),
46 (PumpFunMigrate(b), PumpFunMigrate(i)) => merge_pumpfun_migrate(b, i),
47 (PumpFunMigrateBondingCurveCreator(b), PumpFunMigrateBondingCurveCreator(i)) => {
48 merge_generic(b, i)
49 }
50
51 (PumpFeesCreateFeeSharingConfig(b), PumpFeesCreateFeeSharingConfig(i)) => {
53 merge_generic(b, i)
54 }
55 (PumpFeesInitializeFeeConfig(b), PumpFeesInitializeFeeConfig(i)) => merge_generic(b, i),
56 (PumpFeesResetFeeSharingConfig(b), PumpFeesResetFeeSharingConfig(i)) => merge_generic(b, i),
57 (PumpFeesRevokeFeeSharingAuthority(b), PumpFeesRevokeFeeSharingAuthority(i)) => {
58 merge_generic(b, i)
59 }
60 (PumpFeesTransferFeeSharingAuthority(b), PumpFeesTransferFeeSharingAuthority(i)) => {
61 merge_generic(b, i)
62 }
63 (PumpFeesUpdateAdmin(b), PumpFeesUpdateAdmin(i)) => merge_generic(b, i),
64 (PumpFeesUpdateFeeConfig(b), PumpFeesUpdateFeeConfig(i)) => merge_generic(b, i),
65 (PumpFeesUpdateFeeShares(b), PumpFeesUpdateFeeShares(i)) => merge_generic(b, i),
66 (PumpFeesUpsertFeeTiers(b), PumpFeesUpsertFeeTiers(i)) => merge_generic(b, i),
67
68 (PumpSwapTrade(b), PumpSwapTrade(i)) => merge_generic(b, i),
70 (PumpSwapBuy(b), PumpSwapBuy(i)) => merge_pumpswap_buy(b, i),
71 (PumpSwapSell(b), PumpSwapSell(i)) => merge_pumpswap_sell(b, i),
72 (PumpSwapCreatePool(b), PumpSwapCreatePool(i)) => merge_generic(b, i),
73 (PumpSwapLiquidityAdded(b), PumpSwapLiquidityAdded(i)) => merge_generic(b, i),
74 (PumpSwapLiquidityRemoved(b), PumpSwapLiquidityRemoved(i)) => merge_generic(b, i),
75
76 (RaydiumClmmSwap(b), RaydiumClmmSwap(i)) => merge_generic(b, i),
78 (RaydiumClmmIncreaseLiquidity(b), RaydiumClmmIncreaseLiquidity(i)) => merge_generic(b, i),
79 (RaydiumClmmDecreaseLiquidity(b), RaydiumClmmDecreaseLiquidity(i)) => merge_generic(b, i),
80 (RaydiumClmmCreatePool(b), RaydiumClmmCreatePool(i)) => merge_generic(b, i),
81 (RaydiumClmmOpenPosition(b), RaydiumClmmOpenPosition(i)) => merge_generic(b, i),
82 (RaydiumClmmClosePosition(b), RaydiumClmmClosePosition(i)) => merge_generic(b, i),
83 (RaydiumClmmOpenPositionWithTokenExtNft(b), RaydiumClmmOpenPositionWithTokenExtNft(i)) => {
84 merge_generic(b, i)
85 }
86 (RaydiumClmmCollectFee(b), RaydiumClmmCollectFee(i)) => merge_generic(b, i),
87
88 (RaydiumCpmmSwap(b), RaydiumCpmmSwap(i)) => merge_generic(b, i),
90 (RaydiumCpmmDeposit(b), RaydiumCpmmDeposit(i)) => merge_generic(b, i),
91 (RaydiumCpmmWithdraw(b), RaydiumCpmmWithdraw(i)) => merge_generic(b, i),
92 (RaydiumCpmmInitialize(b), RaydiumCpmmInitialize(i)) => merge_generic(b, i),
93
94 (RaydiumAmmV4Swap(b), RaydiumAmmV4Swap(i)) => merge_generic(b, i),
96 (RaydiumAmmV4Deposit(b), RaydiumAmmV4Deposit(i)) => merge_generic(b, i),
97 (RaydiumAmmV4Withdraw(b), RaydiumAmmV4Withdraw(i)) => merge_generic(b, i),
98 (RaydiumAmmV4Initialize2(b), RaydiumAmmV4Initialize2(i)) => merge_generic(b, i),
99 (RaydiumAmmV4WithdrawPnl(b), RaydiumAmmV4WithdrawPnl(i)) => merge_generic(b, i),
100
101 (OrcaWhirlpoolSwap(b), OrcaWhirlpoolSwap(i)) => merge_generic(b, i),
103 (OrcaWhirlpoolLiquidityIncreased(b), OrcaWhirlpoolLiquidityIncreased(i)) => {
104 merge_generic(b, i)
105 }
106 (OrcaWhirlpoolLiquidityDecreased(b), OrcaWhirlpoolLiquidityDecreased(i)) => {
107 merge_generic(b, i)
108 }
109 (OrcaWhirlpoolPoolInitialized(b), OrcaWhirlpoolPoolInitialized(i)) => merge_generic(b, i),
110
111 (MeteoraPoolsSwap(b), MeteoraPoolsSwap(i)) => merge_generic(b, i),
113 (MeteoraPoolsAddLiquidity(b), MeteoraPoolsAddLiquidity(i)) => merge_generic(b, i),
114 (MeteoraPoolsRemoveLiquidity(b), MeteoraPoolsRemoveLiquidity(i)) => merge_generic(b, i),
115 (MeteoraPoolsBootstrapLiquidity(b), MeteoraPoolsBootstrapLiquidity(i)) => {
116 merge_generic(b, i)
117 }
118 (MeteoraPoolsPoolCreated(b), MeteoraPoolsPoolCreated(i)) => merge_generic(b, i),
119 (MeteoraPoolsSetPoolFees(b), MeteoraPoolsSetPoolFees(i)) => merge_generic(b, i),
120
121 (MeteoraDammV2Swap(b), MeteoraDammV2Swap(i)) => merge_generic(b, i),
123 (MeteoraDammV2AddLiquidity(b), MeteoraDammV2AddLiquidity(i)) => merge_generic(b, i),
124 (MeteoraDammV2RemoveLiquidity(b), MeteoraDammV2RemoveLiquidity(i)) => merge_generic(b, i),
125 (MeteoraDammV2CreatePosition(b), MeteoraDammV2CreatePosition(i)) => merge_generic(b, i),
126 (MeteoraDammV2ClosePosition(b), MeteoraDammV2ClosePosition(i)) => merge_generic(b, i),
127
128 (MeteoraDlmmSwap(b), MeteoraDlmmSwap(i)) => merge_generic(b, i),
130 (MeteoraDlmmAddLiquidity(b), MeteoraDlmmAddLiquidity(i)) => merge_generic(b, i),
131 (MeteoraDlmmRemoveLiquidity(b), MeteoraDlmmRemoveLiquidity(i)) => merge_generic(b, i),
132 (MeteoraDlmmInitializePool(b), MeteoraDlmmInitializePool(i)) => merge_generic(b, i),
133 (MeteoraDlmmInitializeBinArray(b), MeteoraDlmmInitializeBinArray(i)) => merge_generic(b, i),
134 (MeteoraDlmmCreatePosition(b), MeteoraDlmmCreatePosition(i)) => merge_generic(b, i),
135 (MeteoraDlmmClosePosition(b), MeteoraDlmmClosePosition(i)) => merge_generic(b, i),
136 (MeteoraDlmmClaimFee(b), MeteoraDlmmClaimFee(i)) => merge_generic(b, i),
137
138 (BonkTrade(b), BonkTrade(i)) => merge_generic(b, i),
140 (BonkPoolCreate(b), BonkPoolCreate(i)) => merge_generic(b, i),
141 (BonkMigrateAmm(b), BonkMigrateAmm(i)) => merge_generic(b, i),
142
143 _ => {}
145 }
146}
147
148#[inline(always)]
155fn merge_generic<T>(base: &mut T, inner: T) {
156 *base = inner;
157}
158
159#[inline(always)]
164fn put_pk_if_set(to: &mut Pubkey, from: Pubkey) {
165 if from != Pubkey::default() {
166 *to = from;
167 }
168}
169
170#[inline(always)]
171fn put_u64_if_nonzero(to: &mut u64, from: u64) {
172 if from != 0 {
173 *to = from;
174 }
175}
176
177#[inline(always)]
178fn put_i64_if_nonzero(to: &mut i64, from: i64) {
179 if from != 0 {
180 *to = from;
181 }
182}
183
184#[inline(always)]
194fn merge_pumpfun_trade(base: &mut PumpFunTradeEvent, inner: PumpFunTradeEvent) {
195 let leg = inner.sol_amount != 0 || inner.token_amount != 0;
196
197 put_pk_if_set(&mut base.mint, inner.mint);
198 put_pk_if_set(&mut base.user, inner.user);
199 put_pk_if_set(&mut base.fee_recipient, inner.fee_recipient);
200 put_pk_if_set(&mut base.creator, inner.creator);
201
202 if leg {
203 base.sol_amount = inner.sol_amount;
204 base.token_amount = inner.token_amount;
205 base.is_buy = inner.is_buy;
206 base.timestamp = inner.timestamp;
207 base.virtual_sol_reserves = inner.virtual_sol_reserves;
208 base.virtual_token_reserves = inner.virtual_token_reserves;
209 base.real_sol_reserves = inner.real_sol_reserves;
210 base.real_token_reserves = inner.real_token_reserves;
211 base.fee_basis_points = inner.fee_basis_points;
212 base.fee = inner.fee;
213 base.creator_fee_basis_points = inner.creator_fee_basis_points;
214 base.creator_fee = inner.creator_fee;
215 base.track_volume |= inner.track_volume;
216 base.total_unclaimed_tokens = inner.total_unclaimed_tokens;
217 base.total_claimed_tokens = inner.total_claimed_tokens;
218 base.current_sol_volume = inner.current_sol_volume;
219 base.last_update_timestamp = inner.last_update_timestamp;
220 if !inner.ix_name.is_empty() {
221 base.ix_name = inner.ix_name;
222 }
223 base.mayhem_mode |= inner.mayhem_mode;
224 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
225 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
226 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
227 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
228 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
229 base.shareholders = inner.shareholders;
230 }
231 put_pk_if_set(&mut base.quote_mint, inner.quote_mint);
232 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
233 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
234 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
235 base.is_cashback_coin |= inner.is_cashback_coin;
236 } else {
237 put_u64_if_nonzero(&mut base.fee, inner.fee);
238 put_u64_if_nonzero(&mut base.creator_fee, inner.creator_fee);
239 put_u64_if_nonzero(&mut base.fee_basis_points, inner.fee_basis_points);
240 put_u64_if_nonzero(&mut base.creator_fee_basis_points, inner.creator_fee_basis_points);
241 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
242 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
243 put_u64_if_nonzero(&mut base.real_sol_reserves, inner.real_sol_reserves);
244 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
245 put_u64_if_nonzero(&mut base.total_unclaimed_tokens, inner.total_unclaimed_tokens);
246 put_u64_if_nonzero(&mut base.total_claimed_tokens, inner.total_claimed_tokens);
247 put_u64_if_nonzero(&mut base.current_sol_volume, inner.current_sol_volume);
248 put_u64_if_nonzero(&mut base.cashback_fee_basis_points, inner.cashback_fee_basis_points);
249 put_u64_if_nonzero(&mut base.cashback, inner.cashback);
250 put_u64_if_nonzero(&mut base.buyback_fee_basis_points, inner.buyback_fee_basis_points);
251 put_u64_if_nonzero(&mut base.buyback_fee, inner.buyback_fee);
252 if base.shareholders.is_empty() && !inner.shareholders.is_empty() {
253 base.shareholders = inner.shareholders;
254 }
255 put_pk_if_set(&mut base.quote_mint, inner.quote_mint);
256 put_u64_if_nonzero(&mut base.quote_amount, inner.quote_amount);
257 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
258 put_u64_if_nonzero(&mut base.real_quote_reserves, inner.real_quote_reserves);
259 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
260 put_i64_if_nonzero(&mut base.last_update_timestamp, inner.last_update_timestamp);
261 if !inner.ix_name.is_empty() {
262 base.ix_name = inner.ix_name;
263 }
264 base.track_volume |= inner.track_volume;
265 base.mayhem_mode |= inner.mayhem_mode;
266 base.is_cashback_coin |= inner.is_cashback_coin;
267 }
268 put_u64_if_nonzero(&mut base.amount, inner.amount);
269 put_u64_if_nonzero(&mut base.max_sol_cost, inner.max_sol_cost);
270 put_u64_if_nonzero(&mut base.min_sol_output, inner.min_sol_output);
271 put_u64_if_nonzero(&mut base.spendable_sol_in, inner.spendable_sol_in);
272 put_u64_if_nonzero(&mut base.spendable_quote_in, inner.spendable_quote_in);
273 put_u64_if_nonzero(&mut base.min_tokens_out, inner.min_tokens_out);
274 put_pk_if_set(&mut base.global, inner.global);
275 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
276 put_pk_if_set(&mut base.bonding_curve_v2, inner.bonding_curve_v2);
277 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
278 put_pk_if_set(&mut base.associated_user, inner.associated_user);
279 put_pk_if_set(&mut base.system_program, inner.system_program);
280 put_pk_if_set(&mut base.token_program, inner.token_program);
281 put_pk_if_set(&mut base.quote_token_program, inner.quote_token_program);
282 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
283 put_pk_if_set(&mut base.creator_vault, inner.creator_vault);
284 put_pk_if_set(&mut base.associated_quote_fee_recipient, inner.associated_quote_fee_recipient);
285 put_pk_if_set(&mut base.buyback_fee_recipient, inner.buyback_fee_recipient);
286 put_pk_if_set(
287 &mut base.associated_quote_buyback_fee_recipient,
288 inner.associated_quote_buyback_fee_recipient,
289 );
290 put_pk_if_set(&mut base.associated_quote_bonding_curve, inner.associated_quote_bonding_curve);
291 put_pk_if_set(&mut base.associated_quote_user, inner.associated_quote_user);
292 put_pk_if_set(&mut base.associated_creator_vault, inner.associated_creator_vault);
293 put_pk_if_set(&mut base.sharing_config, inner.sharing_config);
294 put_pk_if_set(&mut base.event_authority, inner.event_authority);
295 put_pk_if_set(&mut base.program, inner.program);
296 put_pk_if_set(&mut base.global_volume_accumulator, inner.global_volume_accumulator);
297 put_pk_if_set(&mut base.user_volume_accumulator, inner.user_volume_accumulator);
298 put_pk_if_set(
299 &mut base.associated_user_volume_accumulator,
300 inner.associated_user_volume_accumulator,
301 );
302 put_pk_if_set(&mut base.fee_config, inner.fee_config);
303 put_pk_if_set(&mut base.fee_program, inner.fee_program);
304 if base.account.is_none() {
305 base.account = inner.account;
306 }
307
308 base.is_created_buy |= inner.is_created_buy;
309 }
311
312#[inline(always)]
314fn merge_pumpfun_create(base: &mut PumpFunCreateTokenEvent, inner: PumpFunCreateTokenEvent) {
315 base.name = inner.name;
317 base.symbol = inner.symbol;
318 base.uri = inner.uri;
319 base.mint = inner.mint;
320 base.bonding_curve = inner.bonding_curve;
321 base.user = inner.user;
322 base.creator = inner.creator;
323 base.timestamp = inner.timestamp;
324 base.virtual_token_reserves = inner.virtual_token_reserves;
325 base.virtual_sol_reserves = inner.virtual_sol_reserves;
326 base.real_token_reserves = inner.real_token_reserves;
327 base.token_total_supply = inner.token_total_supply;
328 base.token_program = inner.token_program;
329 base.is_mayhem_mode = inner.is_mayhem_mode;
330 base.is_cashback_enabled = inner.is_cashback_enabled;
331 put_pk_if_set(&mut base.quote_mint, inner.quote_mint);
332 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
333}
334
335#[inline(always)]
337fn merge_pumpfun_create_v2(base: &mut PumpFunCreateV2TokenEvent, inner: PumpFunCreateV2TokenEvent) {
338 fill_str_if_empty(&mut base.name, &inner.name);
339 fill_str_if_empty(&mut base.symbol, &inner.symbol);
340 fill_str_if_empty(&mut base.uri, &inner.uri);
341 put_pk_if_set(&mut base.mint, inner.mint);
342 put_pk_if_set(&mut base.bonding_curve, inner.bonding_curve);
343 put_pk_if_set(&mut base.user, inner.user);
344 put_pk_if_set(&mut base.creator, inner.creator);
345 put_i64_if_nonzero(&mut base.timestamp, inner.timestamp);
346 put_u64_if_nonzero(&mut base.virtual_token_reserves, inner.virtual_token_reserves);
347 put_u64_if_nonzero(&mut base.virtual_sol_reserves, inner.virtual_sol_reserves);
348 put_u64_if_nonzero(&mut base.real_token_reserves, inner.real_token_reserves);
349 put_u64_if_nonzero(&mut base.token_total_supply, inner.token_total_supply);
350 put_pk_if_set(&mut base.token_program, inner.token_program);
351 base.is_mayhem_mode |= inner.is_mayhem_mode;
352 base.is_cashback_enabled |= inner.is_cashback_enabled;
353 put_pk_if_set(&mut base.quote_mint, inner.quote_mint);
354 put_u64_if_nonzero(&mut base.virtual_quote_reserves, inner.virtual_quote_reserves);
355 put_pk_if_set(&mut base.mint_authority, inner.mint_authority);
356 put_pk_if_set(&mut base.associated_bonding_curve, inner.associated_bonding_curve);
357 put_pk_if_set(&mut base.global, inner.global);
358 put_pk_if_set(&mut base.system_program, inner.system_program);
359 put_pk_if_set(&mut base.associated_token_program, inner.associated_token_program);
360 put_pk_if_set(&mut base.mayhem_program_id, inner.mayhem_program_id);
361 put_pk_if_set(&mut base.global_params, inner.global_params);
362 put_pk_if_set(&mut base.sol_vault, inner.sol_vault);
363 put_pk_if_set(&mut base.mayhem_state, inner.mayhem_state);
364 put_pk_if_set(&mut base.mayhem_token_vault, inner.mayhem_token_vault);
365 put_pk_if_set(&mut base.event_authority, inner.event_authority);
366 put_pk_if_set(&mut base.program, inner.program);
367 put_pk_if_set(&mut base.observed_fee_recipient, inner.observed_fee_recipient);
368}
369
370#[inline(always)]
372fn merge_pumpfun_migrate(base: &mut PumpFunMigrateEvent, inner: PumpFunMigrateEvent) {
373 base.user = inner.user;
375 base.mint = inner.mint;
376 base.mint_amount = inner.mint_amount;
377 base.sol_amount = inner.sol_amount;
378 base.pool_migration_fee = inner.pool_migration_fee;
379 base.bonding_curve = inner.bonding_curve;
380 base.timestamp = inner.timestamp;
381 base.pool = inner.pool;
382}
383
384#[inline(always)]
385fn merge_pumpswap_buy(base: &mut PumpSwapBuyEvent, inner: PumpSwapBuyEvent) {
386 let ix = std::mem::take(base);
387 *base = inner;
388 merge_pumpswap_buy_log_preferred(base, ix);
389}
390
391#[inline(always)]
392fn merge_pumpswap_sell(base: &mut PumpSwapSellEvent, inner: PumpSwapSellEvent) {
393 let ix = std::mem::take(base);
394 *base = inner;
395 merge_pumpswap_sell_log_preferred(base, ix);
396}
397
398#[inline(always)]
409pub fn can_merge(base: &DexEvent, inner: &DexEvent) -> bool {
410 if base.metadata().signature != inner.metadata().signature {
412 return false;
413 }
414
415 match (base, inner) {
417 (DexEvent::PumpFunTrade(_), DexEvent::PumpFunTrade(_))
419 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuy(_))
420 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunSell(_))
421 | (DexEvent::PumpFunTrade(_), DexEvent::PumpFunBuyExactSolIn(_))
422 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunTrade(_))
423 | (DexEvent::PumpFunBuy(_), DexEvent::PumpFunBuy(_))
424 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunTrade(_))
425 | (DexEvent::PumpFunSell(_), DexEvent::PumpFunSell(_))
426 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunTrade(_))
427 | (DexEvent::PumpFunBuyExactSolIn(_), DexEvent::PumpFunBuyExactSolIn(_)) => true,
428
429 (DexEvent::PumpFunCreate(_), DexEvent::PumpFunCreate(_)) => true,
431 (DexEvent::PumpFunCreateV2(_), DexEvent::PumpFunCreateV2(_)) => true,
432
433 (DexEvent::PumpFunMigrate(_), DexEvent::PumpFunMigrate(_)) => true,
435
436 _ => false,
438 }
439}
440
441#[inline(always)]
446fn fill_pk(to: &mut Pubkey, from: Pubkey) {
447 if *to == Pubkey::default() && from != Pubkey::default() {
448 *to = from;
449 }
450}
451
452#[inline(always)]
453fn fill_str_if_empty(to: &mut String, from: &str) {
454 if to.is_empty() && !from.is_empty() {
455 to.push_str(from);
456 }
457}
458
459#[inline]
462fn merge_pumpfun_trade_log_preferred(log: &mut PumpFunTradeEvent, ix: PumpFunTradeEvent) {
463 fill_pk(&mut log.global, ix.global);
464 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
465 fill_pk(&mut log.bonding_curve_v2, ix.bonding_curve_v2);
466 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
467 fill_pk(&mut log.associated_user, ix.associated_user);
468 fill_pk(&mut log.system_program, ix.system_program);
469 fill_pk(&mut log.token_program, ix.token_program);
470 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
471 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
472 fill_pk(&mut log.creator_vault, ix.creator_vault);
473 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
474 fill_pk(&mut log.creator, ix.creator);
475 fill_pk(&mut log.quote_mint, ix.quote_mint);
476 fill_pk(&mut log.associated_quote_fee_recipient, ix.associated_quote_fee_recipient);
477 fill_pk(&mut log.buyback_fee_recipient, ix.buyback_fee_recipient);
478 fill_pk(
479 &mut log.associated_quote_buyback_fee_recipient,
480 ix.associated_quote_buyback_fee_recipient,
481 );
482 fill_pk(&mut log.associated_quote_bonding_curve, ix.associated_quote_bonding_curve);
483 fill_pk(&mut log.associated_quote_user, ix.associated_quote_user);
484 fill_pk(&mut log.associated_creator_vault, ix.associated_creator_vault);
485 fill_pk(&mut log.sharing_config, ix.sharing_config);
486 fill_pk(&mut log.event_authority, ix.event_authority);
487 fill_pk(&mut log.program, ix.program);
488 fill_pk(&mut log.global_volume_accumulator, ix.global_volume_accumulator);
489 fill_pk(&mut log.user_volume_accumulator, ix.user_volume_accumulator);
490 fill_pk(&mut log.associated_user_volume_accumulator, ix.associated_user_volume_accumulator);
491 fill_pk(&mut log.fee_config, ix.fee_config);
492 fill_pk(&mut log.fee_program, ix.fee_program);
493 if log.account.is_none() {
494 log.account = ix.account;
495 }
496 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
497 log.ix_name = ix.ix_name;
498 }
499 put_u64_if_nonzero(&mut log.amount, ix.amount);
500 put_u64_if_nonzero(&mut log.max_sol_cost, ix.max_sol_cost);
501 put_u64_if_nonzero(&mut log.min_sol_output, ix.min_sol_output);
502 put_u64_if_nonzero(&mut log.spendable_sol_in, ix.spendable_sol_in);
503 put_u64_if_nonzero(&mut log.spendable_quote_in, ix.spendable_quote_in);
504 put_u64_if_nonzero(&mut log.min_tokens_out, ix.min_tokens_out);
505 put_u64_if_nonzero(&mut log.quote_amount, ix.quote_amount);
506 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
507 put_u64_if_nonzero(&mut log.real_quote_reserves, ix.real_quote_reserves);
508 if !log.is_created_buy && ix.is_created_buy {
509 log.is_created_buy = true;
510 }
511}
512
513#[inline]
514fn merge_pumpfun_create_log_preferred(
515 log: &mut PumpFunCreateTokenEvent,
516 ix: PumpFunCreateTokenEvent,
517) {
518 fill_str_if_empty(&mut log.name, &ix.name);
519 fill_str_if_empty(&mut log.symbol, &ix.symbol);
520 fill_str_if_empty(&mut log.uri, &ix.uri);
521 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
522 fill_pk(&mut log.user, ix.user);
523 fill_pk(&mut log.creator, ix.creator);
524 fill_pk(&mut log.token_program, ix.token_program);
525 fill_pk(&mut log.quote_mint, ix.quote_mint);
526 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
527 log.is_mayhem_mode |= ix.is_mayhem_mode;
528 log.is_cashback_enabled |= ix.is_cashback_enabled;
529}
530
531#[inline]
532fn merge_pumpfun_create_v2_log_preferred(
533 log: &mut PumpFunCreateV2TokenEvent,
534 ix: PumpFunCreateV2TokenEvent,
535) {
536 fill_str_if_empty(&mut log.name, &ix.name);
537 fill_str_if_empty(&mut log.symbol, &ix.symbol);
538 fill_str_if_empty(&mut log.uri, &ix.uri);
539 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
540 fill_pk(&mut log.user, ix.user);
541 fill_pk(&mut log.creator, ix.creator);
542 fill_pk(&mut log.token_program, ix.token_program);
543 fill_pk(&mut log.quote_mint, ix.quote_mint);
544 put_u64_if_nonzero(&mut log.virtual_quote_reserves, ix.virtual_quote_reserves);
545 fill_pk(&mut log.mint_authority, ix.mint_authority);
546 fill_pk(&mut log.associated_bonding_curve, ix.associated_bonding_curve);
547 fill_pk(&mut log.global, ix.global);
548 fill_pk(&mut log.system_program, ix.system_program);
549 fill_pk(&mut log.associated_token_program, ix.associated_token_program);
550 fill_pk(&mut log.mayhem_program_id, ix.mayhem_program_id);
551 fill_pk(&mut log.global_params, ix.global_params);
552 fill_pk(&mut log.sol_vault, ix.sol_vault);
553 fill_pk(&mut log.mayhem_state, ix.mayhem_state);
554 fill_pk(&mut log.mayhem_token_vault, ix.mayhem_token_vault);
555 fill_pk(&mut log.event_authority, ix.event_authority);
556 fill_pk(&mut log.program, ix.program);
557 fill_pk(&mut log.observed_fee_recipient, ix.observed_fee_recipient);
558}
559
560#[inline]
561fn merge_pumpfun_migrate_log_preferred(log: &mut PumpFunMigrateEvent, ix: PumpFunMigrateEvent) {
562 fill_pk(&mut log.bonding_curve, ix.bonding_curve);
563 fill_pk(&mut log.pool, ix.pool);
564 fill_pk(&mut log.user, ix.user);
565}
566
567#[inline]
568fn merge_pumpswap_trade_log_preferred(log: &mut PumpSwapTradeEvent, ix: PumpSwapTradeEvent) {
569 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
570 log.ix_name = ix.ix_name;
571 }
572}
573
574#[inline]
575fn merge_pumpswap_buy_log_preferred(log: &mut PumpSwapBuyEvent, ix: PumpSwapBuyEvent) {
576 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
577 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
578 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
579 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
580 fill_pk(&mut log.coin_creator, ix.coin_creator);
581 fill_pk(&mut log.base_mint, ix.base_mint);
582 fill_pk(&mut log.quote_mint, ix.quote_mint);
583 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
584 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
585 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
586 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
587 fill_pk(&mut log.base_token_program, ix.base_token_program);
588 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
589 fill_pk(&mut log.pool_v2, ix.pool_v2);
590 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
591 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
592 if log.ix_name.is_empty() && !ix.ix_name.is_empty() {
593 log.ix_name = ix.ix_name;
594 }
595}
596
597#[inline]
598fn merge_pumpswap_sell_log_preferred(log: &mut PumpSwapSellEvent, ix: PumpSwapSellEvent) {
599 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
600 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
601 fill_pk(&mut log.protocol_fee_recipient, ix.protocol_fee_recipient);
602 fill_pk(&mut log.protocol_fee_recipient_token_account, ix.protocol_fee_recipient_token_account);
603 fill_pk(&mut log.coin_creator, ix.coin_creator);
604 fill_pk(&mut log.base_mint, ix.base_mint);
605 fill_pk(&mut log.quote_mint, ix.quote_mint);
606 fill_pk(&mut log.pool_base_token_account, ix.pool_base_token_account);
607 fill_pk(&mut log.pool_quote_token_account, ix.pool_quote_token_account);
608 fill_pk(&mut log.coin_creator_vault_ata, ix.coin_creator_vault_ata);
609 fill_pk(&mut log.coin_creator_vault_authority, ix.coin_creator_vault_authority);
610 fill_pk(&mut log.base_token_program, ix.base_token_program);
611 fill_pk(&mut log.quote_token_program, ix.quote_token_program);
612 fill_pk(&mut log.pool_v2, ix.pool_v2);
613 fill_pk(&mut log.fee_recipient, ix.fee_recipient);
614 fill_pk(&mut log.fee_recipient_quote_token_account, ix.fee_recipient_quote_token_account);
615}
616
617#[inline]
618fn merge_raydium_clmm_swap_log_preferred(log: &mut RaydiumClmmSwapEvent, ix: RaydiumClmmSwapEvent) {
619 fill_pk(&mut log.token_account_0, ix.token_account_0);
620 fill_pk(&mut log.token_account_1, ix.token_account_1);
621 fill_pk(&mut log.sender, ix.sender);
622}
623
624#[inline]
625fn merge_raydium_amm_v4_swap_log_preferred(
626 log: &mut RaydiumAmmV4SwapEvent,
627 ix: RaydiumAmmV4SwapEvent,
628) {
629 fill_pk(&mut log.token_program, ix.token_program);
630 fill_pk(&mut log.amm_authority, ix.amm_authority);
631 fill_pk(&mut log.amm_open_orders, ix.amm_open_orders);
632 if let Some(ref o) = ix.amm_target_orders {
633 if log.amm_target_orders.is_none() {
634 log.amm_target_orders = Some(*o);
635 }
636 }
637 fill_pk(&mut log.pool_coin_token_account, ix.pool_coin_token_account);
638 fill_pk(&mut log.pool_pc_token_account, ix.pool_pc_token_account);
639 fill_pk(&mut log.serum_program, ix.serum_program);
640 fill_pk(&mut log.serum_market, ix.serum_market);
641 fill_pk(&mut log.serum_bids, ix.serum_bids);
642 fill_pk(&mut log.serum_asks, ix.serum_asks);
643 fill_pk(&mut log.serum_event_queue, ix.serum_event_queue);
644 fill_pk(&mut log.serum_coin_vault_account, ix.serum_coin_vault_account);
645 fill_pk(&mut log.serum_pc_vault_account, ix.serum_pc_vault_account);
646 fill_pk(&mut log.serum_vault_signer, ix.serum_vault_signer);
647 fill_pk(&mut log.user_source_token_account, ix.user_source_token_account);
648 fill_pk(&mut log.user_destination_token_account, ix.user_destination_token_account);
649}
650
651#[inline]
652fn merge_pumpswap_create_pool_log_preferred(
653 log: &mut PumpSwapCreatePoolEvent,
654 ix: PumpSwapCreatePoolEvent,
655) {
656 fill_pk(&mut log.creator, ix.creator);
657 fill_pk(&mut log.pool, ix.pool);
658 fill_pk(&mut log.lp_mint, ix.lp_mint);
659 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
660 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
661 fill_pk(&mut log.coin_creator, ix.coin_creator);
662}
663
664#[inline]
665fn merge_pumpswap_liquidity_added_log_preferred(
666 log: &mut PumpSwapLiquidityAdded,
667 ix: PumpSwapLiquidityAdded,
668) {
669 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
670 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
671 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
672}
673
674#[inline]
675fn merge_pumpswap_liquidity_removed_log_preferred(
676 log: &mut PumpSwapLiquidityRemoved,
677 ix: PumpSwapLiquidityRemoved,
678) {
679 fill_pk(&mut log.user_base_token_account, ix.user_base_token_account);
680 fill_pk(&mut log.user_quote_token_account, ix.user_quote_token_account);
681 fill_pk(&mut log.user_pool_token_account, ix.user_pool_token_account);
682}
683
684#[inline]
685fn merge_bonk_pool_create_log_preferred(log: &mut BonkPoolCreateEvent, ix: BonkPoolCreateEvent) {
686 fill_pk(&mut log.creator, ix.creator);
687 fill_str_if_empty(&mut log.base_mint_param.name, &ix.base_mint_param.name);
688 fill_str_if_empty(&mut log.base_mint_param.symbol, &ix.base_mint_param.symbol);
689 fill_str_if_empty(&mut log.base_mint_param.uri, &ix.base_mint_param.uri);
690}
691
692#[inline]
693fn merge_bonk_migrate_amm_log_preferred(log: &mut BonkMigrateAmmEvent, ix: BonkMigrateAmmEvent) {
694 fill_pk(&mut log.old_pool, ix.old_pool);
695 fill_pk(&mut log.new_pool, ix.new_pool);
696 fill_pk(&mut log.user, ix.user);
697}
698
699#[inline]
701fn merge_bonk_trade_log_preferred(_log: &mut BonkTradeEvent, _ix: BonkTradeEvent) {}
702
703#[inline]
704fn merge_meteora_dlmm_swap_log_preferred(
705 _log: &mut MeteoraDlmmSwapEvent,
706 _ix: MeteoraDlmmSwapEvent,
707) {
708}
709
710pub fn merge_grpc_instruction_into_log(log: &mut DexEvent, ix: DexEvent) {
716 use DexEvent::*;
717 match log {
718 PumpFunTrade(l) => {
719 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
720 merge_pumpfun_trade_log_preferred(l, i);
721 }
722 }
723 PumpFunBuy(l) => {
724 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
725 merge_pumpfun_trade_log_preferred(l, i);
726 }
727 }
728 PumpFunSell(l) => {
729 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
730 merge_pumpfun_trade_log_preferred(l, i);
731 }
732 }
733 PumpFunBuyExactSolIn(l) => {
734 if let Some(i) = pumpfun_trade_from_ix_variant(ix) {
735 merge_pumpfun_trade_log_preferred(l, i);
736 }
737 }
738 PumpFunCreate(l) => {
739 if let DexEvent::PumpFunCreate(i) = ix {
740 merge_pumpfun_create_log_preferred(l, i);
741 }
742 }
743 PumpFunCreateV2(l) => {
744 if let DexEvent::PumpFunCreateV2(i) = ix {
745 merge_pumpfun_create_v2_log_preferred(l, i);
746 }
747 }
748 PumpFunMigrate(l) => {
749 if let DexEvent::PumpFunMigrate(i) = ix {
750 merge_pumpfun_migrate_log_preferred(l, i);
751 }
752 }
753 PumpSwapTrade(l) => {
754 if let PumpSwapTrade(i) = ix {
755 merge_pumpswap_trade_log_preferred(l, i);
756 }
757 }
758 PumpSwapBuy(l) => {
759 if let PumpSwapBuy(i) = ix {
760 merge_pumpswap_buy_log_preferred(l, i);
761 }
762 }
763 PumpSwapSell(l) => {
764 if let PumpSwapSell(i) = ix {
765 merge_pumpswap_sell_log_preferred(l, i);
766 }
767 }
768 RaydiumClmmSwap(l) => {
769 if let RaydiumClmmSwap(i) = ix {
770 merge_raydium_clmm_swap_log_preferred(l, i);
771 }
772 }
773 RaydiumAmmV4Swap(l) => {
774 if let RaydiumAmmV4Swap(i) = ix {
775 merge_raydium_amm_v4_swap_log_preferred(l, i);
776 }
777 }
778 BonkTrade(l) => {
779 if let BonkTrade(i) = ix {
780 merge_bonk_trade_log_preferred(l, i);
781 }
782 }
783 BonkPoolCreate(l) => {
784 if let BonkPoolCreate(i) = ix {
785 merge_bonk_pool_create_log_preferred(l, i);
786 }
787 }
788 BonkMigrateAmm(l) => {
789 if let BonkMigrateAmm(i) = ix {
790 merge_bonk_migrate_amm_log_preferred(l, i);
791 }
792 }
793 PumpSwapCreatePool(l) => {
794 if let PumpSwapCreatePool(i) = ix {
795 merge_pumpswap_create_pool_log_preferred(l, i);
796 }
797 }
798 PumpSwapLiquidityAdded(l) => {
799 if let PumpSwapLiquidityAdded(i) = ix {
800 merge_pumpswap_liquidity_added_log_preferred(l, i);
801 }
802 }
803 PumpSwapLiquidityRemoved(l) => {
804 if let PumpSwapLiquidityRemoved(i) = ix {
805 merge_pumpswap_liquidity_removed_log_preferred(l, i);
806 }
807 }
808 MeteoraDlmmSwap(l) => {
809 if let MeteoraDlmmSwap(i) = ix {
810 merge_meteora_dlmm_swap_log_preferred(l, i);
811 }
812 }
813 _ => {}
814 }
815}
816
817#[inline]
818fn pumpfun_trade_from_ix_variant(ix: DexEvent) -> Option<PumpFunTradeEvent> {
819 match ix {
820 DexEvent::PumpFunTrade(t)
821 | DexEvent::PumpFunBuy(t)
822 | DexEvent::PumpFunSell(t)
823 | DexEvent::PumpFunBuyExactSolIn(t) => Some(t),
824 _ => None,
825 }
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831 use solana_sdk::{pubkey::Pubkey, signature::Signature};
832
833 #[test]
834 fn test_merge_pumpfun_trade() {
835 let metadata = EventMetadata {
836 signature: Signature::default(),
837 slot: 100,
838 tx_index: 1,
839 block_time_us: 1000,
840 grpc_recv_us: 2000,
841 recent_blockhash: None,
842 };
843
844 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
846 metadata: metadata.clone(),
847 bonding_curve: Pubkey::new_unique(),
848 associated_bonding_curve: Pubkey::new_unique(),
849 ..Default::default()
850 });
851
852 let inner = DexEvent::PumpFunTrade(PumpFunTradeEvent {
854 metadata: metadata.clone(),
855 mint: Pubkey::new_unique(),
856 sol_amount: 1000,
857 token_amount: 2000,
858 is_buy: true,
859 user: Pubkey::new_unique(),
860 ..Default::default()
861 });
862
863 merge_events(&mut base, inner);
865
866 if let DexEvent::PumpFunTrade(trade) = base {
868 assert_eq!(trade.sol_amount, 1000);
869 assert_eq!(trade.token_amount, 2000);
870 assert!(trade.is_buy);
871 assert_ne!(trade.bonding_curve, Pubkey::default());
873 assert_ne!(trade.associated_bonding_curve, Pubkey::default());
874 } else {
875 panic!("Expected PumpFunTrade event");
876 }
877 }
878
879 #[test]
880 fn merge_preserves_instruction_context_when_log_tail_is_absent() {
881 let metadata = EventMetadata {
882 signature: Signature::default(),
883 slot: 100,
884 tx_index: 1,
885 block_time_us: 1000,
886 grpc_recv_us: 2000,
887 recent_blockhash: None,
888 };
889 let quote_mint = Pubkey::new_unique();
890 let associated_quote_user = Pubkey::new_unique();
891
892 let mut base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
893 metadata: metadata.clone(),
894 ix_name: "buy_exact_quote_in".to_string(),
895 quote_mint,
896 spendable_quote_in: 1_000,
897 min_tokens_out: 2_000,
898 associated_quote_user,
899 ..Default::default()
900 });
901
902 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
903 metadata,
904 sol_amount: 1_000,
905 token_amount: 2_000,
906 is_buy: true,
907 ..Default::default()
908 });
909
910 merge_events(&mut base, inner);
911
912 if let DexEvent::PumpFunTrade(t) = base {
913 assert_eq!(t.sol_amount, 1_000);
914 assert_eq!(t.token_amount, 2_000);
915 assert_eq!(t.ix_name, "buy_exact_quote_in");
916 assert_eq!(t.quote_mint, quote_mint);
917 assert_eq!(t.spendable_quote_in, 1_000);
918 assert_eq!(t.min_tokens_out, 2_000);
919 assert_eq!(t.associated_quote_user, associated_quote_user);
920 } else {
921 panic!("Expected PumpFunTrade event");
922 }
923 }
924
925 #[test]
926 fn test_can_merge() {
927 let metadata = EventMetadata {
928 signature: Signature::default(),
929 slot: 100,
930 tx_index: 1,
931 block_time_us: 1000,
932 grpc_recv_us: 2000,
933 recent_blockhash: None,
934 };
935
936 let base = DexEvent::PumpFunTrade(PumpFunTradeEvent {
937 metadata: metadata.clone(),
938 ..Default::default()
939 });
940
941 let inner = DexEvent::PumpFunBuy(PumpFunTradeEvent {
942 metadata: metadata.clone(),
943 ..Default::default()
944 });
945
946 assert!(can_merge(&base, &inner));
948
949 let different_sig = DexEvent::PumpFunTrade(PumpFunTradeEvent {
951 metadata: EventMetadata { signature: Signature::new_unique(), ..metadata },
952 ..Default::default()
953 });
954
955 assert!(!can_merge(&base, &different_sig));
956 }
957
958 #[test]
959 fn grpc_merge_fills_fee_recipient_from_ix_when_log_default() {
960 let metadata = EventMetadata {
961 signature: Signature::default(),
962 slot: 1,
963 tx_index: 0,
964 block_time_us: 0,
965 grpc_recv_us: 0,
966 recent_blockhash: None,
967 };
968 let fr = Pubkey::new_unique();
969 let log_t =
970 PumpFunTradeEvent { metadata: metadata.clone(), sol_amount: 50, ..Default::default() };
971 let mut ix_t = log_t.clone();
972 ix_t.fee_recipient = fr;
973 ix_t.sol_amount = 777;
974 let mut log_ev = DexEvent::PumpFunTrade(log_t);
975 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
976 match log_ev {
977 DexEvent::PumpFunTrade(t) => {
978 assert_eq!(t.fee_recipient, fr);
979 assert_eq!(t.sol_amount, 50);
980 }
981 _ => panic!("expected trade"),
982 }
983 }
984
985 #[test]
986 fn grpc_merge_keeps_log_trade_fields() {
987 let metadata = EventMetadata {
988 signature: Signature::default(),
989 slot: 1,
990 tx_index: 0,
991 block_time_us: 0,
992 grpc_recv_us: 0,
993 recent_blockhash: None,
994 };
995 let log_t = PumpFunTradeEvent {
996 metadata: metadata.clone(),
997 mayhem_mode: true,
998 sol_amount: 100,
999 ..Default::default()
1000 };
1001 let mut ix_t = log_t.clone();
1002 ix_t.mayhem_mode = false;
1003 ix_t.sol_amount = 999;
1004
1005 let mut log_ev = DexEvent::PumpFunTrade(log_t);
1006 merge_grpc_instruction_into_log(&mut log_ev, DexEvent::PumpFunBuy(ix_t));
1007 match log_ev {
1008 DexEvent::PumpFunTrade(t) => {
1009 assert!(t.mayhem_mode);
1010 assert_eq!(t.sol_amount, 100);
1011 }
1012 _ => panic!("variant preserved"),
1013 }
1014 }
1015}