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