// file: kb_lib/src/non_trade_event_materialization.rs

//! Materialization of useful non-trade DEX events.
//!
//! This service persists liquidity, pool lifecycle, fee, reward and pool
//! administration events from already decoded DEX events. It deliberately does
//! not feed trade, metric or candle materialization.

/// Result of non-trade event materialization for one transaction.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct NonTradeEventMaterializationResult {
    /// Number of liquidity events inserted or refreshed.
    pub liquidity_event_count: usize,
    /// Number of pool lifecycle events inserted or refreshed.
    pub pool_lifecycle_event_count: usize,
    /// Number of fee events inserted or refreshed.
    pub fee_event_count: usize,
    /// Number of reward events inserted or refreshed.
    pub reward_event_count: usize,
    /// Number of pool administration events inserted or refreshed.
    pub pool_admin_event_count: usize,
    /// Number of orderbook or limit-order events inserted or refreshed.
    pub orderbook_event_count: usize,
    /// Number of launch-surface specific events inserted or refreshed.
    pub launch_event_count: usize,
    /// Number of token-account events inserted or refreshed.
    pub token_account_event_count: usize,
}

/// Materializes useful non-trade decoded DEX events.
#[derive(Debug, Clone)]
pub struct NonTradeEventMaterializationService {
    database: std::sync::Arc<crate::Database>,
}

#[derive(Debug, Clone)]
struct NonTradeDecodedEventContext {
    dex_id: std::option::Option<i64>,
    pool_id: std::option::Option<i64>,
    pair_id: std::option::Option<i64>,
    pair: std::option::Option<crate::PairDto>,
}

fn should_skip_non_trade_event_due_to_explicit_reason(
    decoded_event: &crate::DexDecodedEventDto,
    payload: &serde_json::Value,
) -> bool {
    if crate::is_dex_liquidity_event_kind(decoded_event.event_kind.as_str())
        && payload_has_non_empty_text(payload, "skipLiquidityReason")
    {
        return true;
    }
    if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str())
        && (payload_has_non_empty_text(payload, "skipLifecycleReason")
            || payload_has_non_empty_text(payload, "skipCatalogReason"))
    {
        return true;
    }
    if crate::is_dex_fee_event_kind(decoded_event.event_kind.as_str())
        && payload_has_non_empty_text(payload, "skipFeeReason")
    {
        return true;
    }
    if crate::is_dex_reward_event_kind(decoded_event.event_kind.as_str())
        && payload_has_non_empty_text(payload, "skipRewardReason")
    {
        return true;
    }
    if crate::is_dex_admin_event_kind(decoded_event.event_kind.as_str())
        && payload_has_non_empty_text(payload, "skipAdminReason")
    {
        return true;
    }
    if crate::is_dex_orderbook_event_kind(decoded_event.event_kind.as_str())
        && payload_has_non_empty_text(payload, "skipOrderbookReason")
    {
        return true;
    }
    return false;
}

fn should_attempt_meteora_dbc_explicit_skip_materialization(
    decoded_event: &crate::DexDecodedEventDto,
    payload: &serde_json::Value,
) -> bool {
    if decoded_event.protocol_name != "meteora_dbc" {
        return false;
    }
    if payload.get("anchorSelfCpiLog").and_then(serde_json::Value::as_bool) == Some(true) {
        return false;
    }
    let event_kind = decoded_event.event_kind.as_str();
    if is_meteora_dbc_instruction_fee_materialization_candidate(event_kind) {
        return true;
    }
    if is_meteora_dbc_instruction_admin_materialization_candidate(event_kind) {
        return true;
    }
    return false;
}

fn should_attempt_pump_swap_explicit_skip_materialization(
    decoded_event: &crate::DexDecodedEventDto,
    _payload: &serde_json::Value,
) -> bool {
    if decoded_event.protocol_name != "pump_swap" {
        return false;
    }
    return is_pump_swap_known_non_trade_materialization_candidate(
        decoded_event.event_kind.as_str(),
    );
}

fn is_pump_swap_known_non_trade_materialization_candidate(event_kind: &str) -> bool {
    return matches!(
        event_kind,
        "pump_swap.admin_set_coin_creator"
            | "pump_swap.admin_set_coin_creator_event"
            | "pump_swap.admin_update_token_incentives"
            | "pump_swap.admin_update_token_incentives_event"
            | "pump_swap.buy_event"
            | "pump_swap.claim_cashback"
            | "pump_swap.claim_cashback_event"
            | "pump_swap.claim_token_incentives"
            | "pump_swap.claim_token_incentives_event"
            | "pump_swap.close_user_volume_accumulator"
            | "pump_swap.close_user_volume_accumulator_event"
            | "pump_swap.collect_coin_creator_fee"
            | "pump_swap.collect_coin_creator_fee_event"
            | "pump_swap.create_config"
            | "pump_swap.create_config_event"
            | "pump_swap.create_pool"
            | "pump_swap.create_pool_event"
            | "pump_swap.deposit"
            | "pump_swap.deposit_event"
            | "pump_swap.disable"
            | "pump_swap.disable_event"
            | "pump_swap.extend_account"
            | "pump_swap.extend_account_event"
            | "pump_swap.init_user_volume_accumulator"
            | "pump_swap.init_user_volume_accumulator_event"
            | "pump_swap.migrate_pool_coin_creator"
            | "pump_swap.migrate_pool_coin_creator_event"
            | "pump_swap.reserved_fee_recipients_event"
            | "pump_swap.sell_event"
            | "pump_swap.set_bonding_curve_coin_creator_event"
            | "pump_swap.set_coin_creator"
            | "pump_swap.set_metaplex_coin_creator_event"
            | "pump_swap.set_reserved_fee_recipient"
            | "pump_swap.set_reserved_fee_recipients"
            | "pump_swap.sync_user_volume_accumulator"
            | "pump_swap.sync_user_volume_accumulator_event"
            | "pump_swap.toggle_cashback_enabled"
            | "pump_swap.toggle_mayhem_mode"
            | "pump_swap.transfer_creator_fees_to_pump"
            | "pump_swap.transfer_creator_fees_to_pump_v2"
            | "pump_swap.update_admin"
            | "pump_swap.update_admin_event"
            | "pump_swap.update_buyback_config"
            | "pump_swap.update_fee_config"
            | "pump_swap.update_fee_config_event"
            | "pump_swap.withdraw"
            | "pump_swap.withdraw_event"
    );
}

fn is_meteora_dbc_instruction_fee_materialization_candidate(event_kind: &str) -> bool {
    return matches!(
        event_kind,
        "meteora_dbc.claim_creator_trading_fee"
            | "meteora_dbc.claim_partner_pool_creation_fee"
            | "meteora_dbc.claim_protocol_fee"
            | "meteora_dbc.claim_protocol_pool_creation_fee"
            | "meteora_dbc.claim_trading_fee"
            | "meteora_dbc.creator_withdraw_surplus"
            | "meteora_dbc.partner_withdraw_surplus"
            | "meteora_dbc.withdraw_leftover"
            | "meteora_dbc.withdraw_migration_fee"
            | "meteora_dbc.zap_protocol_fee"
    );
}

fn is_meteora_dbc_instruction_admin_materialization_candidate(event_kind: &str) -> bool {
    return matches!(
        event_kind,
        "meteora_dbc.create_partner_metadata"
            | "meteora_dbc.create_virtual_pool_metadata"
            | "meteora_dbc.migration_damm_v2_create_metadata"
            | "meteora_dbc.migration_meteora_damm_create_metadata"
    );
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FeeAmountRecoveryPolicy {
    Disabled,
    InnerSplTransfer,
}

fn is_pump_swap_forced_pool_admin_materialization_candidate(event_kind: &str) -> bool {
    return matches!(
        event_kind,
        "pump_swap.admin_set_coin_creator"
            | "pump_swap.admin_set_coin_creator_event"
            | "pump_swap.admin_update_token_incentives"
            | "pump_swap.admin_update_token_incentives_event"
            | "pump_swap.create_config"
            | "pump_swap.create_config_event"
            | "pump_swap.disable"
            | "pump_swap.disable_event"
            | "pump_swap.extend_account"
            | "pump_swap.extend_account_event"
            | "pump_swap.migrate_pool_coin_creator"
            | "pump_swap.migrate_pool_coin_creator_event"
            | "pump_swap.reserved_fee_recipients_event"
            | "pump_swap.set_bonding_curve_coin_creator_event"
            | "pump_swap.set_coin_creator"
            | "pump_swap.set_metaplex_coin_creator_event"
            | "pump_swap.set_reserved_fee_recipient"
            | "pump_swap.set_reserved_fee_recipients"
            | "pump_swap.toggle_cashback_enabled"
            | "pump_swap.toggle_mayhem_mode"
            | "pump_swap.update_admin"
            | "pump_swap.update_admin_event"
            | "pump_swap.update_buyback_config"
            | "pump_swap.update_fee_config"
            | "pump_swap.update_fee_config_event"
    );
}

fn fee_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecoveryPolicy {
    return match event_kind {
        "pump_fees.crank_donation_fee_pda"
        | "pump_fees.sweep_buyback"
        | "pump_fun.collect_creator_fee"
        | "pump_fun.collect_creator_fee_v2"
        | "pump_fun.distribute_creator_fees"
        | "pump_fun.distribute_creator_fees_v2"
        | "pump_swap.collect_coin_creator_fee"
        | "pump_swap.transfer_creator_fees_to_pump"
        | "pump_swap.transfer_creator_fees_to_pump_v2"
        | "raydium_amm_v4.withdraw_pnl"
        | "raydium_amm_v4.withdraw_srm"
        | "raydium_clmm.collect_fund_fee"
        | "raydium_clmm.collect_protocol_fee_event"
        | "raydium_cpmm.collect_creator_fee"
        | "raydium_cpmm.collect_fund_fee"
        | "raydium_cpmm.collect_protocol_fee"
        | "raydium_launchpad.claim_creator_fee"
        | "raydium_launchpad.claim_platform_fee"
        | "raydium_launchpad.claim_platform_fee_from_vault"
        | "raydium_launchpad.collect_fee"
        | "raydium_stable_swap.withdraw_pnl"
        | "raydium_stable_swap.withdraw_srm"
        | "meteora_dlmm.claim_fee"
        | "meteora_dlmm.claim_fee2"
        | "meteora_dlmm.claim_fee_event"
        | "meteora_dlmm.claim_fee2_event"
        | "meteora_dlmm.composition_fee_event"
        | "meteora_dlmm.withdraw_protocol_fee"
        | "meteora_dlmm.zap_protocol_fee" => FeeAmountRecoveryPolicy::InnerSplTransfer,
        _ => FeeAmountRecoveryPolicy::Disabled,
    };
}

fn reward_amount_recovery_policy_for_event_kind(event_kind: &str) -> FeeAmountRecoveryPolicy {
    return match event_kind {
        "meteora_dlmm.claim_reward"
        | "meteora_dlmm.claim_reward2"
        | "meteora_dlmm.claim_reward_event"
        | "meteora_dlmm.claim_reward2_event"
        | "meteora_dlmm.fund_reward"
        | "meteora_dlmm.fund_reward_event"
        | "meteora_dlmm.initialize_reward"
        | "meteora_dlmm.initialize_reward_event"
        | "meteora_dlmm.withdraw_ineligible_reward"
        | "meteora_dlmm.withdraw_ineligible_reward_event" => {
            FeeAmountRecoveryPolicy::InnerSplTransfer
        },
        _ => FeeAmountRecoveryPolicy::Disabled,
    };
}

fn optional_text_is_missing(value: &std::option::Option<std::string::String>) -> bool {
    match value.as_ref() {
        Some(value) => return value.trim().is_empty(),
        None => return true,
    }
}

fn payload_has_non_empty_text(payload: &serde_json::Value, key: &str) -> bool {
    let value = match payload.get(key) {
        Some(value) => value,
        None => return false,
    };
    let text = match value.as_str() {
        Some(text) => text,
        None => return false,
    };
    return !text.is_empty();
}

impl NonTradeEventMaterializationService {
    /// Creates a new non-trade event materialization service.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        return Self { database };
    }

    /// Materializes useful non-trade events for one persisted transaction signature.
    pub async fn record_transaction_by_signature(
        &self,
        signature: &str,
    ) -> Result<crate::NonTradeEventMaterializationResult, crate::Error> {
        let transaction_result =
            crate::query_chain_transactions_get_by_signature(self.database.as_ref(), signature)
                .await;
        let transaction_option = match transaction_result {
            Ok(transaction_option) => transaction_option,
            Err(error) => return Err(error),
        };
        let transaction = match transaction_option {
            Some(transaction) => transaction,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "cannot materialize non-trade events for unknown transaction '{}'",
                    signature
                )));
            },
        };
        if transaction_has_effective_error(&transaction) {
            tracing::debug!(
                signature = %transaction.signature,
                "skipping non-trade materialization for failed transaction"
            );
            return Ok(crate::NonTradeEventMaterializationResult::default());
        }
        let transaction_id = match transaction.id {
            Some(transaction_id) => transaction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "transaction '{}' has no internal id",
                    transaction.signature
                )));
            },
        };
        let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let decoded_events = match decoded_events_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut result = crate::NonTradeEventMaterializationResult::default();
        for decoded_event in &decoded_events {
            let payload_result =
                serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
            let payload = match payload_result {
                Ok(payload) => payload,
                Err(error) => {
                    tracing::warn!(
                        signature = %transaction.signature,
                        event_kind = %decoded_event.event_kind,
                        error = %error,
                        "skipping non-trade materialization for invalid decoded payload"
                    );
                    continue;
                },
            };
            if decoded_event.protocol_name == "pump_swap"
                && is_pump_swap_forced_pool_admin_materialization_candidate(
                    decoded_event.event_kind.as_str(),
                )
            {
                let materialized = self
                    .materialize_pool_admin_event(
                        &transaction,
                        transaction_id,
                        decoded_event,
                        &payload,
                    )
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.pool_admin_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
                continue;
            }
            if is_anchor_event_audit_only(decoded_event, &payload) {
                continue;
            }
            if should_skip_non_trade_event_due_to_explicit_reason(decoded_event, &payload)
                && !should_attempt_meteora_dbc_explicit_skip_materialization(
                    decoded_event,
                    &payload,
                )
                && !should_attempt_pump_swap_explicit_skip_materialization(decoded_event, &payload)
            {
                tracing::debug!(
                    event_kind = %decoded_event.event_kind,
                    decoded_event_id = ?decoded_event.id,
                    signature = %transaction.signature,
                    "skipping non-trade materialization due to explicit decoded payload skip reason"
                );
                continue;
            }
            if should_skip_pump_fun_duplicate_non_trade_event(decoded_event, &decoded_events) {
                tracing::debug!(
                    event_kind = %decoded_event.event_kind,
                    decoded_event_id = ?decoded_event.id,
                    signature = %transaction.signature,
                    "skipping duplicate pump_fun non-trade materialization"
                );
                continue;
            }
            if decoded_event.protocol_name == "pump_swap"
                && is_pump_swap_forced_pool_admin_materialization_candidate(
                    decoded_event.event_kind.as_str(),
                )
            {
                let materialized = self
                    .materialize_pool_admin_event(
                        &transaction,
                        transaction_id,
                        decoded_event,
                        &payload,
                    )
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.pool_admin_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
                continue;
            }
            if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str()) {
                let cleanup_result =
                    self.delete_stale_pool_admin_event_for_lifecycle(decoded_event).await;
                match cleanup_result {
                    Ok(_) => {},
                    Err(error) => return Err(error),
                }
            }
            if crate::is_dex_liquidity_event_kind(decoded_event.event_kind.as_str())
                && !decoded_event.event_kind.ends_with(".lp_change_event")
            {
                let materialized = self
                    .materialize_liquidity_event(
                        &transaction,
                        transaction_id,
                        decoded_event,
                        &payload,
                    )
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.liquidity_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str())
                && !is_launchpad_launch_event_materializable(decoded_event.event_kind.as_str())
            {
                let materialized = self
                    .materialize_pool_lifecycle_event(&transaction, transaction_id, decoded_event)
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.pool_lifecycle_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if crate::is_dex_fee_event_kind(decoded_event.event_kind.as_str()) {
                let materialized = self
                    .materialize_fee_event(&transaction, transaction_id, decoded_event, &payload)
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.fee_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if crate::is_dex_reward_event_kind(decoded_event.event_kind.as_str()) {
                let materialized = self
                    .materialize_reward_event(&transaction, transaction_id, decoded_event, &payload)
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.reward_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if crate::is_dex_admin_event_kind(decoded_event.event_kind.as_str())
                && !crate::is_dex_pool_lifecycle_event_kind(decoded_event.event_kind.as_str())
            {
                let materialized = self
                    .materialize_pool_admin_event(
                        &transaction,
                        transaction_id,
                        decoded_event,
                        &payload,
                    )
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.pool_admin_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if crate::is_dex_orderbook_event_kind(decoded_event.event_kind.as_str()) {
                let materialized = self
                    .materialize_orderbook_event(
                        &transaction,
                        transaction_id,
                        decoded_event,
                        &payload,
                    )
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.orderbook_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if is_token_account_event_materializable(decoded_event.event_kind.as_str()) {
                let materialized = self
                    .materialize_token_account_event(
                        &transaction,
                        transaction_id,
                        decoded_event,
                        &payload,
                    )
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.token_account_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
            if is_launchpad_launch_event_materializable(decoded_event.event_kind.as_str()) {
                let materialized = self
                    .materialize_launch_event(&transaction, transaction_id, decoded_event, &payload)
                    .await;
                match materialized {
                    Ok(was_materialized) => {
                        if was_materialized {
                            result.launch_event_count += 1;
                        }
                    },
                    Err(error) => return Err(error),
                }
            }
        }
        for decoded_event in &decoded_events {
            if !decoded_event.event_kind.ends_with(".lp_change_event") {
                continue;
            }
            let payload_result =
                serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
            let payload = match payload_result {
                Ok(payload) => payload,
                Err(error) => {
                    tracing::warn!(
                        signature = %transaction.signature,
                        event_kind = %decoded_event.event_kind,
                        error = %error,
                        "skipping postponed lp_change_event materialization for invalid decoded payload"
                    );
                    continue;
                },
            };
            let materialized = self
                .materialize_liquidity_event(&transaction, transaction_id, decoded_event, &payload)
                .await;
            match materialized {
                Ok(was_materialized) => {
                    if was_materialized {
                        result.liquidity_event_count += 1;
                    }
                },
                Err(error) => return Err(error),
            }
        }
        return Ok(result);
    }

    async fn materialize_pool_lifecycle_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let dto = crate::PoolLifecycleEventDto::new(
            transaction_id,
            Some(decoded_event_id),
            context.dex_id,
            context.pool_id,
            context.pair_id,
            transaction.signature.clone(),
            transaction.slot,
            decoded_event.protocol_name.clone(),
            decoded_event.program_id.clone(),
            decoded_event.event_kind.clone(),
            decoded_event.pool_account.clone(),
            decoded_event.token_a_mint.clone(),
            decoded_event.token_b_mint.clone(),
            decoded_event.payload_json.clone(),
        );
        let upsert_result =
            crate::query_pool_lifecycle_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_fee_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let actor_wallet = extract_first_string(
            payload,
            &[
                "actorWallet",
                "actor_wallet",
                "receiver",
                "recipient",
                "owner",
                "payer",
                "authority",
                "user",
            ],
        );
        let mut fee_token_mint = extract_first_string(
            payload,
            &[
                "feeTokenMint",
                "fee_token_mint",
                "tokenMint",
                "token_mint",
                "mint",
                "quoteMint",
                "quote_mint",
            ],
        );
        let mut fee_amount_raw = extract_first_amount_string(
            payload,
            &[
                "feeAmountRaw",
                "fee_amount_raw",
                "feeAmount",
                "fee_amount",
                "protocolFeeAmount",
                "protocol_fee_amount",
                "fundFeeAmount",
                "fund_fee_amount",
                "creatorFeeAmount",
                "creator_fee_amount",
                "amount0RequestedRaw",
                "amount_0_requested_raw",
                "amount1RequestedRaw",
                "amount_1_requested_raw",
                "tokenAAmount",
                "tokenBAmount",
                "amount",
            ],
        );
        let mut payload_json = decoded_event.payload_json.clone();
        let mut fee_amount_legs: std::vec::Vec<MeteoraDbcActualAmount> =
            fee_amount_legs_from_payload(payload);
        if !fee_amount_legs.is_empty() {
            match fee_amount_legs.as_slice() {
                [first_leg] => {
                    fee_token_mint = Some(first_leg.token_mint.clone());
                    fee_amount_raw = Some(first_leg.amount_raw.clone());
                },
                _ => {
                    fee_token_mint = None;
                    fee_amount_raw = None;
                },
            }
            let updated_payload =
                decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
            payload_json = updated_payload.to_string();
            let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
                self.database.as_ref(),
                decoded_event_id,
                payload_json.as_str(),
            )
            .await;
            if let Err(error) = update_result {
                return Err(error);
            }
        }
        if fee_amount_legs.is_empty()
            && is_meteora_dbc_instruction_fee_materialization_candidate(
                decoded_event.event_kind.as_str(),
            )
        {
            let inferred_result = self
                .resolve_meteora_dbc_actual_fee_amounts(
                    transaction,
                    transaction_id,
                    decoded_event,
                    payload,
                )
                .await;
            let inferred = match inferred_result {
                Ok(inferred) => inferred,
                Err(error) => return Err(error),
            };
            if !inferred.is_empty() {
                fee_amount_legs = inferred;
                match fee_amount_legs.as_slice() {
                    [first_leg] => {
                        fee_token_mint = Some(first_leg.token_mint.clone());
                        fee_amount_raw = Some(first_leg.amount_raw.clone());
                    },
                    _ => {
                        fee_token_mint = None;
                        fee_amount_raw = None;
                    },
                }
                let updated_payload =
                    decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
                payload_json = updated_payload.to_string();
                let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
                    self.database.as_ref(),
                    decoded_event_id,
                    payload_json.as_str(),
                )
                .await;
                if let Err(error) = update_result {
                    return Err(error);
                }
            } else if fee_amount_raw.is_none() {
                let explanation_result = self
                    .resolve_meteora_dbc_unmaterialized_fee_skip_reason(
                        transaction_id,
                        decoded_event,
                        payload,
                    )
                    .await;
                let explanation = match explanation_result {
                    Ok(explanation) => explanation,
                    Err(error) => return Err(error),
                };
                if let Some(explanation) = explanation {
                    let updated_payload = decoded_payload_with_unmaterialized_fee_reason(
                        payload,
                        explanation.as_str(),
                    );
                    let updated_payload_json = updated_payload.to_string();
                    let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
                        self.database.as_ref(),
                        decoded_event_id,
                        updated_payload_json.as_str(),
                    )
                    .await;
                    if let Err(error) = update_result {
                        return Err(error);
                    }
                }
                return Ok(false);
            }
        }
        let recovery_policy =
            fee_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str());
        if fee_amount_legs.is_empty()
            && fee_amount_raw.is_none()
            && recovery_policy != FeeAmountRecoveryPolicy::Disabled
            && !is_meteora_dbc_instruction_fee_materialization_candidate(
                decoded_event.event_kind.as_str(),
            )
        {
            let inferred_result = self
                .resolve_allowlisted_actual_fee_amounts_from_spl_transfers(
                    transaction_id,
                    decoded_event,
                    payload,
                )
                .await;
            let inferred = match inferred_result {
                Ok(inferred) => inferred,
                Err(error) => return Err(error),
            };
            if !inferred.is_empty() {
                fee_amount_legs = inferred;
                match fee_amount_legs.as_slice() {
                    [first_leg] => {
                        fee_token_mint = Some(first_leg.token_mint.clone());
                        fee_amount_raw = Some(first_leg.amount_raw.clone());
                    },
                    _ => {
                        fee_token_mint = None;
                        fee_amount_raw = None;
                    },
                }
                let updated_payload =
                    decoded_payload_with_materialized_fee_legs(payload, &fee_amount_legs);
                payload_json = updated_payload.to_string();
                let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
                    self.database.as_ref(),
                    decoded_event_id,
                    payload_json.as_str(),
                )
                .await;
                if let Err(error) = update_result {
                    return Err(error);
                }
            } else {
                let explanation_result = self
                    .resolve_allowlisted_unmaterialized_fee_amount_reason(
                        transaction_id,
                        decoded_event,
                        payload,
                    )
                    .await;
                let explanation = match explanation_result {
                    Ok(explanation) => explanation,
                    Err(error) => return Err(error),
                };
                if let Some(explanation) = explanation {
                    let updated_payload =
                        decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
                            payload,
                            explanation.as_str(),
                        );
                    payload_json = updated_payload.to_string();
                    let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
                        self.database.as_ref(),
                        decoded_event_id,
                        payload_json.as_str(),
                    )
                    .await;
                    if let Err(error) = update_result {
                        return Err(error);
                    }
                }
            }
        }
        let dto = crate::FeeEventDto::new(
            transaction_id,
            Some(decoded_event_id),
            context.dex_id,
            context.pool_id,
            context.pair_id,
            transaction.signature.clone(),
            transaction.slot,
            decoded_event.protocol_name.clone(),
            decoded_event.program_id.clone(),
            decoded_event.event_kind.clone(),
            decoded_event.pool_account.clone(),
            actor_wallet,
            fee_token_mint.clone(),
            fee_amount_raw.clone(),
            payload_json,
        );
        let mut amount_dtos = std::vec::Vec::new();
        if !fee_amount_legs.is_empty() {
            for (index, leg) in fee_amount_legs.iter().enumerate() {
                let leg_index = match u32::try_from(index) {
                    Ok(leg_index) => leg_index,
                    Err(error) => {
                        return Err(crate::Error::InvalidState(format!(
                            "cannot convert fee amount leg index '{}' for decoded_event_id '{}' to u32: {}",
                            index, decoded_event_id, error
                        )));
                    },
                };
                let leg_payload = fee_amount_leg_payload_json(decoded_event, leg, leg_index);
                amount_dtos.push(crate::FeeEventAmountDto::new(
                    0,
                    transaction_id,
                    Some(decoded_event_id),
                    leg_index,
                    decoded_event.event_kind.clone(),
                    leg.token_mint.clone(),
                    leg.amount_raw.clone(),
                    leg.source_account.clone(),
                    leg.destination_account.clone(),
                    leg.source.clone(),
                    leg_payload,
                ));
            }
        } else {
            let parent_amount_leg = fee_event_amount_leg_from_parent_amount(
                decoded_event,
                transaction_id,
                decoded_event_id,
                fee_token_mint.as_ref(),
                fee_amount_raw.as_ref(),
            );
            if let Some(parent_amount_leg) = parent_amount_leg {
                amount_dtos.push(parent_amount_leg);
            }
        }
        let upsert_result = if amount_dtos.is_empty() {
            crate::query_fee_events_upsert(self.database.as_ref(), &dto).await
        } else {
            crate::query_fee_events_upsert_with_amount_legs(
                self.database.as_ref(),
                &dto,
                amount_dtos.as_slice(),
            )
            .await
        };
        if let Err(error) = upsert_result {
            return Err(error);
        }
        return Ok(true);
    }

    async fn resolve_meteora_dbc_actual_fee_amounts(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<std::vec::Vec<MeteoraDbcActualAmount>, crate::Error> {
        let spl_amount_result = self
            .resolve_meteora_dbc_actual_fee_amounts_from_spl_transfers(
                transaction_id,
                decoded_event,
                payload,
            )
            .await;
        let spl_amounts = match spl_amount_result {
            Ok(spl_amounts) => spl_amounts,
            Err(error) => return Err(error),
        };
        if !spl_amounts.is_empty() {
            return Ok(spl_amounts);
        }
        let lamport_amount = resolve_meteora_dbc_actual_fee_amount_from_lamport_delta(
            transaction,
            decoded_event,
            payload,
        );
        match lamport_amount {
            Some(lamport_amount) => return Ok(vec![lamport_amount]),
            None => return Ok(std::vec::Vec::new()),
        }
    }

    async fn resolve_meteora_dbc_actual_fee_amounts_from_spl_transfers(
        &self,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<std::vec::Vec<MeteoraDbcActualAmount>, crate::Error> {
        let parent_instruction_id = match decoded_event.id {
            Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
            None => None,
        };
        let instruction_index =
            extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
        let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let instructions = match instructions_result {
            Ok(instructions) => instructions,
            Err(error) => return Err(error),
        };
        let mut amount_legs = std::vec::Vec::new();
        for instruction in &instructions {
            if !instruction_is_child_of_decoded_event(
                instruction,
                parent_instruction_id,
                instruction_index,
            ) {
                continue;
            }
            let parsed_type = match instruction.parsed_type.as_deref() {
                Some(parsed_type) => parsed_type,
                None => continue,
            };
            if parsed_type != "transferChecked" && parsed_type != "transfer" {
                continue;
            }
            let parsed_json = match instruction.parsed_json.as_deref() {
                Some(parsed_json) => parsed_json,
                None => continue,
            };
            let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
            let parsed_value = match parsed_value {
                Ok(parsed_value) => parsed_value,
                Err(_) => continue,
            };
            let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
            let amount_raw = match amount_raw {
                Some(amount_raw) => amount_raw,
                None => continue,
            };
            let amount = amount_raw.parse::<u128>();
            let amount = match amount {
                Ok(amount) => amount,
                Err(_) => continue,
            };
            if amount == 0 {
                continue;
            }
            let mint = extract_first_string(&parsed_value, &["mint"]);
            let mint = match mint {
                Some(mint) => mint,
                None => continue,
            };
            let source_account = extract_first_string(&parsed_value, &["source"]);
            let destination_account = extract_first_string(&parsed_value, &["destination"]);
            amount_legs.push(MeteoraDbcActualAmount {
                token_mint: mint,
                amount_raw,
                source: "inner_spl_transfer".to_string(),
                source_account,
                destination_account,
            });
        }
        return Ok(amount_legs);
    }

    async fn resolve_allowlisted_actual_fee_amounts_from_spl_transfers(
        &self,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<std::vec::Vec<MeteoraDbcActualAmount>, crate::Error> {
        let parent_instruction_id = match decoded_event.id {
            Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
            None => None,
        };
        let instruction_index =
            extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
        let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let instructions = match instructions_result {
            Ok(instructions) => instructions,
            Err(error) => return Err(error),
        };
        let mut amount_legs = std::vec::Vec::new();
        for instruction in &instructions {
            if !instruction_is_child_of_decoded_event(
                instruction,
                parent_instruction_id,
                instruction_index,
            ) {
                continue;
            }
            let parsed_type = match instruction.parsed_type.as_deref() {
                Some(parsed_type) => parsed_type,
                None => continue,
            };
            if parsed_type != "transferChecked" && parsed_type != "transfer" {
                continue;
            }
            let parsed_json = match instruction.parsed_json.as_deref() {
                Some(parsed_json) => parsed_json,
                None => continue,
            };
            let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
            let parsed_value = match parsed_value {
                Ok(parsed_value) => parsed_value,
                Err(_) => continue,
            };
            let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
            let amount_raw = match amount_raw {
                Some(amount_raw) => amount_raw,
                None => continue,
            };
            let amount = amount_raw.parse::<u128>();
            let amount = match amount {
                Ok(amount) => amount,
                Err(_) => continue,
            };
            if amount == 0 {
                continue;
            }
            let mint = extract_first_string(&parsed_value, &["mint"]);
            let mint = match mint {
                Some(mint) => mint,
                None => continue,
            };
            let source_account = extract_first_string(&parsed_value, &["source"]);
            let destination_account = extract_first_string(&parsed_value, &["destination"]);
            amount_legs.push(MeteoraDbcActualAmount {
                token_mint: mint,
                amount_raw,
                source: "allowlisted_inner_spl_transfer".to_string(),
                source_account,
                destination_account,
            });
        }
        return Ok(amount_legs);
    }

    async fn resolve_allowlisted_unmaterialized_fee_amount_reason(
        &self,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<std::option::Option<std::string::String>, crate::Error> {
        let parent_instruction_id = match decoded_event.id {
            Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
            None => None,
        };
        let instruction_index =
            extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
        let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let instructions = match instructions_result {
            Ok(instructions) => instructions,
            Err(error) => return Err(error),
        };
        let mut transfer_count: usize = 0;
        let mut non_zero_transfer_count: usize = 0;
        let mut non_zero_mints: std::collections::BTreeSet<std::string::String> =
            std::collections::BTreeSet::new();
        for instruction in &instructions {
            if !instruction_is_child_of_decoded_event(
                instruction,
                parent_instruction_id,
                instruction_index,
            ) {
                continue;
            }
            let parsed_type = match instruction.parsed_type.as_deref() {
                Some(parsed_type) => parsed_type,
                None => continue,
            };
            if parsed_type != "transferChecked" && parsed_type != "transfer" {
                continue;
            }
            transfer_count += 1;
            let parsed_json = match instruction.parsed_json.as_deref() {
                Some(parsed_json) => parsed_json,
                None => continue,
            };
            let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
            let parsed_value = match parsed_value {
                Ok(parsed_value) => parsed_value,
                Err(_) => continue,
            };
            let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
            let amount_raw = match amount_raw {
                Some(amount_raw) => amount_raw,
                None => continue,
            };
            let amount = amount_raw.parse::<u128>();
            let amount = match amount {
                Ok(amount) => amount,
                Err(_) => continue,
            };
            if amount == 0 {
                continue;
            }
            non_zero_transfer_count += 1;
            let mint = extract_first_string(&parsed_value, &["mint"]);
            let mint = match mint {
                Some(mint) => mint,
                None => continue,
            };
            non_zero_mints.insert(mint);
        }
        if non_zero_mints.len() > 1 {
            return Ok(Some("multi_token_fee_amounts_materialized_as_fee_amount_legs".to_string()));
        }
        if transfer_count > 0 && non_zero_transfer_count == 0 {
            return Ok(Some("fee_instruction_has_only_zero_amount_transfers".to_string()));
        }
        if transfer_count == 0 {
            return Ok(Some("fee_instruction_has_no_actual_transfer".to_string()));
        }
        return Ok(None);
    }

    async fn resolve_allowlisted_actual_reward_amount_from_spl_transfers(
        &self,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<std::option::Option<MeteoraDbcActualAmount>, crate::Error> {
        let parent_instruction_id = match decoded_event.id {
            Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
            None => None,
        };
        let instruction_index =
            extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
        let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let instructions = match instructions_result {
            Ok(instructions) => instructions,
            Err(error) => return Err(error),
        };
        let mut amount_legs = std::vec::Vec::new();
        for instruction in &instructions {
            if !instruction_is_child_of_decoded_event(
                instruction,
                parent_instruction_id,
                instruction_index,
            ) {
                continue;
            }
            let parsed_type = match instruction.parsed_type.as_deref() {
                Some(parsed_type) => parsed_type,
                None => continue,
            };
            if parsed_type != "transferChecked" && parsed_type != "transfer" {
                continue;
            }
            let parsed_json = match instruction.parsed_json.as_deref() {
                Some(parsed_json) => parsed_json,
                None => continue,
            };
            let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
            let parsed_value = match parsed_value {
                Ok(parsed_value) => parsed_value,
                Err(_) => continue,
            };
            let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
            let amount_raw = match amount_raw {
                Some(amount_raw) => amount_raw,
                None => continue,
            };
            let amount = amount_raw.parse::<u128>();
            let amount = match amount {
                Ok(amount) => amount,
                Err(_) => continue,
            };
            if amount == 0 {
                continue;
            }
            let mint = extract_first_string(&parsed_value, &["mint"]);
            let mint = match mint {
                Some(mint) => mint,
                None => continue,
            };
            let source_account = extract_first_string(&parsed_value, &["source"]);
            let destination_account = extract_first_string(&parsed_value, &["destination"]);
            amount_legs.push(MeteoraDbcActualAmount {
                token_mint: mint,
                amount_raw,
                source: "allowlisted_reward_inner_spl_transfer".to_string(),
                source_account,
                destination_account,
            });
        }
        return Ok(single_mint_amount_from_actual_amounts(
            amount_legs.as_slice(),
            "allowlisted_reward_inner_spl_transfer",
        ));
    }

    async fn resolve_meteora_dbc_unmaterialized_fee_skip_reason(
        &self,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<std::option::Option<std::string::String>, crate::Error> {
        let parent_instruction_id = match decoded_event.id {
            Some(_) => extract_first_i64(payload, &["instructionId", "instruction_id"]),
            None => None,
        };
        let instruction_index =
            extract_first_i64(payload, &["instructionIndex", "instruction_index"]);
        let instructions_result = crate::query_chain_instructions_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let instructions = match instructions_result {
            Ok(instructions) => instructions,
            Err(error) => return Err(error),
        };
        let mut transfer_count: usize = 0;
        let mut non_zero_transfer_count: usize = 0;
        let mut non_zero_mints: std::collections::BTreeSet<std::string::String> =
            std::collections::BTreeSet::new();
        for instruction in &instructions {
            if !instruction_is_child_of_decoded_event(
                instruction,
                parent_instruction_id,
                instruction_index,
            ) {
                continue;
            }
            let parsed_type = match instruction.parsed_type.as_deref() {
                Some(parsed_type) => parsed_type,
                None => continue,
            };
            if parsed_type != "transferChecked" && parsed_type != "transfer" {
                continue;
            }
            transfer_count += 1;
            let parsed_json = match instruction.parsed_json.as_deref() {
                Some(parsed_json) => parsed_json,
                None => continue,
            };
            let parsed_value = serde_json::from_str::<serde_json::Value>(parsed_json);
            let parsed_value = match parsed_value {
                Ok(parsed_value) => parsed_value,
                Err(_) => continue,
            };
            let amount_raw = extract_spl_transfer_amount_raw(&parsed_value);
            let amount_raw = match amount_raw {
                Some(amount_raw) => amount_raw,
                None => continue,
            };
            let amount = amount_raw.parse::<u128>();
            let amount = match amount {
                Ok(amount) => amount,
                Err(_) => continue,
            };
            if amount == 0 {
                continue;
            }
            non_zero_transfer_count += 1;
            let mint = extract_first_string(&parsed_value, &["mint"]);
            let mint = match mint {
                Some(mint) => mint,
                None => continue,
            };
            non_zero_mints.insert(mint);
        }
        if non_zero_mints.len() > 1 {
            return Ok(Some("multi_token_fee_amounts_require_multiple_fee_rows".to_string()));
        }
        if transfer_count > 0 && non_zero_transfer_count == 0 {
            return Ok(Some("fee_instruction_has_only_zero_amount_transfers".to_string()));
        }
        if transfer_count == 0 {
            return Ok(Some("fee_instruction_has_no_actual_transfer".to_string()));
        }
        return Ok(None);
    }

    async fn materialize_reward_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let actor_wallet = extract_first_string(
            payload,
            &[
                "actorWallet",
                "actor_wallet",
                "receiver",
                "recipient",
                "owner",
                "payer",
                "authority",
                "user",
            ],
        );
        let mut reward_token_mint = extract_first_string(
            payload,
            &["rewardTokenMint", "reward_token_mint", "tokenMint", "token_mint", "mint"],
        );
        let mut reward_amount_raw = extract_first_amount_string(
            payload,
            &[
                "rewardAmountRaw",
                "reward_amount_raw",
                "rewardAmount",
                "reward_amount",
                "emissionAmount",
                "emission_amount",
                "amount",
            ],
        );
        let mut reward_payload_json = decoded_event.payload_json.clone();
        if reward_amount_recovery_policy_for_event_kind(decoded_event.event_kind.as_str())
            == FeeAmountRecoveryPolicy::InnerSplTransfer
            && (optional_text_is_missing(&reward_token_mint)
                || optional_text_is_missing(&reward_amount_raw))
        {
            let recovered_reward_result = self
                .resolve_allowlisted_actual_reward_amount_from_spl_transfers(
                    transaction_id,
                    decoded_event,
                    payload,
                )
                .await;
            let recovered_reward = match recovered_reward_result {
                Ok(recovered_reward) => recovered_reward,
                Err(error) => return Err(error),
            };
            if let Some(recovered_reward) = recovered_reward {
                if optional_text_is_missing(&reward_token_mint) {
                    reward_token_mint = Some(recovered_reward.token_mint.clone());
                }
                if optional_text_is_missing(&reward_amount_raw) {
                    reward_amount_raw = Some(recovered_reward.amount_raw.clone());
                }
                reward_payload_json =
                    decoded_payload_with_materialized_reward_amount(payload, &recovered_reward)
                        .to_string();
            }
        }
        let dto = crate::RewardEventDto::new(
            transaction_id,
            Some(decoded_event_id),
            context.dex_id,
            context.pool_id,
            context.pair_id,
            transaction.signature.clone(),
            transaction.slot,
            decoded_event.protocol_name.clone(),
            decoded_event.program_id.clone(),
            decoded_event.event_kind.clone(),
            decoded_event.pool_account.clone(),
            actor_wallet,
            reward_token_mint,
            reward_amount_raw,
            reward_payload_json,
        );
        let upsert_result = crate::query_reward_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn delete_stale_pool_admin_event_for_lifecycle(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<(), crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(()),
        };
        let delete_result = crate::query_pool_admin_events_delete_by_decoded_event_id(
            self.database.as_ref(),
            decoded_event_id,
        )
        .await;
        let deleted_count = match delete_result {
            Ok(deleted_count) => deleted_count,
            Err(error) => return Err(error),
        };
        if deleted_count > 0 {
            tracing::debug!(
                decoded_event_id = decoded_event_id,
                event_kind = %decoded_event.event_kind,
                deleted_count = deleted_count,
                "removed stale pool admin materialization for lifecycle event"
            );
        }
        return Ok(());
    }

    async fn materialize_pool_admin_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let actor_wallet = extract_first_string(
            payload,
            &["actorWallet", "actor_wallet", "authority", "admin", "owner", "payer", "user"],
        );
        let admin_action = match extract_first_string(
            payload,
            &["adminAction", "admin_action", "action", "configAction", "config_action"],
        ) {
            Some(admin_action) => Some(admin_action),
            None => Some(decoded_event.event_kind.clone()),
        };
        let dto = crate::PoolAdminEventDto::new(
            transaction_id,
            Some(decoded_event_id),
            context.dex_id,
            context.pool_id,
            context.pair_id,
            transaction.signature.clone(),
            transaction.slot,
            decoded_event.protocol_name.clone(),
            decoded_event.program_id.clone(),
            decoded_event.event_kind.clone(),
            decoded_event.pool_account.clone(),
            actor_wallet,
            admin_action,
            decoded_event.payload_json.clone(),
        );
        let upsert_result =
            crate::query_pool_admin_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_token_account_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let token_account = match extract_first_string(
            payload,
            &[
                "supportMintAssociated",
                "support_mint_associated",
                "tokenAccount",
                "token_account",
            ],
        ) {
            Some(token_account) => Some(token_account),
            None => extract_account_string(payload, 2),
        };
        let token_mint = match extract_first_string(payload, &["tokenMint", "token_mint", "mint"]) {
            Some(token_mint) => Some(token_mint),
            None => extract_account_string(payload, 1),
        };
        let owner_wallet = match extract_first_string(payload, &["owner", "authority", "payer"]) {
            Some(owner_wallet) => Some(owner_wallet),
            None => extract_account_string(payload, 0),
        };
        let instruction_index = match extract_first_i64(
            payload,
            &["instructionIndex", "instruction_index", "outerInstructionIndex"],
        ) {
            Some(instruction_index) => instruction_index,
            None => decoded_event_id,
        };
        let dto = crate::TokenAccountEventDto::new(
            Some(transaction_id),
            Some(decoded_event_id),
            context.dex_id,
            context.pool_id,
            context.pair_id,
            transaction.signature.clone(),
            instruction_index,
            transaction.slot,
            decoded_event.protocol_name.clone(),
            Some(decoded_event.program_id.clone()),
            decoded_event.event_kind.clone(),
            token_account,
            token_mint,
            owner_wallet,
            Some("create_support_mint_associated".to_string()),
            Some(decoded_event.payload_json.clone()),
        );
        let upsert_result =
            crate::query_token_account_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_launch_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let actor_wallet = extract_first_string(
            payload,
            &[
                "actorWallet",
                "actor_wallet",
                "beneficiary",
                "owner",
                "payer",
                "authority",
                "user",
                "creator",
                "platformVestingWallet",
                "platform_vesting_wallet",
            ],
        );
        let event_role = launchpad_launch_event_role(decoded_event.event_kind.as_str());
        let related_account = extract_first_string(
            payload,
            &[
                "platformVestingRecord",
                "platform_vesting_record",
                "platformGlobalAccess",
                "platform_global_access",
                "vestingRecord",
                "vesting_record",
                "config",
                "platformConfig",
                "platform_config",
                "poolState",
                "pool_state",
                "poolAccount",
                "bondingCurve",
                "bonding_curve",
                "sharingConfig",
                "sharing_config",
            ],
        );
        let related_mint = extract_first_string(
            payload,
            &[
                "baseMint",
                "base_mint",
                "baseTokenMint",
                "base_token_mint",
                "tokenMint",
                "token_mint",
                "mint",
            ],
        );
        let slot_i64 = match transaction.slot {
            Some(slot) => {
                let converted = i64::try_from(slot);
                match converted {
                    Ok(converted) => Some(converted),
                    Err(error) => {
                        return Err(crate::Error::Db(format!(
                            "cannot convert launch event slot '{}' to i64: {}",
                            slot, error
                        )));
                    },
                }
            },
            None => None,
        };
        let input = crate::LaunchEventUpsertInput {
            transaction_id,
            decoded_event_id,
            dex_id: context.dex_id,
            pool_id: context.pool_id,
            pair_id: context.pair_id,
            signature: transaction.signature.clone(),
            slot: slot_i64,
            protocol_name: decoded_event.protocol_name.clone(),
            program_id: decoded_event.program_id.clone(),
            event_kind: decoded_event.event_kind.clone(),
            pool_account: decoded_event.pool_account.clone(),
            actor_wallet,
            event_role,
            related_account,
            related_mint,
            payload_json: payload.clone(),
        };
        let upsert_result = crate::query_launch_events_upsert(self.database.as_ref(), &input).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_liquidity_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context =
            self.resolve_liquidity_context(transaction, transaction_id, decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let dex_id = match context.dex_id {
            Some(dex_id) => dex_id,
            None => {
                let annotate_result = self
                    .annotate_decoded_event_payload(
                        decoded_event,
                        "skipLiquidityReason",
                        "missing_dex_catalog",
                    )
                    .await;
                if let Err(error) = annotate_result {
                    return Err(error);
                }
                return Ok(false);
            },
        };
        let pool_id = match context.pool_id {
            Some(pool_id) => pool_id,
            None => {
                let annotate_result = self
                    .annotate_decoded_event_payload(
                        decoded_event,
                        "skipLiquidityReason",
                        "missing_pool_catalog",
                    )
                    .await;
                if let Err(error) = annotate_result {
                    return Err(error);
                }
                return Ok(false);
            },
        };
        let pair = match context.pair {
            Some(pair) => pair,
            None => {
                let annotate_result = self
                    .annotate_decoded_event_payload(
                        decoded_event,
                        "skipLiquidityReason",
                        "missing_pair_catalog",
                    )
                    .await;
                if let Err(error) = annotate_result {
                    return Err(error);
                }
                return Ok(false);
            },
        };
        let pair_id = match pair.id {
            Some(pair_id) => Some(pair_id),
            None => None,
        };
        let event_kind =
            if crate::is_dex_position_open_event_kind(decoded_event.event_kind.as_str()) {
                crate::LiquidityEventKind::PositionOpen
            } else if crate::is_dex_position_close_event_kind(decoded_event.event_kind.as_str()) {
                crate::LiquidityEventKind::PositionClose
            } else if decoded_event.event_kind.ends_with(".lp_change_event") {
                let change_type = extract_first_u64(payload, &["changeType", "change_type"]);
                match change_type {
                    Some(1) => crate::LiquidityEventKind::Remove,
                    _ => crate::LiquidityEventKind::Add,
                }
            } else if crate::is_dex_liquidity_remove_event_kind(decoded_event.event_kind.as_str()) {
                crate::LiquidityEventKind::Remove
            } else {
                crate::LiquidityEventKind::Add
            };
        let actor_wallet = extract_first_string(
            payload,
            &[
                "actorWallet",
                "actor_wallet",
                "user",
                "owner",
                "payer",
                "authority",
                "liquidityProvider",
                "liquidity_provider",
            ],
        );
        let base_amount = extract_first_amount_string(
            payload,
            &[
                "baseAmountRaw",
                "base_amount_raw",
                "baseAmount",
                "base_amount",
                "amountBase",
                "amount_base",
                "tokenAAmount",
                "token_a_amount",
                "token0AmountRaw",
                "token_0_amount_raw",
                "token0Amount",
                "token_0_amount",
                "amount0Raw",
                "amount0_raw",
                "amount0",
                "amount0RequestedRaw",
                "amount_0_requested_raw",
                "amountA",
                "amount_a",
            ],
        );
        let quote_amount = extract_first_amount_string(
            payload,
            &[
                "quoteAmountRaw",
                "quote_amount_raw",
                "quoteAmount",
                "quote_amount",
                "amountQuote",
                "amount_quote",
                "tokenBAmount",
                "token_b_amount",
                "token1AmountRaw",
                "token_1_amount_raw",
                "token1Amount",
                "token_1_amount",
                "amount1Raw",
                "amount1_raw",
                "amount1",
                "amount1RequestedRaw",
                "amount_1_requested_raw",
                "amountB",
                "amount_b",
            ],
        );
        let lp_amount = extract_first_amount_string(
            payload,
            &[
                "lpAmountRaw",
                "lp_amount_raw",
                "lpAmount",
                "lp_amount",
                "liquidity",
                "liquidity_raw",
                "liquidityRaw",
                "liquidityAmount",
                "liquidity_amount",
            ],
        );
        let amounts_are_complete = base_amount.is_some() && quote_amount.is_some();
        let base_amount_value = match base_amount {
            Some(base_amount_value) => base_amount_value,
            None => "0".to_string(),
        };
        let quote_amount_value = match quote_amount {
            Some(quote_amount_value) => quote_amount_value,
            None => "0".to_string(),
        };
        let dto = crate::LiquidityEventDto::new(
            dex_id,
            pool_id,
            pair_id,
            transaction.signature.clone(),
            decoded_event_id,
            transaction.slot,
            event_kind,
            actor_wallet,
            pair.base_token_id,
            pair.quote_token_id,
            None,
            base_amount_value,
            quote_amount_value,
            lp_amount,
        )
        .with_decoded_event_metadata(
            Some(transaction_id),
            Some(decoded_event_id),
            Some(decoded_event.program_id.clone()),
            Some(decoded_event.event_kind.clone()),
            Some(decoded_event.payload_json.clone()),
            amounts_are_complete,
        );
        let upsert_result =
            crate::query_liquidity_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_orderbook_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        payload: &serde_json::Value,
    ) -> Result<bool, crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(false),
        };
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let order_action = normalize_orderbook_action(decoded_event.event_kind.as_str());
        let actor_wallet = extract_first_string(
            payload,
            &["actorWallet", "actor_wallet", "owner", "authority", "payer", "user"],
        );
        let order_account = match extract_first_string(
            payload,
            &["orderAccount", "order_account", "limitOrder", "limit_order", "order"],
        ) {
            Some(order_account) => Some(order_account),
            None => fallback_order_account(decoded_event.event_kind.as_str(), payload),
        };
        let amount_raw = extract_first_amount_string(
            payload,
            &[
                "amountRaw",
                "amount_raw",
                "amount",
                "decreasedAmountRaw",
                "decreased_amount_raw",
                "decreasedAmount",
                "increasedAmountRaw",
                "increased_amount_raw",
                "increasedAmount",
            ],
        );
        let amount_min_raw = extract_first_amount_string(
            payload,
            &["amountMinRaw", "amount_min_raw", "amountMin", "amount_min"],
        );
        let tick_index = extract_first_i64(payload, &["tickIndex", "tick_index"]);
        let zero_for_one = extract_first_bool(payload, &["zeroForOne", "zero_for_one"]);
        let dto = crate::OrderbookEventDto::new(
            transaction_id,
            Some(decoded_event_id),
            context.dex_id,
            context.pool_id,
            context.pair_id,
            transaction.signature.clone(),
            transaction.slot,
            decoded_event.protocol_name.clone(),
            decoded_event.program_id.clone(),
            decoded_event.event_kind.clone(),
            order_action,
            decoded_event.pool_account.clone(),
            decoded_event.market_account.clone(),
            actor_wallet,
            order_account,
            decoded_event.token_a_mint.clone(),
            decoded_event.token_b_mint.clone(),
            amount_raw,
            amount_min_raw,
            tick_index,
            zero_for_one,
            decoded_event.payload_json.clone(),
        );
        let upsert_result =
            crate::query_orderbook_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(true),
            Err(error) => return Err(error),
        }
    }

    async fn annotate_decoded_event_payload(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
        reason_key: &str,
        reason_value: &str,
    ) -> Result<(), crate::Error> {
        let decoded_event_id = match decoded_event.id {
            Some(decoded_event_id) => decoded_event_id,
            None => return Ok(()),
        };
        let payload_result =
            serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
        let mut object = match payload_result {
            Ok(serde_json::Value::Object(object)) => object,
            Ok(other) => {
                let mut object = serde_json::Map::new();
                object.insert("rawPayload".to_string(), other);
                object
            },
            Err(_) => serde_json::Map::new(),
        };
        let existing_reason = match object.get(reason_key).and_then(serde_json::Value::as_str) {
            Some(existing_reason) => existing_reason.trim().to_string(),
            None => std::string::String::new(),
        };
        if existing_reason.is_empty() {
            object.insert(
                reason_key.to_string(),
                serde_json::Value::String(reason_value.to_string()),
            );
        }
        if reason_key == "skipLiquidityReason" {
            object.insert(
                "skipCatalogReason".to_string(),
                serde_json::Value::String(reason_value.to_string()),
            );
        }
        let payload_json = serde_json::Value::Object(object).to_string();
        let update_result = crate::query_dex_decoded_events_update_payload_json_by_id(
            self.database.as_ref(),
            decoded_event_id,
            payload_json.as_str(),
        )
        .await;
        match update_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn resolve_liquidity_context(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<NonTradeDecodedEventContext, crate::Error> {
        let context = self.resolve_decoded_event_context(decoded_event).await;
        let context = match context {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let context = if context.pool_id.is_some() && context.pair.is_some() {
            context
        } else {
            let ensured_context =
                self.ensure_liquidity_context_from_decoded_event(decoded_event, context).await;
            match ensured_context {
                Ok(ensured_context) => ensured_context,
                Err(error) => return Err(error),
            }
        };
        if context.pool_id.is_some() && context.pair.is_some() {
            return Ok(context);
        }
        let sibling_context = self
            .resolve_liquidity_context_from_transaction_siblings(transaction_id, decoded_event)
            .await;
        let context = match sibling_context {
            Ok(Some(sibling_context)) => return Ok(sibling_context),
            Ok(None) => context,
            Err(error) => return Err(error),
        };
        let inferred_context = self
            .resolve_liquidity_context_from_transaction_token_balances(
                transaction,
                transaction_id,
                decoded_event,
                context.clone(),
            )
            .await;
        match inferred_context {
            Ok(Some(inferred_context)) => return Ok(inferred_context),
            Ok(None) => return Ok(context),
            Err(error) => return Err(error),
        }
    }

    async fn resolve_liquidity_context_from_transaction_siblings(
        &self,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<std::option::Option<NonTradeDecodedEventContext>, crate::Error> {
        let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let decoded_events = match decoded_events_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let decoded_event_id = decoded_event.id;
        for sibling in &decoded_events {
            if sibling.id == decoded_event_id {
                continue;
            }
            if sibling.protocol_name != decoded_event.protocol_name {
                continue;
            }
            if sibling.pool_account.is_none() {
                continue;
            }
            let sibling_context = self.resolve_decoded_event_context(sibling).await;
            let sibling_context = match sibling_context {
                Ok(sibling_context) => sibling_context,
                Err(error) => return Err(error),
            };
            let sibling_context =
                if sibling_context.pool_id.is_some() && sibling_context.pair.is_some() {
                    sibling_context
                } else {
                    let ensured_context = self
                        .ensure_liquidity_context_from_decoded_event(sibling, sibling_context)
                        .await;
                    match ensured_context {
                        Ok(ensured_context) => ensured_context,
                        Err(error) => return Err(error),
                    }
                };
            if sibling_context.pool_id.is_some() && sibling_context.pair.is_some() {
                return Ok(Some(sibling_context));
            }
        }
        return Ok(None);
    }

    async fn resolve_liquidity_context_from_transaction_token_balances(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        decoded_event: &crate::DexDecodedEventDto,
        context: NonTradeDecodedEventContext,
    ) -> Result<std::option::Option<NonTradeDecodedEventContext>, crate::Error> {
        let dex_id = match context.dex_id {
            Some(dex_id) => dex_id,
            None => return Ok(None),
        };
        let token_mints_by_account = token_mints_by_account_from_transaction(transaction);
        if token_mints_by_account.is_empty() {
            return Ok(None);
        }
        let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
            self.database.as_ref(),
            transaction_id,
        )
        .await;
        let decoded_events = match decoded_events_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let target_payload_result =
            serde_json::from_str::<serde_json::Value>(decoded_event.payload_json.as_str());
        let target_payload = match target_payload_result {
            Ok(target_payload) => target_payload,
            Err(_) => serde_json::Value::Object(serde_json::Map::new()),
        };
        for candidate in &decoded_events {
            if candidate.protocol_name != decoded_event.protocol_name {
                continue;
            }
            if !candidate.event_kind.starts_with("raydium_clmm.") {
                continue;
            }
            let candidate_payload_result =
                serde_json::from_str::<serde_json::Value>(candidate.payload_json.as_str());
            let candidate_payload = match candidate_payload_result {
                Ok(candidate_payload) => candidate_payload,
                Err(_) => serde_json::Value::Object(serde_json::Map::new()),
            };
            let pool_account = match candidate.pool_account.clone() {
                Some(pool_account) => Some(pool_account),
                None => extract_first_string(
                    &candidate_payload,
                    &["poolState", "pool_state", "poolAccount", "pool_account"],
                ),
            };
            let pool_account = match pool_account {
                Some(pool_account) => pool_account,
                None => continue,
            };
            let direct_token_a = match candidate.token_a_mint.clone() {
                Some(token_a_mint) => Some(token_a_mint),
                None => extract_first_string(
                    &candidate_payload,
                    &[
                        "tokenMint0",
                        "token_mint0",
                        "tokenMintA",
                        "token_mint_a",
                        "baseMint",
                        "base_mint",
                    ],
                ),
            };
            let direct_token_b = match candidate.token_b_mint.clone() {
                Some(token_b_mint) => Some(token_b_mint),
                None => extract_first_string(
                    &candidate_payload,
                    &[
                        "tokenMint1",
                        "token_mint1",
                        "tokenMintB",
                        "token_mint_b",
                        "quoteMint",
                        "quote_mint",
                    ],
                ),
            };
            let inferred_pair = infer_raydium_clmm_pair_mints_from_payload_accounts(
                &candidate_payload,
                &token_mints_by_account,
            );
            let token_a_mint = match direct_token_a {
                Some(token_a_mint) => Some(token_a_mint),
                None => match inferred_pair.as_ref() {
                    Some(pair) => Some(pair.0.clone()),
                    None => None,
                },
            };
            let token_b_mint = match direct_token_b {
                Some(token_b_mint) => Some(token_b_mint),
                None => match inferred_pair.as_ref() {
                    Some(pair) => Some(pair.1.clone()),
                    None => None,
                },
            };
            let token_a_mint = match token_a_mint {
                Some(token_a_mint) => token_a_mint,
                None => continue,
            };
            let token_b_mint = match token_b_mint {
                Some(token_b_mint) => token_b_mint,
                None => continue,
            };
            if token_a_mint == token_b_mint {
                continue;
            }
            let synthetic_payload = merge_payload_with_inferred_pool_context(
                &target_payload,
                pool_account.as_str(),
                token_a_mint.as_str(),
                token_b_mint.as_str(),
                candidate.event_kind.as_str(),
            );
            let mut synthetic_event = decoded_event.clone();
            synthetic_event.pool_account = Some(pool_account);
            synthetic_event.token_a_mint = Some(token_a_mint);
            synthetic_event.token_b_mint = Some(token_b_mint);
            synthetic_event.payload_json = synthetic_payload.to_string();
            let synthetic_context = NonTradeDecodedEventContext {
                dex_id: Some(dex_id),
                pool_id: None,
                pair_id: None,
                pair: None,
            };
            let ensured = self
                .ensure_liquidity_context_from_decoded_event(&synthetic_event, synthetic_context)
                .await;
            let ensured = match ensured {
                Ok(ensured) => ensured,
                Err(error) => return Err(error),
            };
            if ensured.pool_id.is_some() && ensured.pair.is_some() {
                return Ok(Some(ensured));
            }
        }
        return Ok(None);
    }

    async fn ensure_liquidity_context_from_decoded_event(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
        context: NonTradeDecodedEventContext,
    ) -> Result<NonTradeDecodedEventContext, crate::Error> {
        let dex_id = match context.dex_id {
            Some(dex_id) => dex_id,
            None => return Ok(context),
        };
        if context.pool_id.is_some() && context.pair.is_some() {
            return Ok(context);
        }
        if decoded_event.pool_account.is_none()
            || decoded_event.token_a_mint.is_none()
            || decoded_event.token_b_mint.is_none()
        {
            return Ok(context);
        }
        let materialization_input_result =
            crate::dex_pool_materialization::DexPoolMaterializationInput::from_decoded_event(
                decoded_event,
                dex_id,
                crate::PoolKind::Amm,
                crate::PoolStatus::Active,
                crate::dex_pool_materialization::DexPoolTokenOrder::AlreadyBaseQuote,
                None,
                None,
                None,
            );
        let materialization_input = match materialization_input_result {
            Ok(materialization_input) => materialization_input,
            Err(_) => return Ok(context),
        };
        let materialization_result = crate::dex_pool_materialization::materialize_dex_pool(
            self.database.as_ref(),
            &materialization_input,
        )
        .await;
        if let Err(error) = materialization_result {
            return Err(error);
        }
        let refreshed_context = self.resolve_decoded_event_context(decoded_event).await;
        match refreshed_context {
            Ok(refreshed_context) => return Ok(refreshed_context),
            Err(error) => return Err(error),
        }
    }

    async fn resolve_decoded_event_context(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<NonTradeDecodedEventContext, crate::Error> {
        let dex_result = crate::query_dexs_get_by_code(
            self.database.as_ref(),
            decoded_event.protocol_name.as_str(),
        )
        .await;
        let dex_id = match dex_result {
            Ok(Some(dex)) => dex.id,
            Ok(None) => None,
            Err(error) => return Err(error),
        };
        let pool_address = match decoded_event.pool_account.clone() {
            Some(pool_address) => pool_address,
            None => {
                return Ok(NonTradeDecodedEventContext {
                    dex_id,
                    pool_id: None,
                    pair_id: None,
                    pair: None,
                });
            },
        };
        let pool_result =
            crate::query_pools_get_by_address(self.database.as_ref(), pool_address.as_str()).await;
        let pool = match pool_result {
            Ok(Some(pool)) => pool,
            Ok(None) => {
                return Ok(NonTradeDecodedEventContext {
                    dex_id,
                    pool_id: None,
                    pair_id: None,
                    pair: None,
                });
            },
            Err(error) => return Err(error),
        };
        let pool_id = match pool.id {
            Some(pool_id) => pool_id,
            None => {
                return Ok(NonTradeDecodedEventContext {
                    dex_id,
                    pool_id: None,
                    pair_id: None,
                    pair: None,
                });
            },
        };
        let pair_result = crate::query_pairs_get_by_pool_id(self.database.as_ref(), pool_id).await;
        let pair = match pair_result {
            Ok(pair) => pair,
            Err(error) => return Err(error),
        };
        let pair_id = match pair.as_ref() {
            Some(pair) => pair.id,
            None => None,
        };
        return Ok(NonTradeDecodedEventContext {
            dex_id,
            pool_id: Some(pool_id),
            pair_id,
            pair,
        });
    }
}

#[derive(Debug, Clone)]
struct MeteoraDbcActualAmount {
    token_mint: std::string::String,
    amount_raw: std::string::String,
    source: std::string::String,
    source_account: std::option::Option<std::string::String>,
    destination_account: std::option::Option<std::string::String>,
}

fn single_mint_amount_from_actual_amounts(
    amount_legs: &[MeteoraDbcActualAmount],
    source: &str,
) -> std::option::Option<MeteoraDbcActualAmount> {
    let mut token_mint: std::option::Option<std::string::String> = None;
    let mut amount_total: u128 = 0;
    let mut source_account: std::option::Option<std::string::String> = None;
    let mut destination_account: std::option::Option<std::string::String> = None;
    for leg in amount_legs {
        if leg.token_mint.trim().is_empty() {
            continue;
        }
        let amount = leg.amount_raw.parse::<u128>();
        let amount = match amount {
            Ok(amount) => amount,
            Err(_) => continue,
        };
        if amount == 0 {
            continue;
        }
        match token_mint.as_ref() {
            Some(existing_mint) => {
                if existing_mint != &leg.token_mint {
                    return None;
                }
            },
            None => token_mint = Some(leg.token_mint.clone()),
        }
        let checked_total = amount_total.checked_add(amount);
        amount_total = match checked_total {
            Some(checked_total) => checked_total,
            None => return None,
        };
        if source_account.is_none() {
            source_account = leg.source_account.clone();
        }
        if destination_account.is_none() {
            destination_account = leg.destination_account.clone();
        }
    }
    let token_mint = match token_mint {
        Some(token_mint) => token_mint,
        None => return None,
    };
    if amount_total == 0 {
        return None;
    }
    return Some(MeteoraDbcActualAmount {
        token_mint,
        amount_raw: amount_total.to_string(),
        source: source.to_string(),
        source_account,
        destination_account,
    });
}

fn fee_amount_legs_from_payload(
    payload: &serde_json::Value,
) -> std::vec::Vec<MeteoraDbcActualAmount> {
    let mut legs = std::vec::Vec::new();
    let candidate_arrays = ["feeAmountLegs", "materializedFeeAmountLegs"];
    for candidate_key in candidate_arrays {
        let candidate = match payload.get(candidate_key) {
            Some(candidate) => candidate,
            None => continue,
        };
        let array = match candidate.as_array() {
            Some(array) => array,
            None => continue,
        };
        for leg_value in array {
            let token_mint = extract_first_string(
                leg_value,
                &["tokenMint", "token_mint", "feeTokenMint", "fee_token_mint", "mint"],
            );
            let amount_raw = extract_first_amount_string(
                leg_value,
                &["amountRaw", "amount_raw", "feeAmountRaw", "fee_amount_raw", "amount"],
            );
            let token_mint = match token_mint {
                Some(token_mint) => token_mint,
                None => continue,
            };
            let amount_raw = match amount_raw {
                Some(amount_raw) => amount_raw,
                None => continue,
            };
            let amount_trimmed = amount_raw.trim();
            if amount_trimmed.is_empty() || amount_trimmed == "0" {
                continue;
            }
            let source =
                match extract_first_string(leg_value, &["amountSource", "amount_source", "source"])
                {
                    Some(source) => source,
                    None => "decoded_fee_amount_leg".to_string(),
                };
            let source_account =
                extract_first_string(leg_value, &["sourceAccount", "source_account"]);
            let destination_account =
                extract_first_string(leg_value, &["destinationAccount", "destination_account"]);
            legs.push(MeteoraDbcActualAmount {
                token_mint,
                amount_raw: amount_trimmed.to_string(),
                source,
                source_account,
                destination_account,
            });
        }
    }
    return legs;
}

fn instruction_is_child_of_decoded_event(
    instruction: &crate::ChainInstructionDto,
    parent_instruction_id: std::option::Option<i64>,
    instruction_index: std::option::Option<i64>,
) -> bool {
    if instruction.inner_instruction_index.is_none() {
        return false;
    }
    if let Some(parent_instruction_id) = parent_instruction_id {
        if instruction.parent_instruction_id == Some(parent_instruction_id) {
            return true;
        }
    }
    if let Some(instruction_index) = instruction_index {
        let converted = i64::from(instruction.instruction_index);
        if converted == instruction_index {
            return true;
        }
    }
    return false;
}

fn extract_spl_transfer_amount_raw(
    parsed_value: &serde_json::Value,
) -> std::option::Option<std::string::String> {
    let token_amount = parsed_value
        .get("info")
        .and_then(|value| return value.get("tokenAmount"))
        .and_then(|value| return value.get("amount"))
        .and_then(serde_json::Value::as_str);
    if let Some(token_amount) = token_amount {
        if !token_amount.trim().is_empty() {
            return Some(token_amount.trim().to_string());
        }
    }
    let amount = parsed_value
        .get("info")
        .and_then(|value| return value.get("amount"))
        .and_then(serde_json::Value::as_str);
    if let Some(amount) = amount {
        if !amount.trim().is_empty() {
            return Some(amount.trim().to_string());
        }
    }
    return None;
}

fn decoded_payload_with_materialized_reward_amount(
    payload: &serde_json::Value,
    reward_amount: &MeteoraDbcActualAmount,
) -> serde_json::Value {
    let mut object = match payload.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.insert("skipRewardReason".to_string(), serde_json::Value::String(String::new()));
    object.insert("rewardMaterialized".to_string(), serde_json::Value::Bool(true));
    object.insert(
        "rewardMaterializationSource".to_string(),
        serde_json::Value::String(reward_amount.source.clone()),
    );
    object.insert(
        "rewardTokenMint".to_string(),
        serde_json::Value::String(reward_amount.token_mint.clone()),
    );
    object.insert(
        "rewardAmountRaw".to_string(),
        serde_json::Value::String(reward_amount.amount_raw.clone()),
    );
    match reward_amount.source_account.as_ref() {
        Some(source_account) => {
            object.insert(
                "rewardSourceAccount".to_string(),
                serde_json::Value::String(source_account.clone()),
            );
        },
        None => {},
    }
    match reward_amount.destination_account.as_ref() {
        Some(destination_account) => {
            object.insert(
                "rewardDestinationAccount".to_string(),
                serde_json::Value::String(destination_account.clone()),
            );
        },
        None => {},
    }
    object.insert(
        "eventActionability".to_string(),
        serde_json::Value::String("reward_materialized".to_string()),
    );
    return serde_json::Value::Object(object);
}

fn decoded_payload_with_materialized_fee_legs(
    payload: &serde_json::Value,
    legs: &[MeteoraDbcActualAmount],
) -> serde_json::Value {
    let mut object = match payload.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.insert(
        "skipFeeReason".to_string(),
        serde_json::Value::String(std::string::String::new()),
    );
    object.insert("feeMaterialized".to_string(), serde_json::Value::Bool(true));
    object.insert(
        "feeMaterializationSource".to_string(),
        serde_json::Value::String("fee_event_amounts".to_string()),
    );
    let leg_count = legs.len() as u64;
    object.insert(
        "feeLegCount".to_string(),
        serde_json::Value::Number(serde_json::Number::from(leg_count)),
    );
    if legs.len() == 1 {
        if let Some(leg) = legs.first() {
            object.insert(
                "feeTokenMint".to_string(),
                serde_json::Value::String(leg.token_mint.clone()),
            );
            object.insert(
                "feeAmountRaw".to_string(),
                serde_json::Value::String(leg.amount_raw.clone()),
            );
            object.insert(
                "feePrimaryAmountSource".to_string(),
                serde_json::Value::String(leg.source.clone()),
            );
        }
    } else {
        object.insert("feeTokenMint".to_string(), serde_json::Value::Null);
        object.insert("feeAmountRaw".to_string(), serde_json::Value::Null);
        object.insert(
            "feeMultiLegReason".to_string(),
            serde_json::Value::String("multiple_fee_amount_legs".to_string()),
        );
    }
    object.insert(
        "eventActionability".to_string(),
        serde_json::Value::String("fee_materialized".to_string()),
    );
    return serde_json::Value::Object(object);
}

fn fee_amount_leg_payload_json(
    decoded_event: &crate::DexDecodedEventDto,
    leg: &MeteoraDbcActualAmount,
    leg_index: u32,
) -> std::string::String {
    let mut object = serde_json::Map::new();
    object.insert(
        "decodedEventKind".to_string(),
        serde_json::Value::String(decoded_event.event_kind.clone()),
    );
    object.insert(
        "legIndex".to_string(),
        serde_json::Value::Number(serde_json::Number::from(u64::from(leg_index))),
    );
    object.insert("tokenMint".to_string(), serde_json::Value::String(leg.token_mint.clone()));
    object.insert("amountRaw".to_string(), serde_json::Value::String(leg.amount_raw.clone()));
    object.insert("amountSource".to_string(), serde_json::Value::String(leg.source.clone()));
    match leg.source_account.as_ref() {
        Some(source_account) => {
            object.insert(
                "sourceAccount".to_string(),
                serde_json::Value::String(source_account.clone()),
            );
        },
        None => {},
    }
    match leg.destination_account.as_ref() {
        Some(destination_account) => {
            object.insert(
                "destinationAccount".to_string(),
                serde_json::Value::String(destination_account.clone()),
            );
        },
        None => {},
    }
    return serde_json::Value::Object(object).to_string();
}

fn fee_event_amount_leg_from_parent_amount(
    decoded_event: &crate::DexDecodedEventDto,
    transaction_id: i64,
    decoded_event_id: i64,
    fee_token_mint: std::option::Option<&std::string::String>,
    fee_amount_raw: std::option::Option<&std::string::String>,
) -> std::option::Option<crate::FeeEventAmountDto> {
    let fee_token_mint = match fee_token_mint {
        Some(fee_token_mint) => fee_token_mint,
        None => return None,
    };
    if fee_token_mint.trim().is_empty() {
        return None;
    }
    let fee_amount_raw = match fee_amount_raw {
        Some(fee_amount_raw) => fee_amount_raw,
        None => return None,
    };
    if fee_amount_raw.trim().is_empty() {
        return None;
    }
    let payload_json = fee_amount_parent_payload_json(
        decoded_event,
        fee_token_mint.as_str(),
        fee_amount_raw.as_str(),
    );
    return Some(crate::FeeEventAmountDto::new(
        0,
        transaction_id,
        Some(decoded_event_id),
        0,
        decoded_event.event_kind.clone(),
        fee_token_mint.clone(),
        fee_amount_raw.clone(),
        None,
        None,
        "parent_fee_event_amount".to_string(),
        payload_json,
    ));
}

fn fee_amount_parent_payload_json(
    decoded_event: &crate::DexDecodedEventDto,
    token_mint: &str,
    amount_raw: &str,
) -> std::string::String {
    let mut object = serde_json::Map::new();
    object.insert(
        "decodedEventKind".to_string(),
        serde_json::Value::String(decoded_event.event_kind.clone()),
    );
    object.insert(
        "legIndex".to_string(),
        serde_json::Value::Number(serde_json::Number::from(0_u64)),
    );
    object.insert("tokenMint".to_string(), serde_json::Value::String(token_mint.to_string()));
    object.insert("amountRaw".to_string(), serde_json::Value::String(amount_raw.to_string()));
    object.insert(
        "amountSource".to_string(),
        serde_json::Value::String("parent_fee_event_amount".to_string()),
    );
    return serde_json::Value::Object(object).to_string();
}

fn decoded_payload_with_unmaterialized_fee_reason(
    payload: &serde_json::Value,
    reason: &str,
) -> serde_json::Value {
    let mut object = match payload.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.insert("skipFeeReason".to_string(), serde_json::Value::String(reason.to_string()));
    object.insert("feeMaterialized".to_string(), serde_json::Value::Bool(false));
    object.insert(
        "feeMaterializationSource".to_string(),
        serde_json::Value::String("none".to_string()),
    );
    object.insert(
        "eventActionability".to_string(),
        serde_json::Value::String("decoded_only_with_explicit_skip_reason".to_string()),
    );
    return serde_json::Value::Object(object);
}

fn decoded_payload_with_unmaterialized_fee_amount_recovery_reason(
    payload: &serde_json::Value,
    reason: &str,
) -> serde_json::Value {
    let mut object = match payload.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.insert(
        "feeAmountRecoveryPolicy".to_string(),
        serde_json::Value::String("allowlisted_inner_spl_transfer".to_string()),
    );
    object.insert(
        "feeAmountRecoveryReason".to_string(),
        serde_json::Value::String(reason.to_string()),
    );
    object.insert("feeAmountLegsMaterialized".to_string(), serde_json::Value::Bool(false));
    object.insert(
        "feeAmountLegCount".to_string(),
        serde_json::Value::Number(serde_json::Number::from(0_u64)),
    );
    return serde_json::Value::Object(object);
}

fn resolve_meteora_dbc_actual_fee_amount_from_lamport_delta(
    transaction: &crate::ChainTransactionDto,
    decoded_event: &crate::DexDecodedEventDto,
    payload: &serde_json::Value,
) -> std::option::Option<MeteoraDbcActualAmount> {
    if !decoded_event.event_kind.contains("pool_creation_fee") {
        return None;
    }
    let transaction_value =
        serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
    let transaction_value = match transaction_value {
        Ok(transaction_value) => transaction_value,
        Err(_) => return None,
    };
    let meta_value = match transaction.meta_json.as_deref() {
        Some(meta_json) => match serde_json::from_str::<serde_json::Value>(meta_json) {
            Ok(meta_value) => Some(meta_value),
            Err(_) => None,
        },
        None => transaction_value.get("meta").cloned(),
    };
    let meta_value_ref = match meta_value.as_ref() {
        Some(meta_value_ref) => meta_value_ref,
        None => return None,
    };
    let account_keys = materialization_account_keys(&transaction_value, Some(meta_value_ref));
    let mut candidate_accounts = std::vec::Vec::new();
    append_unique_candidate_account(&mut candidate_accounts, decoded_event.pool_account.as_deref());
    let payload_pool_account = extract_first_string(payload, &["poolAccount", "pool", "poolState"]);
    append_unique_candidate_account(&mut candidate_accounts, payload_pool_account.as_deref());
    let payload_related_account =
        extract_first_string(payload, &["relatedAccount", "receiver", "feeReceiver"]);
    append_unique_candidate_account(&mut candidate_accounts, payload_related_account.as_deref());
    if let Some(accounts) = payload.get("accounts").and_then(serde_json::Value::as_array) {
        for account in accounts {
            let account = match account.as_str() {
                Some(account) => account,
                None => continue,
            };
            append_unique_candidate_account(&mut candidate_accounts, Some(account));
        }
    }
    let pre_balances = meta_value_ref.get("preBalances").and_then(serde_json::Value::as_array);
    let post_balances = meta_value_ref.get("postBalances").and_then(serde_json::Value::as_array);
    let pre_balances = match pre_balances {
        Some(pre_balances) => pre_balances,
        None => return None,
    };
    let post_balances = match post_balances {
        Some(post_balances) => post_balances,
        None => return None,
    };
    let mut best_delta: std::option::Option<u64> = None;
    for candidate in &candidate_accounts {
        for account_key in &account_keys {
            if account_key.address != *candidate {
                continue;
            }
            let index = match usize::try_from(account_key.index) {
                Ok(index) => index,
                Err(_) => continue,
            };
            let pre = pre_balances.get(index).and_then(serde_json::Value::as_u64);
            let post = post_balances.get(index).and_then(serde_json::Value::as_u64);
            let pre = match pre {
                Some(pre) => pre,
                None => continue,
            };
            let post = match post {
                Some(post) => post,
                None => continue,
            };
            if post <= pre {
                continue;
            }
            let delta = post - pre;
            if delta == 0 {
                continue;
            }
            match best_delta {
                Some(current) => {
                    if delta > current {
                        best_delta = Some(delta);
                    }
                },
                None => best_delta = Some(delta),
            }
        }
    }
    let best_delta = match best_delta {
        Some(best_delta) => best_delta,
        None => return None,
    };
    return Some(MeteoraDbcActualAmount {
        token_mint: "So11111111111111111111111111111111111111112".to_string(),
        amount_raw: best_delta.to_string(),
        source: "lamport_balance_delta".to_string(),
        source_account: None,
        destination_account: None,
    });
}

fn append_unique_candidate_account(
    accounts: &mut std::vec::Vec<std::string::String>,
    candidate: std::option::Option<&str>,
) {
    let candidate = match candidate {
        Some(candidate) => candidate,
        None => return,
    };
    if candidate.trim().is_empty() {
        return;
    }
    for account in accounts.iter() {
        if account == candidate {
            return;
        }
    }
    accounts.push(candidate.to_string());
}

#[derive(Debug, Clone)]
struct MaterializationAccountKeyInfo {
    index: i64,
    address: std::string::String,
}

fn token_mints_by_account_from_transaction(
    transaction: &crate::ChainTransactionDto,
) -> std::collections::HashMap<std::string::String, std::string::String> {
    let transaction_json =
        serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
    let transaction_json = match transaction_json {
        Ok(transaction_json) => transaction_json,
        Err(_) => return std::collections::HashMap::new(),
    };
    let meta_value = match transaction.meta_json.as_deref() {
        Some(meta_json) => match serde_json::from_str::<serde_json::Value>(meta_json) {
            Ok(meta_value) => Some(meta_value),
            Err(_) => None,
        },
        None => transaction_json.get("meta").cloned(),
    };
    let account_keys = materialization_account_keys(&transaction_json, meta_value.as_ref());
    let mut mints = std::collections::HashMap::new();
    collect_token_mints_by_account_side(
        meta_value.as_ref(),
        account_keys.as_slice(),
        "preTokenBalances",
        &mut mints,
    );
    collect_token_mints_by_account_side(
        meta_value.as_ref(),
        account_keys.as_slice(),
        "postTokenBalances",
        &mut mints,
    );
    return mints;
}

fn materialization_account_keys(
    transaction_json: &serde_json::Value,
    meta_value: std::option::Option<&serde_json::Value>,
) -> std::vec::Vec<MaterializationAccountKeyInfo> {
    let mut account_keys = std::vec::Vec::new();
    let values = transaction_json
        .get("transaction")
        .and_then(|value| return value.get("message"))
        .and_then(|value| return value.get("accountKeys"))
        .and_then(serde_json::Value::as_array);
    if let Some(values) = values {
        let mut index = 0usize;
        for value in values {
            let address = if let Some(address) = value.as_str() {
                Some(address.to_string())
            } else {
                value.get("pubkey").and_then(serde_json::Value::as_str).map(str::to_string)
            };
            if let Some(address) = address {
                account_keys.push(MaterializationAccountKeyInfo { index: index as i64, address });
            }
            index += 1;
        }
    }
    append_materialization_loaded_addresses(&mut account_keys, meta_value, "writable");
    append_materialization_loaded_addresses(&mut account_keys, meta_value, "readonly");
    return account_keys;
}

fn append_materialization_loaded_addresses(
    account_keys: &mut std::vec::Vec<MaterializationAccountKeyInfo>,
    meta_value: std::option::Option<&serde_json::Value>,
    key: &str,
) {
    let addresses = meta_value
        .and_then(|value| return value.get("loadedAddresses"))
        .and_then(|value| return value.get(key))
        .and_then(serde_json::Value::as_array);
    let addresses = match addresses {
        Some(addresses) => addresses,
        None => return,
    };
    for value in addresses {
        let address = match value.as_str() {
            Some(address) => address,
            None => continue,
        };
        let index = account_keys.len() as i64;
        account_keys.push(MaterializationAccountKeyInfo { index, address: address.to_string() });
    }
}

fn collect_token_mints_by_account_side(
    meta_value: std::option::Option<&serde_json::Value>,
    account_keys: &[MaterializationAccountKeyInfo],
    key: &str,
    mints: &mut std::collections::HashMap<std::string::String, std::string::String>,
) {
    let values = meta_value
        .and_then(|value| return value.get(key))
        .and_then(serde_json::Value::as_array);
    let values = match values {
        Some(values) => values,
        None => return,
    };
    for value in values {
        let mint = match value.get("mint").and_then(serde_json::Value::as_str) {
            Some(mint) => mint,
            None => continue,
        };
        let account_index = value.get("accountIndex").and_then(serde_json::Value::as_i64);
        let account_index = match account_index {
            Some(account_index) => account_index,
            None => continue,
        };
        let account_address = materialization_account_address_by_index(account_keys, account_index);
        let account_address = match account_address {
            Some(account_address) => account_address,
            None => continue,
        };
        mints.insert(account_address, mint.to_string());
    }
}

fn materialization_account_address_by_index(
    account_keys: &[MaterializationAccountKeyInfo],
    account_index: i64,
) -> std::option::Option<std::string::String> {
    for account_key in account_keys {
        if account_key.index == account_index {
            return Some(account_key.address.clone());
        }
    }
    return None;
}

fn infer_raydium_clmm_pair_mints_from_payload_accounts(
    payload: &serde_json::Value,
    token_mints_by_account: &std::collections::HashMap<std::string::String, std::string::String>,
) -> std::option::Option<(std::string::String, std::string::String)> {
    let accounts = payload.get("accounts").and_then(serde_json::Value::as_array);
    let accounts = match accounts {
        Some(accounts) => accounts,
        None => return None,
    };
    let instruction_name = payload.get("instructionName").and_then(serde_json::Value::as_str);
    let instruction_name = match instruction_name {
        Some(instruction_name) => instruction_name,
        None => "",
    };
    let candidate_pairs = raydium_clmm_token_account_candidate_pairs(instruction_name);
    for pair in candidate_pairs {
        let inferred =
            infer_mints_from_account_pair(accounts, pair.0, pair.1, token_mints_by_account);
        if let Some(inferred) = inferred {
            return Some(inferred);
        }
    }
    return infer_distinct_mints_from_accounts(accounts, token_mints_by_account);
}

fn raydium_clmm_token_account_candidate_pairs(
    instruction_name: &str,
) -> std::vec::Vec<(usize, usize)> {
    if instruction_name == "open_position" {
        return vec![(12, 13), (10, 11), (18, 19), (20, 21), (7, 8), (8, 9)];
    }
    if instruction_name == "open_position_v2" {
        return vec![(12, 13), (13, 14), (18, 19), (20, 21), (10, 11), (7, 8)];
    }
    if instruction_name == "increase_liquidity" {
        return vec![(9, 10), (7, 8), (13, 14), (14, 15), (5, 6)];
    }
    if instruction_name == "decrease_liquidity" {
        return vec![(5, 6), (9, 10), (14, 15), (15, 16), (7, 8)];
    }
    if instruction_name == "decrease_liquidity_v2" {
        return vec![(14, 15), (5, 6), (9, 10)];
    }
    if instruction_name == "increase_liquidity_v2" {
        return vec![(13, 14), (9, 10), (7, 8)];
    }
    return vec![
        (12, 13),
        (13, 14),
        (9, 10),
        (7, 8),
        (5, 6),
        (10, 11),
        (14, 15),
        (18, 19),
        (20, 21),
    ];
}

fn infer_mints_from_account_pair(
    accounts: &[serde_json::Value],
    left_index: usize,
    right_index: usize,
    token_mints_by_account: &std::collections::HashMap<std::string::String, std::string::String>,
) -> std::option::Option<(std::string::String, std::string::String)> {
    let left_account = accounts.get(left_index).and_then(serde_json::Value::as_str);
    let right_account = accounts.get(right_index).and_then(serde_json::Value::as_str);
    let left_account = match left_account {
        Some(left_account) => left_account,
        None => return None,
    };
    let right_account = match right_account {
        Some(right_account) => right_account,
        None => return None,
    };
    let left_mint = token_mints_by_account.get(left_account);
    let right_mint = token_mints_by_account.get(right_account);
    let left_mint = match left_mint {
        Some(left_mint) => left_mint,
        None => return None,
    };
    let right_mint = match right_mint {
        Some(right_mint) => right_mint,
        None => return None,
    };
    if left_mint == right_mint {
        return None;
    }
    return Some((left_mint.clone(), right_mint.clone()));
}

fn infer_distinct_mints_from_accounts(
    accounts: &[serde_json::Value],
    token_mints_by_account: &std::collections::HashMap<std::string::String, std::string::String>,
) -> std::option::Option<(std::string::String, std::string::String)> {
    let mut first: std::option::Option<std::string::String> = None;
    let mut second: std::option::Option<std::string::String> = None;
    for account in accounts {
        let account = match account.as_str() {
            Some(account) => account,
            None => continue,
        };
        let mint = token_mints_by_account.get(account);
        let mint = match mint {
            Some(mint) => mint,
            None => continue,
        };
        if first.is_none() {
            first = Some(mint.clone());
            continue;
        }
        if first.as_ref() == Some(mint) {
            continue;
        }
        second = Some(mint.clone());
        break;
    }
    match (first, second) {
        (Some(first), Some(second)) => return Some((first, second)),
        _ => return None,
    }
}

fn merge_payload_with_inferred_pool_context(
    payload: &serde_json::Value,
    pool_account: &str,
    token_a_mint: &str,
    token_b_mint: &str,
    context_source_event_kind: &str,
) -> serde_json::Value {
    let mut object = match payload.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.insert("poolState".to_string(), serde_json::Value::String(pool_account.to_string()));
    object.insert("poolAccount".to_string(), serde_json::Value::String(pool_account.to_string()));
    object.insert("tokenMint0".to_string(), serde_json::Value::String(token_a_mint.to_string()));
    object.insert("tokenMint1".to_string(), serde_json::Value::String(token_b_mint.to_string()));
    object.insert("inferredPoolContext".to_string(), serde_json::Value::Bool(true));
    object.insert(
        "inferredPoolContextSourceEventKind".to_string(),
        serde_json::Value::String(context_source_event_kind.to_string()),
    );
    return serde_json::Value::Object(object);
}

fn is_token_account_event_materializable(event_kind: &str) -> bool {
    return event_kind.ends_with(".create_support_mint_associated");
}

fn extract_account_string(
    payload: &serde_json::Value,
    index: usize,
) -> std::option::Option<std::string::String> {
    let accounts_option = payload.get("accounts");
    let accounts = match accounts_option {
        Some(accounts) => accounts.as_array(),
        None => None,
    };
    let accounts = match accounts {
        Some(accounts) => accounts,
        None => return None,
    };
    let value = accounts.get(index);
    match value {
        Some(value) => match value.as_str() {
            Some(value) => return Some(value.to_string()),
            None => return None,
        },
        None => return None,
    }
}

fn is_launchpad_launch_event_materializable(event_kind: &str) -> bool {
    if event_kind.contains("pump_fun.create_v2_token") {
        return true;
    }
    if event_kind == "pump_fun.create" || event_kind == "pump_fun.create_event" {
        return true;
    }
    if event_kind == "pump_fun.migrate"
        || event_kind == "pump_fun.migrate_v2"
        || event_kind == "pump_fun.migrate_bonding_curve_creator"
        || event_kind == "pump_fun.migrate_bonding_curve_creator_event"
        || event_kind == "pump_fun.complete_event"
        || event_kind == "pump_fun.complete_pump_amm_migration_event"
    {
        return true;
    }
    if event_kind.contains("raydium_launchpad.buy_exact_in") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.buy_exact_out") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.sell_exact_in") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.sell_exact_out") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.claim_vested_event") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.claim_vested_token") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.create_platform_vesting_account") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.create_vesting_account") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.create_vesting_event") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.migrate_to_amm") {
        return true;
    }
    if event_kind.contains("raydium_launchpad.migrate_to_cpswap") {
        return true;
    }
    return false;
}

fn launchpad_launch_event_role(event_kind: &str) -> std::string::String {
    if event_kind.contains("buy_exact_in") {
        return "swap_instruction_buy_exact_in".to_string();
    }
    if event_kind.contains("buy_exact_out") {
        return "swap_instruction_buy_exact_out".to_string();
    }
    if event_kind.contains("sell_exact_in") {
        return "swap_instruction_sell_exact_in".to_string();
    }
    if event_kind.contains("sell_exact_out") {
        return "swap_instruction_sell_exact_out".to_string();
    }
    if event_kind.contains("claim_vested") {
        return "vesting_claim".to_string();
    }
    if event_kind.contains("vesting") {
        return "vesting".to_string();
    }
    if event_kind.contains("migrate_to_amm") {
        return "migration_to_amm".to_string();
    }
    if event_kind.contains("migrate_to_cpswap") {
        return "migration_to_cpswap".to_string();
    }
    if event_kind.contains("pump_fun.migrate")
        || event_kind.contains("pump_fun.complete_pump_amm_migration")
    {
        return "pump_fun_migration".to_string();
    }
    if event_kind.contains("pump_fun.complete_event") {
        return "pump_fun_completion".to_string();
    }
    if event_kind.contains("pump_fun.create") {
        return "pump_fun_launch".to_string();
    }
    return "launch".to_string();
}

fn normalize_orderbook_action(event_kind: &str) -> std::string::String {
    if event_kind.contains(".open_limit_order") {
        return "open_limit_order".to_string();
    }
    if event_kind.contains(".increase_limit_order") {
        return "increase_limit_order".to_string();
    }
    if event_kind.contains(".decrease_limit_order") {
        return "decrease_limit_order".to_string();
    }
    if event_kind.contains(".close_limit_order") {
        return "close_limit_order".to_string();
    }
    if event_kind.contains(".settle_limit_order") {
        return "settle_limit_order".to_string();
    }
    if event_kind.contains("order_place") {
        return "order_place".to_string();
    }
    if event_kind.contains("order_cancel") {
        return "order_cancel".to_string();
    }
    if event_kind.contains("settle_funds") {
        return "settle_funds".to_string();
    }
    return event_kind.to_string();
}

fn fallback_order_account(
    event_kind: &str,
    payload: &serde_json::Value,
) -> std::option::Option<std::string::String> {
    if event_kind.contains(".close_limit_order") {
        return extract_account_at(payload, 2);
    }
    if event_kind.contains(".open_limit_order")
        || event_kind.contains(".increase_limit_order")
        || event_kind.contains(".decrease_limit_order")
    {
        return extract_account_at(payload, 3);
    }
    return None;
}

fn extract_account_at(
    value: &serde_json::Value,
    index: usize,
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        let accounts = object.get("accounts");
        if let Some(accounts) = accounts {
            if let Some(array) = accounts.as_array() {
                let candidate = array.get(index);
                if let Some(candidate) = candidate {
                    if let Some(text) = candidate.as_str() {
                        let trimmed = text.trim();
                        if !trimmed.is_empty() {
                            return Some(trimmed.to_string());
                        }
                    }
                }
            }
        }
        for nested_value in object.values() {
            let nested = extract_account_at(nested_value, index);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

fn extract_first_i64(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<i64> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let candidate_value = object.get(*candidate_key);
            if let Some(candidate_value) = candidate_value {
                if let Some(number) = candidate_value.as_i64() {
                    return Some(number);
                }
                if let Some(number) = candidate_value.as_u64() {
                    let converted = i64::try_from(number);
                    if let Ok(converted) = converted {
                        return Some(converted);
                    }
                }
                if let Some(text) = candidate_value.as_str() {
                    let parsed = text.parse::<i64>();
                    if let Ok(parsed) = parsed {
                        return Some(parsed);
                    }
                }
            }
        }
        for nested_value in object.values() {
            let nested = extract_first_i64(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

fn extract_first_bool(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<bool> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let candidate_value = object.get(*candidate_key);
            if let Some(candidate_value) = candidate_value {
                if let Some(flag) = candidate_value.as_bool() {
                    return Some(flag);
                }
                if let Some(number) = candidate_value.as_i64() {
                    return Some(number != 0);
                }
                if let Some(text) = candidate_value.as_str() {
                    if text == "true" || text == "1" {
                        return Some(true);
                    }
                    if text == "false" || text == "0" {
                        return Some(false);
                    }
                }
            }
        }
        for nested_value in object.values() {
            let nested = extract_first_bool(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

fn should_skip_pump_fun_duplicate_non_trade_event(
    decoded_event: &crate::DexDecodedEventDto,
    decoded_events: &[crate::DexDecodedEventDto],
) -> bool {
    if !decoded_event.event_kind.starts_with("pump_fun.") {
        return false;
    }
    let preferred_siblings =
        pump_fun_preferred_non_trade_siblings(decoded_event.event_kind.as_str());
    if preferred_siblings.is_empty() {
        return false;
    }
    for sibling in decoded_events {
        if sibling.id == decoded_event.id {
            continue;
        }
        for preferred in &preferred_siblings {
            if sibling.event_kind.as_str() == *preferred {
                return true;
            }
        }
    }
    return false;
}

fn pump_fun_preferred_non_trade_siblings(event_kind: &str) -> std::vec::Vec<&'static str> {
    match event_kind {
        "pump_fun.admin_set_creator" => return vec!["pump_fun.admin_set_creator_event"],
        "pump_fun.admin_set_idl_authority" => {
            return vec!["pump_fun.admin_set_idl_authority_event"];
        },
        "pump_fun.admin_update_token_incentives" => {
            return vec!["pump_fun.admin_update_token_incentives_event"];
        },
        "pump_fun.claim_cashback" | "pump_fun.claim_cashback_v2" => {
            return vec!["pump_fun.claim_cashback_event"];
        },
        "pump_fun.claim_token_incentives" => return vec!["pump_fun.claim_token_incentives_event"],
        "pump_fun.close_user_volume_accumulator" => {
            return vec!["pump_fun.close_user_volume_accumulator_event"];
        },
        "pump_fun.collect_creator_fee" | "pump_fun.collect_creator_fee_v2" => {
            return vec!["pump_fun.collect_creator_fee_event"];
        },
        "pump_fun.create" => return vec!["pump_fun.create_v2_token", "pump_fun.create_event"],
        "pump_fun.create_event" => return vec!["pump_fun.create_v2_token"],
        "pump_fun.distribute_creator_fees" | "pump_fun.distribute_creator_fees_v2" => {
            return vec!["pump_fun.distribute_creator_fees_event"];
        },
        "pump_fun.extend_account" => return vec!["pump_fun.extend_account_event"],
        "pump_fun.get_minimum_distributable_fee" => {
            return vec!["pump_fun.minimum_distributable_fee_event"];
        },
        "pump_fun.init_user_volume_accumulator" => {
            return vec!["pump_fun.init_user_volume_accumulator_event"];
        },
        "pump_fun.migrate_bonding_curve_creator" => {
            return vec!["pump_fun.migrate_bonding_curve_creator_event"];
        },
        "pump_fun.set_creator" => return vec!["pump_fun.set_creator_event"],
        "pump_fun.set_metaplex_creator" => return vec!["pump_fun.set_metaplex_creator_event"],
        "pump_fun.set_params" => return vec!["pump_fun.set_params_event"],
        "pump_fun.set_reserved_fee_recipients" => {
            return vec!["pump_fun.reserved_fee_recipients_event"];
        },
        "pump_fun.sync_user_volume_accumulator" => {
            return vec!["pump_fun.sync_user_volume_accumulator_event"];
        },
        "pump_fun.update_global_authority" => {
            return vec!["pump_fun.update_global_authority_event"];
        },
        "pump_fun.set_mayhem_virtual_params" => {
            return vec!["pump_fun.update_mayhem_virtual_params_event"];
        },
        _ => return std::vec::Vec::new(),
    }
}

fn is_pump_fun_payload(payload: &serde_json::Value) -> bool {
    if let Some(object) = payload.as_object() {
        let protocol_name = object.get("protocolName").and_then(serde_json::Value::as_str);
        if protocol_name == Some("pump_fun") {
            return true;
        }
        let decoder = object.get("decoder").and_then(serde_json::Value::as_str);
        if decoder == Some("pump_fun") {
            return true;
        }
        let event_kind = object.get("eventKind").and_then(serde_json::Value::as_str);
        if let Some(event_kind) = event_kind {
            if event_kind.starts_with("pump_fun.") {
                return true;
            }
        }
    }
    return false;
}

fn is_anchor_event_audit_only(
    decoded_event: &crate::DexDecodedEventDto,
    payload: &serde_json::Value,
) -> bool {
    if is_pump_fun_payload(payload) {
        return false;
    }
    if decoded_event.protocol_name == "pump_swap"
        && is_pump_swap_known_non_trade_materialization_candidate(decoded_event.event_kind.as_str())
    {
        return false;
    }
    if is_pump_swap_materializable_anchor_payload(payload) {
        return false;
    }
    if let Some(object) = payload.as_object() {
        let flag = object.get("anchorEventAuditOnly");
        if let Some(flag) = flag {
            if flag.as_bool() == Some(true) {
                return true;
            }
        }
        let flag = object.get("instructionAuditOnly");
        if let Some(flag) = flag {
            if flag.as_bool() == Some(true) {
                return true;
            }
        }
    }
    return false;
}

fn is_pump_swap_materializable_anchor_payload(payload: &serde_json::Value) -> bool {
    if !is_pump_swap_payload(payload) {
        return false;
    }
    let event_kind = extract_first_string(payload, &["eventKind", "event_kind"]);
    let event_kind = match event_kind {
        Some(event_kind) => event_kind,
        None => return false,
    };
    return is_pump_swap_known_non_trade_materialization_candidate(event_kind.as_str());
}

fn is_pump_swap_payload(payload: &serde_json::Value) -> bool {
    let decoder = extract_first_string(payload, &["decoder", "protocolName", "protocol_name"]);
    match decoder.as_deref() {
        Some("pump_swap") => return true,
        _ => return false,
    }
}

fn transaction_has_effective_error(transaction: &crate::ChainTransactionDto) -> bool {
    let err_json = match transaction.err_json.as_ref() {
        Some(err_json) => err_json.trim(),
        None => return false,
    };
    if err_json.is_empty() {
        return false;
    }
    if err_json == "null" {
        return false;
    }
    return true;
}

fn extract_first_u64(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<u64> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let candidate_value = object.get(*candidate_key);
            if let Some(candidate_value) = candidate_value {
                if let Some(number) = candidate_value.as_u64() {
                    return Some(number);
                }
                if let Some(text) = candidate_value.as_str() {
                    let parsed = text.parse::<u64>();
                    if let Ok(parsed) = parsed {
                        return Some(parsed);
                    }
                }
            }
        }
    }
    return None;
}

fn extract_first_amount_string(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    let text = extract_first_string(value, candidate_keys);
    if text.is_some() {
        return text;
    }
    return extract_first_number_as_string(value, candidate_keys);
}

fn extract_first_string(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let value_option = object.get(*candidate_key);
            let candidate = match value_option {
                Some(candidate) => candidate,
                None => continue,
            };
            if let Some(text) = candidate.as_str() {
                let trimmed = text.trim();
                if !trimmed.is_empty() {
                    return Some(trimmed.to_string());
                }
            }
        }
        for nested_value in object.values() {
            let nested = extract_first_string(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
        return None;
    }
    if let Some(array) = value.as_array() {
        for nested_value in array {
            let nested = extract_first_string(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

fn extract_first_number_as_string(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let value_option = object.get(*candidate_key);
            let candidate = match value_option {
                Some(candidate) => candidate,
                None => continue,
            };
            if let Some(number) = candidate.as_i64() {
                return Some(number.to_string());
            }
            if let Some(number) = candidate.as_u64() {
                return Some(number.to_string());
            }
            if let Some(number) = candidate.as_f64() {
                return Some(number.to_string());
            }
        }
        for nested_value in object.values() {
            let nested = extract_first_number_as_string(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
        return None;
    }
    if let Some(array) = value.as_array() {
        for nested_value in array {
            let nested = extract_first_number_as_string(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

#[cfg(test)]
mod pump_swap_cleanup_tests {
    fn make_decoded_event(event_kind: &str) -> crate::DexDecodedEventDto {
        return crate::DexDecodedEventDto::new(
            1,
            Some(2),
            "pump_swap".to_string(),
            crate::PUMP_SWAP_PROGRAM_ID.to_string(),
            event_kind.to_string(),
            None,
            None,
            None,
            None,
            None,
            "{}".to_string(),
        );
    }

    #[test]
    fn pump_swap_known_non_trade_candidates_bypass_legacy_trade_skip_reason() {
        let decoded_event = make_decoded_event("pump_swap.update_fee_config");
        let payload = serde_json::json!({
            "decoder": "pump_swap",
            "eventKind": "pump_swap.update_fee_config",
            "skipAdminReason": "legacy_pump_swap_admin_skip",
            "skipTradeReason": "pump_swap_non_trade_or_incomplete_trade_instruction",
            "skipCandleReason": "pump_swap_non_trade_or_incomplete_trade_instruction"
        });
        assert!(super::should_skip_non_trade_event_due_to_explicit_reason(
            &decoded_event,
            &payload,
        ));
        assert!(super::should_attempt_pump_swap_explicit_skip_materialization(
            &decoded_event,
            &payload,
        ));
    }

    #[test]
    fn pump_swap_forced_admin_candidates_include_fee_config_event() {
        assert!(super::is_pump_swap_forced_pool_admin_materialization_candidate(
            "pump_swap.update_fee_config_event"
        ));
        assert!(super::is_pump_swap_forced_pool_admin_materialization_candidate(
            "pump_swap.migrate_pool_coin_creator_event"
        ));
        assert!(!super::is_pump_swap_forced_pool_admin_materialization_candidate(
            "pump_swap.buy_event"
        ));
    }

    #[test]
    fn pump_swap_materializable_anchor_payload_is_not_audit_only() {
        let payload = serde_json::json!({
            "decoder": "pump_swap",
            "eventKind": "pump_swap.buy_event",
            "anchorEventAuditOnly": true
        });
        let decoded_event = crate::DexDecodedEventDto::new(
            1,
            Some(1),
            "pump_swap".to_string(),
            crate::PUMP_SWAP_PROGRAM_ID.to_string(),
            "pump_swap.buy_event".to_string(),
            Some("pool".to_string()),
            None,
            None,
            None,
            None,
            payload.to_string(),
        );
        assert!(!super::is_anchor_event_audit_only(&decoded_event, &payload));
    }
}

#[cfg(test)]
mod tests {

    #[test]
    fn blank_or_null_err_json_is_not_effective_failure() {
        let mut transaction = crate::ChainTransactionDto::new(
            "sig-non-trade-effective-error".to_string(),
            Some(1),
            None,
            None,
            None,
            None,
            None,
            "{}".to_string(),
        );
        assert!(!super::transaction_has_effective_error(&transaction));
        transaction.err_json = Some("".to_string());
        assert!(!super::transaction_has_effective_error(&transaction));
        transaction.err_json = Some("null".to_string());
        assert!(!super::transaction_has_effective_error(&transaction));
        transaction.err_json = Some("{\"InstructionError\":[0,\"Custom\"]}".to_string());
        assert!(super::transaction_has_effective_error(&transaction));
    }

    #[test]
    fn fee_amount_recovery_policy_is_allowlisted_only() {
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("raydium_launchpad.collect_fee"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("future_dex.collect_fee"),
            super::FeeAmountRecoveryPolicy::Disabled
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dbc.claim_trading_fee"),
            super::FeeAmountRecoveryPolicy::Disabled
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_fee"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_fee2"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.composition_fee_event"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.withdraw_protocol_fee"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.zap_protocol_fee"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind(
                "meteora_dlmm.close_claim_fee_operator_account"
            ),
            super::FeeAmountRecoveryPolicy::Disabled
        );
        assert_eq!(
            super::fee_amount_recovery_policy_for_event_kind("meteora_dlmm.swap_event"),
            super::FeeAmountRecoveryPolicy::Disabled
        );
    }

    #[test]
    fn reward_amount_recovery_policy_is_allowlisted_only() {
        assert_eq!(
            super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward2"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.claim_reward_event"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.fund_reward"),
            super::FeeAmountRecoveryPolicy::InnerSplTransfer
        );
        assert_eq!(
            super::reward_amount_recovery_policy_for_event_kind(
                "meteora_dlmm.update_fees_and_rewards"
            ),
            super::FeeAmountRecoveryPolicy::Disabled
        );
        assert_eq!(
            super::reward_amount_recovery_policy_for_event_kind("meteora_dlmm.swap_event"),
            super::FeeAmountRecoveryPolicy::Disabled
        );
    }

    #[test]
    fn single_mint_reward_amount_sums_matching_mints_only() {
        let legs = vec![
            super::MeteoraDbcActualAmount {
                token_mint: "RewardMint111".to_string(),
                amount_raw: "7".to_string(),
                source: "test".to_string(),
                source_account: Some("Source111".to_string()),
                destination_account: Some("Destination111".to_string()),
            },
            super::MeteoraDbcActualAmount {
                token_mint: "RewardMint111".to_string(),
                amount_raw: "5".to_string(),
                source: "test".to_string(),
                source_account: Some("Source222".to_string()),
                destination_account: Some("Destination222".to_string()),
            },
        ];
        let resolved = super::single_mint_amount_from_actual_amounts(
            legs.as_slice(),
            "allowlisted_reward_inner_spl_transfer",
        );
        let resolved = match resolved {
            Some(resolved) => resolved,
            None => panic!("expected single mint reward amount"),
        };
        assert_eq!(resolved.token_mint, "RewardMint111");
        assert_eq!(resolved.amount_raw, "12");
        assert_eq!(resolved.source, "allowlisted_reward_inner_spl_transfer");

        let mixed_mint_legs = vec![
            super::MeteoraDbcActualAmount {
                token_mint: "RewardMint111".to_string(),
                amount_raw: "7".to_string(),
                source: "test".to_string(),
                source_account: None,
                destination_account: None,
            },
            super::MeteoraDbcActualAmount {
                token_mint: "OtherMint111".to_string(),
                amount_raw: "5".to_string(),
                source: "test".to_string(),
                source_account: None,
                destination_account: None,
            },
        ];
        assert!(
            super::single_mint_amount_from_actual_amounts(
                mixed_mint_legs.as_slice(),
                "allowlisted_reward_inner_spl_transfer",
            )
            .is_none()
        );
    }

    #[test]
    fn decoded_payload_with_materialized_reward_amount_marks_source() {
        let payload = serde_json::json!({
            "eventKind": "meteora_dlmm.claim_reward"
        });
        let reward_amount = super::MeteoraDbcActualAmount {
            token_mint: "RewardMint111".to_string(),
            amount_raw: "42".to_string(),
            source: "allowlisted_reward_inner_spl_transfer".to_string(),
            source_account: Some("Source111".to_string()),
            destination_account: Some("Destination111".to_string()),
        };
        let materialized =
            super::decoded_payload_with_materialized_reward_amount(&payload, &reward_amount);
        assert_eq!(
            materialized.get("rewardTokenMint").and_then(serde_json::Value::as_str),
            Some("RewardMint111")
        );
        assert_eq!(
            materialized.get("rewardAmountRaw").and_then(serde_json::Value::as_str),
            Some("42")
        );
        assert_eq!(
            materialized
                .get("rewardMaterializationSource")
                .and_then(serde_json::Value::as_str),
            Some("allowlisted_reward_inner_spl_transfer")
        );
        assert_eq!(
            materialized.get("eventActionability").and_then(serde_json::Value::as_str),
            Some("reward_materialized")
        );
    }

    #[test]
    fn extracts_nested_liquidity_amounts() {
        let payload = serde_json::json!({
            "event": {
                "baseAmountRaw": "100",
                "quoteAmountRaw": 25,
                "owner": "Owner111111111111111111111111111111111111"
            }
        });
        assert_eq!(
            super::extract_first_amount_string(&payload, &["baseAmountRaw"]),
            Some("100".to_string())
        );
        assert_eq!(
            super::extract_first_amount_string(&payload, &["quoteAmountRaw"]),
            Some("25".to_string())
        );
        assert_eq!(
            super::extract_first_string(&payload, &["owner"]),
            Some("Owner111111111111111111111111111111111111".to_string())
        );
    }
}
