// file: kb_lib/src/dex_decode.rs

//! Persistence-oriented DEX decoding service.

const METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX: &str = "e445a52e51cb9a1d";

/// DEX decode service.
#[derive(Debug, Clone)]
pub struct DexDecodeService {
    database: std::sync::Arc<crate::Database>,
    persistence: crate::DetectionPersistenceService,
    raydium_amm_v4_decoder: crate::RaydiumAmmV4Decoder,
    raydium_clmm_decoder: crate::RaydiumClmmDecoder,
    raydium_stable_swap_decoder: crate::RaydiumStableSwapDecoder,
    pump_fun_decoder: crate::PumpFunDecoder,
    pump_fees_decoder: crate::PumpFeesDecoder,
    pump_swap_decoder: crate::PumpSwapDecoder,
    orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder,
    meteora_dbc_decoder: crate::MeteoraDbcDecoder,
    meteora_dlmm_decoder: crate::MeteoraDlmmDecoder,
    meteora_damm_v1_decoder: crate::MeteoraDammV1Decoder,
    meteora_damm_v2_decoder: crate::MeteoraDammV2Decoder,
    fluxbeam_decoder: crate::FluxbeamDecoder,
    dexlab_decoder: crate::DexlabDecoder,
    openbook_v2_decoder: crate::OpenBookV2Decoder,
    phoenix_v1_decoder: crate::PhoenixV1Decoder,
}

impl DexDecodeService {
    /// Creates a new DEX decode service.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        let persistence = crate::DetectionPersistenceService::new(database.clone());
        return Self {
            database,
            persistence,
            raydium_amm_v4_decoder: crate::RaydiumAmmV4Decoder::new(),
            raydium_clmm_decoder: crate::RaydiumClmmDecoder::new(),
            raydium_stable_swap_decoder: crate::RaydiumStableSwapDecoder::new(),
            pump_fun_decoder: crate::PumpFunDecoder::new(),
            pump_fees_decoder: crate::PumpFeesDecoder::new(),
            pump_swap_decoder: crate::PumpSwapDecoder::new(),
            orca_whirlpools_decoder: crate::OrcaWhirlpoolsDecoder::new(),
            meteora_dbc_decoder: crate::MeteoraDbcDecoder::new(),
            meteora_dlmm_decoder: crate::MeteoraDlmmDecoder::new(),
            meteora_damm_v1_decoder: crate::MeteoraDammV1Decoder::new(),
            meteora_damm_v2_decoder: crate::MeteoraDammV2Decoder::new(),
            fluxbeam_decoder: crate::FluxbeamDecoder::new(),
            dexlab_decoder: crate::DexlabDecoder::new(),
            openbook_v2_decoder: crate::OpenBookV2Decoder::new(),
            phoenix_v1_decoder: crate::PhoenixV1Decoder::new(),
        };
    }

    /// Decodes one projected transaction and persists the decoded events.
    pub async fn decode_transaction_by_signature(
        &self,
        signature: &str,
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let context_result = crate::dex_decode_context::load_dex_decode_transaction_context(
            self.database.as_ref(),
            signature,
        )
        .await;
        let context = match context_result {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let transaction = context.transaction;
        let instructions = context.instructions;
        let mut persisted = std::vec::Vec::new();
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_raydium_amm_v4_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_raydium_cpmm_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_raydium_stable_swap_events(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_raydium_clmm_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.preserve_unmatched_raydium_instruction_audits(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let cleanup_result =
            self.cleanup_replaced_raydium_cpmm_instruction_audits(&transaction).await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let cleanup_result =
            self.cleanup_replaced_raydium_clmm_instruction_audits(&transaction).await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let cleanup_result = self
            .cleanup_replaced_raydium_launchpad_anchor_self_cpi_audits(&transaction)
            .await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_pump_fun_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_pump_swap_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_pump_fees_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_meteora_dbc_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_meteora_dlmm_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_meteora_damm_v1_events(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_meteora_damm_v2_events(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.preserve_unmatched_meteora_instruction_audits(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_orca_whirlpools_events(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_fluxbeam_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_dexlab_events(&transaction, &instructions).await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_openbook_v2_audit_events(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_phoenix_v1_audit_events(&transaction, &instructions)
                .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }

        let decoded_instruction_ids = decoded_instruction_ids_from_persisted_events(&persisted);
        let append_result = append_persisted_events_result(
            &mut persisted,
            self.decode_and_persist_upstream_registry_matches(
                &transaction,
                &instructions,
                &decoded_instruction_ids,
            )
            .await,
        );
        if let Err(error) = append_result {
            return Err(error);
        }
        let cleanup_result =
            self.cleanup_replaced_raydium_cpmm_instruction_audits(&transaction).await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let cleanup_result =
            self.cleanup_replaced_raydium_clmm_instruction_audits(&transaction).await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let cleanup_result = self
            .cleanup_replaced_raydium_launchpad_anchor_self_cpi_audits(&transaction)
            .await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let reconcile_result =
            self.reconcile_raydium_clmm_confirmed_non_trade_events(&transaction).await;
        if let Err(error) = reconcile_result {
            return Err(error);
        }
        return Ok(persisted);
    }

    async fn cleanup_replaced_raydium_cpmm_instruction_audits(
        &self,
        transaction: &crate::ChainTransactionDto,
    ) -> Result<(), crate::Error> {
        let transaction_id = match transaction.id {
            Some(transaction_id) => transaction_id,
            None => return Ok(()),
        };
        let cleanup_result =
            crate::query_dex_decoded_events_delete_replaced_raydium_cpmm_instruction_audits(
                self.database.as_ref(),
                Some(transaction_id),
            )
            .await;
        match cleanup_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn cleanup_replaced_raydium_clmm_instruction_audits(
        &self,
        transaction: &crate::ChainTransactionDto,
    ) -> Result<(), crate::Error> {
        let transaction_id = match transaction.id {
            Some(transaction_id) => transaction_id,
            None => return Ok(()),
        };
        let cleanup_result =
            crate::query_dex_decoded_events_delete_replaced_raydium_clmm_instruction_audits(
                self.database.as_ref(),
                Some(transaction_id),
            )
            .await;
        match cleanup_result {
            Ok(deleted_count) => {
                if deleted_count > 0 {
                    tracing::debug!(
                        signature = %transaction.signature,
                        deleted_count,
                        "cleaned replaced Raydium CLMM instruction audits"
                    );
                }
                return Ok(());
            },
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot cleanup replaced Raydium CLMM instruction audits for signature '{}': {}",
                    transaction.signature, error
                )));
            },
        }
    }

    async fn reconcile_raydium_clmm_confirmed_non_trade_events(
        &self,
        transaction: &crate::ChainTransactionDto,
    ) -> Result<(), crate::Error> {
        if dex_decode_transaction_has_effective_error(transaction) {
            return Ok(());
        }
        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 delete_create_pool_audit = false;
        let mut delete_collect_protocol_fee_audit = false;
        for decoded_event in &decoded_events {
            if decoded_event.protocol_name != "raydium_clmm" {
                continue;
            }
            if decoded_event.event_kind == "raydium_clmm.create_pool" {
                let materialize_result = self
                    .materialize_raydium_clmm_create_pool_lifecycle(transaction, decoded_event)
                    .await;
                if let Err(error) = materialize_result {
                    return Err(error);
                }
                delete_create_pool_audit = true;
                continue;
            }
            if decoded_event.event_kind == "raydium_clmm.collect_protocol_fee" {
                let materialize_result = self
                    .materialize_raydium_clmm_collect_protocol_fee(transaction, decoded_event)
                    .await;
                if let Err(error) = materialize_result {
                    return Err(error);
                }
                delete_collect_protocol_fee_audit = true;
            }
        }
        if delete_create_pool_audit {
            let delete_result = self
                .delete_raydium_clmm_instruction_audit_by_discriminator(
                    transaction_id,
                    "e992d18ecf6840bc",
                )
                .await;
            if let Err(error) = delete_result {
                return Err(error);
            }
        }
        if delete_collect_protocol_fee_audit {
            let delete_result = self
                .delete_raydium_clmm_instruction_audit_by_discriminator(
                    transaction_id,
                    "8888fcddc2427e59",
                )
                .await;
            if let Err(error) = delete_result {
                return Err(error);
            }
        }
        return Ok(());
    }

    async fn materialize_raydium_clmm_create_pool_lifecycle(
        &self,
        transaction: &crate::ChainTransactionDto,
        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 context_result = self.resolve_decoded_event_db_context(decoded_event).await;
        let context = match context_result {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let dto = crate::PoolLifecycleEventDto::new(
            decoded_event.transaction_id,
            Some(decoded_event_id),
            context.0,
            context.1,
            context.2,
            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(()),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_raydium_clmm_collect_protocol_fee(
        &self,
        transaction: &crate::ChainTransactionDto,
        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 payload = dex_decode_payload_value(decoded_event.payload_json.as_str());
        let context_result = self.resolve_decoded_event_db_context(decoded_event).await;
        let context = match context_result {
            Ok(context) => context,
            Err(error) => return Err(error),
        };
        let actor_wallet = dex_decode_extract_first_string(
            &payload,
            &["authority", "actorWallet", "actor_wallet", "owner", "payer", "user"],
        );
        let fee_token_mint = dex_decode_extract_first_string(
            &payload,
            &[
                "vault_0_mint",
                "vault0Mint",
                "feeTokenMint",
                "fee_token_mint",
                "tokenMint",
                "token_mint",
                "mint",
            ],
        );
        let fee_amount_raw = dex_decode_extract_first_amount_string(
            &payload,
            &[
                "amount0RequestedRaw",
                "amount_0_requested_raw",
                "tokenAAmount",
                "token_a_amount",
                "feeAmountRaw",
                "fee_amount_raw",
                "protocolFeeAmount",
                "protocol_fee_amount",
                "amount",
            ],
        );
        let dto = crate::FeeEventDto::new(
            decoded_event.transaction_id,
            Some(decoded_event_id),
            context.0,
            context.1,
            context.2,
            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,
            fee_amount_raw,
            decoded_event.payload_json.clone(),
        );
        let upsert_result = crate::query_fee_events_upsert(self.database.as_ref(), &dto).await;
        match upsert_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn resolve_decoded_event_db_context(
        &self,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<
        (std::option::Option<i64>, std::option::Option<i64>, std::option::Option<i64>),
        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_account = match decoded_event.pool_account.as_ref() {
            Some(pool_account) => pool_account,
            None => return Ok((dex_id, None, None)),
        };
        let pool_result =
            crate::query_pools_get_by_address(self.database.as_ref(), pool_account.as_str()).await;
        let pool = match pool_result {
            Ok(Some(pool)) => pool,
            Ok(None) => return Ok((dex_id, None, None)),
            Err(error) => return Err(error),
        };
        let pool_id = match pool.id {
            Some(pool_id) => pool_id,
            None => return Ok((dex_id, None, 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 {
            Some(pair) => pair.id,
            None => None,
        };
        return Ok((dex_id, Some(pool_id), pair_id));
    }

    async fn delete_raydium_clmm_instruction_audit_by_discriminator(
        &self,
        transaction_id: i64,
        discriminator_hex: &str,
    ) -> Result<(), crate::Error> {
        let delete_result =
            crate::query_dex_decoded_events_delete_raydium_clmm_instruction_audit_by_discriminator(
                self.database.as_ref(),
                transaction_id,
                discriminator_hex,
            )
            .await;
        match delete_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn materialize_named_dex_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        transaction_id: i64,
        instruction_id: i64,
        protocol_name: &str,
        program_id: std::string::String,
        event_kind: &str,
        pool_account: std::option::Option<std::string::String>,
        market_account: std::option::Option<std::string::String>,
        token_a_mint: std::option::Option<std::string::String>,
        token_b_mint: std::option::Option<std::string::String>,
        lp_mint: std::option::Option<std::string::String>,
        payload_json: serde_json::Value,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        let payload_json_for_cleanup = payload_json.clone();
        let input = crate::dex_decoded_event_materialization::DexDecodedEventMaterializationInput {
            database: self.database.as_ref(),
            persistence: &self.persistence,
            transaction,
            transaction_id,
            instruction_id: Some(instruction_id),
            protocol_name: protocol_name.to_string(),
            program_id,
            event_kind: event_kind.to_string(),
            pool_account,
            market_account,
            token_a_mint,
            token_b_mint,
            lp_mint,
            enrichment_payload_json: payload_json.clone(),
            observation_payload_json: payload_json,
            observation_kind: format!("dex.{event_kind}"),
            signal_kind: format!("signal.dex.{event_kind}"),
            missing_after_upsert_message: "decoded event disappeared after upsert".to_string(),
        };
        let materialized_result =
            crate::dex_decoded_event_materialization::materialize_dex_decoded_event(input).await;
        let materialized = match materialized_result {
            Ok(materialized) => materialized,
            Err(error) => return Err(error),
        };
        let cleanup_result = self
            .delete_replaced_instruction_audit(
                transaction_id,
                instruction_id,
                protocol_name,
                event_kind,
            )
            .await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let cleanup_result = self
            .delete_replaced_instruction_audit_by_discriminator(
                transaction_id,
                protocol_name,
                event_kind,
                &payload_json_for_cleanup,
            )
            .await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let cleanup_result = self
            .delete_replaced_upstream_registry_match(
                transaction_id,
                instruction_id,
                protocol_name,
                event_kind,
            )
            .await;
        if let Err(error) = cleanup_result {
            return Err(error);
        }
        let non_trade_result = self
            .materialize_direct_decoded_non_trade_if_needed(transaction, &materialized)
            .await;
        if let Err(error) = non_trade_result {
            return Err(error);
        }
        return Ok(materialized);
    }

    async fn materialize_direct_decoded_non_trade_if_needed(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::DexDecodedEventDto,
    ) -> Result<(), crate::Error> {
        if dex_decode_transaction_has_effective_error(transaction) {
            return Ok(());
        }
        if !should_immediately_materialize_decoded_non_trade_event(
            decoded_event.event_kind.as_str(),
        ) {
            return Ok(());
        }
        if decoded_event.event_kind == "raydium_clmm.create_pool" {
            return self
                .materialize_raydium_clmm_create_pool_lifecycle(transaction, decoded_event)
                .await;
        }
        if decoded_event.event_kind == "raydium_clmm.collect_protocol_fee" {
            return self
                .materialize_raydium_clmm_collect_protocol_fee(transaction, decoded_event)
                .await;
        }
        return Ok(());
    }

    async fn delete_replaced_instruction_audit_by_discriminator(
        &self,
        transaction_id: i64,
        protocol_name: &str,
        event_kind: &str,
        payload_json: &serde_json::Value,
    ) -> Result<(), crate::Error> {
        if event_kind.ends_with(".instruction_audit") {
            return Ok(());
        }
        let discriminator_hex = match instruction_discriminator_hex_from_payload(payload_json) {
            Some(discriminator_hex) => discriminator_hex,
            None => return Ok(()),
        };
        return self
            .delete_replaced_instruction_audit_by_discriminator_hex(
                transaction_id,
                protocol_name,
                discriminator_hex.as_str(),
            )
            .await;
    }

    async fn delete_replaced_instruction_audit_by_discriminator_hex(
        &self,
        transaction_id: i64,
        protocol_name: &str,
        discriminator_hex: &str,
    ) -> Result<(), crate::Error> {
        let audit_event_kind = match instruction_audit_event_kind_by_protocol(protocol_name) {
            Some(audit_event_kind) => audit_event_kind,
            None => return Ok(()),
        };
        let delete_result =
            crate::query_dex_decoded_events_delete_instruction_audit_by_discriminator(
                self.database.as_ref(),
                transaction_id,
                protocol_name,
                audit_event_kind,
                discriminator_hex,
            )
            .await;
        match delete_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn delete_replaced_raydium_launchpad_anchor_self_cpi_audit(
        &self,
        transaction_id: i64,
        _instruction_id: i64,
        anchor_event_discriminator_hex: &str,
    ) -> Result<(), crate::Error> {
        let delete_result =
            crate::query_dex_decoded_events_delete_raydium_launchpad_anchor_self_cpi_audit(
                self.database.as_ref(),
                transaction_id,
                METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX,
                anchor_event_discriminator_hex,
            )
            .await;
        match delete_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn cleanup_replaced_raydium_launchpad_anchor_self_cpi_audits(
        &self,
        transaction: &crate::ChainTransactionDto,
    ) -> Result<(), crate::Error> {
        let transaction_id = match transaction.id {
            Some(transaction_id) => transaction_id,
            None => return Ok(()),
        };
        let cleanup_result =
            crate::query_dex_decoded_events_cleanup_raydium_launchpad_anchor_self_cpi_audits(
                self.database.as_ref(),
                transaction_id,
                METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX,
            )
            .await;
        match cleanup_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn delete_replaced_upstream_registry_match(
        &self,
        transaction_id: i64,
        instruction_id: i64,
        protocol_name: &str,
        event_kind: &str,
    ) -> Result<(), crate::Error> {
        if protocol_name == crate::UPSTREAM_REGISTRY_PROTOCOL_NAME {
            return Ok(());
        }
        if event_kind == crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND {
            return Ok(());
        }
        let delete_result = crate::query_dex_decoded_events_delete_by_key(
            self.database.as_ref(),
            transaction_id,
            Some(instruction_id),
            crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND,
        )
        .await;
        match delete_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn delete_replaced_instruction_audit(
        &self,
        transaction_id: i64,
        instruction_id: i64,
        protocol_name: &str,
        event_kind: &str,
    ) -> Result<(), crate::Error> {
        if event_kind.ends_with(".instruction_audit") {
            return Ok(());
        }
        let audit_event_kind = match instruction_audit_event_kind_by_protocol(protocol_name) {
            Some(audit_event_kind) => audit_event_kind,
            None => return Ok(()),
        };
        let delete_result = crate::query_dex_decoded_events_delete_related_instruction_audit(
            self.database.as_ref(),
            transaction_id,
            instruction_id,
            audit_event_kind,
        )
        .await;
        match delete_result {
            Ok(_) => return Ok(()),
            Err(error) => return Err(error),
        }
    }

    async fn decode_and_persist_upstream_registry_matches(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
        already_decoded_instruction_ids: &std::collections::HashSet<i64>,
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        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 decoded_instruction_ids = already_decoded_instruction_ids.clone();
        for decoded_event in &decoded_events {
            let instruction_id = match decoded_event.instruction_id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            decoded_instruction_ids.insert(instruction_id);
        }
        let mut persisted = std::vec::Vec::new();
        for instruction in instructions {
            let instruction_id = match instruction.id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            if decoded_instruction_ids.contains(&instruction_id) {
                continue;
            }
            let program_id = match instruction.program_id.as_ref() {
                Some(program_id) => program_id,
                None => continue,
            };
            let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
            let registry_match =
                crate::upstream_registry_match::upstream_registry_match_instruction_data(
                    program_id.as_str(),
                    data_base58.as_deref(),
                );
            let registry_match = match registry_match {
                Some(registry_match) => registry_match,
                None => continue,
            };
            if upstream_registry_instruction_match_is_locally_covered(&registry_match) {
                continue;
            }
            let payload = build_upstream_registry_instruction_match_payload(
                transaction,
                instruction,
                &registry_match,
                data_base58.as_deref(),
            );
            let persist_result = self
                .materialize_named_dex_event(
                    transaction,
                    transaction_id,
                    instruction_id,
                    crate::UPSTREAM_REGISTRY_PROTOCOL_NAME,
                    program_id.clone(),
                    crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND,
                    None,
                    None,
                    None,
                    None,
                    None,
                    payload,
                )
                .await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn persist_dexlab_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::DexlabDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::DexlabDecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "dexlab",
                        event.program_id.clone(),
                        "dexlab.create_pool",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::DexlabDecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "dexlab",
                        event.program_id.clone(),
                        "dexlab.swap",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_fluxbeam_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::FluxbeamDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::FluxbeamDecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "fluxbeam",
                        event.program_id.clone(),
                        "fluxbeam.create_pool",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::FluxbeamDecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "fluxbeam",
                        event.program_id.clone(),
                        "fluxbeam.swap",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_orca_whirlpools_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::OrcaWhirlpoolsDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::OrcaWhirlpoolsDecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "orca_whirlpools",
                        event.program_id.clone(),
                        "orca_whirlpools.create_pool",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.config_account.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::OrcaWhirlpoolsDecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "orca_whirlpools",
                        event.program_id.clone(),
                        "orca_whirlpools.swap",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_meteora_dbc_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::MeteoraDbcDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::MeteoraDbcDecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dbc",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.config_account.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDbcDecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dbc",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDbcDecodedEvent::Instruction(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dbc",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.related_account.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_meteora_dlmm_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::MeteoraDlmmDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::MeteoraDlmmDecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dlmm",
                        event.program_id.clone(),
                        "meteora_dlmm.create_pool",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.config_account.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDlmmDecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dlmm",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dlmm",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDlmmDecodedEvent::PoolLifecycle(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dlmm",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDlmmDecodedEvent::Fee(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dlmm",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        None,
                        None,
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDlmmDecodedEvent::Reward(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_dlmm",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        None,
                        None,
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_meteora_damm_v1_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::MeteoraDammV1DecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::MeteoraDammV1DecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v1",
                        event.program_id.clone(),
                        "meteora_damm_v1.create_pool",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDammV1DecodedEvent::Swap(event) => {
                let enrichment_payload_json =
                    prepare_meteora_damm_v1_swap_payload_for_classification(event);
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v1",
                        event.program_id.clone(),
                        "meteora_damm_v1.swap",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        enrichment_payload_json,
                    )
                    .await;
            },
            crate::MeteoraDammV1DecodedEvent::Liquidity(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v1",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDammV1DecodedEvent::Fee(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v1",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        None,
                        None,
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDammV1DecodedEvent::PoolLifecycle(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v1",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDammV1DecodedEvent::PoolAdmin(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v1",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        None,
                        None,
                        None,
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_meteora_damm_v2_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::MeteoraDammV2DecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::MeteoraDammV2DecodedEvent::CreatePool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v2",
                        event.program_id.clone(),
                        "meteora_damm_v2.create_pool",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.config_account.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::MeteoraDammV2DecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "meteora_damm_v2",
                        event.program_id.clone(),
                        "meteora_damm_v2.swap",
                        event.pool_account.clone(),
                        None,
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_raydium_amm_v4_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::RaydiumAmmV4DecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::RaydiumAmmV4DecodedEvent::Initialize2Pool(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "raydium_amm_v4",
                        event.program_id.clone(),
                        "raydium_amm_v4.initialize2_pool",
                        event.pool_account.clone(),
                        event.market_account.clone(),
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::RaydiumAmmV4DecodedEvent::Swap(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "raydium_amm_v4",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        Some(event.pool_account.clone()),
                        None,
                        Some(event.token_a_mint.clone()),
                        Some(event.token_b_mint.clone()),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::RaydiumAmmV4DecodedEvent::Instruction(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "raydium_amm_v4",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        event.pool_account.clone(),
                        event.market_account.clone(),
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        event.lp_mint.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn persist_raydium_clmm_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        instruction_id: i64,
        decoded_event: &crate::RaydiumClmmDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        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 event_kind = decoded_event.event_kind().to_string();
        let raw_payload_json = match decoded_event.to_payload_json() {
            Some(payload_json) => payload_json,
            None => {
                return Err(crate::Error::Json(
                    "cannot serialize decoded raydium clmm payload".to_string(),
                ));
            },
        };
        let payload_value_result = enriched_raydium_payload_value(
            "raydium_clmm",
            event_kind.as_str(),
            raw_payload_json.as_str(),
        );
        let payload_value = match payload_value_result {
            Ok(payload_value) => payload_value,
            Err(error) => return Err(error),
        };
        return self
            .materialize_named_dex_event(
                transaction,
                transaction_id,
                instruction_id,
                "raydium_clmm",
                crate::RAYDIUM_CLMM_PROGRAM_ID.to_string(),
                event_kind.as_str(),
                decoded_event.pool_account_option().map(|value| return value.to_string()),
                None,
                decoded_event.base_mint_option().map(|value| return value.to_string()),
                decoded_event.quote_mint_option().map(|value| return value.to_string()),
                None,
                payload_value,
            )
            .await;
    }

    async fn persist_raydium_cpmm_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        instruction: &crate::ChainInstructionDto,
        decoded_event: &crate::RaydiumCpmmDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        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 instruction_id = match instruction.id {
            Some(instruction_id) => instruction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "raydium cpmm instruction for transaction '{}' has no internal id",
                    transaction.signature
                )));
            },
        };
        let event_kind = decoded_event.event_kind().to_string();
        let raw_payload_json = match decoded_event.to_payload_json() {
            Some(payload_json) => payload_json,
            None => {
                return Err(crate::Error::Json(
                    "cannot serialize decoded raydium cpmm payload".to_string(),
                ));
            },
        };
        let payload_value_result = enriched_raydium_payload_value(
            "raydium_cpmm",
            event_kind.as_str(),
            raw_payload_json.as_str(),
        );
        let payload_value = match payload_value_result {
            Ok(payload_value) => payload_value,
            Err(error) => return Err(error),
        };
        return self
            .materialize_named_dex_event(
                transaction,
                transaction_id,
                instruction_id,
                "raydium_cpmm",
                crate::RAYDIUM_CPMM_PROGRAM_ID.to_string(),
                event_kind.as_str(),
                decoded_event.pool_account().map(|value| return value.to_string()),
                None,
                decoded_event.base_mint().map(|value| return value.to_string()),
                decoded_event.quote_mint().map(|value| return value.to_string()),
                decoded_event.lp_mint().map(|value| return value.to_string()),
                payload_value,
            )
            .await;
    }

    async fn persist_raydium_stable_swap_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::RaydiumStableSwapDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        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 instruction_id = match decoded_event.instruction_id() {
            Some(instruction_id) => instruction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "raydium stable swap decoded event for transaction '{}' has no instruction id",
                    transaction.signature
                )));
            },
        };
        let event_kind = decoded_event.event_kind().to_string();
        let raw_payload_json = match decoded_event.to_payload_json() {
            Some(payload_json) => payload_json,
            None => {
                return Err(crate::Error::Json(
                    "cannot serialize decoded raydium stable swap payload".to_string(),
                ));
            },
        };
        let payload_value_result = enriched_raydium_payload_value(
            "raydium_stable_swap",
            event_kind.as_str(),
            raw_payload_json.as_str(),
        );
        let payload_value = match payload_value_result {
            Ok(payload_value) => payload_value,
            Err(error) => return Err(error),
        };
        return self
            .materialize_named_dex_event(
                transaction,
                transaction_id,
                instruction_id,
                "raydium_stable_swap",
                crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID.to_string(),
                event_kind.as_str(),
                decoded_event.pool_account().map(|value| return value.to_string()),
                decoded_event.market_account().map(|value| return value.to_string()),
                decoded_event.base_mint().map(|value| return value.to_string()),
                decoded_event.quote_mint().map(|value| return value.to_string()),
                decoded_event.lp_mint().map(|value| return value.to_string()),
                payload_value,
            )
            .await;
    }

    async fn persist_pump_fun_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::PumpFunDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::PumpFunDecodedEvent::CreateV2Token(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "pump_fun",
                        event.program_id.clone(),
                        "pump_fun.create_v2_token",
                        event.bonding_curve.clone(),
                        None,
                        event.mint.clone(),
                        Some(crate::WSOL_MINT_ID.to_string()),
                        event.associated_bonding_curve.clone(),
                        event.payload_json.clone(),
                    )
                    .await;
            },
            crate::PumpFunDecodedEvent::BuyTrade(event) => {
                return self
                    .persist_pump_fun_trade_event(
                        transaction,
                        event,
                        "pump_fun.buy",
                        "signal.dex.pump_fun.buy",
                        "dex.pump_fun.buy",
                    )
                    .await;
            },
            crate::PumpFunDecodedEvent::SellTrade(event) => {
                return self
                    .persist_pump_fun_trade_event(
                        transaction,
                        event,
                        "pump_fun.sell",
                        "signal.dex.pump_fun.sell",
                        "dex.pump_fun.sell",
                    )
                    .await;
            },
            crate::PumpFunDecodedEvent::InstructionAudit(event) => {
                let pool_account = Self::pump_fun_payload_string(
                    &event.payload_json,
                    &["poolAccount", "bondingCurve", "bonding_curve", "pool"],
                );
                let token_a_mint = Self::pump_fun_payload_string(
                    &event.payload_json,
                    &["tokenAMint", "token_a_mint", "tokenMint", "token_mint", "mint"],
                );
                let token_b_mint = match Self::pump_fun_payload_string(
                    &event.payload_json,
                    &["tokenBMint", "token_b_mint", "quoteMint", "quote_mint"],
                ) {
                    Some(token_b_mint) => Some(token_b_mint),
                    None => {
                        if token_a_mint.is_some() {
                            Some(crate::WSOL_MINT_ID.to_string())
                        } else {
                            None
                        }
                    },
                };
                let lp_mint = Self::pump_fun_payload_string(
                    &event.payload_json,
                    &[
                        "lpMint",
                        "lp_mint",
                        "associatedBondingCurve",
                        "associated_bonding_curve",
                        "poolBaseTokenAccount",
                    ],
                );
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "pump_fun",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        pool_account,
                        None,
                        token_a_mint,
                        token_b_mint,
                        lp_mint,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    fn pump_fun_payload_string(
        payload: &serde_json::Value,
        keys: &[&str],
    ) -> std::option::Option<std::string::String> {
        if let Some(object) = payload.as_object() {
            for key in keys {
                let value = object.get(*key);
                if let Some(value) = value {
                    if let Some(text) = value.as_str() {
                        if !text.trim().is_empty() {
                            return Some(text.to_string());
                        }
                    }
                }
            }
            let decoded_arguments = object.get("decodedArguments");
            if let Some(decoded_arguments) = decoded_arguments {
                let nested = Self::pump_fun_payload_string(decoded_arguments, keys);
                if nested.is_some() {
                    return nested;
                }
            }
        }
        return None;
    }

    async fn persist_pump_fun_trade_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        event: &crate::PumpFunTradeDecoded,
        event_kind: &str,
        signal_kind: &str,
        observation_kind: &str,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        let input = crate::dex_decoded_event_materialization::DexDecodedEventMaterializationInput {
            database: self.database.as_ref(),
            persistence: &self.persistence,
            transaction,
            transaction_id: event.transaction_id,
            instruction_id: Some(event.instruction_id),
            protocol_name: "pump_fun".to_string(),
            program_id: event.program_id.clone(),
            event_kind: event_kind.to_string(),
            pool_account: event.bonding_curve.clone(),
            market_account: None,
            token_a_mint: event.mint.clone(),
            token_b_mint: Some(crate::WSOL_MINT_ID.to_string()),
            lp_mint: event.associated_bonding_curve.clone(),
            enrichment_payload_json: event.payload_json.clone(),
            observation_payload_json: event.payload_json.clone(),
            observation_kind: observation_kind.to_string(),
            signal_kind: signal_kind.to_string(),
            missing_after_upsert_message: "decoded pump.fun trade event disappeared after upsert"
                .to_string(),
        };
        return crate::dex_decoded_event_materialization::materialize_dex_decoded_event(input)
            .await;
    }

    async fn persist_pump_fees_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::PumpFeesDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::PumpFeesDecodedEvent::Instruction(event) => {
                return self.persist_pump_fees_instruction_event(transaction, event).await;
            },
            crate::PumpFeesDecodedEvent::AnchorEvent(event) => {
                return self.persist_pump_fees_instruction_event(transaction, event).await;
            },
        }
    }

    async fn persist_pump_fees_instruction_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        event: &crate::PumpFeesInstructionDecoded,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        return self
            .materialize_named_dex_event(
                transaction,
                event.transaction_id,
                event.instruction_id,
                "pump_fees",
                event.program_id.clone(),
                event.event_kind.as_str(),
                event.pool_account.clone(),
                None,
                event.token_a_mint.clone(),
                event.token_b_mint.clone(),
                event.lp_mint.clone(),
                event.payload_json.clone(),
            )
            .await;
    }

    async fn persist_pump_swap_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::PumpSwapDecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::PumpSwapDecodedEvent::BuyTrade(event) => {
                return self
                    .persist_pump_swap_trade_event(
                        transaction,
                        event,
                        "pump_swap.buy",
                        "signal.dex.pump_swap.buy",
                        "dex.pump_swap.buy",
                    )
                    .await;
            },
            crate::PumpSwapDecodedEvent::BuyExactQuoteInTrade(event) => {
                return self
                    .persist_pump_swap_trade_event(
                        transaction,
                        event,
                        "pump_swap.buy_exact_quote_in",
                        "signal.dex.pump_swap.buy_exact_quote_in",
                        "dex.pump_swap.buy_exact_quote_in",
                    )
                    .await;
            },
            crate::PumpSwapDecodedEvent::SellTrade(event) => {
                return self
                    .persist_pump_swap_trade_event(
                        transaction,
                        event,
                        "pump_swap.sell",
                        "signal.dex.pump_swap.sell",
                        "dex.pump_swap.sell",
                    )
                    .await;
            },
            crate::PumpSwapDecodedEvent::Instruction(event) => {
                return self.persist_pump_swap_instruction_event(transaction, event).await;
            },
        }
    }

    async fn persist_pump_swap_instruction_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        event: &crate::PumpSwapInstructionDecoded,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        return self
            .materialize_named_dex_event(
                transaction,
                event.transaction_id,
                event.instruction_id,
                "pump_swap",
                event.program_id.clone(),
                event.event_kind.as_str(),
                event.pool_account.clone(),
                None,
                event.token_a_mint.clone(),
                event.token_b_mint.clone(),
                event.lp_mint.clone(),
                event.payload_json.clone(),
            )
            .await;
    }

    async fn persist_pump_swap_trade_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        event: &crate::PumpSwapTradeDecoded,
        event_kind: &str,
        signal_kind: &str,
        observation_kind: &str,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        let enrichment_payload_json = prepare_pump_swap_trade_payload_for_classification(event);
        let input = crate::dex_decoded_event_materialization::DexDecodedEventMaterializationInput {
            database: self.database.as_ref(),
            persistence: &self.persistence,
            transaction,
            transaction_id: event.transaction_id,
            instruction_id: Some(event.instruction_id),
            protocol_name: "pump_swap".to_string(),
            program_id: event.program_id.clone(),
            event_kind: event_kind.to_string(),
            pool_account: event.pool_account.clone(),
            market_account: None,
            token_a_mint: event.token_a_mint.clone(),
            token_b_mint: event.token_b_mint.clone(),
            lp_mint: event.pool_v2.clone(),
            enrichment_payload_json,
            observation_payload_json: event.payload_json.clone(),
            observation_kind: observation_kind.to_string(),
            signal_kind: signal_kind.to_string(),
            missing_after_upsert_message: "decoded event disappeared after upsert".to_string(),
        };
        return crate::dex_decoded_event_materialization::materialize_dex_decoded_event(input)
            .await;
    }

    async fn decode_and_persist_raydium_cpmm_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let mut persisted = std::vec::Vec::new();
        let mut program_data_events = collect_raydium_cpmm_program_data_events(transaction);
        for instruction in instructions {
            let program_id = match instruction.program_id.as_ref() {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id.as_str() != crate::RAYDIUM_CPMM_PROGRAM_ID {
                continue;
            }
            let data_json = match instruction.data_json.as_ref() {
                Some(data_json) => data_json,
                None => continue,
            };
            let instruction_kind =
                crate::classify_raydium_cpmm_instruction_data(data_json.as_str());
            let decoded_events = crate::decode_raydium_cpmm_instruction(
                instruction.accounts_json.as_str(),
                data_json.as_str(),
            );
            for decoded_event in &decoded_events {
                let persist_result =
                    self.persist_raydium_cpmm_event(transaction, instruction, decoded_event).await;
                let persisted_event = match persist_result {
                    Ok(persisted_event) => persisted_event,
                    Err(error) => return Err(error),
                };
                persisted.push(persisted_event);
            }
            let program_data_persist_result = persist_matching_raydium_cpmm_program_data_event(
                self,
                transaction,
                instruction,
                instruction_kind,
                &mut program_data_events,
                &mut persisted,
            )
            .await;
            if let Err(error) = program_data_persist_result {
                return Err(error);
            }
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_raydium_stable_swap_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.raydium_stable_swap_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result =
                self.persist_raydium_stable_swap_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_raydium_clmm_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.raydium_clmm_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self
                .persist_raydium_clmm_event(
                    transaction,
                    decoded_event.instruction_id,
                    &decoded_event.decoded_event,
                )
                .await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }

        let mut program_data_events = collect_raydium_clmm_program_data_events(transaction);
        for instruction in instructions {
            let program_id = match instruction.program_id.as_ref() {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id.as_str() != crate::RAYDIUM_CLMM_PROGRAM_ID {
                continue;
            }
            let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
            let discriminator_hex = discriminator_hex_from_base58(data_base58.as_deref());
            let persist_result = persist_matching_raydium_clmm_program_data_events(
                self,
                transaction,
                instruction,
                discriminator_hex.as_deref(),
                &mut program_data_events,
                &mut persisted,
            )
            .await;
            if let Err(error) = persist_result {
                return Err(error);
            }
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_raydium_amm_v4_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.raydium_amm_v4_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result =
                self.persist_raydium_amm_v4_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn preserve_unmatched_raydium_instruction_audits(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        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 decoded_instruction_ids = std::collections::HashSet::<i64>::new();
        let mut decoded_discriminator_keys =
            std::collections::HashSet::<std::string::String>::new();
        for decoded_event in &decoded_events {
            if !decoded_event.protocol_name.starts_with("raydium_") {
                continue;
            }
            if decoded_event.event_kind.ends_with(".instruction_audit") {
                continue;
            }
            if let Some(instruction_id) = decoded_event.instruction_id {
                decoded_instruction_ids.insert(instruction_id);
            }
            let discriminator =
                instruction_discriminator_hex_from_payload_str(decoded_event.payload_json.as_str());
            if let Some(discriminator) = discriminator {
                decoded_discriminator_keys.insert(raydium_decoded_discriminator_key(
                    decoded_event.protocol_name.as_str(),
                    discriminator.as_str(),
                ));
            }
        }
        let mut persisted = std::vec::Vec::new();
        for instruction in instructions {
            let program_id = match instruction.program_id.as_ref() {
                Some(program_id) => program_id,
                None => continue,
            };
            let audit_spec = match raydium_instruction_audit_spec(program_id.as_str()) {
                Some(audit_spec) => audit_spec,
                None => continue,
            };
            let instruction_id = match instruction.id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str());
            let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
            let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref());
            let discriminator_hex = raydium_instruction_discriminator_hex(
                audit_spec.protocol_name,
                data_bytes.as_deref(),
                0,
            );
            let anchor_event_spec = raydium_launchpad_anchor_self_cpi_event_spec(
                audit_spec.protocol_name,
                data_bytes.as_deref(),
            );
            let dedupe_discriminator_hex = match anchor_event_spec {
                Some(anchor_event_spec) => Some(anchor_event_spec.discriminator_hex.to_string()),
                None => discriminator_hex.clone(),
            };
            if decoded_instruction_ids.contains(&instruction_id) && anchor_event_spec.is_none() {
                if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() {
                    let cleanup_result = self
                        .delete_replaced_instruction_audit_by_discriminator_hex(
                            transaction_id,
                            audit_spec.protocol_name,
                            discriminator_hex,
                        )
                        .await;
                    if let Err(error) = cleanup_result {
                        return Err(error);
                    }
                }
                continue;
            }
            if anchor_event_spec.is_none()
                && raydium_instruction_already_decoded_by_discriminator(
                    &decoded_discriminator_keys,
                    audit_spec.protocol_name,
                    dedupe_discriminator_hex.as_deref(),
                )
            {
                if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() {
                    let cleanup_result = self
                        .delete_replaced_instruction_audit_by_discriminator_hex(
                            transaction_id,
                            audit_spec.protocol_name,
                            discriminator_hex,
                        )
                        .await;
                    if let Err(error) = cleanup_result {
                        return Err(error);
                    }
                }
                continue;
            }
            let mapped_spec = if anchor_event_spec.is_some() {
                None
            } else {
                raydium_mapped_non_trade_instruction_spec(
                    audit_spec.protocol_name,
                    discriminator_hex.as_deref(),
                    accounts.len(),
                )
            };
            if let Some(mapped_spec) = mapped_spec {
                if raydium_mapped_event_kind_already_decoded(
                    decoded_events.as_slice(),
                    audit_spec.protocol_name,
                    mapped_spec.event_kind,
                ) {
                    if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() {
                        let cleanup_result = self
                            .delete_replaced_instruction_audit_by_discriminator_hex(
                                transaction_id,
                                audit_spec.protocol_name,
                                discriminator_hex,
                            )
                            .await;
                        if let Err(error) = cleanup_result {
                            return Err(error);
                        }
                    }
                    continue;
                }
            }
            let event_kind = match anchor_event_spec {
                Some(anchor_event_spec) => anchor_event_spec.event_kind,
                None => match mapped_spec {
                    Some(mapped_spec) => mapped_spec.event_kind,
                    None => audit_spec.event_kind,
                },
            };
            let mut payload = build_raydium_instruction_audit_payload(
                transaction,
                instruction,
                audit_spec.protocol_name,
                event_kind,
                program_id.as_str(),
            );
            if let Some(anchor_event_spec) = anchor_event_spec {
                payload = enrich_raydium_launchpad_anchor_self_cpi_payload(
                    payload,
                    anchor_event_spec,
                    data_bytes.as_deref(),
                );
            }
            if let Some(mapped_spec) = mapped_spec {
                payload = enrich_raydium_mapped_non_trade_payload(
                    payload,
                    mapped_spec,
                    data_base58.as_deref(),
                );
            }
            let pool_account = match anchor_event_spec {
                Some(anchor_event_spec) => raydium_launchpad_anchor_self_cpi_pool_account(
                    anchor_event_spec,
                    data_bytes.as_deref(),
                ),
                None => candidate_raydium_mapped_pool_account(
                    mapped_spec,
                    accounts.as_slice(),
                    audit_spec.protocol_name,
                    instruction.accounts_json.as_str(),
                ),
            };
            let token_a_mint = candidate_raydium_mapped_account(
                mapped_spec.and_then(|spec| return spec.token_a_mint_index),
                accounts.as_slice(),
            );
            let token_b_mint = candidate_raydium_mapped_account(
                mapped_spec.and_then(|spec| return spec.token_b_mint_index),
                accounts.as_slice(),
            );
            let lp_mint = candidate_raydium_mapped_account(
                mapped_spec.and_then(|spec| return spec.lp_mint_index),
                accounts.as_slice(),
            );
            let persist_result = self
                .materialize_named_dex_event(
                    transaction,
                    transaction_id,
                    instruction_id,
                    audit_spec.protocol_name,
                    program_id.clone(),
                    event_kind,
                    pool_account,
                    None,
                    token_a_mint,
                    token_b_mint,
                    lp_mint,
                    payload,
                )
                .await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            if let Some(anchor_event_spec) = anchor_event_spec {
                let cleanup_result = self
                    .delete_replaced_raydium_launchpad_anchor_self_cpi_audit(
                        transaction_id,
                        instruction_id,
                        anchor_event_spec.discriminator_hex,
                    )
                    .await;
                if let Err(error) = cleanup_result {
                    return Err(error);
                }
            }
            if anchor_event_spec.is_none() {
                if let Some(discriminator_hex) = dedupe_discriminator_hex.as_deref() {
                    let cleanup_result = self
                        .delete_replaced_instruction_audit_by_discriminator_hex(
                            transaction_id,
                            audit_spec.protocol_name,
                            discriminator_hex,
                        )
                        .await;
                    if let Err(error) = cleanup_result {
                        return Err(error);
                    }
                }
            }
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn preserve_unmatched_meteora_instruction_audits(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        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 decoded_instruction_ids = std::collections::HashSet::<i64>::new();
        for decoded_event in &decoded_events {
            if !decoded_event.protocol_name.starts_with("meteora_") {
                continue;
            }
            if decoded_event.event_kind.ends_with(".instruction_audit") {
                continue;
            }
            let instruction_id = match decoded_event.instruction_id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            decoded_instruction_ids.insert(instruction_id);
        }
        let mut persisted = std::vec::Vec::new();
        for instruction in instructions {
            let program_id = match instruction.program_id.as_ref() {
                Some(program_id) => program_id,
                None => continue,
            };
            let audit_spec = match meteora_instruction_audit_spec(program_id.as_str()) {
                Some(audit_spec) => audit_spec,
                None => continue,
            };
            let instruction_id = match instruction.id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            if decoded_instruction_ids.contains(&instruction_id) {
                continue;
            }
            if is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
                audit_spec.protocol_name,
                instruction,
                decoded_events.as_slice(),
            ) {
                continue;
            }
            let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str());
            let payload = build_meteora_instruction_audit_payload(
                transaction,
                instruction,
                audit_spec.protocol_name,
                audit_spec.event_kind,
                program_id.as_str(),
            );
            let pool_account =
                candidate_meteora_audit_pool_account(audit_spec, accounts.as_slice());
            let persist_result = self
                .materialize_named_dex_event(
                    transaction,
                    transaction_id,
                    instruction_id,
                    audit_spec.protocol_name,
                    program_id.clone(),
                    audit_spec.event_kind,
                    pool_account,
                    None,
                    None,
                    None,
                    None,
                    payload,
                )
                .await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_pump_fun_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.pump_fun_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let decoded_events = pump_fun_enrich_trade_events_with_instruction_context(decoded_events);
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            if !pump_fun_decoded_event_is_trade_event(decoded_event) {
                continue;
            }
            let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        for decoded_event in &decoded_events {
            if pump_fun_decoded_event_is_trade_event(decoded_event) {
                continue;
            }
            let persist_result = self.persist_pump_fun_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_pump_swap_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.pump_swap_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_pump_swap_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };

            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_pump_fees_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.pump_fees_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_pump_fees_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_meteora_dbc_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.meteora_dbc_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_meteora_dbc_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_meteora_dlmm_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.meteora_dlmm_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_meteora_dlmm_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_meteora_damm_v1_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.meteora_damm_v1_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result =
                self.persist_meteora_damm_v1_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_meteora_damm_v2_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.meteora_damm_v2_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result =
                self.persist_meteora_damm_v2_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_orca_whirlpools_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result =
            self.orca_whirlpools_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result =
                self.persist_orca_whirlpools_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn persist_openbook_v2_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::OpenBookV2DecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::OpenBookV2DecodedEvent::Audit(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "openbook_v2",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        None,
                        event.market_account.clone(),
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn decode_and_persist_openbook_v2_audit_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.openbook_v2_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_openbook_v2_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn persist_phoenix_v1_event(
        &self,
        transaction: &crate::ChainTransactionDto,
        decoded_event: &crate::PhoenixV1DecodedEvent,
    ) -> Result<crate::DexDecodedEventDto, crate::Error> {
        match decoded_event {
            crate::PhoenixV1DecodedEvent::Audit(event) => {
                return self
                    .materialize_named_dex_event(
                        transaction,
                        event.transaction_id,
                        event.instruction_id,
                        "phoenix_v1",
                        event.program_id.clone(),
                        event.event_kind.as_str(),
                        None,
                        event.market_account.clone(),
                        event.token_a_mint.clone(),
                        event.token_b_mint.clone(),
                        None,
                        event.payload_json.clone(),
                    )
                    .await;
            },
        }
    }

    async fn decode_and_persist_phoenix_v1_audit_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.phoenix_v1_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_phoenix_v1_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_fluxbeam_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.fluxbeam_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_fluxbeam_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }

    async fn decode_and_persist_dexlab_events(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
        let decoded_result = self.dexlab_decoder.decode_transaction(transaction, instructions);
        let decoded_events = match decoded_result {
            Ok(decoded_events) => decoded_events,
            Err(error) => return Err(error),
        };
        let mut persisted = std::vec::Vec::new();
        for decoded_event in &decoded_events {
            let persist_result = self.persist_dexlab_event(transaction, decoded_event).await;
            let persisted_event = match persist_result {
                Ok(persisted_event) => persisted_event,
                Err(error) => return Err(error),
            };
            persisted.push(persisted_event);
        }
        return Ok(persisted);
    }
}

struct RaydiumInstructionAuditSpec {
    protocol_name: &'static str,
    event_kind: &'static str,
    candidate_pool_account_index: usize,
}

#[derive(Clone, Copy)]
struct MeteoraInstructionAuditSpec {
    protocol_name: &'static str,
    event_kind: &'static str,
    candidate_pool_account_index: std::option::Option<usize>,
}

#[derive(Clone, Copy)]
struct RaydiumMappedNonTradeInstructionSpec {
    instruction_name: &'static str,
    event_kind: &'static str,
    pool_account_index: std::option::Option<usize>,
    token_a_mint_index: std::option::Option<usize>,
    token_b_mint_index: std::option::Option<usize>,
    lp_mint_index: std::option::Option<usize>,
    amount_layout: RaydiumMappedNonTradeAmountLayout,
}

#[derive(Clone, Copy)]
enum RaydiumMappedNonTradeAmountLayout {
    None,
    ClmmCreatePool,
    ClmmFeePair,
    ClmmLiquidityV2,
    ClmmOpenLimitOrder,
    ClmmIncreaseLimitOrder,
    ClmmDecreaseLimitOrder,
    AnchorIdl,
    CpmmAmmConfig,
    CpmmDeposit,
    CpmmFeePair,
    CpmmInitialize,
    CpmmPoolStatus,
    CpmmWithdraw,
    LaunchpadInitialize,
    AmmV4Initialize,
    AmmV4Initialize2,
    AmmV4MonitorStep,
    AmmV4Deposit,
    AmmV4Withdraw,
    AmmV4SetParams,
    AmmV4WithdrawSrm,
    AmmV4PreInitialize,
    AmmV4SimulateInfo,
    AmmV4AdminCancelOrders,
    AmmV4UpdateConfigAccount,
}

fn raydium_instruction_audit_spec(
    program_id: &str,
) -> std::option::Option<RaydiumInstructionAuditSpec> {
    if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID {
        return Some(RaydiumInstructionAuditSpec {
            protocol_name: "raydium_amm_v4",
            event_kind: "raydium_amm_v4.instruction_audit",
            candidate_pool_account_index: 1,
        });
    }
    if program_id == crate::RAYDIUM_CLMM_PROGRAM_ID {
        return Some(RaydiumInstructionAuditSpec {
            protocol_name: "raydium_clmm",
            event_kind: "raydium_clmm.instruction_audit",
            candidate_pool_account_index: 2,
        });
    }
    if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID {
        return Some(RaydiumInstructionAuditSpec {
            protocol_name: "raydium_cpmm",
            event_kind: "raydium_cpmm.instruction_audit",
            candidate_pool_account_index: 3,
        });
    }
    if program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID {
        return Some(RaydiumInstructionAuditSpec {
            protocol_name: "raydium_stable_swap",
            event_kind: "raydium_stable_swap.instruction_audit",
            candidate_pool_account_index: 1,
        });
    }
    if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID {
        return Some(RaydiumInstructionAuditSpec {
            protocol_name: "raydium_launchpad",
            event_kind: "raydium_launchpad.instruction_audit",
            candidate_pool_account_index: 4,
        });
    }
    return None;
}

fn raydium_mapped_non_trade_instruction_spec(
    protocol_name: &str,
    discriminator_hex: std::option::Option<&str>,
    account_count: usize,
) -> std::option::Option<RaydiumMappedNonTradeInstructionSpec> {
    let discriminator_hex = match discriminator_hex {
        Some(discriminator_hex) => discriminator_hex,
        None => return None,
    };
    if protocol_name == "raydium_launchpad" {
        return raydium_launchpad_mapped_non_trade_instruction_spec(
            discriminator_hex,
            account_count,
        );
    }
    if protocol_name == "raydium_amm_v4" {
        return raydium_amm_v4_mapped_non_trade_instruction_spec(discriminator_hex, account_count);
    }
    if protocol_name == "raydium_clmm" {
        if discriminator_hex == "e445a52e51cb9a1d" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "cpi_event",
                event_kind: "raydium_clmm.cpi_event",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "4c7c800fd55725fa" && account_count >= 3 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "close_limit_order",
                event_kind: "raydium_clmm.close_limit_order",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "9d20dab7471d1293" && account_count >= 11 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "open_limit_order",
                event_kind: "raydium_clmm.open_limit_order",
                pool_account_index: Some(1),
                token_a_mint_index: Some(7),
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmOpenLimitOrder,
            });
        }
        if discriminator_hex == "b19059ecfaba7d63" && account_count >= 8 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "increase_limit_order",
                event_kind: "raydium_clmm.increase_limit_order",
                pool_account_index: Some(1),
                token_a_mint_index: Some(6),
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmIncreaseLimitOrder,
            });
        }
        if discriminator_hex == "759d3c674231a300" && account_count >= 12 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "decrease_limit_order",
                event_kind: "raydium_clmm.decrease_limit_order",
                pool_account_index: Some(1),
                token_a_mint_index: Some(8),
                token_b_mint_index: Some(9),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmDecreaseLimitOrder,
            });
        }
        if discriminator_hex == "c975989055556cb2" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "close_protocol_position",
                event_kind: "raydium_clmm.close_protocol_position",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "a78a4e95dfc2067e" && account_count >= 7 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "collect_fund_fee",
                event_kind: "raydium_clmm.collect_fund_fee",
                pool_account_index: Some(1),
                token_a_mint_index: Some(5),
                token_b_mint_index: Some(6),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmFeePair,
            });
        }
        if discriminator_hex == "8888fcddc2427e59" && account_count >= 7 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "collect_protocol_fee",
                event_kind: "raydium_clmm.collect_protocol_fee",
                pool_account_index: Some(1),
                token_a_mint_index: Some(5),
                token_b_mint_index: Some(6),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmFeePair,
            });
        }
        if discriminator_hex == "e992d18ecf6840bc" && account_count >= 13 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_pool",
                event_kind: "raydium_clmm.create_pool",
                pool_account_index: Some(2),
                token_a_mint_index: Some(3),
                token_b_mint_index: Some(4),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmCreatePool,
            });
        }
        if discriminator_hex == "12eda6c52210d590" && account_count >= 5 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "collect_remaining_rewards",
                event_kind: "raydium_clmm.collect_remaining_rewards",
                pool_account_index: Some(2),
                token_a_mint_index: Some(4),
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "8934edd4d7756c68" && account_count >= 3 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_amm_config",
                event_kind: "raydium_clmm.create_amm_config",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "2b44d4a7592fa401" && account_count >= 13 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_customizable_pool",
                event_kind: "raydium_clmm.create_customizable_pool",
                pool_account_index: Some(2),
                token_a_mint_index: Some(3),
                token_b_mint_index: Some(4),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "bd0eb5785576e33e" && account_count >= 3 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_dynamic_fee_config",
                event_kind: "raydium_clmm.create_dynamic_fee_config",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "3f5794216d230868" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_operation_account",
                event_kind: "raydium_clmm.create_operation_account",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "11fb415c88f20ea9" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_support_mint_associated",
                event_kind: "raydium_clmm.create_support_mint_associated",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "a026d06f685b2c01" && account_count >= 4 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "decrease_liquidity",
                event_kind: "raydium_clmm.decrease_liquidity",
                pool_account_index: Some(3),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: Some(1),
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "2e9cf3760dcdfbb2" && account_count >= 3 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "increase_liquidity",
                event_kind: "raydium_clmm.increase_liquidity",
                pool_account_index: Some(2),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: Some(1),
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "5f87c0c4f281e644" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "initialize_reward",
                event_kind: "raydium_clmm.initialize_reward",
                pool_account_index: Some(1),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "87802f4d0f98f031" && account_count >= 6 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "open_position",
                event_kind: "raydium_clmm.open_position",
                pool_account_index: Some(5),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: Some(2),
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "4db84ad67056f1c7" && account_count >= 6 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "open_position_v2",
                event_kind: "raydium_clmm.open_position_v2",
                pool_account_index: Some(5),
                token_a_mint_index: if account_count >= 22 { Some(20) } else { None },
                token_b_mint_index: if account_count >= 22 { Some(21) } else { None },
                lp_mint_index: Some(2),
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "7034a74b20c9d389" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "set_reward_params",
                event_kind: "raydium_clmm.set_reward_params",
                pool_account_index: Some(1),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "cd4e74215c691a60" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "settle_limit_order",
                event_kind: "raydium_clmm.settle_limit_order",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "457d73daf5baf2c4" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "swap_router_base_in",
                event_kind: "raydium_clmm.swap_router_base_in",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "07160c53f22b3079" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "transfer_reward_owner",
                event_kind: "raydium_clmm.transfer_reward_owner",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "313cae889a1c74c8" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_amm_config",
                event_kind: "raydium_clmm.update_amm_config",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "0707500802c784f0" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_dynamic_fee_config",
                event_kind: "raydium_clmm.update_dynamic_fee_config",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "7f467728bce33d07" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_operation_account",
                event_kind: "raydium_clmm.update_operation_account",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "82576c062ee0757b" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_pool_status",
                event_kind: "raydium_clmm.update_pool_status",
                pool_account_index: Some(0),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "a3ace0340b9a6adf" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_reward_infos",
                event_kind: "raydium_clmm.update_reward_infos",
                pool_account_index: Some(0),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "3a7fbc3e4f52c460" && account_count >= 16 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "decrease_liquidity_v2",
                event_kind: "raydium_clmm.decrease_liquidity_v2",
                pool_account_index: Some(3),
                token_a_mint_index: Some(14),
                token_b_mint_index: Some(15),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmLiquidityV2,
            });
        }
        if discriminator_hex == "851d59df45eeb00a" && account_count >= 15 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "increase_liquidity_v2",
                event_kind: "raydium_clmm.increase_liquidity_v2",
                pool_account_index: Some(2),
                token_a_mint_index: Some(13),
                token_b_mint_index: Some(14),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::ClmmLiquidityV2,
            });
        }
        if discriminator_hex == "4dffae527d1dc92e" && account_count >= 20 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "open_position_with_token22_nft",
                event_kind: "raydium_clmm.open_position_with_token22_nft",
                pool_account_index: Some(4),
                token_a_mint_index: Some(18),
                token_b_mint_index: Some(19),
                lp_mint_index: Some(2),
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "7b86510031446262" && account_count >= 6 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "close_position",
                event_kind: "raydium_clmm.close_position",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: Some(1),
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
    }
    if protocol_name == "raydium_cpmm" {
        if discriminator_hex == "40f4bc78a7e9690a" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "anchor_idl_instruction",
                event_kind: "raydium_cpmm.anchor_idl_instruction",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::AnchorIdl,
            });
        }
        if discriminator_hex == "e445a52e51cb9a1d" {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "cpi_event",
                event_kind: "raydium_cpmm.cpi_event",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "9c5420764587467b" && account_count >= 4 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "close_permission_pda",
                event_kind: "raydium_cpmm.close_permission_pda",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "1416567bc61cdb84" && account_count >= 13 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "collect_creator_fee",
                event_kind: "raydium_cpmm.collect_creator_fee",
                pool_account_index: Some(2),
                token_a_mint_index: Some(6),
                token_b_mint_index: Some(7),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "a78a4e95dfc2067e" && account_count >= 12 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "collect_fund_fee",
                event_kind: "raydium_cpmm.collect_fund_fee",
                pool_account_index: Some(2),
                token_a_mint_index: Some(6),
                token_b_mint_index: Some(7),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmFeePair,
            });
        }
        if discriminator_hex == "8888fcddc2427e59" && account_count >= 12 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "collect_protocol_fee",
                event_kind: "raydium_cpmm.collect_protocol_fee",
                pool_account_index: Some(2),
                token_a_mint_index: Some(6),
                token_b_mint_index: Some(7),
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmFeePair,
            });
        }
        if discriminator_hex == "8934edd4d7756c68" && account_count >= 3 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_amm_config",
                event_kind: "raydium_cpmm.create_amm_config",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmAmmConfig,
            });
        }
        if discriminator_hex == "878802d889a9b5ca" && account_count >= 4 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "create_permission_pda",
                event_kind: "raydium_cpmm.create_permission_pda",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::None,
            });
        }
        if discriminator_hex == "f223c68952e1f2b6" && account_count >= 13 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "deposit",
                event_kind: "raydium_cpmm.deposit",
                pool_account_index: Some(2),
                token_a_mint_index: Some(10),
                token_b_mint_index: Some(11),
                lp_mint_index: Some(12),
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmDeposit,
            });
        }
        if discriminator_hex == "afaf6d1f0d989bed" && account_count >= 20 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "initialize",
                event_kind: "raydium_cpmm.initialize",
                pool_account_index: Some(3),
                token_a_mint_index: Some(4),
                token_b_mint_index: Some(5),
                lp_mint_index: Some(6),
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmInitialize,
            });
        }
        if discriminator_hex == "3f37fe4131b25979" && account_count >= 21 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "initialize_with_permission",
                event_kind: "raydium_cpmm.initialize_with_permission",
                pool_account_index: Some(4),
                token_a_mint_index: Some(5),
                token_b_mint_index: Some(6),
                lp_mint_index: Some(7),
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmInitialize,
            });
        }
        if discriminator_hex == "313cae889a1c74c8" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_amm_config",
                event_kind: "raydium_cpmm.update_amm_config",
                pool_account_index: None,
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmAmmConfig,
            });
        }
        if discriminator_hex == "82576c062ee0757b" && account_count >= 2 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "update_pool_status",
                event_kind: "raydium_cpmm.update_pool_status",
                pool_account_index: Some(1),
                token_a_mint_index: None,
                token_b_mint_index: None,
                lp_mint_index: None,
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmPoolStatus,
            });
        }
        if discriminator_hex == "b712469c946da122" && account_count >= 14 {
            return Some(RaydiumMappedNonTradeInstructionSpec {
                instruction_name: "withdraw",
                event_kind: "raydium_cpmm.withdraw",
                pool_account_index: Some(2),
                token_a_mint_index: Some(10),
                token_b_mint_index: Some(11),
                lp_mint_index: Some(12),
                amount_layout: RaydiumMappedNonTradeAmountLayout::CpmmWithdraw,
            });
        }
    }
    return None;
}

fn raydium_amm_v4_mapped_non_trade_instruction_spec(
    discriminator_hex: &str,
    account_count: usize,
) -> std::option::Option<RaydiumMappedNonTradeInstructionSpec> {
    match discriminator_hex {
        "00" => {
            if account_count >= 4 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "initialize",
                    event_kind: "raydium_amm_v4.initialize",
                    pool_account_index: Some(3),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4Initialize,
                });
            }
        },
        "01" => {
            if account_count >= 10 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "initialize2",
                    event_kind: "raydium_amm_v4.initialize2_pool",
                    pool_account_index: Some(4),
                    token_a_mint_index: Some(8),
                    token_b_mint_index: Some(9),
                    lp_mint_index: Some(7),
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4Initialize2,
                });
            }
        },
        "02" => {
            if account_count >= 4 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "monitor_step",
                    event_kind: "raydium_amm_v4.monitor_step",
                    pool_account_index: Some(3),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4MonitorStep,
                });
            }
        },
        "03" => {
            if account_count >= 8 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "deposit",
                    event_kind: "raydium_amm_v4.deposit",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: Some(5),
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4Deposit,
                });
            }
        },
        "04" => {
            if account_count >= 8 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "withdraw",
                    event_kind: "raydium_amm_v4.withdraw",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: Some(5),
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4Withdraw,
                });
            }
        },
        "05" => {
            if account_count >= 4 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "migrate_to_open_book",
                    event_kind: "raydium_amm_v4.migrate_to_open_book",
                    pool_account_index: Some(3),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::None,
                });
            }
        },
        "06" => {
            if account_count >= 2 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "set_params",
                    event_kind: "raydium_amm_v4.set_params",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4SetParams,
                });
            }
        },
        "07" => {
            if account_count >= 2 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "withdraw_pnl",
                    event_kind: "raydium_amm_v4.withdraw_pnl",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::None,
                });
            }
        },
        "08" => {
            if account_count >= 2 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "withdraw_srm",
                    event_kind: "raydium_amm_v4.withdraw_srm",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4WithdrawSrm,
                });
            }
        },
        "0a" => {
            if account_count >= 5 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "pre_initialize",
                    event_kind: "raydium_amm_v4.pre_initialize",
                    pool_account_index: Some(4),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4PreInitialize,
                });
            }
        },
        "0c" => {
            if account_count >= 2 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "simulate_info",
                    event_kind: "raydium_amm_v4.simulate_info",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4SimulateInfo,
                });
            }
        },
        "0d" => {
            if account_count >= 2 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "admin_cancel_orders",
                    event_kind: "raydium_amm_v4.admin_cancel_orders",
                    pool_account_index: Some(1),
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4AdminCancelOrders,
                });
            }
        },
        "0e" => {
            if account_count >= 1 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "create_config_account",
                    event_kind: "raydium_amm_v4.create_config_account",
                    pool_account_index: None,
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::None,
                });
            }
        },
        "0f" => {
            if account_count >= 1 {
                return Some(RaydiumMappedNonTradeInstructionSpec {
                    instruction_name: "update_config_account",
                    event_kind: "raydium_amm_v4.update_config_account",
                    pool_account_index: None,
                    token_a_mint_index: None,
                    token_b_mint_index: None,
                    lp_mint_index: None,
                    amount_layout: RaydiumMappedNonTradeAmountLayout::AmmV4UpdateConfigAccount,
                });
            }
        },
        _ => {},
    }
    return None;
}

fn raydium_launchpad_mapped_non_trade_instruction_spec(
    discriminator_hex: &str,
    account_count: usize,
) -> std::option::Option<RaydiumMappedNonTradeInstructionSpec> {
    let layout =
        match crate::dex::raydium_launchpad::account_layout(discriminator_hex, account_count) {
            Some(layout) => layout,
            None => return None,
        };
    let amount_layout = if layout.creates_pool {
        RaydiumMappedNonTradeAmountLayout::LaunchpadInitialize
    } else {
        RaydiumMappedNonTradeAmountLayout::None
    };
    return Some(RaydiumMappedNonTradeInstructionSpec {
        instruction_name: layout.instruction_name,
        event_kind: layout.event_kind,
        pool_account_index: layout.pool_account_index,
        token_a_mint_index: layout.base_mint_index,
        token_b_mint_index: layout.quote_mint_index,
        lp_mint_index: None,
        amount_layout,
    });
}

fn candidate_raydium_mapped_pool_account(
    mapped_spec: std::option::Option<RaydiumMappedNonTradeInstructionSpec>,
    accounts: &[std::string::String],
    protocol_name: &str,
    accounts_json: &str,
) -> std::option::Option<std::string::String> {
    if let Some(mapped_spec) = mapped_spec {
        if let Some(pool_account_index) = mapped_spec.pool_account_index {
            return candidate_raydium_mapped_account(Some(pool_account_index), accounts);
        }
        return None;
    }
    return candidate_raydium_audit_pool_account(protocol_name, accounts_json);
}

fn candidate_raydium_mapped_account(
    index: std::option::Option<usize>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let index = match index {
        Some(index) => index,
        None => return None,
    };
    return accounts.get(index).cloned();
}

fn enrich_raydium_mapped_non_trade_payload(
    payload: serde_json::Value,
    mapped_spec: RaydiumMappedNonTradeInstructionSpec,
    data_base58: std::option::Option<&str>,
) -> serde_json::Value {
    let mut object = match payload {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.remove("tradeCandidate");
    object.remove("candleCandidate");
    object.remove("nonTradeUseful");
    object.remove("skipTradeReason");
    object.remove("skipCandleReason");
    object.insert(
        "instructionName".to_string(),
        serde_json::Value::String(mapped_spec.instruction_name.to_string()),
    );
    object.insert(
        "upstreamInstructionName".to_string(),
        serde_json::Value::String(mapped_spec.instruction_name.to_string()),
    );
    object.insert("localSpecializedDecoder".to_string(), serde_json::Value::Bool(true));
    object.insert(
        "adminAction".to_string(),
        serde_json::Value::String(mapped_spec.instruction_name.to_string()),
    );
    object.insert("decodedFromAudit".to_string(), serde_json::Value::Bool(true));
    object.insert(
        "auditReason".to_string(),
        serde_json::Value::String("raydium_non_swap_instruction_mapped_from_corpus".to_string()),
    );
    object.insert(
        "proofSource".to_string(),
        serde_json::Value::String(
            "local_corpus_discriminator_and_raydium_idl_instruction_name".to_string(),
        ),
    );
    let data_bytes = instruction_data_bytes_from_base58(data_base58);
    if let Some(data_bytes) = data_bytes {
        insert_raydium_mapped_amounts(
            &mut object,
            mapped_spec.amount_layout,
            data_bytes.as_slice(),
        );
    }
    return serde_json::Value::Object(object);
}

fn insert_raydium_mapped_amounts(
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
    amount_layout: RaydiumMappedNonTradeAmountLayout,
    data: &[u8],
) {
    match amount_layout {
        RaydiumMappedNonTradeAmountLayout::None => return,
        RaydiumMappedNonTradeAmountLayout::AnchorIdl => {
            let payload_len = data.len();
            object.insert("idlManagementInstruction".to_string(), serde_json::Value::Bool(true));
            object.insert(
                "instructionDataLength".to_string(),
                serde_json::Value::Number(serde_json::Number::from(payload_len as u64)),
            );
            if payload_len >= 8 {
                object.insert(
                    "idlManagementDiscriminatorHex".to_string(),
                    serde_json::Value::String("40f4bc78a7e9690a".to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::ClmmCreatePool => {
            if let Some(sqrt_price_x64) = read_u128_le_from_bytes(data, 8) {
                object.insert(
                    "sqrtPriceX64".to_string(),
                    serde_json::Value::String(sqrt_price_x64.to_string()),
                );
            }
            if let Some(open_time) = read_u64_le_from_bytes(data, 24) {
                object.insert(
                    "openTime".to_string(),
                    serde_json::Value::String(open_time.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::ClmmFeePair => {
            if let Some(amount_0) = read_u64_le_from_bytes(data, 8) {
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
                object.insert(
                    "amount0RequestedRaw".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
            }
            if let Some(amount_1) = read_u64_le_from_bytes(data, 16) {
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
                object.insert(
                    "amount1RequestedRaw".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::ClmmLiquidityV2 => {
            if let Some(liquidity) = read_u128_le_from_bytes(data, 8) {
                object.insert(
                    "liquidity".to_string(),
                    serde_json::Value::String(liquidity.to_string()),
                );
                object.insert(
                    "lpAmountRaw".to_string(),
                    serde_json::Value::String(liquidity.to_string()),
                );
            }
            if let Some(amount_0) = read_u64_le_from_bytes(data, 24) {
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
            }
            if let Some(amount_1) = read_u64_le_from_bytes(data, 32) {
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::ClmmOpenLimitOrder => {
            if let Some(nonce_index) = read_u8_from_bytes(data, 8) {
                object.insert(
                    "nonceIndex".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(nonce_index as u64)),
                );
            }
            if let Some(zero_for_one) = read_u8_from_bytes(data, 9) {
                object.insert("zeroForOne".to_string(), serde_json::Value::Bool(zero_for_one != 0));
            }
            if let Some(tick_index) = read_i32_le_from_bytes(data, 10) {
                object.insert(
                    "tickIndex".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(tick_index as i64)),
                );
            }
            if let Some(amount) = read_u64_le_from_bytes(data, 14) {
                object
                    .insert("amountRaw".to_string(), serde_json::Value::String(amount.to_string()));
                object.insert(
                    "orderAmountRaw".to_string(),
                    serde_json::Value::String(amount.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::ClmmIncreaseLimitOrder => {
            if let Some(amount) = read_u64_le_from_bytes(data, 8) {
                object
                    .insert("amountRaw".to_string(), serde_json::Value::String(amount.to_string()));
                object.insert(
                    "increasedAmountRaw".to_string(),
                    serde_json::Value::String(amount.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::ClmmDecreaseLimitOrder => {
            if let Some(amount) = read_u64_le_from_bytes(data, 8) {
                object
                    .insert("amountRaw".to_string(), serde_json::Value::String(amount.to_string()));
                object.insert(
                    "decreasedAmountRaw".to_string(),
                    serde_json::Value::String(amount.to_string()),
                );
            }
            if let Some(amount_min) = read_u64_le_from_bytes(data, 16) {
                object.insert(
                    "amountMinRaw".to_string(),
                    serde_json::Value::String(amount_min.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::CpmmAmmConfig => {
            if let Some(param) = read_u8_from_bytes(data, 8) {
                object.insert(
                    "configParam".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(param as u64)),
                );
            }
            if let Some(value) = read_u64_le_from_bytes(data, 9) {
                object.insert(
                    "configValue".to_string(),
                    serde_json::Value::String(value.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::CpmmDeposit => {
            if let Some(lp_amount) = read_u64_le_from_bytes(data, 8) {
                object.insert(
                    "lpAmountRaw".to_string(),
                    serde_json::Value::String(lp_amount.to_string()),
                );
                object.insert(
                    "liquidity".to_string(),
                    serde_json::Value::String(lp_amount.to_string()),
                );
            }
            if let Some(amount_0) = read_u64_le_from_bytes(data, 16) {
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
            }
            if let Some(amount_1) = read_u64_le_from_bytes(data, 24) {
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::CpmmFeePair => {
            if let Some(amount_0) = read_u64_le_from_bytes(data, 8) {
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
                object.insert(
                    "amount0RequestedRaw".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
            }
            if let Some(amount_1) = read_u64_le_from_bytes(data, 16) {
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
                object.insert(
                    "amount1RequestedRaw".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::CpmmInitialize => {
            if let Some(amount_0) = read_u64_le_from_bytes(data, 8) {
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
            }
            if let Some(amount_1) = read_u64_le_from_bytes(data, 16) {
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
            }
            if let Some(open_time) = read_u64_le_from_bytes(data, 24) {
                object.insert(
                    "openTime".to_string(),
                    serde_json::Value::String(open_time.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::CpmmPoolStatus => {
            if let Some(status) = read_u8_from_bytes(data, 8) {
                object.insert(
                    "poolStatus".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(status as u64)),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4Initialize => {
            if let Some(nonce) = read_u8_from_bytes(data, 1) {
                object.insert(
                    "nonce".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(nonce as u64)),
                );
            }
            if let Some(open_time) = read_u64_le_from_bytes(data, 2) {
                object.insert(
                    "openTime".to_string(),
                    serde_json::Value::String(open_time.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4Initialize2 => {
            if let Some(nonce) = read_u8_from_bytes(data, 1) {
                object.insert(
                    "nonce".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(nonce as u64)),
                );
            }
            if let Some(open_time) = read_u64_le_from_bytes(data, 2) {
                object.insert(
                    "openTime".to_string(),
                    serde_json::Value::String(open_time.to_string()),
                );
            }
            if let Some(init_pc_amount) = read_u64_le_from_bytes(data, 10) {
                object.insert(
                    "initPcAmount".to_string(),
                    serde_json::Value::String(init_pc_amount.to_string()),
                );
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(init_pc_amount.to_string()),
                );
            }
            if let Some(init_coin_amount) = read_u64_le_from_bytes(data, 18) {
                object.insert(
                    "initCoinAmount".to_string(),
                    serde_json::Value::String(init_coin_amount.to_string()),
                );
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(init_coin_amount.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4MonitorStep => {
            if let Some(plan_order_limit) = read_u16_le_from_bytes(data, 1) {
                object.insert(
                    "planOrderLimit".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(plan_order_limit as u64)),
                );
            }
            if let Some(place_order_limit) = read_u16_le_from_bytes(data, 3) {
                object.insert(
                    "placeOrderLimit".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(place_order_limit as u64)),
                );
            }
            if let Some(cancel_order_limit) = read_u16_le_from_bytes(data, 5) {
                object.insert(
                    "cancelOrderLimit".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(cancel_order_limit as u64)),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4Deposit => {
            if let Some(max_coin_amount) = read_u64_le_from_bytes(data, 1) {
                object.insert(
                    "maxCoinAmount".to_string(),
                    serde_json::Value::String(max_coin_amount.to_string()),
                );
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(max_coin_amount.to_string()),
                );
            }
            if let Some(max_pc_amount) = read_u64_le_from_bytes(data, 9) {
                object.insert(
                    "maxPcAmount".to_string(),
                    serde_json::Value::String(max_pc_amount.to_string()),
                );
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(max_pc_amount.to_string()),
                );
            }
            if let Some(base_side) = read_u64_le_from_bytes(data, 17) {
                object.insert(
                    "baseSide".to_string(),
                    serde_json::Value::String(base_side.to_string()),
                );
            }
            if let Some(other_amount_min) = read_u64_le_from_bytes(data, 25) {
                object.insert(
                    "otherAmountMin".to_string(),
                    serde_json::Value::String(other_amount_min.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4Withdraw => {
            if let Some(lp_amount) = read_u64_le_from_bytes(data, 1) {
                object.insert(
                    "lpAmountRaw".to_string(),
                    serde_json::Value::String(lp_amount.to_string()),
                );
                object.insert(
                    "liquidity".to_string(),
                    serde_json::Value::String(lp_amount.to_string()),
                );
            }
            if let Some(min_coin_amount) = read_u64_le_from_bytes(data, 9) {
                object.insert(
                    "minCoinAmount".to_string(),
                    serde_json::Value::String(min_coin_amount.to_string()),
                );
            }
            if let Some(min_pc_amount) = read_u64_le_from_bytes(data, 17) {
                object.insert(
                    "minPcAmount".to_string(),
                    serde_json::Value::String(min_pc_amount.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4SetParams => {
            if let Some(param) = read_u8_from_bytes(data, 1) {
                object.insert(
                    "configParam".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(param as u64)),
                );
            }
            if let Some(value) = read_u64_le_from_bytes(data, 2) {
                object.insert(
                    "configValue".to_string(),
                    serde_json::Value::String(value.to_string()),
                );
            }
            if let Some(last_order_denominator) = read_u64_le_from_bytes(data, 10) {
                object.insert(
                    "lastOrderDenominator".to_string(),
                    serde_json::Value::String(last_order_denominator.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4WithdrawSrm => {
            if let Some(amount) = read_u64_le_from_bytes(data, 1) {
                object
                    .insert("amountRaw".to_string(), serde_json::Value::String(amount.to_string()));
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4PreInitialize => {
            object.insert("deprecatedInstruction".to_string(), serde_json::Value::Bool(true));
            object.insert("partialLifecycle".to_string(), serde_json::Value::Bool(true));
            object.insert(
                "skipCatalogReason".to_string(),
                serde_json::Value::String("missing_token_mints".to_string()),
            );
            if let Some(nonce) = read_u8_from_bytes(data, 1) {
                object.insert(
                    "nonce".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(nonce as u64)),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4SimulateInfo => {
            if let Some(param) = read_u8_from_bytes(data, 1) {
                object.insert(
                    "simulateParam".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(param as u64)),
                );
            }
            if let Some(amount_in) = read_u64_le_from_bytes(data, 2) {
                object.insert(
                    "amountIn".to_string(),
                    serde_json::Value::String(amount_in.to_string()),
                );
            }
            if let Some(amount_out) = read_u64_le_from_bytes(data, 10) {
                object.insert(
                    "amountOutOrMinimumAmountOut".to_string(),
                    serde_json::Value::String(amount_out.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4AdminCancelOrders => {
            if let Some(limit) = read_u16_le_from_bytes(data, 1) {
                object.insert(
                    "orderCancelLimit".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(limit as u64)),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::AmmV4UpdateConfigAccount => {
            if let Some(param) = read_u8_from_bytes(data, 1) {
                object.insert(
                    "configParam".to_string(),
                    serde_json::Value::Number(serde_json::Number::from(param as u64)),
                );
            }
            if let Some(create_pool_fee) = read_u64_le_from_bytes(data, 2) {
                object.insert(
                    "createPoolFee".to_string(),
                    serde_json::Value::String(create_pool_fee.to_string()),
                );
            }
        },
        RaydiumMappedNonTradeAmountLayout::LaunchpadInitialize => {
            object.insert(
                "poolKindHint".to_string(),
                serde_json::Value::String("bonding_curve".to_string()),
            );
            object.insert(
                "poolStatusHint".to_string(),
                serde_json::Value::String("pending".to_string()),
            );
        },
        RaydiumMappedNonTradeAmountLayout::CpmmWithdraw => {
            if let Some(lp_amount) = read_u64_le_from_bytes(data, 8) {
                object.insert(
                    "lpAmountRaw".to_string(),
                    serde_json::Value::String(lp_amount.to_string()),
                );
                object.insert(
                    "liquidity".to_string(),
                    serde_json::Value::String(lp_amount.to_string()),
                );
            }
            if let Some(amount_0) = read_u64_le_from_bytes(data, 16) {
                object.insert(
                    "tokenAAmount".to_string(),
                    serde_json::Value::String(amount_0.to_string()),
                );
            }
            if let Some(amount_1) = read_u64_le_from_bytes(data, 24) {
                object.insert(
                    "tokenBAmount".to_string(),
                    serde_json::Value::String(amount_1.to_string()),
                );
            }
        },
    }
}

fn instruction_data_bytes_from_base58(
    data_base58: std::option::Option<&str>,
) -> std::option::Option<std::vec::Vec<u8>> {
    let data_base58 = match data_base58 {
        Some(data_base58) => data_base58,
        None => return None,
    };
    let bytes_result = bs58::decode(data_base58).into_vec();
    match bytes_result {
        Ok(bytes) => return Some(bytes),
        Err(_) => return None,
    }
}

fn read_u8_from_bytes(data: &[u8], offset: usize) -> std::option::Option<u8> {
    if data.len() < offset + 1 {
        return None;
    }
    return Some(data[offset]);
}

fn read_u16_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option<u16> {
    if data.len() < offset + 2 {
        return None;
    }
    let mut bytes = [0_u8; 2];
    let mut index = 0_usize;
    while index < 2 {
        bytes[index] = data[offset + index];
        index += 1;
    }
    return Some(u16::from_le_bytes(bytes));
}

fn read_i32_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option<i32> {
    if data.len() < offset + 4 {
        return None;
    }
    let mut bytes = [0_u8; 4];
    let mut index = 0_usize;
    while index < 4 {
        bytes[index] = data[offset + index];
        index += 1;
    }
    return Some(i32::from_le_bytes(bytes));
}

fn read_pubkey_base58_from_bytes(
    data: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    let data = match data {
        Some(data) => data,
        None => return None,
    };
    let end = offset.checked_add(32_usize);
    let end = match end {
        Some(end) => end,
        None => return None,
    };
    if data.len() < end {
        return None;
    }
    return Some(bs58::encode(&data[offset..end]).into_string());
}

fn read_u64_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option<u64> {
    if data.len() < offset + 8 {
        return None;
    }
    let mut bytes = [0_u8; 8];
    let mut index = 0_usize;
    while index < 8 {
        bytes[index] = data[offset + index];
        index += 1;
    }
    return Some(u64::from_le_bytes(bytes));
}

fn read_u128_le_from_bytes(data: &[u8], offset: usize) -> std::option::Option<u128> {
    if data.len() < offset + 16 {
        return None;
    }
    let mut bytes = [0_u8; 16];
    let mut index = 0_usize;
    while index < 16 {
        bytes[index] = data[offset + index];
        index += 1;
    }
    return Some(u128::from_le_bytes(bytes));
}

fn meteora_instruction_audit_spec(
    program_id: &str,
) -> std::option::Option<MeteoraInstructionAuditSpec> {
    if program_id == crate::METEORA_DBC_PROGRAM_ID {
        return Some(MeteoraInstructionAuditSpec {
            protocol_name: "meteora_dbc",
            event_kind: "meteora_dbc.instruction_audit",
            candidate_pool_account_index: Some(1),
        });
    }
    if program_id == crate::METEORA_DLMM_PROGRAM_ID {
        return Some(MeteoraInstructionAuditSpec {
            protocol_name: "meteora_dlmm",
            event_kind: "meteora_dlmm.instruction_audit",
            candidate_pool_account_index: Some(0),
        });
    }
    if program_id == crate::METEORA_DAMM_V1_PROGRAM_ID {
        return Some(MeteoraInstructionAuditSpec {
            protocol_name: "meteora_damm_v1",
            event_kind: "meteora_damm_v1.instruction_audit",
            candidate_pool_account_index: Some(0),
        });
    }
    if program_id == crate::METEORA_DAMM_V2_PROGRAM_ID {
        return Some(MeteoraInstructionAuditSpec {
            protocol_name: "meteora_damm_v2",
            event_kind: "meteora_damm_v2.instruction_audit",
            candidate_pool_account_index: Some(1),
        });
    }
    return None;
}

fn candidate_meteora_audit_pool_account(
    audit_spec: MeteoraInstructionAuditSpec,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let index = match audit_spec.candidate_pool_account_index {
        Some(index) => index,
        None => return None,
    };
    return accounts.get(index).cloned();
}

fn is_meteora_dlmm_instruction_swap_event_kind(event_kind: &str) -> bool {
    return matches!(
        event_kind,
        "meteora_dlmm.swap"
            | "meteora_dlmm.swap2"
            | "meteora_dlmm.swap_exact_out"
            | "meteora_dlmm.swap_exact_out2"
            | "meteora_dlmm.swap_with_price_impact"
            | "meteora_dlmm.swap_with_price_impact2"
    );
}

fn is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
    protocol_name: &str,
    instruction: &crate::ChainInstructionDto,
    decoded_events: &[crate::DexDecodedEventDto],
) -> bool {
    if protocol_name != "meteora_dlmm" {
        return false;
    }
    let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
    let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref());
    let selector_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 0);
    if selector_hex.as_deref() != Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) {
        return false;
    }
    let event_discriminator_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 8);
    match event_discriminator_hex.as_deref() {
        Some("516ce3becdd00ac4") | Some("2e7452d7941b544d") => {},
        _ => return false,
    }
    for decoded_event in decoded_events {
        if decoded_event.protocol_name == "meteora_dlmm"
            && is_meteora_dlmm_instruction_swap_event_kind(decoded_event.event_kind.as_str())
        {
            return true;
        }
    }
    return false;
}

fn build_meteora_instruction_audit_payload(
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    protocol_name: &str,
    event_kind: &str,
    program_id: &str,
) -> serde_json::Value {
    let accounts = parse_instruction_accounts_value(instruction.accounts_json.as_str());
    let account_count = match accounts.as_array() {
        Some(items) => items.len(),
        None => 0,
    };
    let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
    let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref());
    let discriminator_hex =
        raydium_instruction_discriminator_hex(protocol_name, data_bytes.as_deref(), 0);
    let anchor_self_cpi_log =
        discriminator_hex.as_deref() == Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX);
    let anchor_event_discriminator_hex = if anchor_self_cpi_log {
        discriminator_hex_from_bytes(data_bytes.as_deref(), 8)
    } else {
        None
    };
    let anchor_event_payload_size = if anchor_self_cpi_log {
        match data_bytes.as_ref() {
            Some(data_bytes) => data_bytes.len().checked_sub(8),
            None => None,
        }
    } else {
        None
    };
    let data_prefix = data_base58
        .as_ref()
        .map(|value| return value.chars().take(16).collect::<std::string::String>());
    let audit_reason = if anchor_self_cpi_log {
        "meteora_anchor_self_cpi_log_not_decoded_by_specific_event_decoder"
    } else {
        "meteora_instruction_not_decoded_by_specific_decoder"
    };
    let proof_status = if anchor_self_cpi_log {
        "observed_local_corpus_anchor_self_cpi_log"
    } else {
        "unclassified_local_corpus_instruction"
    };
    return serde_json::json!({
        "decoder": protocol_name,
        "eventKind": event_kind,
        "signature": transaction.signature.clone(),
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "parentInstructionId": instruction.parent_instruction_id,
        "programId": program_id,
        "programFamily": "meteora",
        "accounts": accounts,
        "accountCount": account_count,
        "data": data_base58,
        "dataPrefix": data_prefix,
        "discriminatorHex": discriminator_hex,
        "anchorSelfCpiLog": anchor_self_cpi_log,
        "anchorSelfCpiLogSelectorHex": if anchor_self_cpi_log { Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) } else { None },
        "anchorEventDiscriminatorHex": anchor_event_discriminator_hex,
        "anchorEventPayloadSize": anchor_event_payload_size,
        "auditReason": audit_reason,
        "proofStatus": proof_status,
        "tradeCandidate": false,
        "candleCandidate": false,
        "nonTradeUseful": false,
        "skipTradeReason": "instruction_audit_only",
        "skipCandleReason": "instruction_audit_only"
    });
}

fn instruction_discriminator_hex_from_payload(
    payload_json: &serde_json::Value,
) -> std::option::Option<std::string::String> {
    let candidates = [
        "instructionDiscriminatorHex",
        "instruction_discriminator_hex",
        "discriminatorHex",
        "discriminator_hex",
        "anchorEventDiscriminatorHex",
        "anchor_event_discriminator_hex",
    ];
    for candidate in candidates {
        let value = payload_json.get(candidate).and_then(serde_json::Value::as_str);
        let value = match value {
            Some(value) => value.trim(),
            None => continue,
        };
        if !value.is_empty() {
            return Some(value.to_string());
        }
    }
    return None;
}

fn instruction_discriminator_hex_from_payload_str(
    payload_json: &str,
) -> std::option::Option<std::string::String> {
    let parsed = serde_json::from_str::<serde_json::Value>(payload_json);
    let parsed = match parsed {
        Ok(parsed) => parsed,
        Err(_) => return None,
    };
    return instruction_discriminator_hex_from_payload(&parsed);
}

fn raydium_decoded_discriminator_key(
    protocol_name: &str,
    discriminator_hex: &str,
) -> std::string::String {
    return format!("{}:{}", protocol_name, discriminator_hex);
}

fn raydium_instruction_already_decoded_by_discriminator(
    decoded_discriminator_keys: &std::collections::HashSet<std::string::String>,
    protocol_name: &str,
    discriminator_hex: std::option::Option<&str>,
) -> bool {
    let discriminator_hex = match discriminator_hex {
        Some(discriminator_hex) => discriminator_hex,
        None => return false,
    };
    let key = raydium_decoded_discriminator_key(protocol_name, discriminator_hex);
    return decoded_discriminator_keys.contains(&key);
}

fn raydium_mapped_event_kind_already_decoded(
    decoded_events: &[crate::DexDecodedEventDto],
    protocol_name: &str,
    event_kind: &str,
) -> bool {
    for decoded_event in decoded_events {
        if decoded_event.protocol_name != protocol_name {
            continue;
        }
        if decoded_event.event_kind == event_kind {
            return true;
        }
    }
    return false;
}

fn should_immediately_materialize_decoded_non_trade_event(event_kind: &str) -> bool {
    if event_kind == "raydium_clmm.create_pool" {
        return true;
    }
    if event_kind == "raydium_clmm.collect_protocol_fee" {
        return true;
    }
    return false;
}

fn dex_decode_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 dex_decode_payload_value(payload_json: &str) -> serde_json::Value {
    let parsed = serde_json::from_str::<serde_json::Value>(payload_json);
    match parsed {
        Ok(parsed) => return parsed,
        Err(_) => return serde_json::Value::Object(serde_json::Map::new()),
    }
}

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

fn pump_fun_enrich_trade_events_with_instruction_context(
    decoded_events: std::vec::Vec<crate::PumpFunDecodedEvent>,
) -> std::vec::Vec<crate::PumpFunDecodedEvent> {
    let mut enriched_events = std::vec::Vec::new();
    for decoded_event in &decoded_events {
        let enriched_event = match decoded_event {
            crate::PumpFunDecodedEvent::InstructionAudit(event) => {
                if event.event_kind.as_str() == "pump_fun.trade_event" {
                    let mut enriched_event = event.clone();
                    pump_fun_merge_matching_instruction_context_into_trade_event(
                        &decoded_events,
                        &mut enriched_event,
                    );
                    pump_fun_mark_trade_event_duplicate_when_direct_instruction_exists(
                        &decoded_events,
                        &mut enriched_event,
                    );
                    crate::PumpFunDecodedEvent::InstructionAudit(enriched_event)
                } else {
                    decoded_event.clone()
                }
            },
            _ => decoded_event.clone(),
        };
        enriched_events.push(enriched_event);
    }
    return enriched_events;
}

fn pump_fun_merge_matching_instruction_context_into_trade_event(
    decoded_events: &[crate::PumpFunDecodedEvent],
    trade_event: &mut crate::PumpFunInstructionAuditDecoded,
) {
    let trade_payload = trade_event.payload_json.clone();
    let trade_instruction_id =
        dex_decode_extract_first_i64(&trade_payload, &["instructionId", "instruction_id"]);
    let trade_mint =
        dex_decode_extract_first_string(&trade_payload, &["mint", "tokenMint", "tokenAMint"]);
    let trade_actor =
        dex_decode_extract_first_string(&trade_payload, &["user", "actorWallet", "userWallet"]);
    for sibling in decoded_events {
        let sibling_event = match sibling {
            crate::PumpFunDecodedEvent::InstructionAudit(sibling_event) => sibling_event,
            _ => continue,
        };
        if sibling_event.event_kind.as_str() == "pump_fun.trade_event" {
            continue;
        }
        if !pump_fun_instruction_context_can_back_trade_event(sibling_event.event_kind.as_str()) {
            continue;
        }
        let sibling_instruction_id = Some(sibling_event.instruction_id);
        if trade_instruction_id.is_some()
            && sibling_instruction_id.is_some()
            && trade_instruction_id != sibling_instruction_id
        {
            continue;
        }
        let sibling_mint = dex_decode_extract_first_string(
            &sibling_event.payload_json,
            &["mint", "tokenMint", "tokenAMint"],
        );
        if !dex_decode_optional_strings_match(trade_mint.as_deref(), sibling_mint.as_deref()) {
            continue;
        }
        let sibling_actor = dex_decode_extract_first_string(
            &sibling_event.payload_json,
            &["user", "actorWallet", "userWallet"],
        );
        if !dex_decode_optional_strings_match(trade_actor.as_deref(), sibling_actor.as_deref()) {
            continue;
        }
        pump_fun_merge_instruction_context_payload(
            &sibling_event.payload_json,
            &mut trade_event.payload_json,
        );
        return;
    }
}

fn pump_fun_instruction_context_can_back_trade_event(event_kind: &str) -> bool {
    match event_kind {
        "pump_fun.buy_exact_quote_in_v2" => return true,
        "pump_fun.buy_v2" => return true,
        "pump_fun.sell_v2" => return true,
        "pump_fun.buy_exact_sol_in" => return true,
        _ => return false,
    }
}

fn pump_fun_mark_trade_event_duplicate_when_direct_instruction_exists(
    decoded_events: &[crate::PumpFunDecodedEvent],
    trade_event: &mut crate::PumpFunInstructionAuditDecoded,
) {
    let trade_payload = trade_event.payload_json.clone();
    let trade_instruction_id =
        dex_decode_extract_first_i64(&trade_payload, &["instructionId", "instruction_id"]);
    let trade_mint =
        dex_decode_extract_first_string(&trade_payload, &["mint", "tokenMint", "tokenAMint"]);
    let trade_actor =
        dex_decode_extract_first_string(&trade_payload, &["user", "actorWallet", "userWallet"]);
    for sibling in decoded_events {
        let direct_match = match sibling {
            crate::PumpFunDecodedEvent::BuyTrade(event) => {
                pump_fun_direct_trade_matches_anchor_trade_event(
                    event.instruction_id,
                    event.mint.as_deref(),
                    event.user.as_deref(),
                    trade_instruction_id,
                    trade_mint.as_deref(),
                    trade_actor.as_deref(),
                )
            },
            crate::PumpFunDecodedEvent::SellTrade(event) => {
                pump_fun_direct_trade_matches_anchor_trade_event(
                    event.instruction_id,
                    event.mint.as_deref(),
                    event.user.as_deref(),
                    trade_instruction_id,
                    trade_mint.as_deref(),
                    trade_actor.as_deref(),
                )
            },
            crate::PumpFunDecodedEvent::InstructionAudit(event) => {
                if event.event_kind.as_str() != "pump_fun.buy_exact_sol_in" {
                    false
                } else {
                    let instruction_mint = dex_decode_extract_first_string(
                        &event.payload_json,
                        &["mint", "tokenMint", "tokenAMint"],
                    );
                    let instruction_actor = dex_decode_extract_first_string(
                        &event.payload_json,
                        &["user", "actorWallet", "userWallet"],
                    );
                    pump_fun_direct_trade_matches_anchor_trade_event(
                        event.instruction_id,
                        instruction_mint.as_deref(),
                        instruction_actor.as_deref(),
                        trade_instruction_id,
                        trade_mint.as_deref(),
                        trade_actor.as_deref(),
                    )
                }
            },
            _ => false,
        };
        if !direct_match {
            continue;
        }
        let object = match trade_event.payload_json.as_object_mut() {
            Some(object) => object,
            None => return,
        };
        object.insert(
            "skipTradeReason".to_string(),
            serde_json::Value::String(
                "pump_fun_trade_event_covered_by_direct_instruction_trade".to_string(),
            ),
        );
        object.insert(
            "skipCandleReason".to_string(),
            serde_json::Value::String(
                "pump_fun_trade_event_covered_by_direct_instruction_trade".to_string(),
            ),
        );
        object.insert(
            "anchorTradeEventCoveredByDirectInstructionTrade".to_string(),
            serde_json::Value::Bool(true),
        );
        return;
    }
}

fn pump_fun_direct_trade_matches_anchor_trade_event(
    direct_instruction_id: i64,
    direct_mint: std::option::Option<&str>,
    direct_actor: std::option::Option<&str>,
    trade_instruction_id: std::option::Option<i64>,
    trade_mint: std::option::Option<&str>,
    trade_actor: std::option::Option<&str>,
) -> bool {
    if let Some(trade_instruction_id) = trade_instruction_id {
        if direct_instruction_id != trade_instruction_id {
            return false;
        }
    }
    if !dex_decode_optional_strings_match(trade_mint, direct_mint) {
        return false;
    }
    if !dex_decode_optional_strings_match(trade_actor, direct_actor) {
        return false;
    }
    return true;
}

fn pump_fun_merge_instruction_context_payload(
    instruction_payload: &serde_json::Value,
    trade_payload: &mut serde_json::Value,
) {
    let trade_object = match trade_payload.as_object_mut() {
        Some(trade_object) => trade_object,
        None => return,
    };
    let instruction_object = match instruction_payload.as_object() {
        Some(instruction_object) => instruction_object,
        None => return,
    };
    let copy_keys = [
        "poolAccount",
        "bondingCurve",
        "associatedBondingCurve",
        "poolBaseTokenAccount",
        "poolQuoteTokenAccount",
        "associatedQuoteBondingCurve",
        "lpMint",
        "tokenAMint",
        "tokenBMint",
        "quoteMint",
        "feeRecipient",
        "creatorVault",
        "associatedCreatorVault",
    ];
    for key in copy_keys {
        if trade_object.contains_key(key) {
            continue;
        }
        let value = match instruction_object.get(key) {
            Some(value) => value.clone(),
            None => continue,
        };
        trade_object.insert(key.to_string(), value);
    }
    trade_object.insert(
        "amountSource".to_string(),
        serde_json::Value::String("pump_fun_anchor_trade_event".to_string()),
    );
    trade_object.insert(
        "anchorTradeEventInstructionContextSource".to_string(),
        serde_json::Value::String("matching_instruction_audit".to_string()),
    );
    trade_object.remove("skipTradeReason");
    trade_object.remove("skipCandleReason");
    trade_object.remove("skipCatalogReason");
}

fn dex_decode_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 = match object.get(*candidate_key) {
                Some(candidate_value) => candidate_value,
                None => continue,
            };
            if let Some(number) = candidate_value.as_i64() {
                return Some(number);
            }
            if let Some(text) = candidate_value.as_str() {
                let parsed = text.parse::<i64>();
                match parsed {
                    Ok(parsed) => return Some(parsed),
                    Err(_) => {},
                }
            }
        }
    }
    return None;
}

fn dex_decode_optional_strings_match(
    left: std::option::Option<&str>,
    right: std::option::Option<&str>,
) -> bool {
    match (left, right) {
        (Some(left), Some(right)) => return left == right,
        _ => return true,
    }
}

fn pump_fun_decoded_event_is_trade_event(decoded_event: &crate::PumpFunDecodedEvent) -> bool {
    match decoded_event {
        crate::PumpFunDecodedEvent::InstructionAudit(event) => {
            return event.event_kind.as_str() == "pump_fun.trade_event";
        },
        _ => return false,
    }
}

fn dex_decode_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 candidate_value = object.get(*candidate_key);
            let candidate_value = match candidate_value {
                Some(candidate_value) => candidate_value,
                None => continue,
            };
            if let Some(text) = candidate_value.as_str() {
                let trimmed = text.trim();
                if !trimmed.is_empty() {
                    return Some(trimmed.to_string());
                }
            }
        }
        for nested_value in object.values() {
            let nested = dex_decode_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 = dex_decode_extract_first_string(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

fn dex_decode_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 candidate_value = object.get(*candidate_key);
            let candidate_value = match candidate_value {
                Some(candidate_value) => candidate_value,
                None => continue,
            };
            if let Some(number) = candidate_value.as_i64() {
                return Some(number.to_string());
            }
            if let Some(number) = candidate_value.as_u64() {
                return Some(number.to_string());
            }
            if let Some(number) = candidate_value.as_f64() {
                return Some(number.to_string());
            }
        }
        for nested_value in object.values() {
            let nested = dex_decode_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 = dex_decode_extract_first_number_as_string(nested_value, candidate_keys);
            if nested.is_some() {
                return nested;
            }
        }
    }
    return None;
}

fn instruction_audit_event_kind_by_protocol(
    protocol_name: &str,
) -> std::option::Option<&'static str> {
    match protocol_name {
        "raydium_amm_v4" => return Some("raydium_amm_v4.instruction_audit"),
        "raydium_clmm" => return Some("raydium_clmm.instruction_audit"),
        "raydium_cpmm" => return Some("raydium_cpmm.instruction_audit"),
        "raydium_stable_swap" => return Some("raydium_stable_swap.instruction_audit"),
        "raydium_launchpad" => return Some("raydium_launchpad.instruction_audit"),
        "meteora_dlmm" => return Some("meteora_dlmm.instruction_audit"),
        "meteora_damm_v1" => return Some("meteora_damm_v1.instruction_audit"),
        "meteora_damm_v2" => return Some("meteora_damm_v2.instruction_audit"),
        "meteora_dbc" => return Some("meteora_dbc.instruction_audit"),
        _ => return None,
    }
}

#[derive(Clone, Copy)]
struct RaydiumAnchorSelfCpiEventSpec {
    entry_name: &'static str,
    event_kind: &'static str,
    event_family: &'static str,
    discriminator_hex: &'static str,
}

fn raydium_launchpad_anchor_self_cpi_event_spec(
    protocol_name: &str,
    data_bytes: std::option::Option<&[u8]>,
) -> std::option::Option<RaydiumAnchorSelfCpiEventSpec> {
    if protocol_name != "raydium_launchpad" {
        return None;
    }
    let selector_hex = discriminator_hex_from_bytes(data_bytes, 0);
    if selector_hex.as_deref() != Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) {
        return None;
    }
    let event_discriminator_hex = discriminator_hex_from_bytes(data_bytes, 8);
    let event_discriminator_hex = match event_discriminator_hex.as_deref() {
        Some(event_discriminator_hex) => event_discriminator_hex,
        None => return None,
    };
    match event_discriminator_hex {
        "bddb7fd34ee661ee" => {
            return Some(RaydiumAnchorSelfCpiEventSpec {
                entry_name: "trade_event",
                event_kind: "raydium_launchpad.trade_event",
                event_family: "swap",
                discriminator_hex: "bddb7fd34ee661ee",
            });
        },
        "97d7e20976a173ae" => {
            return Some(RaydiumAnchorSelfCpiEventSpec {
                entry_name: "pool_create_event",
                event_kind: "raydium_launchpad.pool_create_event",
                event_family: "pool_create",
                discriminator_hex: "97d7e20976a173ae",
            });
        },
        "15c2725778d3e220" => {
            return Some(RaydiumAnchorSelfCpiEventSpec {
                entry_name: "claim_vested_event",
                event_kind: "raydium_launchpad.claim_vested_event",
                event_family: "vesting",
                discriminator_hex: "15c2725778d3e220",
            });
        },
        "96980bb334d2bf7d" => {
            return Some(RaydiumAnchorSelfCpiEventSpec {
                entry_name: "create_vesting_event",
                event_kind: "raydium_launchpad.create_vesting_event",
                event_family: "vesting",
                discriminator_hex: "96980bb334d2bf7d",
            });
        },
        _ => return None,
    }
}

fn raydium_launchpad_anchor_self_cpi_pool_account(
    spec: RaydiumAnchorSelfCpiEventSpec,
    data_bytes: std::option::Option<&[u8]>,
) -> std::option::Option<std::string::String> {
    match spec.entry_name {
        "trade_event" => return read_pubkey_base58_from_bytes(data_bytes, 16),
        "pool_create_event" => return read_pubkey_base58_from_bytes(data_bytes, 16),
        _ => return None,
    }
}

fn enrich_raydium_launchpad_anchor_self_cpi_payload(
    payload: serde_json::Value,
    spec: RaydiumAnchorSelfCpiEventSpec,
    data_bytes: std::option::Option<&[u8]>,
) -> serde_json::Value {
    let mut object = match payload {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    object.insert(
        "entryKind".to_string(),
        serde_json::Value::String(crate::ENTRY_KIND_EVENT.to_string()),
    );
    object.insert("entryName".to_string(), serde_json::Value::String(spec.entry_name.to_string()));
    object.insert(
        "eventFamily".to_string(),
        serde_json::Value::String(spec.event_family.to_string()),
    );
    object.insert("eventName".to_string(), serde_json::Value::String(spec.entry_name.to_string()));
    object.insert(
        "upstreamEventName".to_string(),
        serde_json::Value::String(spec.entry_name.to_string()),
    );
    object.insert(
        "upstreamEntryName".to_string(),
        serde_json::Value::String(spec.entry_name.to_string()),
    );
    object.insert(
        "upstreamEntryKind".to_string(),
        serde_json::Value::String(crate::ENTRY_KIND_EVENT.to_string()),
    );
    object.insert(
        "upstreamDiscriminatorHex".to_string(),
        serde_json::Value::String(spec.discriminator_hex.to_string()),
    );
    object.insert("localSpecializedDecoder".to_string(), serde_json::Value::Bool(true));
    object.insert("decodedFromAnchorSelfCpiLog".to_string(), serde_json::Value::Bool(true));
    object.insert("decodedFromAudit".to_string(), serde_json::Value::Bool(false));
    object.insert(
        "auditReason".to_string(),
        serde_json::Value::String("raydium_launchpad_anchor_self_cpi_event_decoded".to_string()),
    );
    object.insert(
        "proofSource".to_string(),
        serde_json::Value::String(
            "local_corpus_anchor_self_cpi_and_raydium_launchpad_event_discriminator".to_string(),
        ),
    );
    if let Some(data_bytes) = data_bytes {
        object.insert(
            "anchorSelfCpiDataLength".to_string(),
            serde_json::Value::Number(serde_json::Number::from(data_bytes.len() as u64)),
        );
        if data_bytes.len() >= 16 {
            object.insert(
                "anchorEventPayloadSize".to_string(),
                serde_json::Value::Number(serde_json::Number::from((data_bytes.len() - 16) as u64)),
            );
        }
    }
    if let Some(pool_state) = raydium_launchpad_anchor_self_cpi_pool_account(spec, data_bytes) {
        object.insert("poolState".to_string(), serde_json::Value::String(pool_state.clone()));
        object.insert("poolAccount".to_string(), serde_json::Value::String(pool_state));
    }
    if spec.entry_name == "trade_event" {
        insert_raydium_launchpad_trade_event_amounts(&mut object, data_bytes);
        object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(true));
        object.insert("candleCandidate".to_string(), serde_json::Value::Bool(true));
        object.insert("nonTradeUseful".to_string(), serde_json::Value::Bool(false));
        object.remove("skipTradeReason");
        object.remove("skipCandleReason");
    } else if spec.entry_name == "pool_create_event" {
        object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false));
        object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false));
        object.insert("nonTradeUseful".to_string(), serde_json::Value::Bool(true));
        object.insert(
            "skipTradeReason".to_string(),
            serde_json::Value::String("pool_lifecycle_event".to_string()),
        );
        object.insert(
            "skipCandleReason".to_string(),
            serde_json::Value::String("pool_lifecycle_event".to_string()),
        );
    } else {
        object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false));
        object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false));
        object.insert("nonTradeUseful".to_string(), serde_json::Value::Bool(false));
        object.insert(
            "skipTradeReason".to_string(),
            serde_json::Value::String("launchpad_event_audit_only".to_string()),
        );
        object.insert(
            "skipCandleReason".to_string(),
            serde_json::Value::String("launchpad_event_audit_only".to_string()),
        );
    }
    return serde_json::Value::Object(object);
}

fn insert_raydium_launchpad_trade_event_amounts(
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
    data_bytes: std::option::Option<&[u8]>,
) {
    let data = match data_bytes {
        Some(data) => data,
        None => return,
    };
    let fields = [
        ("totalBaseSellRaw", 48_usize),
        ("virtualBaseRaw", 56_usize),
        ("virtualQuoteRaw", 64_usize),
        ("realBaseBeforeRaw", 72_usize),
        ("realQuoteBeforeRaw", 80_usize),
        ("realBaseAfterRaw", 88_usize),
        ("realQuoteAfterRaw", 96_usize),
        ("amountInRaw", 104_usize),
        ("amountOutRaw", 112_usize),
        ("protocolFeeRaw", 120_usize),
        ("platformFeeRaw", 128_usize),
        ("creatorFeeRaw", 136_usize),
        ("shareFeeRaw", 144_usize),
    ];
    for field in fields {
        if let Some(value) = read_u64_le_from_bytes(data, field.1) {
            object.insert(field.0.to_string(), serde_json::Value::String(value.to_string()));
        }
    }
    if let Some(direction) = read_u8_from_bytes(data, 152) {
        object.insert(
            "tradeDirectionRaw".to_string(),
            serde_json::Value::Number(serde_json::Number::from(direction as u64)),
        );
    }
    if let Some(pool_status) = read_u8_from_bytes(data, 153) {
        object.insert(
            "poolStatusRaw".to_string(),
            serde_json::Value::Number(serde_json::Number::from(pool_status as u64)),
        );
    }
    if let Some(exact_in) = read_u8_from_bytes(data, 154) {
        object.insert("exactIn".to_string(), serde_json::Value::Bool(exact_in != 0));
    }
    insert_raydium_launchpad_normalized_trade_amounts(object);
}

fn insert_raydium_launchpad_normalized_trade_amounts(
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
) {
    let direction = object.get("tradeDirectionRaw").and_then(serde_json::Value::as_u64);
    let amount_in = object
        .get("amountInRaw")
        .and_then(serde_json::Value::as_str)
        .map(str::to_string);
    let amount_out = object
        .get("amountOutRaw")
        .and_then(serde_json::Value::as_str)
        .map(str::to_string);
    match direction {
        Some(0) => {
            object
                .insert("tradeSide".to_string(), serde_json::Value::String("BuyBase".to_string()));
            if let Some(amount_out) = amount_out {
                object.insert("baseAmountRaw".to_string(), serde_json::Value::String(amount_out));
            }
            if let Some(amount_in) = amount_in {
                object.insert("quoteAmountRaw".to_string(), serde_json::Value::String(amount_in));
            }
        },
        Some(1) => {
            object
                .insert("tradeSide".to_string(), serde_json::Value::String("SellBase".to_string()));
            if let Some(amount_in) = amount_in {
                object.insert("baseAmountRaw".to_string(), serde_json::Value::String(amount_in));
            }
            if let Some(amount_out) = amount_out {
                object.insert("quoteAmountRaw".to_string(), serde_json::Value::String(amount_out));
            }
        },
        _ => {},
    }
}

fn build_raydium_instruction_audit_payload(
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    protocol_name: &str,
    event_kind: &str,
    program_id: &str,
) -> serde_json::Value {
    let accounts = parse_instruction_accounts_value(instruction.accounts_json.as_str());
    let account_count = match accounts.as_array() {
        Some(items) => items.len(),
        None => 0,
    };
    let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
    let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref());
    let discriminator_hex =
        raydium_instruction_discriminator_hex(protocol_name, data_bytes.as_deref(), 0);
    let anchor_self_cpi_log =
        discriminator_hex.as_deref() == Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX);
    let anchor_event_discriminator_hex = if anchor_self_cpi_log {
        discriminator_hex_from_bytes(data_bytes.as_deref(), 8)
    } else {
        None
    };
    let anchor_event_payload_size = if anchor_self_cpi_log {
        match data_bytes.as_ref() {
            Some(data_bytes) => data_bytes.len().checked_sub(8),
            None => None,
        }
    } else {
        None
    };
    let audit_reason = if anchor_self_cpi_log {
        "raydium_anchor_self_cpi_log_not_decoded_by_specific_event_decoder"
    } else {
        "raydium_instruction_not_decoded_by_specific_decoder"
    };
    let proof_status = if anchor_self_cpi_log {
        "observed_local_corpus_anchor_self_cpi_log"
    } else {
        "unclassified_local_corpus_instruction"
    };
    return serde_json::json!({
        "decoder": protocol_name,
        "eventKind": event_kind,
        "signature": transaction.signature.clone(),
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "parentInstructionId": instruction.parent_instruction_id,
        "programId": program_id,
        "accounts": accounts,
        "accountCount": account_count,
        "data": data_base58,
        "discriminatorHex": discriminator_hex,
        "anchorSelfCpiLog": anchor_self_cpi_log,
        "anchorSelfCpiLogSelectorHex": if anchor_self_cpi_log { Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) } else { None },
        "anchorEventDiscriminatorHex": anchor_event_discriminator_hex,
        "anchorEventPayloadSize": anchor_event_payload_size,
        "auditReason": audit_reason,
        "proofStatus": proof_status,
        "tradeCandidate": false,
        "candleCandidate": false,
        "nonTradeUseful": false,
        "skipTradeReason": "instruction_audit_only",
        "skipCandleReason": "instruction_audit_only"
    });
}

fn candidate_raydium_audit_pool_account(
    protocol_name: &str,
    accounts_json: &str,
) -> std::option::Option<std::string::String> {
    let spec = match protocol_name {
        "raydium_amm_v4" => RaydiumInstructionAuditSpec {
            protocol_name: "raydium_amm_v4",
            event_kind: "raydium_amm_v4.instruction_audit",
            candidate_pool_account_index: 1,
        },
        "raydium_clmm" => RaydiumInstructionAuditSpec {
            protocol_name: "raydium_clmm",
            event_kind: "raydium_clmm.instruction_audit",
            candidate_pool_account_index: 2,
        },
        "raydium_cpmm" => RaydiumInstructionAuditSpec {
            protocol_name: "raydium_cpmm",
            event_kind: "raydium_cpmm.instruction_audit",
            candidate_pool_account_index: 3,
        },
        "raydium_launchpad" => RaydiumInstructionAuditSpec {
            protocol_name: "raydium_launchpad",
            event_kind: "raydium_launchpad.instruction_audit",
            candidate_pool_account_index: 4,
        },
        _ => return None,
    };
    let accounts_result = serde_json::from_str::<std::vec::Vec<std::string::String>>(accounts_json);
    let accounts = match accounts_result {
        Ok(accounts) => accounts,
        Err(_) => return None,
    };
    return accounts.get(spec.candidate_pool_account_index).cloned();
}

fn upstream_registry_instruction_match_is_locally_covered(
    registry_match: &crate::UpstreamRegistryEntryDto,
) -> bool {
    if registry_match.entry_kind != crate::ENTRY_KIND_INSTRUCTION {
        return false;
    }
    let local_event_kind = crate::dex_event_coverage::known_local_event_kind(
        registry_match.decoder_code.as_str(),
        registry_match.entry_name.as_str(),
    );
    match local_event_kind {
        Some(_) => return true,
        None => return false,
    }
}

fn build_upstream_registry_instruction_match_payload(
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    registry_match: &crate::UpstreamRegistryEntryDto,
    data_base58: std::option::Option<&str>,
) -> serde_json::Value {
    let data_bytes = instruction_data_bytes_from_base58(data_base58);
    let data_byte_len = match data_bytes.as_ref() {
        Some(data_bytes) => {
            let len_result = u64::try_from(data_bytes.len());
            match len_result {
                Ok(len) => serde_json::Value::Number(serde_json::Number::from(len)),
                Err(_) => serde_json::Value::Null,
            }
        },
        None => serde_json::Value::Null,
    };
    return serde_json::json!({
        "decoder": crate::UPSTREAM_REGISTRY_PROTOCOL_NAME,
        "matchKind": "instruction_discriminator",
        "signature": transaction.signature.clone(),
        "slot": transaction.slot,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "programId": instruction.program_id.clone(),
        "programName": instruction.program_name.clone(),
        "accounts": parse_instruction_accounts_value(instruction.accounts_json.as_str()),
        "accountCount": parse_instruction_accounts_vec(instruction.accounts_json.as_str()).len(),
        "dataBase58": data_base58,
        "dataByteLen": data_byte_len,
        "upstreamSourceRepo": registry_match.source_repo.clone(),
        "upstreamSourcePath": registry_match.source_path.clone(),
        "upstreamDecoderCode": registry_match.decoder_code.clone(),
        "upstreamProgramFamily": registry_match.program_family.clone(),
        "upstreamSurfaceKind": registry_match.surface_kind.clone(),
        "upstreamEntryKind": registry_match.entry_kind.clone(),
        "upstreamEntryName": registry_match.entry_name.clone(),
        "upstreamDiscriminatorHex": registry_match.discriminator_hex.clone(),
        "upstreamDiscriminatorLen": registry_match.discriminator_len,
        "upstreamProofStatus": registry_match.proof_status.clone(),
        "upstreamNotes": registry_match.notes.clone(),
        "tradeCandidate": false,
        "candleCandidate": false,
        "nonTradeUseful": false,
        "skipTradeReason": "upstream_git_unverified_registry_match",
        "skipCandleReason": "upstream_git_unverified_registry_match"
    });
}

fn parse_instruction_accounts_vec(accounts_json: &str) -> std::vec::Vec<std::string::String> {
    let accounts_result = serde_json::from_str::<std::vec::Vec<std::string::String>>(accounts_json);
    match accounts_result {
        Ok(accounts) => return accounts,
        Err(_) => return std::vec::Vec::new(),
    }
}

fn parse_instruction_accounts_value(accounts_json: &str) -> serde_json::Value {
    let accounts_result = serde_json::from_str::<serde_json::Value>(accounts_json);
    match accounts_result {
        Ok(accounts) => return accounts,
        Err(_) => return serde_json::Value::Array(std::vec::Vec::new()),
    }
}

fn parse_instruction_data_base58(
    data_json: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return None,
    };
    let data_result = serde_json::from_str::<std::string::String>(data_json);
    match data_result {
        Ok(data) => return Some(data),
        Err(_) => {
            if data_json.trim().is_empty() {
                return None;
            }
            return Some(data_json.to_string());
        },
    }
}

fn discriminator_hex_from_base58(
    data_base58: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let bytes = instruction_data_bytes_from_base58(data_base58);
    return discriminator_hex_from_bytes(bytes.as_deref(), 0);
}

fn raydium_instruction_discriminator_hex(
    protocol_name: &str,
    bytes: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    if protocol_name == "raydium_amm_v4" || protocol_name == "raydium_stable_swap" {
        return discriminator_hex_from_bytes_with_len(bytes, offset, 1);
    }
    return discriminator_hex_from_bytes(bytes, offset);
}

fn discriminator_hex_from_bytes_with_len(
    bytes: std::option::Option<&[u8]>,
    offset: usize,
    length: usize,
) -> std::option::Option<std::string::String> {
    let bytes = match bytes {
        Some(bytes) => bytes,
        None => return None,
    };
    if bytes.len() < offset + length {
        return None;
    }
    let mut text = std::string::String::new();
    let mut index = offset;
    let end = offset + length;
    while index < end {
        let byte = bytes[index];
        text.push_str(format!("{byte:02x}").as_str());
        index += 1;
    }
    return Some(text);
}

fn discriminator_hex_from_bytes(
    bytes: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    let bytes = match bytes {
        Some(bytes) => bytes,
        None => return None,
    };
    if bytes.len() < offset + 8 {
        return None;
    }
    let mut text = std::string::String::new();
    let mut index = offset;
    let end = offset + 8;
    while index < end {
        let byte = bytes[index];
        text.push_str(format!("{byte:02x}").as_str());
        index += 1;
    }
    return Some(text);
}

fn append_persisted_events(
    target: &mut std::vec::Vec<crate::DexDecodedEventDto>,
    source: std::vec::Vec<crate::DexDecodedEventDto>,
) {
    for persisted_event in source {
        target.push(persisted_event);
    }
}

#[derive(Clone, Debug)]
struct RaydiumClmmProgramDataEventCandidate {
    decoded_event: crate::RaydiumClmmDecodedEvent,
    consumed: bool,
}

fn collect_raydium_clmm_program_data_events(
    transaction: &crate::ChainTransactionDto,
) -> std::vec::Vec<RaydiumClmmProgramDataEventCandidate> {
    let logs = extract_transaction_log_messages(transaction.transaction_json.as_str());
    let mut events = std::vec::Vec::new();
    let mut clmm_stack_depth = 0_u32;
    for log_message in logs {
        if is_program_invoke_log(log_message.as_str(), crate::RAYDIUM_CLMM_PROGRAM_ID) {
            clmm_stack_depth += 1;
            continue;
        }
        if is_program_success_or_failed_log(log_message.as_str(), crate::RAYDIUM_CLMM_PROGRAM_ID) {
            clmm_stack_depth = clmm_stack_depth.saturating_sub(1);
            continue;
        }
        if clmm_stack_depth == 0 {
            continue;
        }
        let data_base64 = match log_message.strip_prefix("Program data: ") {
            Some(data_base64) => data_base64.trim(),
            None => continue,
        };
        if data_base64.is_empty() {
            continue;
        }
        let decoded_event = crate::decode_raydium_clmm_program_data_event(data_base64);
        if let Some(decoded_event) = decoded_event {
            events.push(RaydiumClmmProgramDataEventCandidate { decoded_event, consumed: false });
        }
    }
    return events;
}

async fn persist_matching_raydium_clmm_program_data_events(
    service: &DexDecodeService,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    instruction_discriminator_hex: std::option::Option<&str>,
    program_data_events: &mut [RaydiumClmmProgramDataEventCandidate],
    persisted: &mut std::vec::Vec<crate::DexDecodedEventDto>,
) -> Result<(), crate::Error> {
    let instruction_id = match instruction.id {
        Some(instruction_id) => instruction_id,
        None => return Ok(()),
    };
    let expected_event_kinds =
        raydium_clmm_program_data_event_kinds_for_instruction(instruction_discriminator_hex);
    if expected_event_kinds.is_empty() {
        return Ok(());
    }
    let mut index = 0_usize;
    while index < program_data_events.len() {
        if program_data_events[index].consumed {
            index += 1;
            continue;
        }
        let event_kind = program_data_events[index].decoded_event.event_kind();
        if !string_slice_contains(expected_event_kinds.as_slice(), event_kind) {
            index += 1;
            continue;
        }
        program_data_events[index].consumed = true;
        let persist_result = service
            .persist_raydium_clmm_event(
                transaction,
                instruction_id,
                &program_data_events[index].decoded_event,
            )
            .await;
        let persisted_event = match persist_result {
            Ok(persisted_event) => persisted_event,
            Err(error) => return Err(error),
        };
        persisted.push(persisted_event);
        index += 1;
    }
    return Ok(());
}

fn raydium_clmm_program_data_event_kinds_for_instruction(
    instruction_discriminator_hex: std::option::Option<&str>,
) -> std::vec::Vec<&'static str> {
    let discriminator = match instruction_discriminator_hex {
        Some(discriminator) => discriminator,
        None => return std::vec::Vec::new(),
    };
    match discriminator {
        "e992d18ecf6840bc" | "2b44d4a7592fa401" => {
            return vec!["raydium_clmm.pool_created_event"];
        },
        "8888fcddc2427e59" => {
            return vec!["raydium_clmm.collect_protocol_fee_event"];
        },
        "f8c69e91e17587c8" | "2b04ed0b1ac91e62" | "457d73daf5baf2c4" => {
            return vec!["raydium_clmm.swap_event"];
        },
        "87802f4d0f98f031" | "4db84ad67056f1c7" | "4dffae527d1dc92e" => {
            return vec![
                "raydium_clmm.liquidity_calculate_event",
                "raydium_clmm.create_personal_position_event",
                "raydium_clmm.increase_liquidity_event",
                "raydium_clmm.liquidity_change_event",
            ];
        },
        "2e9cf3760dcdfbb2" | "851d59df45eeb00a" => {
            return vec![
                "raydium_clmm.liquidity_calculate_event",
                "raydium_clmm.increase_liquidity_event",
                "raydium_clmm.liquidity_change_event",
            ];
        },
        "a026d06f685b2c01" | "3a7fbc3e4f52c460" => {
            return vec![
                "raydium_clmm.liquidity_calculate_event",
                "raydium_clmm.decrease_liquidity_event",
                "raydium_clmm.liquidity_change_event",
            ];
        },
        "7b86510031446262" | "c975989055556cb2" => {
            return vec![
                "raydium_clmm.decrease_liquidity_event",
                "raydium_clmm.collect_personal_fee_event",
                "raydium_clmm.liquidity_change_event",
            ];
        },
        "8934edd4d7756c68" | "313cae889a1c74c8" | "bd0eb5785576e33e" => {
            return vec!["raydium_clmm.config_change_event"];
        },
        "5f87c0c4f281e644" | "7034a74b20c9d389" | "a3ace0340b9a6adf" => {
            return vec!["raydium_clmm.update_reward_infos_event"];
        },
        _ => return std::vec::Vec::new(),
    }
}

fn string_slice_contains(values: &[&'static str], expected: &str) -> bool {
    for value in values {
        if *value == expected {
            return true;
        }
    }
    return false;
}

#[derive(Clone, Debug)]
struct RaydiumCpmmProgramDataEventCandidate {
    decoded_event: crate::RaydiumCpmmDecodedEvent,
    consumed: bool,
}

fn collect_raydium_cpmm_program_data_events(
    transaction: &crate::ChainTransactionDto,
) -> std::vec::Vec<RaydiumCpmmProgramDataEventCandidate> {
    let logs = extract_transaction_log_messages(transaction.transaction_json.as_str());
    let mut events = std::vec::Vec::new();
    let mut cpmm_stack_depth = 0_u32;
    for log_message in logs {
        if is_program_invoke_log(log_message.as_str(), crate::RAYDIUM_CPMM_PROGRAM_ID) {
            cpmm_stack_depth += 1;
            continue;
        }
        if is_program_success_or_failed_log(log_message.as_str(), crate::RAYDIUM_CPMM_PROGRAM_ID) {
            cpmm_stack_depth = cpmm_stack_depth.saturating_sub(1);
            continue;
        }
        if cpmm_stack_depth == 0 {
            continue;
        }
        let data_base64 = match log_message.strip_prefix("Program data: ") {
            Some(data_base64) => data_base64.trim(),
            None => continue,
        };
        if data_base64.is_empty() {
            continue;
        }
        let decoded_event = crate::decode_raydium_cpmm_program_data_event(data_base64);
        if let Some(decoded_event) = decoded_event {
            events.push(RaydiumCpmmProgramDataEventCandidate { decoded_event, consumed: false });
        }
    }
    return events;
}

async fn persist_matching_raydium_cpmm_program_data_event(
    service: &DexDecodeService,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    instruction_kind: std::option::Option<&str>,
    program_data_events: &mut [RaydiumCpmmProgramDataEventCandidate],
    persisted: &mut std::vec::Vec<crate::DexDecodedEventDto>,
) -> Result<(), crate::Error> {
    let expected_event_kind = match instruction_kind {
        Some("swap_base_input") => Some("swap_event"),
        Some("swap_base_output") => Some("swap_event"),
        Some("deposit") => Some("lp_change_event"),
        Some("withdraw") => Some("lp_change_event"),
        _ => None,
    };
    let expected_event_kind = match expected_event_kind {
        Some(expected_event_kind) => expected_event_kind,
        None => return Ok(()),
    };
    let mut index = 0_usize;
    while index < program_data_events.len() {
        if program_data_events[index].consumed {
            index += 1;
            continue;
        }
        let event_matches = match (&program_data_events[index].decoded_event, expected_event_kind) {
            (crate::RaydiumCpmmDecodedEvent::SwapEvent(_), "swap_event") => true,
            (crate::RaydiumCpmmDecodedEvent::LpChangeEvent(_), "lp_change_event") => true,
            _ => false,
        };
        if !event_matches {
            index += 1;
            continue;
        }
        program_data_events[index].consumed = true;
        let persist_result = service
            .persist_raydium_cpmm_event(
                transaction,
                instruction,
                &program_data_events[index].decoded_event,
            )
            .await;
        let persisted_event = match persist_result {
            Ok(persisted_event) => persisted_event,
            Err(error) => return Err(error),
        };
        persisted.push(persisted_event);
        return Ok(());
    }
    return Ok(());
}

fn extract_transaction_log_messages(transaction_json: &str) -> std::vec::Vec<std::string::String> {
    let value_result = serde_json::from_str::<serde_json::Value>(transaction_json);
    let value = match value_result {
        Ok(value) => value,
        Err(_) => return std::vec::Vec::new(),
    };
    let meta = match value.get("meta") {
        Some(meta) => meta,
        None => return std::vec::Vec::new(),
    };
    let logs = match meta.get("logMessages") {
        Some(logs) => logs,
        None => return std::vec::Vec::new(),
    };
    let logs = match logs.as_array() {
        Some(logs) => logs,
        None => return std::vec::Vec::new(),
    };
    let mut output = std::vec::Vec::new();
    for log in logs {
        if let Some(log) = log.as_str() {
            output.push(log.to_string());
        }
    }
    return output;
}

fn is_program_invoke_log(log_message: &str, program_id: &str) -> bool {
    if !log_message.starts_with("Program ") {
        return false;
    }
    if !log_message.contains(" invoke [") {
        return false;
    }
    return log_message.contains(program_id);
}

fn is_program_success_or_failed_log(log_message: &str, program_id: &str) -> bool {
    if !log_message.starts_with("Program ") {
        return false;
    }
    if !log_message.contains(program_id) {
        return false;
    }
    if log_message.ends_with(" success") {
        return true;
    }
    if log_message.contains(" failed: ") {
        return true;
    }
    return false;
}

fn decoded_instruction_ids_from_persisted_events(
    persisted: &[crate::DexDecodedEventDto],
) -> std::collections::HashSet<i64> {
    let mut decoded_instruction_ids = std::collections::HashSet::<i64>::new();
    for decoded_event in persisted {
        let instruction_id = match decoded_event.instruction_id {
            Some(instruction_id) => instruction_id,
            None => continue,
        };
        decoded_instruction_ids.insert(instruction_id);
    }
    return decoded_instruction_ids;
}

fn append_persisted_events_result(
    target: &mut std::vec::Vec<crate::DexDecodedEventDto>,
    source_result: Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error>,
) -> Result<(), crate::Error> {
    let source = match source_result {
        Ok(source) => source,
        Err(error) => return Err(error),
    };
    append_persisted_events(target, source);
    return Ok(());
}

fn enriched_raydium_payload_value(
    protocol_name: &str,
    event_kind: &str,
    raw_payload_json: &str,
) -> Result<serde_json::Value, crate::Error> {
    let payload_value_result = serde_json::from_str::<serde_json::Value>(raw_payload_json);
    let payload_value = match payload_value_result {
        Ok(payload_value) => payload_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse decoded {} payload for '{}': {}",
                protocol_name, event_kind, error
            )));
        },
    };
    return Ok(crate::enrich_dex_decoded_payload(protocol_name, event_kind, payload_value));
}

// Marks Meteora DAMM v1 swaps without direct amounts as non-materializable candidates
// before generic classification metadata is inserted.
fn prepare_meteora_damm_v1_swap_payload_for_classification(
    event: &crate::MeteoraDammV1SwapDecoded,
) -> serde_json::Value {
    let mut object = match event.payload_json.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    let payload_json = serde_json::Value::Object(object.clone());
    if crate::dex_event_classification::decoded_payload_has_trade_amount_or_price_payload(
        &payload_json,
    ) {
        return serde_json::Value::Object(object);
    }
    object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false));
    object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false));
    object.insert(
        "skipTradeReason".to_string(),
        serde_json::Value::String("meteora_damm_v1_swap_without_amount_payload".to_string()),
    );
    object.insert(
        "skipCandleReason".to_string(),
        serde_json::Value::String("meteora_damm_v1_swap_without_amount_payload".to_string()),
    );
    return serde_json::Value::Object(object);
}

// Marks incomplete PumpSwap decoded trades as non-materializable candidates before generic
// classification metadata is inserted.
fn prepare_pump_swap_trade_payload_for_classification(
    event: &crate::PumpSwapTradeDecoded,
) -> serde_json::Value {
    let mut object = match event.payload_json.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawPayload".to_string(), other);
            object
        },
    };
    if event.pool_account.is_some() && event.token_a_mint.is_some() && event.token_b_mint.is_some()
    {
        object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(true));
        object.insert("candleCandidate".to_string(), serde_json::Value::Bool(true));
        return serde_json::Value::Object(object);
    }
    object.insert("tradeCandidate".to_string(), serde_json::Value::Bool(false));
    object.insert("candleCandidate".to_string(), serde_json::Value::Bool(false));
    object.insert(
        "skipTradeReason".to_string(),
        serde_json::Value::String("incomplete_pump_swap_trade_accounts".to_string()),
    );
    object.insert(
        "skipCandleReason".to_string(),
        serde_json::Value::String("incomplete_pump_swap_trade_accounts".to_string()),
    );
    return serde_json::Value::Object(object);
}

#[cfg(test)]
mod tests {

    async fn make_database() -> std::sync::Arc<crate::Database> {
        let tempdir_result = tempfile::tempdir();
        let tempdir = match tempdir_result {
            Ok(tempdir) => tempdir,
            Err(error) => panic!("tempdir must succeed: {}", error),
        };
        let database_path = tempdir.path().join("dex_decode.sqlite3");
        let config = crate::DatabaseConfig {
            enabled: true,
            backend: crate::DatabaseBackend::Sqlite,
            sqlite: crate::SqliteDatabaseConfig {
                path: database_path.to_string_lossy().to_string(),
                create_if_missing: true,
                busy_timeout_ms: 5000,
                max_connections: 1,
                auto_initialize_schema: true,
                use_wal: true,
            },
        };
        let database_result = crate::Database::connect_and_initialize(&config).await;
        let database = match database_result {
            Ok(database) => database,
            Err(error) => panic!("database init must succeed: {}", error),
        };
        return std::sync::Arc::new(database);
    }

    async fn seed_projected_raydium_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999001,
            "blockTime": 1779000001,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::RAYDIUM_AMM_V4_PROGRAM_ID,
                            "program": "raydium_amm_v4",
                            "stackHeight": 1,
                            "accounts": [
                                "Account0",
                                "Account1",
                                "Account2",
                                "Account3",
                                "PoolXYZ",
                                "Account5",
                                "Account6",
                                "LpMintXYZ",
                                "TokenAXYZ",
                                "TokenBXYZ",
                                "Account10",
                                "Account11",
                                "Account12",
                                "Account13",
                                "Account14",
                                "Account15",
                                "MarketXYZ"
                            ],
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: initialize2"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    async fn seed_projected_pump_fun_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999002,
            "blockTime": 1779000002,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::PUMP_FUN_PROGRAM_ID,
                            "program": "pump",
                            "stackHeight": 1,
                            "accounts": [
                                "MintPF111",
                                "MintAuthorityPF111",
                                "BondingCurvePF111",
                                "AssociatedBondingCurvePF111",
                                "GlobalPF111",
                                "CreatorPF111",
                                "System111",
                                "Token2022Program111",
                                "AtaProgram111"
                            ],
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: CreateV2"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    async fn seed_projected_pump_swap_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999003,
            "blockTime": 1779000003,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::PUMP_SWAP_PROGRAM_ID,
                            "program": "pump-amm",
                            "stackHeight": 1,
                            "accounts": [
                                "PumpSwapPool111",
                                "PumpSwapTokenA111",
                                "PumpSwapTokenB111",
                                "PumpSwapPoolV2_111"
                            ],
                            "parsed": {
                                "info": {
                                    "pool": "PumpSwapPool111",
                                    "baseMint": "PumpSwapTokenA111",
                                    "quoteMint": "PumpSwapTokenB111",
                                    "poolV2": "PumpSwapPoolV2_111"
                                }
                            },
                            "data": "AJTQ2h9DXrBfqJi53PDQG2Fvki5tkaTU3"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: Buy"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_raydium_event() {
        let database = make_database().await;
        seed_projected_raydium_transaction(database.clone(), "sig-dex-decode-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result = service.decode_transaction_by_signature("sig-dex-decode-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "raydium_amm_v4");
        assert_eq!(decoded[0].event_kind, "raydium_amm_v4.initialize2_pool");
        assert_eq!(decoded[0].pool_account, Some("PoolXYZ".to_string()));
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_pump_fun_event() {
        let database = make_database().await;
        seed_projected_pump_fun_transaction(database.clone(), "sig-dex-decode-pump-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result = service.decode_transaction_by_signature("sig-dex-decode-pump-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "pump_fun");
        assert_eq!(decoded[0].event_kind, "pump_fun.create_v2_token");
        assert_eq!(decoded[0].pool_account, Some("BondingCurvePF111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("MintPF111".to_string()));
    }

    #[test]
    fn prepare_meteora_damm_v1_swap_without_amounts_marks_event_non_actionable() {
        let event = crate::MeteoraDammV1SwapDecoded {
            transaction_id: 1,
            instruction_id: 2,
            signature: "sig-damm-v1-no-amounts".to_string(),
            program_id: crate::METEORA_DAMM_V1_PROGRAM_ID.to_string(),
            trade_side: crate::SwapTradeSide::Unknown,
            pool_account: Some("PoolDammV1".to_string()),
            token_a_mint: Some("TokenA".to_string()),
            token_b_mint: Some("TokenB".to_string()),
            payload_json: serde_json::json!({
                "decoder": "meteora_damm_v1",
                "eventKind": "swap"
            }),
        };
        let prepared_payload =
            super::prepare_meteora_damm_v1_swap_payload_for_classification(&event);
        let object_option = prepared_payload.as_object();
        let object = match object_option {
            Some(object) => object,
            None => {
                panic!("expected prepared payload object");
            },
        };
        assert_eq!(object.get("tradeCandidate"), Some(&serde_json::Value::Bool(false)));
        assert_eq!(object.get("candleCandidate"), Some(&serde_json::Value::Bool(false)));
        assert_eq!(
            object.get("skipTradeReason"),
            Some(&serde_json::Value::String(
                "meteora_damm_v1_swap_without_amount_payload".to_string()
            ))
        );
    }

    #[test]
    fn prepare_meteora_damm_v1_swap_with_amounts_keeps_event_actionable() {
        let event = crate::MeteoraDammV1SwapDecoded {
            transaction_id: 1,
            instruction_id: 2,
            signature: "sig-damm-v1-with-amounts".to_string(),
            program_id: crate::METEORA_DAMM_V1_PROGRAM_ID.to_string(),
            trade_side: crate::SwapTradeSide::Unknown,
            pool_account: Some("PoolDammV1".to_string()),
            token_a_mint: Some("TokenA".to_string()),
            token_b_mint: Some("TokenB".to_string()),
            payload_json: serde_json::json!({
                "decoder": "meteora_damm_v1",
                "eventKind": "swap",
                "baseAmountRaw": "100",
                "quoteAmountRaw": "250"
            }),
        };
        let prepared_payload =
            super::prepare_meteora_damm_v1_swap_payload_for_classification(&event);
        let object_option = prepared_payload.as_object();
        let object = match object_option {
            Some(object) => object,
            None => {
                panic!("expected prepared payload object");
            },
        };
        assert_eq!(object.get("tradeCandidate"), None);
        assert_eq!(object.get("candleCandidate"), None);
        assert_eq!(object.get("skipTradeReason"), None);
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_pump_swap_event() {
        let database = make_database().await;
        seed_projected_pump_swap_transaction(database.clone(), "sig-dex-decode-pumpswap-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result =
            service.decode_transaction_by_signature("sig-dex-decode-pumpswap-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "pump_swap");
        assert_eq!(decoded[0].event_kind, "pump_swap.buy");
        assert_eq!(decoded[0].pool_account, Some("PumpSwapPool111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("PumpSwapTokenA111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some("PumpSwapTokenB111".to_string()));
        assert_eq!(decoded[0].market_account, Some("PumpSwapPoolV2_111".to_string()));
    }

    async fn seed_projected_meteora_dbc_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999004,
            "blockTime": 1779000004,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::METEORA_DBC_PROGRAM_ID,
                            "program": "meteora_dbc",
                            "stackHeight": 1,
                            "accounts": [
                                "DbcPoolDecode111",
                                "DbcTokenDecode111",
                                crate::WSOL_MINT_ID,
                                "DbcConfigDecode111",
                                "DbcCreatorDecode111"
                            ],
                            "parsed": {
                                "info": {
                                    "pool": "DbcPoolDecode111",
                                    "baseMint": "DbcTokenDecode111",
                                    "quoteMint": crate::WSOL_MINT_ID,
                                    "poolConfig": "DbcConfigDecode111",
                                    "creator": "DbcCreatorDecode111"
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: CreatePool"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_meteora_dbc_event() {
        let database = make_database().await;
        seed_projected_meteora_dbc_transaction(database.clone(), "sig-dex-decode-dbc-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result = service.decode_transaction_by_signature("sig-dex-decode-dbc-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "meteora_dbc");
        assert_eq!(decoded[0].event_kind, "meteora_dbc.create_pool");
        assert_eq!(decoded[0].pool_account, Some("DbcPoolDecode111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("DbcTokenDecode111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
        assert_eq!(decoded[0].market_account, Some("DbcConfigDecode111".to_string()));
    }

    async fn seed_projected_meteora_damm_v2_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999005,
            "blockTime": 1779000005,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::METEORA_DAMM_V2_PROGRAM_ID,
                            "program": "meteora_damm_v2",
                            "stackHeight": 1,
                            "accounts": [
                                "DammV2DecodePool111",
                                "DammV2DecodeTokenA111",
                                crate::WSOL_MINT_ID,
                                "DammV2DecodeConfig111",
                                "DammV2DecodeCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "instruction": "initialize_customizable_pool",
                                    "pool": "DammV2DecodePool111",
                                    "tokenAMint": "DammV2DecodeTokenA111",
                                    "tokenBMint": crate::WSOL_MINT_ID,
                                    "creator": "DammV2DecodeCreator111",
                                    "isCustomizablePool": true
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: InitializeCustomizablePool"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_meteora_damm_v2_event() {
        let database = make_database().await;
        seed_projected_meteora_damm_v2_transaction(database.clone(), "sig-dex-decode-dammv2-1")
            .await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result =
            service.decode_transaction_by_signature("sig-dex-decode-dammv2-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "meteora_damm_v2");
        assert_eq!(decoded[0].event_kind, "meteora_damm_v2.create_pool");
        assert_eq!(decoded[0].pool_account, Some("DammV2DecodePool111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("DammV2DecodeTokenA111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
    }

    async fn seed_projected_meteora_damm_v1_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999006,
            "blockTime": 1779000006,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::METEORA_DAMM_V1_PROGRAM_ID,
                            "program": "meteora_damm_v1",
                            "stackHeight": 1,
                            "accounts": [
                                "DammV1DecodePool111",
                                "DammV1DecodeTokenA111",
                                crate::WSOL_MINT_ID,
                                "DammV1DecodeConfig111",
                                "DammV1DecodeCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "instruction": "initialize_pool_with_config",
                                    "pool": "DammV1DecodePool111",
                                    "tokenAMint": "DammV1DecodeTokenA111",
                                    "tokenBMint": crate::WSOL_MINT_ID,
                                    "config": "DammV1DecodeConfig111",
                                    "creator": "DammV1DecodeCreator111"
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: InitializePoolWithConfig"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_meteora_damm_v1_event() {
        let database = make_database().await;
        seed_projected_meteora_damm_v1_transaction(database.clone(), "sig-dex-decode-dammv1-1")
            .await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result =
            service.decode_transaction_by_signature("sig-dex-decode-dammv1-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "meteora_damm_v1");
        assert_eq!(decoded[0].event_kind, "meteora_damm_v1.create_pool");
        assert_eq!(decoded[0].pool_account, Some("DammV1DecodePool111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("DammV1DecodeTokenA111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
    }

    async fn seed_projected_orca_whirlpools_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999007,
            "blockTime": 1779000007,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::ORCA_WHIRLPOOLS_PROGRAM_ID,
                            "program": "orca_whirlpools",
                            "stackHeight": 1,
                            "accounts": [
                                "OrcaDecodePool111",
                                "OrcaDecodeTokenA111",
                                crate::WSOL_MINT_ID,
                                "OrcaDecodeConfig111",
                                "OrcaDecodeCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "instruction": "initialize_pool_v2",
                                    "whirlpool": "OrcaDecodePool111",
                                    "tokenMintA": "OrcaDecodeTokenA111",
                                    "tokenMintB": crate::WSOL_MINT_ID,
                                    "whirlpoolsConfig": "OrcaDecodeConfig111",
                                    "funder": "OrcaDecodeCreator111",
                                    "tokenProgramA": crate::SPL_TOKEN_PROGRAM_ID,
                                    "tokenProgramB": crate::SPL_TOKEN_PROGRAM_ID
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: InitializePoolV2"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_orca_whirlpools_event() {
        let database = make_database().await;
        seed_projected_orca_whirlpools_transaction(database.clone(), "sig-dex-decode-orca-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result = service.decode_transaction_by_signature("sig-dex-decode-orca-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "orca_whirlpools");
        assert_eq!(decoded[0].event_kind, "orca_whirlpools.create_pool");
        assert_eq!(decoded[0].pool_account, Some("OrcaDecodePool111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("OrcaDecodeTokenA111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
        assert_eq!(decoded[0].market_account, Some("OrcaDecodeConfig111".to_string()));
    }

    async fn seed_projected_fluxbeam_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999008,
            "blockTime": 1779000008,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::FLUXBEAM_PROGRAM_ID,
                            "program": "fluxbeam",
                            "stackHeight": 1,
                            "accounts": [
                                "FluxDecodePool111",
                                "FluxDecodeLpMint111",
                                "FluxDecodeTokenA111",
                                crate::WSOL_MINT_ID,
                                "FluxDecodeCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "instruction": "create_pool",
                                    "pool": "FluxDecodePool111",
                                    "lpMint": "FluxDecodeLpMint111",
                                    "tokenA": "FluxDecodeTokenA111",
                                    "tokenB": crate::WSOL_MINT_ID,
                                    "payer": "FluxDecodeCreator111"
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: CreatePool"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_fluxbeam_event() {
        let database = make_database().await;
        seed_projected_fluxbeam_transaction(database.clone(), "sig-dex-decode-fluxbeam-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result =
            service.decode_transaction_by_signature("sig-dex-decode-fluxbeam-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "fluxbeam");
        assert_eq!(decoded[0].event_kind, "fluxbeam.create_pool");
        assert_eq!(decoded[0].pool_account, Some("FluxDecodePool111".to_string()));
        assert_eq!(decoded[0].market_account, Some("FluxDecodeLpMint111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("FluxDecodeTokenA111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
    }

    async fn seed_projected_dexlab_transaction(
        database: std::sync::Arc<crate::Database>,
        signature: &str,
    ) {
        let service = crate::TransactionModelService::new(database);
        let resolved_transaction = serde_json::json!({
            "slot": 999009,
            "blockTime": 1779000009,
            "version": 0,
            "transaction": {
                "message": {
                    "instructions": [
                        {
                            "programId": crate::DEXLAB_PROGRAM_ID,
                            "program": "dexlab",
                            "stackHeight": 1,
                            "accounts": [
                                "DexlabDecodePool111",
                                "DexlabDecodeTokenA111",
                                crate::WSOL_MINT_ID,
                                "DexlabDecodeCreator111"
                            ],
                            "parsed": {
                                "info": {
                                    "instruction": "create_pool",
                                    "pool": "DexlabDecodePool111",
                                    "tokenA": "DexlabDecodeTokenA111",
                                    "tokenB": crate::WSOL_MINT_ID,
                                    "payer": "DexlabDecodeCreator111",
                                    "feeTier": "0.3%"
                                }
                            },
                            "data": "opaque"
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": [
                    "Program log: Instruction: CreatePool"
                ]
            }
        });
        let persist_result = service
            .persist_resolved_transaction(
                signature,
                Some("helius_primary_http".to_string()),
                &resolved_transaction,
            )
            .await;
        if let Err(error) = persist_result {
            panic!("projection must succeed: {}", error);
        }
    }

    #[tokio::test]
    async fn decode_transaction_by_signature_persists_decoded_dexlab_event() {
        let database = make_database().await;
        seed_projected_dexlab_transaction(database.clone(), "sig-dex-decode-dexlab-1").await;
        let service = crate::DexDecodeService::new(database.clone());
        let decoded_result =
            service.decode_transaction_by_signature("sig-dex-decode-dexlab-1").await;
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].protocol_name, "dexlab");
        assert_eq!(decoded[0].event_kind, "dexlab.create_pool");
        assert_eq!(decoded[0].pool_account, Some("DexlabDecodePool111".to_string()));
        assert_eq!(decoded[0].token_a_mint, Some("DexlabDecodeTokenA111".to_string()));
        assert_eq!(decoded[0].token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
    }

    #[test]
    fn classifies_swap_events_as_trade_candidates() {
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_cpmm.swap_base_input"),
            "trade"
        );
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_cpmm.swap_base_output"),
            "trade"
        );
        assert_eq!(crate::classify_dex_event_category_code("raydium_clmm.swap"), "trade");
        assert_eq!(crate::classify_dex_event_category_code("raydium_clmm.swap_v2"), "trade");
        assert_eq!(crate::classify_dex_event_category_code("pump_fun.buy"), "trade");
        assert!(crate::is_dex_trade_event_kind("raydium_cpmm.swap_base_input"));
        assert!(crate::is_dex_candle_candidate_event_kind("raydium_cpmm.swap_base_input"));
    }

    #[test]
    fn classifies_router_swap_as_trade_but_not_direct_candle_candidate() {
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_clmm.swap_router_base_in"),
            "trade"
        );
        assert!(crate::is_dex_trade_event_kind("raydium_clmm.swap_router_base_in"));
        assert!(!crate::is_dex_candle_candidate_event_kind("raydium_clmm.swap_router_base_in"));
    }

    #[test]
    fn classifies_fee_reward_liquidity_and_lifecycle_events() {
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_cpmm.collect_creator_fee"),
            "fee"
        );
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_clmm.collect_protocol_fee"),
            "fee"
        );
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_clmm.set_reward_params"),
            "reward"
        );
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_clmm.increase_liquidity_v2"),
            "liquidity"
        );
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_cpmm.initialize"),
            "pool_lifecycle"
        );
        assert_eq!(
            crate::classify_dex_event_category_code("raydium_clmm.instruction_audit"),
            "informational"
        );
        assert_eq!(
            crate::classify_dex_event_lifecycle_kind_code("raydium_clmm.instruction_audit"),
            "instruction_audit"
        );
        assert_eq!(
            crate::classify_dex_event_actionability_code(
                "raydium_clmm.instruction_audit",
                false,
                false
            ),
            "informational"
        );
    }

    #[test]
    fn enriches_payload_without_overriding_existing_fields() {
        let payload_json = serde_json::json!({
            "eventCategory": "custom",
            "amountIn": "10"
        });
        let enriched_payload = crate::enrich_dex_decoded_payload(
            "raydium_cpmm",
            "raydium_cpmm.swap_base_input",
            payload_json,
        );
        let object_option = enriched_payload.as_object();
        let object = match object_option {
            Some(object) => object,
            None => {
                panic!("expected enriched payload object");
            },
        };
        assert_eq!(
            object.get("eventCategory"),
            Some(&serde_json::Value::String("custom".to_owned()))
        );
        assert_eq!(
            object.get("protocolName"),
            Some(&serde_json::Value::String("raydium_cpmm".to_owned()))
        );
        assert_eq!(
            object.get("eventKind"),
            Some(&serde_json::Value::String("raydium_cpmm.swap_base_input".to_owned()))
        );
        assert_eq!(object.get("tradeCandidate"), Some(&serde_json::Value::Bool(true)));
        assert_eq!(object.get("candleCandidate"), Some(&serde_json::Value::Bool(true)));
    }

    #[test]
    fn enriches_non_object_payload_as_raw_payload() {
        let payload_json = serde_json::Value::String("raw".to_owned());
        let enriched_payload = crate::enrich_dex_decoded_payload(
            "raydium_clmm",
            "raydium_clmm.collect_protocol_fee",
            payload_json,
        );
        let object_option = enriched_payload.as_object();
        let object = match object_option {
            Some(object) => object,
            None => {
                panic!("expected enriched payload object");
            },
        };
        assert_eq!(object.get("rawPayload"), Some(&serde_json::Value::String("raw".to_owned())));
        assert_eq!(object.get("eventCategory"), Some(&serde_json::Value::String("fee".to_owned())));
        assert_eq!(object.get("tradeCandidate"), Some(&serde_json::Value::Bool(false)));
        assert_eq!(object.get("candleCandidate"), Some(&serde_json::Value::Bool(false)));
        assert_eq!(
            object.get("skipTradeReason"),
            Some(&serde_json::Value::String("non_trade_event".to_owned()))
        );
    }

    #[test]
    fn maps_observed_raydium_clmm_non_swap_discriminators() {
        let create_pool = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("e992d18ecf6840bc"),
            13,
        );
        let create_pool = match create_pool {
            Some(create_pool) => create_pool,
            None => panic!("create_pool discriminator must be mapped"),
        };
        assert_eq!(create_pool.event_kind, "raydium_clmm.create_pool");
        assert_eq!(create_pool.pool_account_index, Some(2));
        assert_eq!(create_pool.token_a_mint_index, Some(3));
        assert_eq!(create_pool.token_b_mint_index, Some(4));
        let collect_protocol_fee = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("8888fcddc2427e59"),
            11,
        );
        let collect_protocol_fee = match collect_protocol_fee {
            Some(collect_protocol_fee) => collect_protocol_fee,
            None => panic!("collect_protocol_fee discriminator must be mapped"),
        };
        assert_eq!(collect_protocol_fee.event_kind, "raydium_clmm.collect_protocol_fee");
        assert_eq!(collect_protocol_fee.pool_account_index, Some(1));
        assert_eq!(collect_protocol_fee.token_a_mint_index, Some(5));
        assert_eq!(collect_protocol_fee.token_b_mint_index, Some(6));
        let decrease = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("3a7fbc3e4f52c460"),
            19,
        );
        let decrease = match decrease {
            Some(decrease) => decrease,
            None => panic!("decrease_liquidity_v2 discriminator must be mapped"),
        };
        assert_eq!(decrease.event_kind, "raydium_clmm.decrease_liquidity_v2");
        assert_eq!(decrease.pool_account_index, Some(3));
        assert_eq!(decrease.token_a_mint_index, Some(14));
        assert_eq!(decrease.token_b_mint_index, Some(15));
        let increase = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("851d59df45eeb00a"),
            15,
        );
        let increase = match increase {
            Some(increase) => increase,
            None => panic!("increase_liquidity_v2 discriminator must be mapped"),
        };
        assert_eq!(increase.event_kind, "raydium_clmm.increase_liquidity_v2");
        assert_eq!(increase.pool_account_index, Some(2));
        let open_limit_order = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("9d20dab7471d1293"),
            11,
        );
        let open_limit_order = match open_limit_order {
            Some(open_limit_order) => open_limit_order,
            None => panic!("open_limit_order discriminator must be mapped"),
        };
        assert_eq!(open_limit_order.event_kind, "raydium_clmm.open_limit_order");
        assert_eq!(open_limit_order.pool_account_index, Some(1));
        assert_eq!(open_limit_order.token_a_mint_index, Some(7));
        let increase_limit_order = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("b19059ecfaba7d63"),
            8,
        );
        let increase_limit_order = match increase_limit_order {
            Some(increase_limit_order) => increase_limit_order,
            None => panic!("increase_limit_order discriminator must be mapped"),
        };
        assert_eq!(increase_limit_order.event_kind, "raydium_clmm.increase_limit_order");
        assert_eq!(increase_limit_order.pool_account_index, Some(1));
        assert_eq!(increase_limit_order.token_a_mint_index, Some(6));
        let decrease_limit_order = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("759d3c674231a300"),
            13,
        );
        let decrease_limit_order = match decrease_limit_order {
            Some(decrease_limit_order) => decrease_limit_order,
            None => panic!("decrease_limit_order discriminator must be mapped"),
        };
        assert_eq!(decrease_limit_order.event_kind, "raydium_clmm.decrease_limit_order");
        assert_eq!(decrease_limit_order.pool_account_index, Some(1));
        assert_eq!(decrease_limit_order.token_a_mint_index, Some(8));
        assert_eq!(decrease_limit_order.token_b_mint_index, Some(9));
        let update_dynamic_fee_config = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("0707500802c784f0"),
            2,
        );
        let update_dynamic_fee_config = match update_dynamic_fee_config {
            Some(update_dynamic_fee_config) => update_dynamic_fee_config,
            None => panic!("update_dynamic_fee_config discriminator must be mapped"),
        };
        assert_eq!(update_dynamic_fee_config.event_kind, "raydium_clmm.update_dynamic_fee_config");
        let cpi_event = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_clmm",
            Some("e445a52e51cb9a1d"),
            1,
        );
        let cpi_event = match cpi_event {
            Some(cpi_event) => cpi_event,
            None => panic!("clmm cpi_event discriminator must be mapped"),
        };
        assert_eq!(cpi_event.event_kind, "raydium_clmm.cpi_event");
    }

    #[test]
    fn maps_observed_raydium_cpmm_non_swap_discriminators() {
        let expected = [
            ("9c5420764587467b", 4_usize, "raydium_cpmm.close_permission_pda"),
            ("1416567bc61cdb84", 13_usize, "raydium_cpmm.collect_creator_fee"),
            ("a78a4e95dfc2067e", 12_usize, "raydium_cpmm.collect_fund_fee"),
            ("8888fcddc2427e59", 12_usize, "raydium_cpmm.collect_protocol_fee"),
            ("8934edd4d7756c68", 3_usize, "raydium_cpmm.create_amm_config"),
            ("878802d889a9b5ca", 4_usize, "raydium_cpmm.create_permission_pda"),
            ("f223c68952e1f2b6", 13_usize, "raydium_cpmm.deposit"),
            ("afaf6d1f0d989bed", 20_usize, "raydium_cpmm.initialize"),
            ("3f37fe4131b25979", 21_usize, "raydium_cpmm.initialize_with_permission"),
            ("313cae889a1c74c8", 2_usize, "raydium_cpmm.update_amm_config"),
            ("82576c062ee0757b", 2_usize, "raydium_cpmm.update_pool_status"),
            ("b712469c946da122", 14_usize, "raydium_cpmm.withdraw"),
            ("e445a52e51cb9a1d", 1_usize, "raydium_cpmm.cpi_event"),
            ("40f4bc78a7e9690a", 3_usize, "raydium_cpmm.anchor_idl_instruction"),
            ("40f4bc78a7e9690a", 6_usize, "raydium_cpmm.anchor_idl_instruction"),
        ];
        for (discriminator, account_count, event_kind) in expected {
            let mapped = super::raydium_mapped_non_trade_instruction_spec(
                "raydium_cpmm",
                Some(discriminator),
                account_count,
            );
            let mapped = match mapped {
                Some(mapped) => mapped,
                None => panic!("raydium cpmm discriminator must be mapped: {}", discriminator),
            };
            assert_eq!(mapped.event_kind, event_kind);
        }
    }

    #[test]
    fn extracts_instruction_discriminator_from_camel_and_snake_payload_keys() {
        let camel_payload = serde_json::json!({
            "instructionDiscriminatorHex": "e992d18ecf6840bc"
        });
        assert_eq!(
            super::instruction_discriminator_hex_from_payload(&camel_payload),
            Some("e992d18ecf6840bc".to_string())
        );
        let snake_payload = serde_json::json!({
            "instruction_discriminator_hex": "8888fcddc2427e59"
        });
        assert_eq!(
            super::instruction_discriminator_hex_from_payload(&snake_payload),
            Some("8888fcddc2427e59".to_string())
        );
    }

    #[test]
    fn skips_raydium_audit_when_discriminator_was_already_decoded() {
        let mut keys = std::collections::HashSet::<std::string::String>::new();
        keys.insert(super::raydium_decoded_discriminator_key("raydium_clmm", "e992d18ecf6840bc"));
        assert!(super::raydium_instruction_already_decoded_by_discriminator(
            &keys,
            "raydium_clmm",
            Some("e992d18ecf6840bc"),
        ));
        assert!(!super::raydium_instruction_already_decoded_by_discriminator(
            &keys,
            "raydium_clmm",
            Some("8888fcddc2427e59"),
        ));
    }

    #[test]
    fn immediately_materializes_only_targeted_clmm_non_trade_events() {
        assert!(super::should_immediately_materialize_decoded_non_trade_event(
            "raydium_clmm.create_pool",
        ));
        assert!(super::should_immediately_materialize_decoded_non_trade_event(
            "raydium_clmm.collect_protocol_fee",
        ));
        assert!(!super::should_immediately_materialize_decoded_non_trade_event(
            "raydium_clmm.swap",
        ));
        assert!(!super::should_immediately_materialize_decoded_non_trade_event(
            "raydium_cpmm.collect_protocol_fee",
        ));
    }

    #[test]
    fn maps_raydium_launchpad_non_trade_discriminators() {
        let buy = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_launchpad",
            Some("faea0d7bd59c13ec"),
            11,
        );
        let buy = match buy {
            Some(buy) => buy,
            None => panic!("buy_exact_in discriminator must map"),
        };
        assert_eq!(buy.instruction_name, "buy_exact_in");
        assert_eq!(buy.event_kind, "raydium_launchpad.buy_exact_in");
        assert_eq!(buy.pool_account_index, Some(4));
        assert_eq!(buy.token_a_mint_index, Some(9));
        assert_eq!(buy.token_b_mint_index, Some(10));
        assert_eq!(buy.lp_mint_index, None);

        let migration = super::raydium_mapped_non_trade_instruction_spec(
            "raydium_launchpad",
            Some("cf52c091fecf91df"),
            1,
        );
        let migration = match migration {
            Some(migration) => migration,
            None => panic!("migrate_to_amm discriminator must map"),
        };
        assert_eq!(migration.event_kind, "raydium_launchpad.migrate_to_amm");
        assert_eq!(migration.pool_account_index, None);
    }

    #[test]
    fn maps_instruction_audit_event_kind_for_raydium_and_meteora_dlmm_protocols() {
        assert_eq!(
            super::instruction_audit_event_kind_by_protocol("raydium_clmm"),
            Some("raydium_clmm.instruction_audit")
        );
        assert_eq!(
            super::instruction_audit_event_kind_by_protocol("raydium_launchpad"),
            Some("raydium_launchpad.instruction_audit")
        );
        assert_eq!(
            super::instruction_audit_event_kind_by_protocol("meteora_dlmm"),
            Some("meteora_dlmm.instruction_audit")
        );
        assert_eq!(
            super::instruction_audit_event_kind_by_protocol("meteora_damm_v1"),
            Some("meteora_damm_v1.instruction_audit")
        );
        assert_eq!(
            super::instruction_audit_event_kind_by_protocol("meteora_damm_v2"),
            Some("meteora_damm_v2.instruction_audit")
        );
        assert_eq!(
            super::instruction_audit_event_kind_by_protocol("meteora_dbc"),
            Some("meteora_dbc.instruction_audit")
        );
        assert_eq!(super::instruction_audit_event_kind_by_protocol("unknown"), None);
    }

    #[test]
    fn upstream_registry_match_payload_is_never_trade_or_candle_candidate() {
        let transaction = crate::ChainTransactionDto::new(
            "upstream-registry-test-signature".to_string(),
            Some(123),
            Some(123456),
            Some("test".to_string()),
            None,
            None,
            None,
            "{}".to_string(),
        );
        let instruction = crate::ChainInstructionDto::new(
            1,
            None,
            0,
            None,
            Some(crate::METEORA_DAMM_V2_PROGRAM_ID.to_string()),
            None,
            None,
            "[]".to_string(),
            Some("data".to_string()),
            None,
            None,
        );
        let registry_match = crate::UpstreamRegistryEntryDto {
            source_repo: Some("sevenlabs-hq/carbon".to_string()),
            source_path: Some("decoders/example.rs".to_string()),
            decoder_code: "meteora_damm_v2".to_string(),
            program_id: Some(crate::METEORA_DAMM_V2_PROGRAM_ID.to_string()),
            program_family: "meteora".to_string(),
            surface_kind: "amm".to_string(),
            entry_kind: crate::ENTRY_KIND_INSTRUCTION.to_string(),
            entry_name: "swap".to_string(),
            discriminator_hex: Some("f8c69e91e17587c8".to_string()),
            discriminator_len: Some(8),
            proof_status: crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED.to_string(),
            notes: "test".to_string(),
        };
        let payload = super::build_upstream_registry_instruction_match_payload(
            &transaction,
            &instruction,
            &registry_match,
            Some("data"),
        );
        assert_eq!(payload.get("tradeCandidate").and_then(serde_json::Value::as_bool), Some(false));
        assert_eq!(
            payload.get("candleCandidate").and_then(serde_json::Value::as_bool),
            Some(false)
        );
        assert_eq!(
            payload.get("upstreamProofStatus").and_then(serde_json::Value::as_str),
            Some(crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED)
        );
    }
}
