// file: kb_lib/src/onchain_dex_pair_discovery.rs

//! On-chain DEX pair/pool discovery helpers used by Demo3.
//!
//! This module intentionally does not persist discovered transactions and does
//! not promote a program or pool as verified. It only queries Solana RPC for
//! recent signatures of a DEX program id, fetches the corresponding
//! transactions, and extracts candidate signatures / pools / mints that can be
//! fed later into the existing backfill pipeline.

/// Request for on-chain DEX pair/pool discovery.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexPairDiscoveryRequestDto {
    /// Optional DEX code from the support matrix.
    pub dex_code: std::option::Option<std::string::String>,
    /// Optional Solana program id used to filter matched instructions. When absent, `dex_code` must resolve to a matrix program id.
    pub program_id: std::option::Option<std::string::String>,
    /// Optional signature source. Supported values: `program_id` and `address`.
    pub signature_source: std::option::Option<std::string::String>,
    /// Optional address used with `signature_source = address` to fetch signatures for a pool/vault/position/config/mint account.
    pub source_address: std::option::Option<std::string::String>,
    /// Optional extra source addresses. Demo3 scans every valid address in this list and de-duplicates signatures.
    #[serde(default)]
    pub source_addresses: std::vec::Vec<std::string::String>,
    /// Optional signature cursor passed to Solana `getSignaturesForAddress.before`.
    #[serde(default)]
    pub before_signature: std::option::Option<std::string::String>,
    /// Optional signature cursor passed to Solana `getSignaturesForAddress.until`.
    #[serde(default)]
    pub until_signature: std::option::Option<std::string::String>,
    /// Maximum number of signature pages to fetch per source address.
    #[serde(default)]
    pub max_pages: u32,
    /// Signature processing order: `newest_first` or `oldest_first` after all requested pages are fetched.
    #[serde(default)]
    pub scan_order: std::option::Option<std::string::String>,
    /// Optional target event family used to score and filter candidate signatures.
    pub target_event: std::option::Option<std::string::String>,
    /// Optional instruction-name filter, for example `raydium_cpmm.withdraw` or `withdraw`.
    #[serde(default)]
    pub target_instruction_name: std::option::Option<std::string::String>,
    /// Optional first-eight-byte discriminator filter as lower hex; accepts comma/space separated values.
    #[serde(default)]
    pub target_discriminator_hex: std::option::Option<std::string::String>,
    /// Whether transactions containing swap-like logs should be skipped.
    pub exclude_swaps: bool,
    /// Whether failed transactions should be returned as candidates.
    pub include_failed: bool,
    /// HTTP endpoint role used to query Solana RPC.
    pub http_role: std::string::String,
    /// Maximum number of recent signatures to inspect for the program id.
    pub signature_limit: u32,
    /// Maximum number of transactions to fetch from the signature list.
    pub transaction_limit: u32,
    /// Maximum number of candidate rows to return.
    pub candidate_limit: u32,
}

/// Result of one on-chain DEX pair/pool discovery run.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexPairDiscoveryResultDto {
    /// Normalized request actually used by the service.
    pub request: crate::OnchainDexPairDiscoveryRequestDto,
    /// DEX code resolved from the matrix when available.
    pub resolved_dex_code: std::option::Option<std::string::String>,
    /// Program id used to filter matched instructions.
    pub resolved_program_id: std::string::String,
    /// Signature source actually used by `getSignaturesForAddress`.
    pub resolved_signature_source: std::string::String,
    /// Address scanned through `getSignaturesForAddress`.
    pub resolved_signature_address: std::string::String,
    /// All addresses scanned through `getSignaturesForAddress` after normalization.
    pub resolved_signature_addresses: std::vec::Vec<std::string::String>,
    /// Last page cursor candidates by source address, useful for a manual next paged run.
    pub next_before_by_address: std::vec::Vec<crate::OnchainDexPaginationCursorDto>,
    /// Number of signature pages fetched across all source addresses.
    pub fetched_signature_page_count: usize,
    /// Number of unique signatures fetched after de-duplicating multi-source scans.
    pub unique_fetched_signature_count: usize,
    /// Number of unique signatures returned as candidates.
    pub unique_signature_count: usize,
    /// Unique signatures returned as backfill-ready hints.
    pub unique_backfill_signatures: std::vec::Vec<std::string::String>,
    /// Unique successful transaction signatures returned as preferred backfill hints.
    pub successful_backfill_signatures: std::vec::Vec<std::string::String>,
    /// Unique failed transaction signatures returned as audit-only backfill hints.
    pub failed_backfill_signatures: std::vec::Vec<std::string::String>,
    /// Successful backfill signatures grouped by candidate kind.
    pub successful_backfill_signatures_by_kind:
        std::vec::Vec<crate::OnchainDexBackfillSignaturesByKindDto>,
    /// Failed backfill signatures grouped by candidate kind.
    pub failed_backfill_signatures_by_kind:
        std::vec::Vec<crate::OnchainDexBackfillSignaturesByKindDto>,
    /// Candidate summary grouped by kind and transaction success state.
    pub candidate_kind_summary: std::vec::Vec<crate::OnchainDexCandidateKindSummaryDto>,
    /// Rejected candidate summary grouped by kind/prefix/name/reason.
    pub rejected_candidate_summary: std::vec::Vec<crate::OnchainDexRejectedCandidateSummaryDto>,
    /// Number of signatures returned by the RPC endpoint.
    pub fetched_signature_count: usize,
    /// Number of transactions fetched with `getTransaction`.
    pub fetched_transaction_count: usize,
    /// Number of `getTransaction` calls returning null.
    pub missing_transaction_count: usize,
    /// Number of failed transactions encountered.
    pub failed_transaction_count: usize,
    /// Number of failed transactions skipped because `include_failed` is false.
    pub skipped_failed_transaction_count: usize,
    /// Number of swap-log transactions skipped by the transaction-level swap guard.
    pub skipped_swap_log_transaction_count: usize,
    /// Number of top-level and inner instructions scanned after RPC transaction decoding.
    pub scanned_instruction_count: usize,
    /// Number of scanned instructions whose resolved program id matches the requested program.
    pub target_program_instruction_count: usize,
    /// Number of fetched transactions whose account keys mention the requested program id.
    pub target_program_account_mention_transaction_count: usize,
    /// Number of fetched transactions whose logs mention the requested program id.
    pub target_program_log_mention_transaction_count: usize,
    /// Number of fetched transactions mentioning the requested program without a resolved matching instruction.
    pub target_program_mention_without_instruction_count: usize,
    /// Number of failed transactions mentioning the requested program without a resolved matching instruction.
    pub failed_target_program_mention_without_instruction_count: usize,
    /// Number of candidate rows extracted before target-event filtering.
    pub extracted_candidate_count: usize,
    /// Number of candidate rows rejected by target-event filtering.
    pub target_rejected_candidate_count: usize,
    /// Number of candidate rows returned.
    pub candidate_count: usize,
    /// Candidate on-chain rows.
    pub candidates: std::vec::Vec<crate::OnchainDexPairCandidateDto>,
}

/// Cursor hint for one source address scanned by Demo3.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexPaginationCursorDto {
    /// Source address scanned by Solana `getSignaturesForAddress`.
    pub address: std::string::String,
    /// Signature usable as the next `before_signature` value for this same source address.
    pub next_before_signature: std::option::Option<std::string::String>,
    /// Number of raw signatures fetched for this address.
    pub fetched_signature_count: usize,
    /// Number of RPC pages fetched for this address.
    pub fetched_page_count: usize,
}

/// Backfill signatures grouped by candidate kind.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexBackfillSignaturesByKindDto {
    /// Candidate kind.
    pub candidate_kind: std::string::String,
    /// Unique transaction signatures for this candidate kind.
    pub signatures: std::vec::Vec<std::string::String>,
}

/// Candidate summary grouped by kind and transaction success state.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexCandidateKindSummaryDto {
    /// Candidate kind.
    pub candidate_kind: std::string::String,
    /// Number of returned candidate rows of this kind.
    pub count: usize,
    /// Number of returned successful candidate rows of this kind.
    pub success_count: usize,
    /// Number of returned failed candidate rows of this kind.
    pub failed_count: usize,
    /// Number of unique transaction signatures for this kind.
    pub unique_signature_count: usize,
}

/// Rejected candidate summary for one discovery run.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexRejectedCandidateSummaryDto {
    /// Candidate kind at rejection time.
    pub candidate_kind: std::string::String,
    /// Instruction data prefix when known.
    pub instruction_data_prefix: std::option::Option<std::string::String>,
    /// Instruction name when known.
    pub instruction_name: std::option::Option<std::string::String>,
    /// Rejection reason.
    pub rejection_reason: std::string::String,
    /// Number of matching rejected rows.
    pub count: usize,
}

/// Candidate transaction/instruction observed on-chain for one DEX program id.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexPairCandidateDto {
    /// Transaction signature.
    pub signature: std::string::String,
    /// Transaction slot when present.
    pub slot: std::option::Option<u64>,
    /// Transaction block time when present.
    pub block_time: std::option::Option<i64>,
    /// Whether the transaction has a non-null `meta.err` value.
    pub failed: bool,
    /// Program id matched by this candidate.
    pub program_id: std::string::String,
    /// DEX code resolved from the support matrix when available.
    pub dex_code: std::option::Option<std::string::String>,
    /// Candidate kind inferred from decoded instruction data, parsed fields or logs.
    pub candidate_kind: std::string::String,
    /// Confidence of the candidate extraction: `high`, `medium` or `low`.
    pub confidence: std::string::String,
    /// Top-level instruction index when known.
    pub instruction_index: std::option::Option<i64>,
    /// Inner instruction index when this candidate came from `meta.innerInstructions`.
    pub inner_instruction_index: std::option::Option<i64>,
    /// Instruction name inferred from parsed payload or logs.
    pub instruction_name: std::option::Option<std::string::String>,
    /// Prefix of the raw base58 instruction data, useful for audit grouping.
    pub instruction_data_prefix: std::option::Option<std::string::String>,
    /// First eight instruction-data bytes as lower hex.
    pub instruction_discriminator_hex: std::option::Option<std::string::String>,
    /// Candidate pool address when it can be extracted safely or heuristically.
    pub pool_address: std::option::Option<std::string::String>,
    /// Candidate token A/base mint when it can be extracted.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Candidate token B/quote mint when it can be extracted.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Verified pool address when a DEX decoder or stable layout proves it.
    pub verified_pool_address: std::option::Option<std::string::String>,
    /// Token mints observed generically from transaction token balance arrays.
    pub observed_token_mints: std::vec::Vec<std::string::String>,
    /// Token balance deltas observed through transaction metadata.
    pub token_balance_deltas: std::vec::Vec<crate::OnchainDexTokenBalanceDeltaDto>,
    /// Program-owned or writable accounts that may be pool/config/state accounts.
    pub candidate_pool_accounts: std::vec::Vec<crate::OnchainDexCandidateAccountDto>,
    /// Token accounts that may be pool vaults.
    pub candidate_token_vault_accounts: std::vec::Vec<crate::OnchainDexCandidateAccountDto>,
    /// Other candidate accounts attached to the matched instruction.
    pub candidate_program_accounts: std::vec::Vec<crate::OnchainDexCandidateAccountDto>,
    /// Short account sample from the matched instruction.
    pub account_samples: std::vec::Vec<std::string::String>,
    /// Short log sample from the transaction.
    pub log_samples: std::vec::Vec<std::string::String>,
    /// Suggested next action for the user.
    pub backfill_hint: std::string::String,
}

/// Token-balance delta observed in one transaction through Solana transaction metadata.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexTokenBalanceDeltaDto {
    /// Token account index in the transaction message when available.
    pub account_index: std::option::Option<i64>,
    /// Token account address resolved from the transaction account keys.
    pub account_address: std::option::Option<std::string::String>,
    /// SPL Token or Token-2022 mint address.
    pub mint: std::string::String,
    /// Token account owner when Solana RPC exposes it.
    pub owner: std::option::Option<std::string::String>,
    /// Token program id when Solana RPC exposes it.
    pub token_program: std::option::Option<std::string::String>,
    /// Raw token amount before the transaction.
    pub pre_amount_raw: std::option::Option<std::string::String>,
    /// Raw token amount after the transaction.
    pub post_amount_raw: std::option::Option<std::string::String>,
    /// Signed raw delta when both raw amounts fit in a signed integer.
    pub delta_raw: std::option::Option<std::string::String>,
}

/// Candidate account inferred from generic transaction evidence.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnchainDexCandidateAccountDto {
    /// Account address.
    pub address: std::string::String,
    /// Account index in the transaction message when known.
    pub account_index: std::option::Option<i64>,
    /// Whether the account is writable in the transaction message when known.
    pub writable: std::option::Option<bool>,
    /// Whether the account is a signer in the transaction message when known.
    pub signer: std::option::Option<bool>,
    /// Generic role inferred by Demo3, for example `program_owned_candidate` or `token_vault_candidate`.
    pub inferred_role: std::string::String,
    /// Confidence of the generic account inference.
    pub confidence: std::string::String,
    /// Short reason explaining why the account is listed.
    pub reason: std::string::String,
}

/// On-chain pair/pool discovery service.
#[derive(Debug, Clone)]
pub struct OnchainDexPairDiscoveryService {
    http_pool: std::sync::Arc<crate::HttpEndpointPool>,
}

impl OnchainDexPairDiscoveryService {
    /// Creates a new on-chain DEX discovery service.
    pub fn new(http_pool: std::sync::Arc<crate::HttpEndpointPool>) -> Self {
        return Self { http_pool };
    }

    /// Searches recent on-chain transactions for DEX program candidates.
    pub async fn discover(
        &self,
        request: crate::OnchainDexPairDiscoveryRequestDto,
    ) -> Result<crate::OnchainDexPairDiscoveryResultDto, crate::Error> {
        let normalized_request = normalize_request(request);
        let resolved = match resolve_program_id(&normalized_request) {
            Ok(resolved) => resolved,
            Err(error) => return Err(error),
        };
        let signature_sources = match resolve_signature_sources(&normalized_request, &resolved) {
            Ok(signature_sources) => signature_sources,
            Err(error) => return Err(error),
        };
        let signature_fetch_result =
            self.fetch_signature_pages(&normalized_request, &signature_sources).await;
        let signature_fetch = match signature_fetch_result {
            Ok(signature_fetch) => signature_fetch,
            Err(error) => return Err(error),
        };
        let mut result = crate::OnchainDexPairDiscoveryResultDto {
            request: normalized_request.clone(),
            resolved_dex_code: resolved.dex_code.clone(),
            resolved_program_id: resolved.program_id.clone(),
            resolved_signature_source: summarize_signature_source(signature_sources.as_slice()),
            resolved_signature_address: summarize_signature_address(signature_sources.as_slice()),
            resolved_signature_addresses: signature_sources_to_addresses(
                signature_sources.as_slice(),
            ),
            next_before_by_address: signature_fetch.next_before_by_address,
            fetched_signature_page_count: signature_fetch.fetched_signature_page_count,
            unique_fetched_signature_count: signature_fetch.unique_signature_count,
            unique_signature_count: 0,
            unique_backfill_signatures: std::vec::Vec::new(),
            successful_backfill_signatures: std::vec::Vec::new(),
            failed_backfill_signatures: std::vec::Vec::new(),
            successful_backfill_signatures_by_kind: std::vec::Vec::new(),
            failed_backfill_signatures_by_kind: std::vec::Vec::new(),
            candidate_kind_summary: std::vec::Vec::new(),
            rejected_candidate_summary: std::vec::Vec::new(),
            fetched_signature_count: signature_fetch.raw_signature_count,
            fetched_transaction_count: 0,
            missing_transaction_count: 0,
            failed_transaction_count: 0,
            skipped_failed_transaction_count: 0,
            skipped_swap_log_transaction_count: 0,
            scanned_instruction_count: 0,
            target_program_instruction_count: 0,
            target_program_account_mention_transaction_count: 0,
            target_program_log_mention_transaction_count: 0,
            target_program_mention_without_instruction_count: 0,
            failed_target_program_mention_without_instruction_count: 0,
            extracted_candidate_count: 0,
            target_rejected_candidate_count: 0,
            candidate_count: 0,
            candidates: std::vec::Vec::new(),
        };
        let transaction_limit = normalized_request.transaction_limit as usize;
        let candidate_limit = normalized_request.candidate_limit as usize;
        let mut returned_candidate_keys: std::vec::Vec<std::string::String> = std::vec::Vec::new();
        let mut scanned = 0usize;
        for signature_status in signature_fetch.signatures {
            if scanned >= transaction_limit {
                break;
            }
            if result.candidates.len() >= candidate_limit {
                break;
            }
            scanned += 1;
            let signature = signature_status.signature.clone();
            let transaction_result = self
                .fetch_transaction(normalized_request.http_role.as_str(), signature.clone())
                .await;
            let transaction_value = match transaction_result {
                Ok(transaction_value) => transaction_value,
                Err(error) => return Err(error),
            };
            if transaction_value.is_null() {
                result.missing_transaction_count += 1;
                continue;
            }
            result.fetched_transaction_count += 1;
            let failed = transaction_failed(&transaction_value);
            if failed {
                result.failed_transaction_count += 1;
                if !normalized_request.include_failed {
                    result.skipped_failed_transaction_count += 1;
                    continue;
                }
            }
            let logs = extract_log_messages(&transaction_value);
            let target_keeps_mixed_swaps = target_event_keeps_mixed_swap_transactions(
                normalized_request.target_event.as_deref(),
                normalized_request.target_instruction_name.as_deref(),
                normalized_request.target_discriminator_hex.as_deref(),
            );
            if normalized_request.exclude_swaps
                && logs_contain_swap(logs.as_slice())
                && !target_keeps_mixed_swaps
            {
                result.skipped_swap_log_transaction_count += 1;
                continue;
            }
            let extraction = extract_candidates_from_transaction(
                signature.as_str(),
                &transaction_value,
                resolved.program_id.as_str(),
                resolved.dex_code.clone(),
                normalized_request.target_event.as_deref(),
                normalized_request.target_instruction_name.as_deref(),
                normalized_request.target_discriminator_hex.as_deref(),
            );
            result.scanned_instruction_count += extraction.scanned_instruction_count;
            result.target_program_instruction_count += extraction.target_program_instruction_count;
            if extraction.target_program_account_mentioned {
                result.target_program_account_mention_transaction_count += 1;
            }
            if extraction.target_program_log_mentioned {
                result.target_program_log_mention_transaction_count += 1;
            }
            if extraction.target_program_instruction_count == 0
                && (extraction.target_program_account_mentioned
                    || extraction.target_program_log_mentioned)
            {
                result.target_program_mention_without_instruction_count += 1;
                if failed {
                    result.failed_target_program_mention_without_instruction_count += 1;
                }
            }
            result.extracted_candidate_count += extraction.extracted_candidate_count;
            result.target_rejected_candidate_count += extraction.target_rejected_candidate_count;
            merge_rejected_candidate_summaries(
                &mut result.rejected_candidate_summary,
                extraction.rejected_candidate_summary,
            );
            for candidate in extraction.candidates {
                if result.candidates.len() >= candidate_limit {
                    break;
                }
                let candidate_key = candidate_identity_key(&candidate);
                if returned_candidate_keys.iter().any(|existing| return existing == &candidate_key)
                {
                    continue;
                }
                returned_candidate_keys.push(candidate_key);
                push_unique_string(
                    &mut result.unique_backfill_signatures,
                    candidate.signature.clone(),
                );
                result.candidates.push(candidate);
            }
        }
        rebuild_candidate_backfill_indexes(&mut result);
        return Ok(result);
    }

    async fn fetch_signature_pages(
        &self,
        request: &crate::OnchainDexPairDiscoveryRequestDto,
        sources: &[ResolvedSignatureSource],
    ) -> Result<SignaturePageFetch, crate::Error> {
        let effective_limit = clamp_usize(request.signature_limit as usize, 1, 1000);
        let effective_max_pages = clamp_usize(request.max_pages as usize, 1, 25);
        let mut unique_signatures: std::vec::Vec<
            solana_rpc_client_api::response::RpcConfirmedTransactionStatusWithSignature,
        > = std::vec::Vec::new();
        let mut seen_signatures: std::vec::Vec<std::string::String> = std::vec::Vec::new();
        let mut next_before_by_address = std::vec::Vec::new();
        let mut raw_signature_count = 0usize;
        let mut fetched_signature_page_count = 0usize;
        let initial_before_signature =
            match parse_signature_cursor(request.before_signature.as_deref(), "before_signature") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        let until_signature =
            match parse_signature_cursor(request.until_signature.as_deref(), "until_signature") {
                Ok(value) => value,
                Err(error) => return Err(error),
            };
        for source in sources {
            let mut fetched_for_source = 0usize;
            let mut pages_for_source = 0usize;
            let mut next_before_signature = initial_before_signature.clone();
            let mut last_seen_signature: std::option::Option<std::string::String> = None;
            let mut page_index = 0usize;
            while page_index < effective_max_pages {
                let config = solana_rpc_client_api::config::RpcSignaturesForAddressConfig {
                    before: next_before_signature.clone(),
                    until: until_signature.clone(),
                    limit: Some(effective_limit),
                    commitment: None,
                    min_context_slot: None,
                };
                let page_result = self
                    .http_pool
                    .get_signatures_for_address_for_role(
                        request.http_role.as_str(),
                        source.address.clone(),
                        Some(config),
                    )
                    .await;
                let page = match page_result {
                    Ok(page) => page,
                    Err(error) => return Err(error),
                };
                pages_for_source += 1;
                fetched_signature_page_count += 1;
                let page_len = page.len();
                raw_signature_count += page_len;
                if page.is_empty() {
                    break;
                }
                let mut next_cursor_option: std::option::Option<std::string::String> = None;
                for signature_status in page {
                    fetched_for_source += 1;
                    next_cursor_option = Some(signature_status.signature.clone());
                    last_seen_signature = Some(signature_status.signature.clone());
                    if seen_signatures
                        .iter()
                        .any(|existing| return existing == &signature_status.signature)
                    {
                        continue;
                    }
                    seen_signatures.push(signature_status.signature.clone());
                    unique_signatures.push(signature_status);
                }
                let next_cursor = match next_cursor_option {
                    Some(next_cursor) => next_cursor,
                    None => break,
                };
                let parsed_next_cursor = match parse_signature_cursor(
                    Some(next_cursor.as_str()),
                    "next_before_signature",
                ) {
                    Ok(parsed_next_cursor) => parsed_next_cursor,
                    Err(error) => return Err(error),
                };
                next_before_signature = parsed_next_cursor;
                if page_len < effective_limit {
                    break;
                }
                page_index += 1;
            }
            next_before_by_address.push(crate::OnchainDexPaginationCursorDto {
                address: source.address.clone(),
                next_before_signature: last_seen_signature,
                fetched_signature_count: fetched_for_source,
                fetched_page_count: pages_for_source,
            });
        }
        if normalize_scan_order(request.scan_order.as_deref()) == "oldest_first" {
            unique_signatures.reverse();
        }
        return Ok(SignaturePageFetch {
            signatures: unique_signatures,
            raw_signature_count,
            fetched_signature_page_count,
            unique_signature_count: seen_signatures.len(),
            next_before_by_address,
        });
    }

    async fn fetch_transaction(
        &self,
        http_role: &str,
        signature: std::string::String,
    ) -> Result<serde_json::Value, crate::Error> {
        let config = Some(serde_json::json!({
            "encoding": "jsonParsed",
            "maxSupportedTransactionVersion": 0
        }));
        return self.http_pool.get_transaction_raw_for_role(http_role, signature, config).await;
    }
}

#[derive(Debug, Clone)]
struct ResolvedDiscoveryTarget {
    dex_code: std::option::Option<std::string::String>,
    program_id: std::string::String,
}

#[derive(Debug, Clone)]
struct ResolvedSignatureSource {
    source: std::string::String,
    address: std::string::String,
}

#[derive(Debug, Clone)]
struct SignaturePageFetch {
    signatures:
        std::vec::Vec<solana_rpc_client_api::response::RpcConfirmedTransactionStatusWithSignature>,
    raw_signature_count: usize,
    fetched_signature_page_count: usize,
    unique_signature_count: usize,
    next_before_by_address: std::vec::Vec<crate::OnchainDexPaginationCursorDto>,
}

#[derive(Debug, Clone)]
struct OnchainInstructionCandidate {
    instruction_index: std::option::Option<i64>,
    inner_instruction_index: std::option::Option<i64>,
    program_id: std::option::Option<std::string::String>,
    accounts: std::vec::Vec<std::string::String>,
    data: std::option::Option<std::string::String>,
    parsed: std::option::Option<serde_json::Value>,
}

#[derive(Debug, Clone)]
struct AccountKeyInfo {
    index: i64,
    address: std::string::String,
    signer: std::option::Option<bool>,
    writable: std::option::Option<bool>,
}

#[derive(Debug, Clone)]
struct TransactionGenericEvidence {
    account_keys: std::vec::Vec<AccountKeyInfo>,
    observed_token_mints: std::vec::Vec<std::string::String>,
    token_balance_deltas: std::vec::Vec<crate::OnchainDexTokenBalanceDeltaDto>,
}

#[derive(Debug, Clone)]
struct TokenBalanceAccumulator {
    account_index: std::option::Option<i64>,
    account_address: std::option::Option<std::string::String>,
    mint: std::string::String,
    owner: std::option::Option<std::string::String>,
    token_program: std::option::Option<std::string::String>,
    pre_amount_raw: std::option::Option<std::string::String>,
    post_amount_raw: std::option::Option<std::string::String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Demo3MeteoraDammV1InstructionKind {
    InitializePermissionlessConstantProductPoolWithConfig,
    InitializePermissionlessConstantProductPoolWithConfig2,
    InitializeCustomizablePermissionlessConstantProductPool,
    Swap,
    AddBalanceLiquidity,
    AddImbalanceLiquidity,
    BootstrapLiquidity,
    RemoveBalanceLiquidity,
    RemoveLiquiditySingleSide,
    ClaimFee,
    CreateLockEscrow,
    Lock,
    Unknown,
}

const DEMO3_DAMM_V1_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG: [u8; 8] =
    [0x07, 0xa6, 0x8a, 0xab, 0xce, 0xab, 0xec, 0xf4];
const DEMO3_DAMM_V1_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG2: [u8; 8] =
    [0x30, 0x95, 0xdc, 0x82, 0x3d, 0x0b, 0x09, 0xb2];
const DEMO3_DAMM_V1_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_CP_POOL: [u8; 8] =
    [0x91, 0x18, 0xac, 0xc2, 0xdb, 0x7d, 0x03, 0xbe];
const DEMO3_DAMM_V1_SWAP: [u8; 8] = [0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8];
const DEMO3_DAMM_V1_ADD_BALANCE_LIQUIDITY: [u8; 8] =
    [0xa8, 0xe3, 0x32, 0x3e, 0xbd, 0xab, 0x54, 0xb0];
const DEMO3_DAMM_V1_ADD_IMBALANCE_LIQUIDITY: [u8; 8] =
    [0x4f, 0x23, 0x7a, 0x54, 0xad, 0x0f, 0x5d, 0xbf];
const DEMO3_DAMM_V1_BOOTSTRAP_LIQUIDITY: [u8; 8] = [0x04, 0xe4, 0xd7, 0x47, 0xe1, 0xfd, 0x77, 0xce];
const DEMO3_DAMM_V1_REMOVE_BALANCE_LIQUIDITY: [u8; 8] =
    [0x85, 0x6d, 0x2c, 0xb3, 0x38, 0xee, 0x72, 0x21];
const DEMO3_DAMM_V1_REMOVE_LIQUIDITY_SINGLE_SIDE: [u8; 8] =
    [0x54, 0x54, 0xb1, 0x42, 0xfe, 0xb9, 0x0a, 0xfb];
const DEMO3_DAMM_V1_CLAIM_FEE: [u8; 8] = [0xa9, 0x20, 0x4f, 0x89, 0x88, 0xe8, 0x46, 0x89];
const DEMO3_DAMM_V1_CREATE_LOCK_ESCROW: [u8; 8] = [0x36, 0x57, 0xa5, 0x13, 0x45, 0xe3, 0xda, 0xe0];
const DEMO3_DAMM_V1_LOCK: [u8; 8] = [0x15, 0x13, 0xd0, 0x2b, 0xed, 0x3e, 0xff, 0x57];

#[derive(Debug, Clone)]
struct OnchainCandidateExtraction {
    candidates: std::vec::Vec<crate::OnchainDexPairCandidateDto>,
    scanned_instruction_count: usize,
    target_program_instruction_count: usize,
    target_program_account_mentioned: bool,
    target_program_log_mentioned: bool,
    extracted_candidate_count: usize,
    target_rejected_candidate_count: usize,
    rejected_candidate_summary: std::vec::Vec<crate::OnchainDexRejectedCandidateSummaryDto>,
}

fn normalize_request(
    request: crate::OnchainDexPairDiscoveryRequestDto,
) -> crate::OnchainDexPairDiscoveryRequestDto {
    let http_role = normalize_string(request.http_role, "history_backfill");
    return crate::OnchainDexPairDiscoveryRequestDto {
        dex_code: normalize_optional_string(request.dex_code),
        program_id: normalize_optional_string(request.program_id),
        signature_source: normalize_signature_source(request.signature_source),
        source_address: normalize_optional_string(request.source_address),
        source_addresses: normalize_source_addresses(request.source_addresses),
        before_signature: normalize_optional_signature(request.before_signature),
        until_signature: normalize_optional_signature(request.until_signature),
        max_pages: clamp_u32(default_if_zero(request.max_pages, 1), 1, 25),
        scan_order: Some(normalize_scan_order(request.scan_order.as_deref()).to_string()),
        target_event: normalize_target_event(request.target_event),
        target_instruction_name: normalize_instruction_name_filter(request.target_instruction_name),
        target_discriminator_hex: normalize_discriminator_filter(request.target_discriminator_hex),
        exclude_swaps: request.exclude_swaps,
        include_failed: request.include_failed,
        http_role,
        signature_limit: clamp_u32(request.signature_limit, 1, 1000),
        transaction_limit: clamp_u32(request.transaction_limit, 1, 250),
        candidate_limit: clamp_u32(request.candidate_limit, 1, 100),
    };
}

fn normalize_string(value: std::string::String, fallback: &str) -> std::string::String {
    let trimmed = value.trim().to_string();
    if trimmed.is_empty() {
        return fallback.to_string();
    }
    return trimmed;
}

fn normalize_optional_string(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value.trim().to_string(),
        None => return None,
    };
    if value.is_empty() {
        return None;
    }
    return Some(value);
}

fn normalize_optional_signature(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    return normalize_optional_string(value);
}

fn normalize_source_addresses(
    values: std::vec::Vec<std::string::String>,
) -> std::vec::Vec<std::string::String> {
    let mut output = std::vec::Vec::new();
    for value in values {
        append_source_address_tokens(value.as_str(), &mut output);
    }
    return output;
}

fn append_source_address_tokens(value: &str, output: &mut std::vec::Vec<std::string::String>) {
    for token in value.split(|character: char| {
        return character == ',' || character == ';' || character.is_whitespace();
    }) {
        let trimmed = token.trim();
        if trimmed.is_empty() {
            continue;
        }
        push_unique_string(output, trimmed.to_string());
    }
}

fn default_if_zero(value: u32, fallback: u32) -> u32 {
    if value == 0 {
        return fallback;
    }
    return value;
}

fn normalize_scan_order(value: std::option::Option<&str>) -> &str {
    let value = match value {
        Some(value) => value.trim().to_ascii_lowercase().replace(['-', ' '], "_"),
        None => return "newest_first",
    };
    if value == "oldest_first" || value == "oldest" {
        return "oldest_first";
    }
    return "newest_first";
}

fn normalize_target_event(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let value = match normalize_optional_string(value) {
        Some(value) => value,
        None => return None,
    };
    let targets = split_target_event_filter(Some(value.as_str()));
    if targets.is_empty() {
        return None;
    }
    return Some(targets.join(","));
}

fn normalize_instruction_name_filter(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let value = match normalize_optional_string(value) {
        Some(value) => value,
        None => return None,
    };
    let mut normalized = std::vec::Vec::new();
    for token in value.split(|character: char| {
        return character == ',' || character == ';' || character.is_whitespace();
    }) {
        let token = token.trim().to_ascii_lowercase();
        if token.is_empty() {
            continue;
        }
        push_unique_string(&mut normalized, token.replace('-', "_"));
    }
    if normalized.is_empty() {
        return None;
    }
    return Some(normalized.join(","));
}

fn normalize_discriminator_filter(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let value = match normalize_optional_string(value) {
        Some(value) => value,
        None => return None,
    };
    let mut normalized = std::vec::Vec::new();
    for token in value.split(|character: char| {
        return character == ',' || character == ';' || character.is_whitespace();
    }) {
        let mut token = token.trim().to_ascii_lowercase();
        if token.starts_with("0x") {
            token = token[2..].to_string();
        }
        if token.len() != 16 || !token.chars().all(|character| return character.is_ascii_hexdigit())
        {
            continue;
        }
        push_unique_string(&mut normalized, token);
    }
    if normalized.is_empty() {
        return None;
    }
    return Some(normalized.join(","));
}

fn normalize_signature_source(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let value = match normalize_optional_string(value) {
        Some(value) => value.to_ascii_lowercase(),
        None => return None,
    };
    let normalized = value.replace(['-', ' '], "_");
    if normalized == "address" || normalized == "program_id" {
        return Some(normalized);
    }
    return None;
}

fn resolve_program_id(
    request: &crate::OnchainDexPairDiscoveryRequestDto,
) -> Result<ResolvedDiscoveryTarget, crate::Error> {
    if let Some(program_id) = &request.program_id {
        let dex_code = match crate::dex_support_matrix_entry_by_program_id(program_id.as_str()) {
            Some(entry) => Some(entry.code.to_string()),
            None => request.dex_code.clone(),
        };
        return Ok(ResolvedDiscoveryTarget { dex_code, program_id: program_id.clone() });
    }
    let dex_code = match &request.dex_code {
        Some(dex_code) => dex_code.clone(),
        None => {
            return Err(crate::Error::Config(
                "on-chain DEX discovery requires a dex_code or a program_id".to_string(),
            ));
        },
    };
    let entry = match crate::dex_support_matrix_entry_by_code(dex_code.as_str()) {
        Some(entry) => entry,
        None => {
            return Err(crate::Error::Config(format!(
                "unknown dex_code '{}' for on-chain DEX discovery",
                dex_code
            )));
        },
    };
    let program_id = match entry.program_id {
        Some(program_id) => program_id.to_string(),
        None => {
            return Err(crate::Error::Config(format!(
                "dex_code '{}' has no verified program_id; provide a program_id explicitly after verification",
                dex_code
            )));
        },
    };
    return Ok(ResolvedDiscoveryTarget { dex_code: Some(dex_code), program_id });
}

fn resolve_signature_sources(
    request: &crate::OnchainDexPairDiscoveryRequestDto,
    resolved: &ResolvedDiscoveryTarget,
) -> Result<std::vec::Vec<ResolvedSignatureSource>, crate::Error> {
    let explicit_source = request.signature_source.as_deref();
    let mut addresses = std::vec::Vec::new();
    if let Some(source_address) = &request.source_address {
        append_source_address_tokens(source_address.as_str(), &mut addresses);
    }
    for source_address in &request.source_addresses {
        append_source_address_tokens(source_address.as_str(), &mut addresses);
    }
    if explicit_source == Some("address") || !addresses.is_empty() {
        if addresses.is_empty() {
            return Err(crate::Error::Config(
                "signature_source='address' requires at least one non-empty source address"
                    .to_string(),
            ));
        }
        let mut sources = std::vec::Vec::new();
        for address in addresses {
            if !looks_like_solana_address(address.as_str()) {
                return Err(crate::Error::Config(format!(
                    "invalid source_address '{}' for on-chain DEX discovery; provide Solana account addresses separated by commas, spaces or new lines",
                    address
                )));
            }
            sources.push(ResolvedSignatureSource { source: "address".to_string(), address });
        }
        return Ok(sources);
    }
    if !looks_like_solana_address(resolved.program_id.as_str()) {
        return Err(crate::Error::Config(format!(
            "invalid program_id '{}' for on-chain DEX discovery",
            resolved.program_id
        )));
    }
    return Ok(vec![ResolvedSignatureSource {
        source: "program_id".to_string(),
        address: resolved.program_id.clone(),
    }]);
}

fn summarize_signature_source(sources: &[ResolvedSignatureSource]) -> std::string::String {
    if sources.len() > 1 {
        return "addresses".to_string();
    }
    let source = match sources.first() {
        Some(source) => source.source.clone(),
        None => return "program_id".to_string(),
    };
    return source;
}

fn summarize_signature_address(sources: &[ResolvedSignatureSource]) -> std::string::String {
    if sources.len() > 1 {
        let mut text = std::string::String::new();
        let mut index = 0usize;
        for source in sources {
            if index > 0 {
                text.push(',');
            }
            text.push_str(source.address.as_str());
            index += 1;
        }
        return text;
    }
    let address = match sources.first() {
        Some(source) => source.address.clone(),
        None => return std::string::String::new(),
    };
    return address;
}

fn signature_sources_to_addresses(
    sources: &[ResolvedSignatureSource],
) -> std::vec::Vec<std::string::String> {
    let mut addresses = std::vec::Vec::new();
    for source in sources {
        push_unique_string(&mut addresses, source.address.clone());
    }
    return addresses;
}

fn looks_like_solana_address(value: &str) -> bool {
    let length = value.len();
    if !(32..=44).contains(&length) {
        return false;
    }
    for character in value.chars() {
        if !is_base58_character(character) {
            return false;
        }
    }
    return value.parse::<solana_sdk::pubkey::Pubkey>().is_ok();
}

fn looks_like_solana_signature(value: &str) -> bool {
    let length = value.len();
    if !(64..=128).contains(&length) {
        return false;
    }
    for character in value.chars() {
        if !is_base58_character(character) {
            return false;
        }
    }
    return true;
}

fn parse_signature_cursor(
    value: std::option::Option<&str>,
    label: &str,
) -> Result<std::option::Option<std::string::String>, crate::Error> {
    let value = match value {
        Some(value) => value.trim(),
        None => return Ok(None),
    };
    if value.is_empty() {
        return Ok(None);
    }
    if !looks_like_solana_signature(value) {
        return Err(crate::Error::Config(format!(
            "{} must be a Solana transaction signature, got '{}'",
            label, value
        )));
    }
    return Ok(Some(value.to_string()));
}

fn is_base58_character(character: char) -> bool {
    return matches!(
        character,
        '1'..='9'
            | 'A'..='H'
            | 'J'..='N'
            | 'P'..='Z'
            | 'a'..='k'
            | 'm'..='z'
    );
}

fn extract_candidates_from_transaction(
    signature: &str,
    transaction: &serde_json::Value,
    target_program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    target_event: std::option::Option<&str>,
    target_instruction_name: std::option::Option<&str>,
    target_discriminator_hex: std::option::Option<&str>,
) -> OnchainCandidateExtraction {
    let mut candidates = std::vec::Vec::new();
    let mut rejected_candidate_summary = std::vec::Vec::new();
    let mut extracted_candidate_count = 0usize;
    let mut target_rejected_candidate_count = 0usize;
    let slot = transaction.get("slot").and_then(serde_json::Value::as_u64);
    let block_time = transaction.get("blockTime").and_then(serde_json::Value::as_i64);
    let failed = transaction_failed(transaction);
    let logs = extract_log_messages(transaction);
    let evidence = extract_transaction_generic_evidence(transaction);
    let target_program_account_mentioned =
        account_keys_contain_address(evidence.account_keys.as_slice(), target_program_id);
    let target_program_log_mentioned = logs_contain_text(logs.as_slice(), target_program_id);
    let prefer_non_swap_audit = target_event_prefers_instruction_local_classification(target_event);
    let instructions = extract_onchain_instructions(transaction, evidence.account_keys.as_slice());
    let scanned_instruction_count = instructions.len();
    let mut target_program_instruction_count = 0usize;
    for instruction in instructions {
        let program_id = match &instruction.program_id {
            Some(program_id) => program_id.clone(),
            None => continue,
        };
        if program_id.as_str() != target_program_id {
            continue;
        }
        target_program_instruction_count += 1;
        let decoded_candidate = decode_known_candidate(
            signature,
            slot,
            block_time,
            failed,
            program_id.as_str(),
            dex_code.clone(),
            &instruction,
            logs.as_slice(),
        );
        let mut candidate = match decoded_candidate {
            Some(candidate) => candidate,
            None => build_heuristic_candidate(
                signature,
                slot,
                block_time,
                failed,
                program_id.as_str(),
                dex_code.clone(),
                &instruction,
                logs.as_slice(),
                prefer_non_swap_audit,
            ),
        };
        enrich_candidate_with_generic_evidence(&mut candidate, &instruction, &evidence);
        extracted_candidate_count += 1;
        refine_candidate_for_target(&mut candidate, target_event);
        if !candidate_matches_target_event(&candidate, target_event) {
            target_rejected_candidate_count += 1;
            push_rejected_candidate_summary(
                &mut rejected_candidate_summary,
                &candidate,
                "target_event_filter",
            );
            continue;
        }
        if !candidate_matches_instruction_filters(
            &candidate,
            target_instruction_name,
            target_discriminator_hex,
        ) {
            target_rejected_candidate_count += 1;
            push_rejected_candidate_summary(
                &mut rejected_candidate_summary,
                &candidate,
                "target_instruction_filter",
            );
            continue;
        }
        if candidates.iter().any(|existing| {
            return candidate_identity_key(existing) == candidate_identity_key(&candidate);
        }) {
            continue;
        }
        candidates.push(candidate);
    }
    return OnchainCandidateExtraction {
        candidates,
        scanned_instruction_count,
        target_program_instruction_count,
        target_program_account_mentioned,
        target_program_log_mentioned,
        extracted_candidate_count,
        target_rejected_candidate_count,
        rejected_candidate_summary,
    };
}

fn decode_known_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
) -> std::option::Option<crate::OnchainDexPairCandidateDto> {
    if program_id == crate::RAYDIUM_CLMM_PROGRAM_ID {
        return decode_raydium_clmm_candidate(
            signature,
            slot,
            block_time,
            failed,
            program_id,
            dex_code,
            instruction,
            logs,
        );
    }
    if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID {
        return decode_raydium_cpmm_candidate(
            signature,
            slot,
            block_time,
            failed,
            program_id,
            dex_code,
            instruction,
            logs,
        );
    }
    if program_id == crate::METEORA_DAMM_V1_PROGRAM_ID {
        return decode_meteora_damm_v1_candidate(
            signature,
            slot,
            block_time,
            failed,
            program_id,
            dex_code,
            instruction,
            logs,
        );
    }
    return None;
}

fn decode_raydium_clmm_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
) -> std::option::Option<crate::OnchainDexPairCandidateDto> {
    let data = match &instruction.data {
        Some(data) => data.clone(),
        None => return None,
    };
    let accounts_json = match serde_json::to_string(&instruction.accounts) {
        Ok(accounts_json) => accounts_json,
        Err(_) => return None,
    };
    let data_json = match serde_json::to_string(&data) {
        Ok(data_json) => data_json,
        Err(_) => return None,
    };
    let decoded_events =
        crate::decode_raydium_clmm_instruction(accounts_json.as_str(), data_json.as_str());
    if let Some(decoded) = decoded_events.into_iter().next() {
        match decoded {
            crate::RaydiumClmmDecodedEvent::Swap(event) => {
                return Some(crate::OnchainDexPairCandidateDto {
                    signature: signature.to_string(),
                    slot,
                    block_time,
                    failed,
                    program_id: program_id.to_string(),
                    dex_code,
                    candidate_kind: "swap".to_string(),
                    confidence: "high".to_string(),
                    instruction_index: instruction.instruction_index,
                    inner_instruction_index: instruction.inner_instruction_index,
                    instruction_name: Some("raydium_clmm.swap".to_string()),
                    instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
                    instruction_discriminator_hex: instruction_discriminator_hex(
                        instruction.data.as_deref(),
                    ),
                    pool_address: Some(event.pool_state.clone()),
                    token_a_mint: Some(event.base_mint),
                    token_b_mint: Some(event.quote_mint),
                    verified_pool_address: Some(event.pool_state.clone()),
                    observed_token_mints: std::vec::Vec::new(),
                    token_balance_deltas: std::vec::Vec::new(),
                    candidate_pool_accounts: std::vec::Vec::new(),
                    candidate_token_vault_accounts: std::vec::Vec::new(),
                    candidate_program_accounts: std::vec::Vec::new(),
                    account_samples: sample_strings(instruction.accounts.as_slice(), 12),
                    log_samples: sample_logs(logs, 8),
                    backfill_hint: build_backfill_hint(
                        "pool",
                        Some(event.pool_state.as_str()),
                        signature,
                    ),
                });
            },
            crate::RaydiumClmmDecodedEvent::SwapV2(event) => {
                return Some(crate::OnchainDexPairCandidateDto {
                    signature: signature.to_string(),
                    slot,
                    block_time,
                    failed,
                    program_id: program_id.to_string(),
                    dex_code,
                    candidate_kind: "swap".to_string(),
                    confidence: "high".to_string(),
                    instruction_index: instruction.instruction_index,
                    inner_instruction_index: instruction.inner_instruction_index,
                    instruction_name: Some("raydium_clmm.swap_v2".to_string()),
                    instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
                    instruction_discriminator_hex: instruction_discriminator_hex(
                        instruction.data.as_deref(),
                    ),
                    pool_address: Some(event.pool_state.clone()),
                    token_a_mint: Some(event.base_mint),
                    token_b_mint: Some(event.quote_mint),
                    verified_pool_address: Some(event.pool_state.clone()),
                    observed_token_mints: std::vec::Vec::new(),
                    token_balance_deltas: std::vec::Vec::new(),
                    candidate_pool_accounts: std::vec::Vec::new(),
                    candidate_token_vault_accounts: std::vec::Vec::new(),
                    candidate_program_accounts: std::vec::Vec::new(),
                    account_samples: sample_strings(instruction.accounts.as_slice(), 12),
                    log_samples: sample_logs(logs, 8),
                    backfill_hint: build_backfill_hint(
                        "pool",
                        Some(event.pool_state.as_str()),
                        signature,
                    ),
                });
            },
            crate::RaydiumClmmDecodedEvent::CreatePool(event) => {
                return Some(crate::OnchainDexPairCandidateDto {
                    signature: signature.to_string(),
                    slot,
                    block_time,
                    failed,
                    program_id: program_id.to_string(),
                    dex_code,
                    candidate_kind: "create_pool".to_string(),
                    confidence: "high".to_string(),
                    instruction_index: instruction.instruction_index,
                    inner_instruction_index: instruction.inner_instruction_index,
                    instruction_name: Some("raydium_clmm.create_pool".to_string()),
                    instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
                    instruction_discriminator_hex: instruction_discriminator_hex(
                        instruction.data.as_deref(),
                    ),
                    pool_address: Some(event.pool_state.clone()),
                    token_a_mint: Some(event.token_mint_0),
                    token_b_mint: Some(event.token_mint_1),
                    verified_pool_address: Some(event.pool_state.clone()),
                    observed_token_mints: std::vec::Vec::new(),
                    token_balance_deltas: std::vec::Vec::new(),
                    candidate_pool_accounts: std::vec::Vec::new(),
                    candidate_token_vault_accounts: std::vec::Vec::new(),
                    candidate_program_accounts: std::vec::Vec::new(),
                    account_samples: sample_strings(instruction.accounts.as_slice(), 12),
                    log_samples: sample_logs(logs, 8),
                    backfill_hint: build_backfill_hint(
                        "pool",
                        Some(event.pool_state.as_str()),
                        signature,
                    ),
                });
            },
            crate::RaydiumClmmDecodedEvent::CollectProtocolFee(_)
            | crate::RaydiumClmmDecodedEvent::CollectPersonalFeeEvent(_)
            | crate::RaydiumClmmDecodedEvent::CollectProtocolFeeEvent(_)
            | crate::RaydiumClmmDecodedEvent::ConfigChangeEvent(_)
            | crate::RaydiumClmmDecodedEvent::CreatePersonalPositionEvent(_)
            | crate::RaydiumClmmDecodedEvent::DecreaseLiquidityEvent(_)
            | crate::RaydiumClmmDecodedEvent::IncreaseLiquidityEvent(_)
            | crate::RaydiumClmmDecodedEvent::LiquidityCalculateEvent(_)
            | crate::RaydiumClmmDecodedEvent::LiquidityChangeEvent(_)
            | crate::RaydiumClmmDecodedEvent::PoolCreatedEvent(_)
            | crate::RaydiumClmmDecodedEvent::SwapEvent(_)
            | crate::RaydiumClmmDecodedEvent::UpdateRewardInfosEvent(_) => return None,
        }
    }
    return None;
}

fn decode_raydium_cpmm_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
) -> std::option::Option<crate::OnchainDexPairCandidateDto> {
    let data = match &instruction.data {
        Some(data) => data.clone(),
        None => return None,
    };
    let accounts_json = match serde_json::to_string(&instruction.accounts) {
        Ok(accounts_json) => accounts_json,
        Err(_) => return None,
    };
    let data_json = match serde_json::to_string(&data) {
        Ok(data_json) => data_json,
        Err(_) => return None,
    };
    let decoded_events =
        crate::decode_raydium_cpmm_instruction(accounts_json.as_str(), data_json.as_str());
    if let Some(decoded) = decoded_events.into_iter().next() {
        match decoded {
            crate::RaydiumCpmmDecodedEvent::SwapBaseInput(event) => {
                return Some(build_raydium_cpmm_candidate(
                    signature,
                    slot,
                    block_time,
                    failed,
                    program_id,
                    dex_code,
                    instruction,
                    logs,
                    "raydium_cpmm.swap_base_input",
                    event.pool_state,
                    event.base_mint,
                    event.quote_mint,
                ));
            },
            crate::RaydiumCpmmDecodedEvent::SwapBaseOutput(event) => {
                return Some(build_raydium_cpmm_candidate(
                    signature,
                    slot,
                    block_time,
                    failed,
                    program_id,
                    dex_code,
                    instruction,
                    logs,
                    "raydium_cpmm.swap_base_output",
                    event.pool_state,
                    event.base_mint,
                    event.quote_mint,
                ));
            },
            _ => return None,
        }
    }
    let raw_data = match decode_onchain_instruction_data(instruction.data.as_deref()) {
        Some(raw_data) => raw_data,
        None => return None,
    };
    let instruction_kind = classify_demo3_raydium_cpmm_instruction(raw_data.as_slice());
    if instruction_kind == Demo3RaydiumCpmmInstructionKind::Unknown
        || instruction_kind == Demo3RaydiumCpmmInstructionKind::SwapBaseInput
        || instruction_kind == Demo3RaydiumCpmmInstructionKind::SwapBaseOutput
    {
        return None;
    }
    return Some(build_raydium_cpmm_non_swap_candidate(
        signature,
        slot,
        block_time,
        failed,
        program_id,
        dex_code,
        instruction,
        logs,
        instruction_kind,
    ));
}

fn build_raydium_cpmm_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
    instruction_name: &str,
    pool_address: std::string::String,
    token_a_mint: std::string::String,
    token_b_mint: std::string::String,
) -> crate::OnchainDexPairCandidateDto {
    return crate::OnchainDexPairCandidateDto {
        signature: signature.to_string(),
        slot,
        block_time,
        failed,
        program_id: program_id.to_string(),
        dex_code,
        candidate_kind: "swap".to_string(),
        confidence: "high".to_string(),
        instruction_index: instruction.instruction_index,
        inner_instruction_index: instruction.inner_instruction_index,
        instruction_name: Some(instruction_name.to_string()),
        instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
        instruction_discriminator_hex: instruction_discriminator_hex(instruction.data.as_deref()),
        pool_address: Some(pool_address.clone()),
        token_a_mint: Some(token_a_mint),
        token_b_mint: Some(token_b_mint),
        verified_pool_address: Some(pool_address.clone()),
        observed_token_mints: std::vec::Vec::new(),
        token_balance_deltas: std::vec::Vec::new(),
        candidate_pool_accounts: std::vec::Vec::new(),
        candidate_token_vault_accounts: std::vec::Vec::new(),
        candidate_program_accounts: std::vec::Vec::new(),
        account_samples: sample_strings(instruction.accounts.as_slice(), 12),
        log_samples: sample_logs(logs, 8),
        backfill_hint: build_backfill_hint("pool", Some(pool_address.as_str()), signature),
    };
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Demo3RaydiumCpmmInstructionKind {
    Unknown,
    ClosePermissionPda,
    CollectCreatorFee,
    CollectFundFee,
    CollectProtocolFee,
    CreateAmmConfig,
    CreatePermissionPda,
    Deposit,
    Initialize,
    InitializeWithPermission,
    SwapBaseInput,
    SwapBaseOutput,
    UpdateAmmConfig,
    UpdatePoolStatus,
    Withdraw,
}

fn build_raydium_cpmm_non_swap_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
) -> crate::OnchainDexPairCandidateDto {
    let pool_address =
        demo3_raydium_cpmm_pool_account(instruction.accounts.as_slice(), instruction_kind);
    let token_a_mint =
        demo3_raydium_cpmm_token_a_mint(instruction.accounts.as_slice(), instruction_kind);
    let token_b_mint =
        demo3_raydium_cpmm_token_b_mint(instruction.accounts.as_slice(), instruction_kind);
    let candidate_kind = demo3_raydium_cpmm_candidate_kind(instruction_kind).to_string();
    let instruction_name = demo3_raydium_cpmm_instruction_name(instruction_kind).to_string();
    let verified_pool_address = pool_address.clone();
    let backfill_hint = demo3_raydium_cpmm_backfill_hint(
        instruction_kind,
        candidate_kind.as_str(),
        pool_address.as_deref(),
        signature,
    );
    return crate::OnchainDexPairCandidateDto {
        signature: signature.to_string(),
        slot,
        block_time,
        failed,
        program_id: program_id.to_string(),
        dex_code,
        candidate_kind,
        confidence: "high".to_string(),
        instruction_index: instruction.instruction_index,
        inner_instruction_index: instruction.inner_instruction_index,
        instruction_name: Some(instruction_name),
        instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
        instruction_discriminator_hex: instruction_discriminator_hex(instruction.data.as_deref()),
        pool_address,
        token_a_mint,
        token_b_mint,
        verified_pool_address,
        observed_token_mints: std::vec::Vec::new(),
        token_balance_deltas: std::vec::Vec::new(),
        candidate_pool_accounts: std::vec::Vec::new(),
        candidate_token_vault_accounts: std::vec::Vec::new(),
        candidate_program_accounts: std::vec::Vec::new(),
        account_samples: sample_strings(instruction.accounts.as_slice(), 12),
        log_samples: sample_logs(logs, 8),
        backfill_hint,
    };
}

fn classify_demo3_raydium_cpmm_instruction(data: &[u8]) -> Demo3RaydiumCpmmInstructionKind {
    if data.len() < 8 {
        return Demo3RaydiumCpmmInstructionKind::Unknown;
    }
    let discriminator = &data[0..8];
    if discriminator == [156, 84, 32, 118, 69, 135, 70, 123] {
        return Demo3RaydiumCpmmInstructionKind::ClosePermissionPda;
    }
    if discriminator == [20, 22, 86, 123, 198, 28, 219, 132] {
        return Demo3RaydiumCpmmInstructionKind::CollectCreatorFee;
    }
    if discriminator == [167, 138, 78, 149, 223, 194, 6, 126] {
        return Demo3RaydiumCpmmInstructionKind::CollectFundFee;
    }
    if discriminator == [136, 136, 252, 221, 194, 66, 126, 89] {
        return Demo3RaydiumCpmmInstructionKind::CollectProtocolFee;
    }
    if discriminator == [137, 52, 237, 212, 215, 117, 108, 104] {
        return Demo3RaydiumCpmmInstructionKind::CreateAmmConfig;
    }
    if discriminator == [135, 136, 2, 216, 137, 169, 181, 202] {
        return Demo3RaydiumCpmmInstructionKind::CreatePermissionPda;
    }
    if discriminator == [242, 35, 198, 137, 82, 225, 242, 182] {
        return Demo3RaydiumCpmmInstructionKind::Deposit;
    }
    if discriminator == [175, 175, 109, 31, 13, 152, 155, 237] {
        return Demo3RaydiumCpmmInstructionKind::Initialize;
    }
    if discriminator == [63, 55, 254, 65, 49, 178, 89, 121] {
        return Demo3RaydiumCpmmInstructionKind::InitializeWithPermission;
    }
    if discriminator == [143, 190, 90, 218, 196, 30, 51, 222] {
        return Demo3RaydiumCpmmInstructionKind::SwapBaseInput;
    }
    if discriminator == [55, 217, 98, 86, 163, 74, 180, 173] {
        return Demo3RaydiumCpmmInstructionKind::SwapBaseOutput;
    }
    if discriminator == [49, 60, 174, 136, 154, 28, 116, 200] {
        return Demo3RaydiumCpmmInstructionKind::UpdateAmmConfig;
    }
    if discriminator == [130, 87, 108, 6, 46, 224, 117, 123] {
        return Demo3RaydiumCpmmInstructionKind::UpdatePoolStatus;
    }
    if discriminator == [183, 18, 70, 156, 148, 109, 161, 34] {
        return Demo3RaydiumCpmmInstructionKind::Withdraw;
    }
    return Demo3RaydiumCpmmInstructionKind::Unknown;
}

fn demo3_raydium_cpmm_candidate_kind(
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
) -> &'static str {
    match instruction_kind {
        Demo3RaydiumCpmmInstructionKind::ClosePermissionPda => return "pool_admin",
        Demo3RaydiumCpmmInstructionKind::CollectCreatorFee => return "claim_fee",
        Demo3RaydiumCpmmInstructionKind::CollectFundFee => return "claim_fee",
        Demo3RaydiumCpmmInstructionKind::CollectProtocolFee => return "claim_fee",
        Demo3RaydiumCpmmInstructionKind::CreateAmmConfig => return "pool_admin",
        Demo3RaydiumCpmmInstructionKind::CreatePermissionPda => return "pool_admin",
        Demo3RaydiumCpmmInstructionKind::Deposit => return "add_liquidity",
        Demo3RaydiumCpmmInstructionKind::Initialize => return "create_pool",
        Demo3RaydiumCpmmInstructionKind::InitializeWithPermission => return "create_pool",
        Demo3RaydiumCpmmInstructionKind::UpdateAmmConfig => return "pool_admin",
        Demo3RaydiumCpmmInstructionKind::UpdatePoolStatus => return "pool_admin",
        Demo3RaydiumCpmmInstructionKind::Withdraw => return "remove_liquidity",
        _ => return "unclassified_instruction",
    }
}

fn demo3_raydium_cpmm_instruction_name(
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
) -> &'static str {
    match instruction_kind {
        Demo3RaydiumCpmmInstructionKind::ClosePermissionPda => {
            return "raydium_cpmm.close_permission_pda";
        },
        Demo3RaydiumCpmmInstructionKind::CollectCreatorFee => {
            return "raydium_cpmm.collect_creator_fee";
        },
        Demo3RaydiumCpmmInstructionKind::CollectFundFee => return "raydium_cpmm.collect_fund_fee",
        Demo3RaydiumCpmmInstructionKind::CollectProtocolFee => {
            return "raydium_cpmm.collect_protocol_fee";
        },
        Demo3RaydiumCpmmInstructionKind::CreateAmmConfig => return "raydium_cpmm.create_amm_config",
        Demo3RaydiumCpmmInstructionKind::CreatePermissionPda => {
            return "raydium_cpmm.create_permission_pda";
        },
        Demo3RaydiumCpmmInstructionKind::Deposit => return "raydium_cpmm.deposit",
        Demo3RaydiumCpmmInstructionKind::Initialize => return "raydium_cpmm.initialize",
        Demo3RaydiumCpmmInstructionKind::InitializeWithPermission => {
            return "raydium_cpmm.initialize_with_permission";
        },
        Demo3RaydiumCpmmInstructionKind::UpdateAmmConfig => return "raydium_cpmm.update_amm_config",
        Demo3RaydiumCpmmInstructionKind::UpdatePoolStatus => {
            return "raydium_cpmm.update_pool_status";
        },
        Demo3RaydiumCpmmInstructionKind::Withdraw => return "raydium_cpmm.withdraw",
        Demo3RaydiumCpmmInstructionKind::SwapBaseInput => return "raydium_cpmm.swap_base_input",
        Demo3RaydiumCpmmInstructionKind::SwapBaseOutput => return "raydium_cpmm.swap_base_output",
        Demo3RaydiumCpmmInstructionKind::Unknown => return "raydium_cpmm.unknown",
    }
}

fn demo3_raydium_cpmm_pool_account(
    accounts: &[std::string::String],
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
) -> std::option::Option<std::string::String> {
    let index = match instruction_kind {
        Demo3RaydiumCpmmInstructionKind::CollectCreatorFee => Some(2usize),
        Demo3RaydiumCpmmInstructionKind::CollectFundFee => Some(2usize),
        Demo3RaydiumCpmmInstructionKind::CollectProtocolFee => Some(2usize),
        Demo3RaydiumCpmmInstructionKind::Deposit => Some(2usize),
        Demo3RaydiumCpmmInstructionKind::Initialize => Some(3usize),
        Demo3RaydiumCpmmInstructionKind::InitializeWithPermission => Some(4usize),
        Demo3RaydiumCpmmInstructionKind::UpdatePoolStatus => Some(1usize),
        Demo3RaydiumCpmmInstructionKind::Withdraw => Some(2usize),
        _ => None,
    };
    let index = match index {
        Some(index) => index,
        None => return None,
    };
    return demo3_account_at(accounts, index);
}

fn demo3_raydium_cpmm_token_a_mint(
    accounts: &[std::string::String],
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
) -> std::option::Option<std::string::String> {
    let index = match instruction_kind {
        Demo3RaydiumCpmmInstructionKind::CollectCreatorFee => Some(6usize),
        Demo3RaydiumCpmmInstructionKind::CollectFundFee => Some(6usize),
        Demo3RaydiumCpmmInstructionKind::CollectProtocolFee => Some(6usize),
        Demo3RaydiumCpmmInstructionKind::Deposit => Some(10usize),
        Demo3RaydiumCpmmInstructionKind::Initialize => Some(4usize),
        Demo3RaydiumCpmmInstructionKind::InitializeWithPermission => Some(5usize),
        Demo3RaydiumCpmmInstructionKind::Withdraw => Some(10usize),
        _ => None,
    };
    let index = match index {
        Some(index) => index,
        None => return None,
    };
    return demo3_account_at(accounts, index);
}

fn demo3_raydium_cpmm_token_b_mint(
    accounts: &[std::string::String],
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
) -> std::option::Option<std::string::String> {
    let index = match instruction_kind {
        Demo3RaydiumCpmmInstructionKind::CollectCreatorFee => Some(7usize),
        Demo3RaydiumCpmmInstructionKind::CollectFundFee => Some(7usize),
        Demo3RaydiumCpmmInstructionKind::CollectProtocolFee => Some(7usize),
        Demo3RaydiumCpmmInstructionKind::Deposit => Some(11usize),
        Demo3RaydiumCpmmInstructionKind::Initialize => Some(5usize),
        Demo3RaydiumCpmmInstructionKind::InitializeWithPermission => Some(6usize),
        Demo3RaydiumCpmmInstructionKind::Withdraw => Some(11usize),
        _ => None,
    };
    let index = match index {
        Some(index) => index,
        None => return None,
    };
    return demo3_account_at(accounts, index);
}

fn demo3_raydium_cpmm_backfill_hint(
    instruction_kind: Demo3RaydiumCpmmInstructionKind,
    candidate_kind: &str,
    pool_address: std::option::Option<&str>,
    signature: &str,
) -> std::string::String {
    let instruction_name = demo3_raydium_cpmm_instruction_name(instruction_kind);
    if let Some(pool_address) = pool_address {
        return format!(
            "Raydium CPMM {} candidate '{}'; backfill pool in Demo Pipeline 2: {} ; signature: {}",
            candidate_kind, instruction_name, pool_address, signature
        );
    }
    return format!(
        "Raydium CPMM {} candidate '{}'; inspect/backfill transaction signature: {}",
        candidate_kind, instruction_name, signature
    );
}

fn decode_meteora_damm_v1_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
) -> std::option::Option<crate::OnchainDexPairCandidateDto> {
    let data = match decode_onchain_instruction_data(instruction.data.as_deref()) {
        Some(data) => data,
        None => return None,
    };
    let instruction_kind = classify_demo3_meteora_damm_v1_instruction(data.as_slice());
    if instruction_kind == Demo3MeteoraDammV1InstructionKind::Unknown {
        return None;
    }
    let candidate_kind = demo3_meteora_damm_v1_candidate_kind(instruction_kind).to_string();
    let instruction_name = demo3_meteora_damm_v1_instruction_name(instruction_kind).to_string();
    let pool_address = demo3_account_at(instruction.accounts.as_slice(), 0);
    let token_a_mint =
        demo3_meteora_damm_v1_token_a_mint(instruction.accounts.as_slice(), instruction_kind);
    let token_b_mint =
        demo3_meteora_damm_v1_token_b_mint(instruction.accounts.as_slice(), instruction_kind);
    let verified_pool_address = pool_address.clone();
    let backfill_hint =
        demo3_meteora_damm_v1_backfill_hint(instruction_kind, pool_address.as_deref(), signature);
    return Some(crate::OnchainDexPairCandidateDto {
        signature: signature.to_string(),
        slot,
        block_time,
        failed,
        program_id: program_id.to_string(),
        dex_code,
        candidate_kind,
        confidence: "high".to_string(),
        instruction_index: instruction.instruction_index,
        inner_instruction_index: instruction.inner_instruction_index,
        instruction_name: Some(instruction_name),
        instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
        instruction_discriminator_hex: instruction_discriminator_hex(instruction.data.as_deref()),
        pool_address,
        token_a_mint,
        token_b_mint,
        verified_pool_address,
        observed_token_mints: std::vec::Vec::new(),
        token_balance_deltas: std::vec::Vec::new(),
        candidate_pool_accounts: std::vec::Vec::new(),
        candidate_token_vault_accounts: std::vec::Vec::new(),
        candidate_program_accounts: std::vec::Vec::new(),
        account_samples: sample_strings(instruction.accounts.as_slice(), 12),
        log_samples: sample_logs(logs, 8),
        backfill_hint,
    });
}

fn decode_onchain_instruction_data(
    data: std::option::Option<&str>,
) -> std::option::Option<std::vec::Vec<u8>> {
    let data = match data {
        Some(data) => data.trim(),
        None => return None,
    };
    if data.is_empty() {
        return None;
    }
    let decoded_result = bs58::decode(data).into_vec();
    match decoded_result {
        Ok(decoded) => return Some(decoded),
        Err(_) => return None,
    }
}

fn classify_demo3_meteora_damm_v1_instruction(data: &[u8]) -> Demo3MeteoraDammV1InstructionKind {
    if data.len() < 8 {
        return Demo3MeteoraDammV1InstructionKind::Unknown;
    }
    let discriminator = &data[0..8];
    if discriminator == DEMO3_DAMM_V1_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig;
    }
    if discriminator == DEMO3_DAMM_V1_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG2.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2;
    }
    if discriminator == DEMO3_DAMM_V1_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_CP_POOL.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool;
    }
    if discriminator == DEMO3_DAMM_V1_SWAP.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::Swap;
    }
    if discriminator == DEMO3_DAMM_V1_ADD_BALANCE_LIQUIDITY.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::AddBalanceLiquidity;
    }
    if discriminator == DEMO3_DAMM_V1_ADD_IMBALANCE_LIQUIDITY.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::AddImbalanceLiquidity;
    }
    if discriminator == DEMO3_DAMM_V1_BOOTSTRAP_LIQUIDITY.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::BootstrapLiquidity;
    }
    if discriminator == DEMO3_DAMM_V1_REMOVE_BALANCE_LIQUIDITY.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::RemoveBalanceLiquidity;
    }
    if discriminator == DEMO3_DAMM_V1_REMOVE_LIQUIDITY_SINGLE_SIDE.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide;
    }
    if discriminator == DEMO3_DAMM_V1_CLAIM_FEE.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::ClaimFee;
    }
    if discriminator == DEMO3_DAMM_V1_CREATE_LOCK_ESCROW.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::CreateLockEscrow;
    }
    if discriminator == DEMO3_DAMM_V1_LOCK.as_slice() {
        return Demo3MeteoraDammV1InstructionKind::Lock;
    }
    return Demo3MeteoraDammV1InstructionKind::Unknown;
}

fn demo3_meteora_damm_v1_candidate_kind(
    instruction_kind: Demo3MeteoraDammV1InstructionKind,
) -> &'static str {
    match instruction_kind {
        Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2
        | Demo3MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return "create_pool";
        },
        Demo3MeteoraDammV1InstructionKind::Swap => return "swap",
        Demo3MeteoraDammV1InstructionKind::AddBalanceLiquidity
        | Demo3MeteoraDammV1InstructionKind::AddImbalanceLiquidity
        | Demo3MeteoraDammV1InstructionKind::BootstrapLiquidity => return "add_liquidity",
        Demo3MeteoraDammV1InstructionKind::RemoveBalanceLiquidity
        | Demo3MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide => return "remove_liquidity",
        Demo3MeteoraDammV1InstructionKind::ClaimFee => return "claim_fee",
        Demo3MeteoraDammV1InstructionKind::CreateLockEscrow => return "create_lock_escrow",
        Demo3MeteoraDammV1InstructionKind::Lock => return "lock_liquidity",
        Demo3MeteoraDammV1InstructionKind::Unknown => return "unclassified_instruction",
    }
}

fn demo3_meteora_damm_v1_instruction_name(
    instruction_kind: Demo3MeteoraDammV1InstructionKind,
) -> &'static str {
    match instruction_kind {
        Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig => {
            return "meteora_damm_v1.InitializePermissionlessConstantProductPoolWithConfig";
        },
        Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return "meteora_damm_v1.InitializePermissionlessConstantProductPoolWithConfig2";
        },
        Demo3MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return "meteora_damm_v1.InitializeCustomizablePermissionlessConstantProductPool";
        },
        Demo3MeteoraDammV1InstructionKind::Swap => return "meteora_damm_v1.Swap",
        Demo3MeteoraDammV1InstructionKind::AddBalanceLiquidity => {
            return "meteora_damm_v1.AddBalanceLiquidity";
        },
        Demo3MeteoraDammV1InstructionKind::AddImbalanceLiquidity => {
            return "meteora_damm_v1.AddImbalanceLiquidity";
        },
        Demo3MeteoraDammV1InstructionKind::BootstrapLiquidity => {
            return "meteora_damm_v1.BootstrapLiquidity";
        },
        Demo3MeteoraDammV1InstructionKind::RemoveBalanceLiquidity => {
            return "meteora_damm_v1.RemoveBalanceLiquidity";
        },
        Demo3MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide => {
            return "meteora_damm_v1.RemoveLiquiditySingleSide";
        },
        Demo3MeteoraDammV1InstructionKind::ClaimFee => return "meteora_damm_v1.ClaimFee",
        Demo3MeteoraDammV1InstructionKind::CreateLockEscrow => {
            return "meteora_damm_v1.CreateLockEscrow";
        },
        Demo3MeteoraDammV1InstructionKind::Lock => return "meteora_damm_v1.Lock",
        Demo3MeteoraDammV1InstructionKind::Unknown => return "meteora_damm_v1.Unknown",
    }
}

fn demo3_meteora_damm_v1_token_a_mint(
    accounts: &[std::string::String],
    instruction_kind: Demo3MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return demo3_account_at(accounts, 3);
        },
        Demo3MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return demo3_account_at(accounts, 2);
        },
        _ => return None,
    }
}

fn demo3_meteora_damm_v1_token_b_mint(
    accounts: &[std::string::String],
    instruction_kind: Demo3MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | Demo3MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return demo3_account_at(accounts, 4);
        },
        Demo3MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return demo3_account_at(accounts, 3);
        },
        _ => return None,
    }
}

fn demo3_meteora_damm_v1_backfill_hint(
    instruction_kind: Demo3MeteoraDammV1InstructionKind,
    pool_address: std::option::Option<&str>,
    signature: &str,
) -> std::string::String {
    let label = demo3_meteora_damm_v1_candidate_kind(instruction_kind);
    if let Some(pool_address) = pool_address {
        return format!(
            "Upstream Git DAMM v1 {} candidate; backfill pool in Demo Pipeline 2: {} ; signature: {}",
            label, pool_address, signature
        );
    }
    return format!(
        "Upstream Git DAMM v1 {} candidate; inspect/backfill transaction signature: {}",
        label, signature
    );
}

fn demo3_account_at(
    accounts: &[std::string::String],
    index: usize,
) -> std::option::Option<std::string::String> {
    if index >= accounts.len() {
        return None;
    }
    return Some(accounts[index].clone());
}

fn build_heuristic_candidate(
    signature: &str,
    slot: std::option::Option<u64>,
    block_time: std::option::Option<i64>,
    failed: bool,
    program_id: &str,
    dex_code: std::option::Option<std::string::String>,
    instruction: &OnchainInstructionCandidate,
    logs: &[std::string::String],
    prefer_instruction_local_classification: bool,
) -> crate::OnchainDexPairCandidateDto {
    let registry_match = crate::upstream_registry_match::upstream_registry_match_instruction_data(
        program_id,
        instruction.data.as_deref(),
    );
    let instruction_name = match registry_match.as_ref() {
        Some(registry_match) => Some(registry_match.entry_name.clone()),
        None => {
            if prefer_instruction_local_classification {
                infer_instruction_name_from_parsed(instruction.parsed.as_ref())
            } else {
                infer_instruction_name(instruction.parsed.as_ref(), logs)
            }
        },
    };
    let pool_address = extract_string_by_candidate_keys_from_instruction(
        instruction,
        &["pool", "poolAddress", "poolAccount", "poolState", "amm", "ammPool", "whirlpool"],
    );
    let token_a_mint = extract_string_by_candidate_keys_from_instruction(
        instruction,
        &["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0", "coinMint"],
    );
    let token_b_mint = extract_string_by_candidate_keys_from_instruction(
        instruction,
        &["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1", "pcMint"],
    );
    let candidate_kind = match registry_match.as_ref() {
        Some(registry_match) => {
            upstream_registry_entry_name_to_candidate_kind(registry_match.entry_name.as_str())
        },
        None => infer_candidate_kind(
            instruction_name.as_ref(),
            logs,
            instruction.data.is_some(),
            prefer_instruction_local_classification,
        ),
    };
    let confidence = if registry_match.is_some() {
        "high".to_string()
    } else if pool_address.is_some() || token_a_mint.is_some() || token_b_mint.is_some() {
        "medium".to_string()
    } else {
        "low".to_string()
    };
    let hint = if pool_address.is_some() {
        build_backfill_hint("pool", pool_address.as_deref(), signature)
    } else if token_a_mint.is_some() {
        build_backfill_hint("token", token_a_mint.as_deref(), signature)
    } else {
        build_backfill_hint("signature", None, signature)
    };
    return crate::OnchainDexPairCandidateDto {
        signature: signature.to_string(),
        slot,
        block_time,
        failed,
        program_id: program_id.to_string(),
        dex_code,
        candidate_kind,
        confidence,
        instruction_index: instruction.instruction_index,
        inner_instruction_index: instruction.inner_instruction_index,
        instruction_name,
        instruction_data_prefix: instruction_data_prefix(instruction.data.as_deref()),
        instruction_discriminator_hex: instruction_discriminator_hex(instruction.data.as_deref()),
        pool_address: pool_address.clone(),
        token_a_mint,
        token_b_mint,
        verified_pool_address: None,
        observed_token_mints: std::vec::Vec::new(),
        token_balance_deltas: std::vec::Vec::new(),
        candidate_pool_accounts: std::vec::Vec::new(),
        candidate_token_vault_accounts: std::vec::Vec::new(),
        candidate_program_accounts: std::vec::Vec::new(),
        account_samples: sample_strings(instruction.accounts.as_slice(), 12),
        log_samples: sample_logs(logs, 8),
        backfill_hint: hint,
    };
}

fn enrich_candidate_with_generic_evidence(
    candidate: &mut crate::OnchainDexPairCandidateDto,
    instruction: &OnchainInstructionCandidate,
    evidence: &TransactionGenericEvidence,
) {
    candidate.observed_token_mints = evidence.observed_token_mints.clone();
    candidate.token_balance_deltas =
        sample_token_balance_deltas(evidence.token_balance_deltas.as_slice(), 20);
    if candidate.token_a_mint.is_none() || candidate.token_b_mint.is_none() {
        let inferred = infer_token_pair_from_deltas(evidence.token_balance_deltas.as_slice());
        if candidate.token_a_mint.is_none() {
            candidate.token_a_mint = inferred.0;
        }
        if candidate.token_b_mint.is_none() {
            candidate.token_b_mint = inferred.1;
        }
    }
    candidate.candidate_token_vault_accounts = infer_candidate_token_vault_accounts(
        instruction.accounts.as_slice(),
        evidence.token_balance_deltas.as_slice(),
        evidence.account_keys.as_slice(),
    );
    candidate.candidate_pool_accounts = infer_candidate_pool_accounts(
        instruction.accounts.as_slice(),
        evidence.account_keys.as_slice(),
        candidate.candidate_token_vault_accounts.as_slice(),
    );
    candidate.candidate_program_accounts = infer_candidate_program_accounts(
        instruction.accounts.as_slice(),
        evidence.account_keys.as_slice(),
        candidate.candidate_pool_accounts.as_slice(),
        candidate.candidate_token_vault_accounts.as_slice(),
    );
    if candidate.confidence == "low"
        && (!candidate.observed_token_mints.is_empty()
            || !candidate.candidate_pool_accounts.is_empty())
    {
        candidate.confidence = "medium".to_string();
    }
    if candidate_is_known_trade_like_surface(candidate)
        && candidate.candidate_kind == "unclassified_instruction"
    {
        candidate.candidate_kind = "trade_like_unclassified".to_string();
        candidate.confidence = "medium".to_string();
        candidate.backfill_hint = format!(
            "Trade-like unclassified instruction; inspect/backfill transaction signature: {}",
            candidate.signature
        );
    }
    if candidate.pool_address.is_none() && candidate.verified_pool_address.is_none() {
        let first_pool = candidate.candidate_pool_accounts.first();
        if let Some(first_pool) = first_pool {
            candidate.backfill_hint = build_backfill_hint(
                "candidate_pool",
                Some(first_pool.address.as_str()),
                candidate.signature.as_str(),
            );
        }
    }
}

fn account_keys_contain_address(account_keys: &[AccountKeyInfo], address: &str) -> bool {
    for account_key in account_keys {
        if account_key.address == address {
            return true;
        }
    }
    return false;
}

fn logs_contain_text(logs: &[std::string::String], text: &str) -> bool {
    for log in logs {
        if log.contains(text) {
            return true;
        }
    }
    return false;
}

fn extract_transaction_generic_evidence(
    transaction: &serde_json::Value,
) -> TransactionGenericEvidence {
    let account_keys = extract_transaction_account_keys(transaction);
    let token_balance_deltas = extract_token_balance_deltas(transaction, account_keys.as_slice());
    let observed_token_mints = collect_observed_token_mints(token_balance_deltas.as_slice());
    return TransactionGenericEvidence {
        account_keys,
        observed_token_mints,
        token_balance_deltas,
    };
}

fn extract_transaction_account_keys(
    transaction: &serde_json::Value,
) -> std::vec::Vec<AccountKeyInfo> {
    let mut account_keys = std::vec::Vec::new();
    let values = transaction
        .get("transaction")
        .and_then(|value| return value.get("message"))
        .and_then(|value| return value.get("accountKeys"))
        .and_then(serde_json::Value::as_array);
    if let Some(values) = values {
        let mut index = 0usize;
        for value in values {
            let parsed = parse_account_key_info(value, index as i64);
            if let Some(parsed) = parsed {
                account_keys.push(parsed);
            }
            index += 1;
        }
    }
    append_loaded_address_account_keys(transaction, &mut account_keys);
    return account_keys;
}

fn append_loaded_address_account_keys(
    transaction: &serde_json::Value,
    account_keys: &mut std::vec::Vec<AccountKeyInfo>,
) {
    let loaded_addresses =
        transaction.get("meta").and_then(|value| return value.get("loadedAddresses"));
    let loaded_addresses = match loaded_addresses {
        Some(loaded_addresses) => loaded_addresses,
        None => return,
    };
    append_loaded_address_side(loaded_addresses, "writable", account_keys, Some(true));
    append_loaded_address_side(loaded_addresses, "readonly", account_keys, Some(false));
}

fn append_loaded_address_side(
    loaded_addresses: &serde_json::Value,
    key: &str,
    account_keys: &mut std::vec::Vec<AccountKeyInfo>,
    writable: std::option::Option<bool>,
) {
    let values = loaded_addresses.get(key).and_then(serde_json::Value::as_array);
    let values = match values {
        Some(values) => values,
        None => return,
    };
    for value in values {
        let address = match value.as_str() {
            Some(address) => address,
            None => continue,
        };
        let index_result = i64::try_from(account_keys.len());
        let index = match index_result {
            Ok(index) => index,
            Err(_) => return,
        };
        account_keys.push(AccountKeyInfo {
            index,
            address: address.to_string(),
            signer: Some(false),
            writable,
        });
    }
}

fn parse_account_key_info(
    value: &serde_json::Value,
    index: i64,
) -> std::option::Option<AccountKeyInfo> {
    if let Some(address) = value.as_str() {
        return Some(AccountKeyInfo {
            index,
            address: address.to_string(),
            signer: None,
            writable: None,
        });
    }
    let address = match value.get("pubkey").and_then(serde_json::Value::as_str) {
        Some(address) => address.to_string(),
        None => return None,
    };
    let signer = value.get("signer").and_then(serde_json::Value::as_bool);
    let writable = value.get("writable").and_then(serde_json::Value::as_bool);
    return Some(AccountKeyInfo { index, address, signer, writable });
}

fn extract_token_balance_deltas(
    transaction: &serde_json::Value,
    account_keys: &[AccountKeyInfo],
) -> std::vec::Vec<crate::OnchainDexTokenBalanceDeltaDto> {
    let mut accumulators = std::vec::Vec::new();
    collect_token_balance_side(
        transaction,
        "preTokenBalances",
        true,
        account_keys,
        &mut accumulators,
    );
    collect_token_balance_side(
        transaction,
        "postTokenBalances",
        false,
        account_keys,
        &mut accumulators,
    );
    let mut deltas = std::vec::Vec::new();
    for accumulator in accumulators {
        deltas.push(crate::OnchainDexTokenBalanceDeltaDto {
            account_index: accumulator.account_index,
            account_address: accumulator.account_address,
            mint: accumulator.mint,
            owner: accumulator.owner,
            token_program: accumulator.token_program,
            pre_amount_raw: accumulator.pre_amount_raw.clone(),
            post_amount_raw: accumulator.post_amount_raw.clone(),
            delta_raw: compute_delta_raw(
                accumulator.pre_amount_raw.as_deref(),
                accumulator.post_amount_raw.as_deref(),
            ),
        });
    }
    return deltas;
}

fn collect_token_balance_side(
    transaction: &serde_json::Value,
    key: &str,
    is_pre: bool,
    account_keys: &[AccountKeyInfo],
    accumulators: &mut std::vec::Vec<TokenBalanceAccumulator>,
) {
    let values = transaction
        .get("meta")
        .and_then(|value| return value.get(key))
        .and_then(serde_json::Value::as_array);
    let values = match values {
        Some(values) => values,
        None => return,
    };
    for value in values {
        let account_index = value.get("accountIndex").and_then(serde_json::Value::as_i64);
        let mint = match value.get("mint").and_then(serde_json::Value::as_str) {
            Some(mint) => mint.to_string(),
            None => continue,
        };
        let owner = value
            .get("owner")
            .and_then(serde_json::Value::as_str)
            .map(|value| return value.to_string());
        let token_program = value
            .get("programId")
            .and_then(serde_json::Value::as_str)
            .map(|value| return value.to_string());
        let amount = value
            .get("uiTokenAmount")
            .and_then(|amount| return amount.get("amount"))
            .and_then(serde_json::Value::as_str)
            .map(|value| return value.to_string());
        let account_address = match account_index {
            Some(account_index) => account_address_by_index(account_keys, account_index),
            None => None,
        };
        let accumulator_index =
            find_token_balance_accumulator(accumulators.as_slice(), account_index, mint.as_str());
        let index = match accumulator_index {
            Some(index) => index,
            None => {
                accumulators.push(TokenBalanceAccumulator {
                    account_index,
                    account_address,
                    mint,
                    owner: owner.clone(),
                    token_program: token_program.clone(),
                    pre_amount_raw: None,
                    post_amount_raw: None,
                });
                accumulators.len() - 1
            },
        };
        if accumulators[index].owner.is_none() {
            accumulators[index].owner = owner;
        }
        if accumulators[index].token_program.is_none() {
            accumulators[index].token_program = token_program;
        }
        if is_pre {
            accumulators[index].pre_amount_raw = amount;
        } else {
            accumulators[index].post_amount_raw = amount;
        }
    }
}

fn find_token_balance_accumulator(
    accumulators: &[TokenBalanceAccumulator],
    account_index: std::option::Option<i64>,
    mint: &str,
) -> std::option::Option<usize> {
    let mut index = 0usize;
    while index < accumulators.len() {
        let accumulator = &accumulators[index];
        if accumulator.account_index == account_index && accumulator.mint == mint {
            return Some(index);
        }
        index += 1;
    }
    return None;
}

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

fn account_key_info_by_address(
    account_keys: &[AccountKeyInfo],
    address: &str,
) -> std::option::Option<AccountKeyInfo> {
    for account_key in account_keys {
        if account_key.address == address {
            return Some(account_key.clone());
        }
    }
    return None;
}

fn collect_observed_token_mints(
    deltas: &[crate::OnchainDexTokenBalanceDeltaDto],
) -> std::vec::Vec<std::string::String> {
    let mut mints = std::vec::Vec::new();
    for delta in deltas {
        push_unique_string(&mut mints, delta.mint.clone());
    }
    return mints;
}

fn sample_token_balance_deltas(
    deltas: &[crate::OnchainDexTokenBalanceDeltaDto],
    limit: usize,
) -> std::vec::Vec<crate::OnchainDexTokenBalanceDeltaDto> {
    let mut samples = std::vec::Vec::new();
    let mut index = 0usize;
    while index < deltas.len() && index < limit {
        samples.push(deltas[index].clone());
        index += 1;
    }
    return samples;
}

fn infer_token_pair_from_deltas(
    deltas: &[crate::OnchainDexTokenBalanceDeltaDto],
) -> (
    std::option::Option<std::string::String>,
    std::option::Option<std::string::String>,
) {
    let mut mints = std::vec::Vec::new();
    for delta in deltas {
        if delta.delta_raw.as_deref() == Some("0") {
            continue;
        }
        push_unique_string(&mut mints, delta.mint.clone());
    }
    if mints.len() < 2 {
        for delta in deltas {
            push_unique_string(&mut mints, delta.mint.clone());
        }
    }
    let first = mints.first().cloned();
    let second = if mints.len() > 1 { Some(mints[1].clone()) } else { None };
    return (first, second);
}

fn infer_candidate_token_vault_accounts(
    instruction_accounts: &[std::string::String],
    deltas: &[crate::OnchainDexTokenBalanceDeltaDto],
    account_keys: &[AccountKeyInfo],
) -> std::vec::Vec<crate::OnchainDexCandidateAccountDto> {
    let mut candidates = std::vec::Vec::new();
    for delta in deltas {
        let address = match &delta.account_address {
            Some(address) => address.clone(),
            None => continue,
        };
        if !string_slice_contains(instruction_accounts, address.as_str()) {
            continue;
        }
        let account_info = account_key_info_by_address(account_keys, address.as_str());
        let account_index = match &account_info {
            Some(account_info) => Some(account_info.index),
            None => delta.account_index,
        };
        let writable = match &account_info {
            Some(account_info) => account_info.writable,
            None => None,
        };
        let signer = match &account_info {
            Some(account_info) => account_info.signer,
            None => None,
        };
        push_unique_candidate_account(
            &mut candidates,
            crate::OnchainDexCandidateAccountDto {
                address,
                account_index,
                writable,
                signer,
                inferred_role: "token_vault_candidate".to_string(),
                confidence: "medium".to_string(),
                reason: format!(
                    "instruction account has token balance delta for mint {}",
                    delta.mint
                ),
            },
        );
    }
    return candidates;
}

fn infer_candidate_pool_accounts(
    instruction_accounts: &[std::string::String],
    account_keys: &[AccountKeyInfo],
    vault_candidates: &[crate::OnchainDexCandidateAccountDto],
) -> std::vec::Vec<crate::OnchainDexCandidateAccountDto> {
    let mut candidates = std::vec::Vec::new();
    for account in instruction_accounts {
        if is_known_program_or_sysvar(account.as_str()) {
            continue;
        }
        if candidate_account_slice_contains(vault_candidates, account.as_str()) {
            continue;
        }
        let info = account_key_info_by_address(account_keys, account.as_str());
        let writable = match &info {
            Some(info) => info.writable,
            None => None,
        };
        let signer = match &info {
            Some(info) => info.signer,
            None => None,
        };
        if signer == Some(true) {
            continue;
        }
        if writable == Some(false) {
            continue;
        }
        let account_index = match &info {
            Some(info) => Some(info.index),
            None => None,
        };
        push_unique_candidate_account(
            &mut candidates,
            crate::OnchainDexCandidateAccountDto {
                address: account.clone(),
                account_index,
                writable,
                signer,
                inferred_role: "pool_or_state_candidate".to_string(),
                confidence: "low".to_string(),
                reason: "writable non-signer account used by matched DEX instruction".to_string(),
            },
        );
        if candidates.len() >= 12 {
            break;
        }
    }
    return candidates;
}

fn infer_candidate_program_accounts(
    instruction_accounts: &[std::string::String],
    account_keys: &[AccountKeyInfo],
    pool_candidates: &[crate::OnchainDexCandidateAccountDto],
    vault_candidates: &[crate::OnchainDexCandidateAccountDto],
) -> std::vec::Vec<crate::OnchainDexCandidateAccountDto> {
    let mut candidates = std::vec::Vec::new();
    for account in instruction_accounts {
        if is_known_program_or_sysvar(account.as_str()) {
            continue;
        }
        if candidate_account_slice_contains(pool_candidates, account.as_str()) {
            continue;
        }
        if candidate_account_slice_contains(vault_candidates, account.as_str()) {
            continue;
        }
        let info = account_key_info_by_address(account_keys, account.as_str());
        let account_index = match &info {
            Some(info) => Some(info.index),
            None => None,
        };
        let writable = match &info {
            Some(info) => info.writable,
            None => None,
        };
        let signer = match &info {
            Some(info) => info.signer,
            None => None,
        };
        push_unique_candidate_account(
            &mut candidates,
            crate::OnchainDexCandidateAccountDto {
                address: account.clone(),
                account_index,
                writable,
                signer,
                inferred_role: "instruction_account_candidate".to_string(),
                confidence: "low".to_string(),
                reason: "non-program account referenced by matched DEX instruction".to_string(),
            },
        );
        if candidates.len() >= 12 {
            break;
        }
    }
    return candidates;
}

fn compute_delta_raw(
    pre: std::option::Option<&str>,
    post: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let pre_value = parse_i128_or_zero(pre);
    let post_value = parse_i128_or_zero(post);
    match (pre_value, post_value) {
        (Some(pre_value), Some(post_value)) => return Some((post_value - pre_value).to_string()),
        _ => return None,
    }
}

fn parse_i128_or_zero(value: std::option::Option<&str>) -> std::option::Option<i128> {
    let value = match value {
        Some(value) => value,
        None => return Some(0),
    };
    let parsed = value.parse::<i128>();
    match parsed {
        Ok(parsed) => return Some(parsed),
        Err(_) => return None,
    }
}

fn push_unique_string(values: &mut std::vec::Vec<std::string::String>, value: std::string::String) {
    if values.iter().any(|existing| return existing == &value) {
        return;
    }
    values.push(value);
}

fn rebuild_candidate_backfill_indexes(result: &mut crate::OnchainDexPairDiscoveryResultDto) {
    result.unique_backfill_signatures.clear();
    result.successful_backfill_signatures.clear();
    result.failed_backfill_signatures.clear();
    result.successful_backfill_signatures_by_kind.clear();
    result.failed_backfill_signatures_by_kind.clear();
    result.candidate_kind_summary.clear();

    for candidate in &result.candidates {
        push_unique_string(&mut result.unique_backfill_signatures, candidate.signature.clone());
        if candidate.failed {
            push_unique_string(&mut result.failed_backfill_signatures, candidate.signature.clone());
            push_backfill_signature_by_kind(
                &mut result.failed_backfill_signatures_by_kind,
                candidate.candidate_kind.as_str(),
                candidate.signature.as_str(),
            );
        } else {
            push_unique_string(
                &mut result.successful_backfill_signatures,
                candidate.signature.clone(),
            );
            push_backfill_signature_by_kind(
                &mut result.successful_backfill_signatures_by_kind,
                candidate.candidate_kind.as_str(),
                candidate.signature.as_str(),
            );
        }
        push_candidate_kind_summary(
            &mut result.candidate_kind_summary,
            candidate.candidate_kind.as_str(),
            candidate.failed,
        );
    }

    refresh_candidate_kind_unique_signature_counts(
        &mut result.candidate_kind_summary,
        result.successful_backfill_signatures_by_kind.as_slice(),
        result.failed_backfill_signatures_by_kind.as_slice(),
    );
    result.candidate_count = result.candidates.len();
    result.unique_signature_count = result.unique_backfill_signatures.len();
}

fn push_backfill_signature_by_kind(
    values: &mut std::vec::Vec<crate::OnchainDexBackfillSignaturesByKindDto>,
    candidate_kind: &str,
    signature: &str,
) {
    for value in values.iter_mut() {
        if value.candidate_kind == candidate_kind {
            push_unique_string(&mut value.signatures, signature.to_string());
            return;
        }
    }
    values.push(crate::OnchainDexBackfillSignaturesByKindDto {
        candidate_kind: candidate_kind.to_string(),
        signatures: vec![signature.to_string()],
    });
}

fn push_candidate_kind_summary(
    values: &mut std::vec::Vec<crate::OnchainDexCandidateKindSummaryDto>,
    candidate_kind: &str,
    failed: bool,
) {
    for value in values.iter_mut() {
        if value.candidate_kind == candidate_kind {
            value.count += 1;
            if failed {
                value.failed_count += 1;
            } else {
                value.success_count += 1;
            }
            return;
        }
    }
    let success_count = if failed { 0 } else { 1 };
    let failed_count = if failed { 1 } else { 0 };
    values.push(crate::OnchainDexCandidateKindSummaryDto {
        candidate_kind: candidate_kind.to_string(),
        count: 1,
        success_count,
        failed_count,
        unique_signature_count: 0,
    });
}

fn refresh_candidate_kind_unique_signature_counts(
    summaries: &mut [crate::OnchainDexCandidateKindSummaryDto],
    successful: &[crate::OnchainDexBackfillSignaturesByKindDto],
    failed: &[crate::OnchainDexBackfillSignaturesByKindDto],
) {
    for summary in summaries {
        let mut signatures: std::vec::Vec<std::string::String> = std::vec::Vec::new();
        collect_kind_signatures(&mut signatures, successful, summary.candidate_kind.as_str());
        collect_kind_signatures(&mut signatures, failed, summary.candidate_kind.as_str());
        summary.unique_signature_count = signatures.len();
    }
}

fn collect_kind_signatures(
    destination: &mut std::vec::Vec<std::string::String>,
    source: &[crate::OnchainDexBackfillSignaturesByKindDto],
    candidate_kind: &str,
) {
    for value in source {
        if value.candidate_kind != candidate_kind {
            continue;
        }
        for signature in &value.signatures {
            push_unique_string(destination, signature.clone());
        }
    }
}

fn push_unique_candidate_account(
    values: &mut std::vec::Vec<crate::OnchainDexCandidateAccountDto>,
    value: crate::OnchainDexCandidateAccountDto,
) {
    if values.iter().any(|existing| return existing.address == value.address) {
        return;
    }
    values.push(value);
}

fn string_slice_contains(values: &[std::string::String], needle: &str) -> bool {
    for value in values {
        if value == needle {
            return true;
        }
    }
    return false;
}

fn candidate_account_slice_contains(
    values: &[crate::OnchainDexCandidateAccountDto],
    needle: &str,
) -> bool {
    for value in values {
        if value.address == needle {
            return true;
        }
    }
    return false;
}

fn is_known_program_or_sysvar(address: &str) -> bool {
    if address == "11111111111111111111111111111111" {
        return true;
    }
    if address == "ComputeBudget111111111111111111111111111111" {
        return true;
    }
    if address == "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" {
        return true;
    }
    if address == "TokenzQdBNbLqP5VEhdkAS6EPVDJGcDq6QubKX8TPp6" {
        return true;
    }
    if address == "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" {
        return true;
    }
    if address == "SysvarRent111111111111111111111111111111111" {
        return true;
    }
    if address == "SysvarC1ock11111111111111111111111111111111" {
        return true;
    }
    if address == "SysvarInstructions1111111111111111111111111" {
        return true;
    }
    return false;
}

fn extract_onchain_instructions(
    transaction: &serde_json::Value,
    account_keys: &[AccountKeyInfo],
) -> std::vec::Vec<OnchainInstructionCandidate> {
    let mut instructions = std::vec::Vec::new();
    let top_level = transaction
        .get("transaction")
        .and_then(|value| return value.get("message"))
        .and_then(|value| return value.get("instructions"))
        .and_then(serde_json::Value::as_array);
    if let Some(top_level) = top_level {
        let mut index = 0usize;
        for instruction in top_level {
            instructions.push(parse_instruction_candidate(
                instruction,
                Some(index as i64),
                None,
                account_keys,
            ));
            index += 1;
        }
    }
    let inner_groups = transaction
        .get("meta")
        .and_then(|value| return value.get("innerInstructions"))
        .and_then(serde_json::Value::as_array);
    if let Some(inner_groups) = inner_groups {
        for group in inner_groups {
            let parent_index = group.get("index").and_then(serde_json::Value::as_i64);
            let inner_array = group.get("instructions").and_then(serde_json::Value::as_array);
            if let Some(inner_array) = inner_array {
                let mut inner_index = 0usize;
                for instruction in inner_array {
                    instructions.push(parse_instruction_candidate(
                        instruction,
                        parent_index,
                        Some(inner_index as i64),
                        account_keys,
                    ));
                    inner_index += 1;
                }
            }
        }
    }
    return instructions;
}

fn parse_instruction_candidate(
    instruction: &serde_json::Value,
    instruction_index: std::option::Option<i64>,
    inner_instruction_index: std::option::Option<i64>,
    account_keys: &[AccountKeyInfo],
) -> OnchainInstructionCandidate {
    return OnchainInstructionCandidate {
        instruction_index,
        inner_instruction_index,
        program_id: extract_instruction_program_id(instruction, account_keys),
        accounts: extract_accounts(instruction, account_keys),
        data: extract_instruction_data(instruction),
        parsed: instruction.get("parsed").cloned(),
    };
}

fn extract_instruction_program_id(
    instruction: &serde_json::Value,
    account_keys: &[AccountKeyInfo],
) -> std::option::Option<std::string::String> {
    if let Some(program_id) = extract_string_field(instruction, "programId") {
        return Some(program_id);
    }
    let program_id_index = instruction.get("programIdIndex").and_then(serde_json::Value::as_i64);
    let program_id_index = match program_id_index {
        Some(program_id_index) => program_id_index,
        None => return None,
    };
    return account_address_by_index(account_keys, program_id_index);
}

fn extract_accounts(
    instruction: &serde_json::Value,
    account_keys: &[AccountKeyInfo],
) -> std::vec::Vec<std::string::String> {
    let mut accounts = std::vec::Vec::new();
    let account_values = instruction.get("accounts").and_then(serde_json::Value::as_array);
    if let Some(account_values) = account_values {
        for account_value in account_values {
            if let Some(account) = account_value.as_str() {
                accounts.push(account.to_string());
                continue;
            }
            if let Some(account_index) = account_value.as_i64() {
                if let Some(account) = account_address_by_index(account_keys, account_index) {
                    accounts.push(account);
                }
                continue;
            }
            if let Some(pubkey) = account_value.get("pubkey").and_then(serde_json::Value::as_str) {
                accounts.push(pubkey.to_string());
            }
        }
    }
    return accounts;
}

fn extract_instruction_data(
    instruction: &serde_json::Value,
) -> std::option::Option<std::string::String> {
    let data = match instruction.get("data") {
        Some(data) => data,
        None => return None,
    };
    if let Some(data_string) = data.as_str() {
        return Some(data_string.to_string());
    }
    if let Some(data_array) = data.as_array() {
        let first = match data_array.first() {
            Some(first) => first,
            None => return None,
        };
        if let Some(first_string) = first.as_str() {
            return Some(first_string.to_string());
        }
    }
    return None;
}

fn extract_string_field(
    value: &serde_json::Value,
    key: &str,
) -> std::option::Option<std::string::String> {
    let found = value.get(key).and_then(serde_json::Value::as_str);
    match found {
        Some(found) => return Some(found.to_string()),
        None => return None,
    }
}

fn extract_string_by_candidate_keys_from_instruction(
    instruction: &OnchainInstructionCandidate,
    keys: &[&str],
) -> std::option::Option<std::string::String> {
    let parsed = match &instruction.parsed {
        Some(parsed) => parsed,
        None => return None,
    };
    let info = parsed.get("info");
    for key in keys {
        if let Some(info) = info {
            if let Some(value) = info.get(*key).and_then(serde_json::Value::as_str) {
                return Some(value.to_string());
            }
        }
        if let Some(value) = parsed.get(*key).and_then(serde_json::Value::as_str) {
            return Some(value.to_string());
        }
    }
    return None;
}

fn infer_instruction_name_from_parsed(
    parsed: std::option::Option<&serde_json::Value>,
) -> std::option::Option<std::string::String> {
    if let Some(parsed) = parsed {
        if let Some(name) = parsed.get("type").and_then(serde_json::Value::as_str) {
            return Some(name.to_string());
        }
        if let Some(info) = parsed.get("info") {
            let keys = ["instruction", "instructionType", "type", "name"];
            for key in keys {
                if let Some(name) = info.get(key).and_then(serde_json::Value::as_str) {
                    return Some(name.to_string());
                }
            }
        }
    }
    return None;
}

fn infer_instruction_name(
    parsed: std::option::Option<&serde_json::Value>,
    logs: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_name = infer_instruction_name_from_parsed(parsed);
    if parsed_name.is_some() {
        return parsed_name;
    }
    for log in logs {
        let lower = log.to_ascii_lowercase();
        if let Some(index) = lower.find("instruction:") {
            let original = log.as_str();
            let start_index = index + "instruction:".len();
            if start_index <= original.len() {
                return Some(original[start_index..].trim().to_string());
            }
        }
    }
    return None;
}

fn upstream_registry_entry_name_to_candidate_kind(entry_name: &str) -> std::string::String {
    let lower = entry_name.to_ascii_lowercase();
    if lower.contains("swap") || lower == "buy" || lower == "sell" {
        return "swap".to_string();
    }
    if lower.contains("add_liquidity")
        || lower.contains("increase_liquidity")
        || lower == "deposit"
        || lower.ends_with("_deposit")
    {
        return "add_liquidity".to_string();
    }
    if lower.contains("remove_liquidity")
        || lower.contains("decrease_liquidity")
        || lower == "withdraw"
        || lower.ends_with("_withdraw")
    {
        return "remove_liquidity".to_string();
    }
    if lower.contains("claim") && lower.contains("reward") {
        return "claim_reward".to_string();
    }
    if lower.contains("claim") && lower.contains("fee") {
        return "claim_fee".to_string();
    }
    if lower.contains("open_position") || lower.contains("initialize_position") {
        return "position_open".to_string();
    }
    if lower.contains("close_position") {
        return "position_close".to_string();
    }
    if text_matches_burn(lower.as_str()) {
        return "burn".to_string();
    }
    if text_matches_mint(lower.as_str()) {
        return "mint".to_string();
    }
    if text_matches_transfer(lower.as_str()) {
        return "transfer".to_string();
    }
    if text_matches_close_account(lower.as_str()) {
        return "close_account".to_string();
    }
    if text_matches_token_account_create(lower.as_str()) {
        return "token_account_create".to_string();
    }
    if text_matches_wrap_sol(lower.as_str()) {
        return "wrap_sol".to_string();
    }
    if text_matches_unwrap_sol(lower.as_str()) {
        return "unwrap_sol".to_string();
    }
    if text_matches_unstake(lower.as_str()) {
        return "unstake".to_string();
    }
    if text_matches_stake(lower.as_str()) {
        return "stake".to_string();
    }
    if lower.contains("create_lock_escrow") {
        return "create_lock_escrow".to_string();
    }
    if lower.contains("lock_liquidity") {
        return "lock_liquidity".to_string();
    }
    if lower.contains("create") && lower.contains("pool") {
        return "create_pool".to_string();
    }
    if lower.contains("initialize") && lower.contains("pool") {
        return "initialize_pool".to_string();
    }
    if text_matches_orderbook_fill(lower.as_str()) {
        return "order_fill".to_string();
    }
    if text_matches_orderbook_place(lower.as_str()) {
        return "order_place".to_string();
    }
    if text_matches_orderbook_cancel(lower.as_str()) {
        return "order_cancel".to_string();
    }
    if text_matches_settle_funds(lower.as_str()) {
        return "settle_funds".to_string();
    }
    if text_matches_consume_events(lower.as_str()) {
        return "consume_events".to_string();
    }
    if text_matches_market_create(lower.as_str()) {
        return "market_create".to_string();
    }
    if text_matches_open_orders_create(lower.as_str()) {
        return "open_orders_create".to_string();
    }
    if text_matches_open_orders_close(lower.as_str()) {
        return "open_orders_close".to_string();
    }
    if text_matches_close_market(lower.as_str()) {
        return "close_market".to_string();
    }
    if lower.contains("set_") || lower.contains("update_") || lower.contains("admin") {
        return "pool_admin".to_string();
    }
    return "upstream_git_instruction".to_string();
}

fn infer_candidate_kind(
    instruction_name: std::option::Option<&std::string::String>,
    logs: &[std::string::String],
    has_raw_instruction_data: bool,
    prefer_instruction_local_classification: bool,
) -> std::string::String {
    let mut instruction_text = std::string::String::new();
    if let Some(instruction_name) = instruction_name {
        instruction_text.push_str(instruction_name.as_str());
        instruction_text.push(' ');
    }
    let mut log_text = std::string::String::new();
    for log in logs {
        log_text.push_str(log.as_str());
        log_text.push(' ');
    }
    let mut text = instruction_text.clone();
    text.push_str(log_text.as_str());
    let lower = text.to_ascii_lowercase();
    let instruction_lower = instruction_text.to_ascii_lowercase();
    if instruction_lower.contains("swap")
        || instruction_lower.contains("buy")
        || instruction_lower.contains("sell")
    {
        return "swap".to_string();
    }
    if prefer_instruction_local_classification {
        if text_matches_non_swap_target(lower.as_str()) {
            return infer_non_swap_candidate_kind(lower.as_str());
        }
        if has_raw_instruction_data {
            return "unclassified_instruction".to_string();
        }
        return "program_activity".to_string();
    }
    if text_matches_pool_admin(lower.as_str()) {
        return "pool_admin".to_string();
    }
    if text_matches_claim_reward(lower.as_str()) {
        return "claim_reward".to_string();
    }
    if text_matches_claim_fee(lower.as_str()) {
        return "claim_fee".to_string();
    }
    if text_matches_position_close(lower.as_str()) {
        return "position_close".to_string();
    }
    if text_matches_position_open(lower.as_str()) {
        return "position_open".to_string();
    }
    if text_matches_add_liquidity(lower.as_str()) {
        return "add_liquidity".to_string();
    }
    if text_matches_remove_liquidity(lower.as_str()) {
        return "remove_liquidity".to_string();
    }
    if lower.contains("create") && lower.contains("pool") {
        return "create_pool".to_string();
    }
    if lower.contains("initialize") && lower.contains("pool") {
        return "initialize_pool".to_string();
    }
    if lower.contains("swap") || lower.contains("buy") || lower.contains("sell") {
        return "swap".to_string();
    }
    if text_matches_orderbook_fill(lower.as_str()) {
        return "order_fill".to_string();
    }
    if text_matches_orderbook_place(lower.as_str()) {
        return "order_place".to_string();
    }
    if text_matches_orderbook_cancel(lower.as_str()) {
        return "order_cancel".to_string();
    }
    if text_matches_settle_funds(lower.as_str()) {
        return "settle_funds".to_string();
    }
    if text_matches_consume_events(lower.as_str()) {
        return "consume_events".to_string();
    }
    if text_matches_market_create(lower.as_str()) {
        return "market_create".to_string();
    }
    if text_matches_open_orders_create(lower.as_str()) {
        return "open_orders_create".to_string();
    }
    if text_matches_open_orders_close(lower.as_str()) {
        return "open_orders_close".to_string();
    }
    if text_matches_close_market(lower.as_str()) {
        return "close_market".to_string();
    }
    if lower.contains("liquidity") || lower.contains("position") {
        return "liquidity_or_position".to_string();
    }
    return "program_activity".to_string();
}

fn target_event_prefers_instruction_local_classification(
    target_event: std::option::Option<&str>,
) -> bool {
    for target in split_target_event_filter(target_event) {
        if target == "unknown_non_swap"
            || target == "audit_non_swap_like"
            || target == "unclassified_instruction"
            || target == "non_swap"
            || target == "burn"
            || target == "mint"
            || target == "transfer"
            || target == "close_account"
            || target == "token_account_create"
            || target == "wrap_sol"
            || target == "unwrap_sol"
            || target == "stake"
            || target == "unstake"
            || target == "order_place"
            || target == "order_cancel"
            || target == "order_fill"
            || target == "settle_funds"
            || target == "consume_events"
            || target == "market_create"
            || target == "open_orders_create"
            || target == "open_orders_close"
            || target == "close_market"
        {
            return true;
        }
    }
    return false;
}

fn target_event_keeps_mixed_swap_transactions(
    target_event: std::option::Option<&str>,
    target_instruction_name: std::option::Option<&str>,
    target_discriminator_hex: std::option::Option<&str>,
) -> bool {
    if !split_target_event_filter(target_event).is_empty() {
        return true;
    }
    if !split_comma_filter(target_instruction_name).is_empty() {
        return true;
    }
    if !split_comma_filter(target_discriminator_hex).is_empty() {
        return true;
    }
    return false;
}

fn instruction_data_prefix(
    data: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let data = match data {
        Some(data) => data.trim(),
        None => return None,
    };
    if data.is_empty() {
        return None;
    }
    let mut prefix = std::string::String::new();
    for character in data.chars().take(16) {
        prefix.push(character);
    }
    if prefix.is_empty() {
        return None;
    }
    return Some(prefix);
}

fn instruction_discriminator_hex(
    data: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let decoded = match decode_onchain_instruction_data(data) {
        Some(decoded) => decoded,
        None => return None,
    };
    if decoded.len() < 8 {
        return None;
    }
    let mut text = std::string::String::new();
    for byte in &decoded[0..8] {
        text.push_str(format!("{:02x}", byte).as_str());
    }
    return Some(text);
}

fn text_matches_non_swap_target(lower: &str) -> bool {
    return text_matches_pool_admin(lower)
        || text_matches_claim_reward(lower)
        || text_matches_claim_fee(lower)
        || text_matches_position_close(lower)
        || text_matches_position_open(lower)
        || text_matches_add_liquidity(lower)
        || text_matches_remove_liquidity(lower)
        || text_matches_burn(lower)
        || text_matches_mint(lower)
        || text_matches_transfer(lower)
        || text_matches_close_account(lower)
        || text_matches_token_account_create(lower)
        || text_matches_wrap_sol(lower)
        || text_matches_unwrap_sol(lower)
        || text_matches_stake(lower)
        || text_matches_unstake(lower)
        || text_matches_orderbook_place(lower)
        || text_matches_orderbook_cancel(lower)
        || text_matches_orderbook_fill(lower)
        || text_matches_settle_funds(lower)
        || text_matches_consume_events(lower)
        || text_matches_market_create(lower)
        || text_matches_open_orders_create(lower)
        || text_matches_open_orders_close(lower)
        || text_matches_close_market(lower)
        || ((lower.contains("create") || lower.contains("initialize")) && lower.contains("pool"));
}

fn infer_non_swap_candidate_kind(lower: &str) -> std::string::String {
    if text_matches_pool_admin(lower) {
        return "pool_admin".to_string();
    }
    if text_matches_claim_reward(lower) {
        return "claim_reward".to_string();
    }
    if text_matches_claim_fee(lower) {
        return "claim_fee".to_string();
    }
    if text_matches_position_close(lower) {
        return "position_close".to_string();
    }
    if text_matches_position_open(lower) {
        return "position_open".to_string();
    }
    if text_matches_add_liquidity(lower) {
        return "add_liquidity".to_string();
    }
    if text_matches_remove_liquidity(lower) {
        return "remove_liquidity".to_string();
    }
    if text_matches_burn(lower) {
        return "burn".to_string();
    }
    if text_matches_mint(lower) {
        return "mint".to_string();
    }
    if text_matches_transfer(lower) {
        return "transfer".to_string();
    }
    if text_matches_close_account(lower) {
        return "close_account".to_string();
    }
    if text_matches_token_account_create(lower) {
        return "token_account_create".to_string();
    }
    if text_matches_wrap_sol(lower) {
        return "wrap_sol".to_string();
    }
    if text_matches_unwrap_sol(lower) {
        return "unwrap_sol".to_string();
    }
    if text_matches_unstake(lower) {
        return "unstake".to_string();
    }
    if text_matches_stake(lower) {
        return "stake".to_string();
    }
    if lower.contains("create") && lower.contains("pool") {
        return "create_pool".to_string();
    }
    if lower.contains("initialize") && lower.contains("pool") {
        return "initialize_pool".to_string();
    }
    if text_matches_orderbook_fill(lower) {
        return "order_fill".to_string();
    }
    if text_matches_orderbook_place(lower) {
        return "order_place".to_string();
    }
    if text_matches_orderbook_cancel(lower) {
        return "order_cancel".to_string();
    }
    if text_matches_settle_funds(lower) {
        return "settle_funds".to_string();
    }
    if text_matches_consume_events(lower) {
        return "consume_events".to_string();
    }
    if text_matches_market_create(lower) {
        return "market_create".to_string();
    }
    if text_matches_open_orders_create(lower) {
        return "open_orders_create".to_string();
    }
    if text_matches_open_orders_close(lower) {
        return "open_orders_close".to_string();
    }
    if text_matches_close_market(lower) {
        return "close_market".to_string();
    }
    return "non_swap_activity".to_string();
}

fn split_target_event_filter(
    target_event: std::option::Option<&str>,
) -> std::vec::Vec<std::string::String> {
    let mut targets = std::vec::Vec::new();
    let value = match target_event {
        Some(value) => value,
        None => return targets,
    };
    for token in value.split(|character: char| {
        return character == ',' || character == ';' || character.is_whitespace();
    }) {
        let normalized = normalize_target_event_token(token.trim());
        if normalized.is_empty() || normalized == "any" || normalized == "all" {
            continue;
        }
        push_unique_string(&mut targets, normalized);
    }
    return targets;
}

fn normalize_target_event_token(value: &str) -> std::string::String {
    let normalized = value.trim().to_ascii_lowercase().replace(['-', ' '], "_");
    match normalized.as_str() {
        "token_burn" | "burn_token" | "burn_tokens" => return "burn".to_string(),
        "token_mint" | "mint_token" | "mint_tokens" | "mint_to" => return "mint".to_string(),
        "token_transfer" | "transfer_token" | "transfer_tokens" | "transfer_checked" => {
            return "transfer".to_string();
        },
        "close_token_account" | "token_account_close" | "close_account" => {
            return "close_account".to_string();
        },
        "create_token_account" | "associated_token_account" | "ata_create" => {
            return "token_account_create".to_string();
        },
        "wrap" | "wrap_native" | "wrap_sol" => return "wrap_sol".to_string(),
        "unwrap" | "unwrap_native" | "unwrap_sol" | "sync_native_close" => {
            return "unwrap_sol".to_string();
        },
        "staking" | "delegate_stake" => return "stake".to_string(),
        "unstaking" | "withdraw_stake" | "deactivate_stake" => return "unstake".to_string(),
        _ => return normalized,
    }
}

fn refine_candidate_for_target(
    candidate: &mut crate::OnchainDexPairCandidateDto,
    target_event: std::option::Option<&str>,
) {
    if split_target_event_filter(target_event).is_empty() {
        return;
    }
    let matched_target_event = match first_matching_target_event(candidate, target_event) {
        Some(matched_target_event) => matched_target_event,
        None => return,
    };
    if candidate.confidence == "low" {
        candidate.confidence = "medium".to_string();
    }
    let pool_address =
        candidate.verified_pool_address.as_deref().or(candidate.pool_address.as_deref());
    if let Some(pool_address) = pool_address {
        candidate.backfill_hint = format!(
            "Target '{}' candidate; backfill pool in Demo Pipeline 2: {} ; signature: {}",
            matched_target_event, pool_address, candidate.signature
        );
        return;
    }
    candidate.backfill_hint = format!(
        "Target '{}' candidate; inspect/backfill transaction signature: {}",
        matched_target_event, candidate.signature
    );
}

fn first_matching_target_event(
    candidate: &crate::OnchainDexPairCandidateDto,
    target_event: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let targets = split_target_event_filter(target_event);
    if targets.is_empty() {
        return Some("any".to_string());
    }
    for target in targets {
        if candidate_matches_single_target_event(candidate, target.as_str()) {
            return Some(target);
        }
    }
    return None;
}

fn candidate_matches_instruction_filters(
    candidate: &crate::OnchainDexPairCandidateDto,
    target_instruction_name: std::option::Option<&str>,
    target_discriminator_hex: std::option::Option<&str>,
) -> bool {
    if !candidate_matches_instruction_name_filter(candidate, target_instruction_name) {
        return false;
    }
    if !candidate_matches_discriminator_filter(candidate, target_discriminator_hex) {
        return false;
    }
    return true;
}

fn candidate_matches_instruction_name_filter(
    candidate: &crate::OnchainDexPairCandidateDto,
    target_instruction_name: std::option::Option<&str>,
) -> bool {
    let targets = split_comma_filter(target_instruction_name);
    if targets.is_empty() {
        return true;
    }
    let instruction_name = match &candidate.instruction_name {
        Some(instruction_name) => instruction_name.to_ascii_lowercase(),
        None => return false,
    };
    for target in targets {
        if instruction_name == target || instruction_name.ends_with(format!(".{}", target).as_str())
        {
            return true;
        }
    }
    return false;
}

fn candidate_matches_discriminator_filter(
    candidate: &crate::OnchainDexPairCandidateDto,
    target_discriminator_hex: std::option::Option<&str>,
) -> bool {
    let targets = split_comma_filter(target_discriminator_hex);
    if targets.is_empty() {
        return true;
    }
    let discriminator_hex = match &candidate.instruction_discriminator_hex {
        Some(discriminator_hex) => discriminator_hex.to_ascii_lowercase(),
        None => return false,
    };
    for target in targets {
        if discriminator_hex == target {
            return true;
        }
    }
    return false;
}

fn split_comma_filter(value: std::option::Option<&str>) -> std::vec::Vec<std::string::String> {
    let value = match value {
        Some(value) => value,
        None => return std::vec::Vec::new(),
    };
    let mut output = std::vec::Vec::new();
    for token in value.split(',') {
        let token = token.trim();
        if token.is_empty() {
            continue;
        }
        push_unique_string(&mut output, token.to_string());
    }
    return output;
}

fn candidate_matches_target_event(
    candidate: &crate::OnchainDexPairCandidateDto,
    target_event: std::option::Option<&str>,
) -> bool {
    return first_matching_target_event(candidate, target_event).is_some();
}

fn candidate_matches_single_target_event(
    candidate: &crate::OnchainDexPairCandidateDto,
    target_event: &str,
) -> bool {
    if target_event == "unknown_non_swap" {
        return candidate_is_unknown_non_swap_candidate(candidate);
    }
    if target_event == "audit_non_swap_like" || target_event == "non_swap" {
        return candidate_is_non_swap_audit_candidate(candidate);
    }
    if target_event == "unclassified_instruction" {
        return candidate.candidate_kind == "unclassified_instruction"
            && !candidate_is_known_trade_like_surface(candidate);
    }
    if exact_candidate_kind_matches_target(candidate.candidate_kind.as_str(), target_event) {
        return true;
    }
    if candidate_kind_is_explicit_surface(candidate.candidate_kind.as_str()) {
        return false;
    }
    let mut text = candidate.candidate_kind.clone();
    text.push(' ');
    if let Some(instruction_name) = &candidate.instruction_name {
        text.push_str(instruction_name.as_str());
        text.push(' ');
    }
    for log in &candidate.log_samples {
        text.push_str(log.as_str());
        text.push(' ');
    }
    let lower = text.to_ascii_lowercase();
    match target_event {
        "swap" => {
            return lower.contains("swap")
                || lower.contains("buy")
                || lower.contains("sell")
                || candidate_is_known_trade_like_surface(candidate);
        },
        "add_liquidity" => return text_matches_add_liquidity(lower.as_str()),
        "remove_liquidity" => return text_matches_remove_liquidity(lower.as_str()),
        "claim_fee" => return text_matches_claim_fee(lower.as_str()),
        "claim_reward" | "reward" => return text_matches_claim_reward(lower.as_str()),
        "position_open" | "open_position" => return text_matches_position_open(lower.as_str()),
        "position_close" | "close_position" => return text_matches_position_close(lower.as_str()),
        "pool_create" | "create_pool" => {
            return (lower.contains("create") || lower.contains("initialize"))
                && lower.contains("pool");
        },
        "pool_admin" | "admin" | "config" | "authority" => {
            return text_matches_pool_admin(lower.as_str());
        },
        "create_lock_escrow" => {
            return lower.contains("createlockescrow")
                || lower.contains("create_lock_escrow")
                || lower.contains("create lock escrow");
        },
        "lock_liquidity" | "lock" => {
            return lower.contains("lockliquidity")
                || lower.contains("lock_liquidity")
                || lower.contains("lock liquidity")
                || lower.contains("meteora_damm_v1.lock");
        },
        "burn" => return text_matches_burn(lower.as_str()),
        "mint" => return text_matches_mint(lower.as_str()),
        "transfer" => return text_matches_transfer(lower.as_str()),
        "close_account" => return text_matches_close_account(lower.as_str()),
        "token_account_create" => return text_matches_token_account_create(lower.as_str()),
        "wrap_sol" => return text_matches_wrap_sol(lower.as_str()),
        "unwrap_sol" => return text_matches_unwrap_sol(lower.as_str()),
        "stake" => return text_matches_stake(lower.as_str()),
        "unstake" => return text_matches_unstake(lower.as_str()),
        "order_place" => return text_matches_orderbook_place(lower.as_str()),
        "order_cancel" => return text_matches_orderbook_cancel(lower.as_str()),
        "order_fill" => return text_matches_orderbook_fill(lower.as_str()),
        "settle_funds" => return text_matches_settle_funds(lower.as_str()),
        "consume_events" => return text_matches_consume_events(lower.as_str()),
        "market_create" => return text_matches_market_create(lower.as_str()),
        "open_orders_create" => return text_matches_open_orders_create(lower.as_str()),
        "open_orders_close" => return text_matches_open_orders_close(lower.as_str()),
        "close_market" => return text_matches_close_market(lower.as_str()),
        _ => return true,
    }
}

fn exact_candidate_kind_matches_target(candidate_kind: &str, target_event: &str) -> bool {
    match target_event {
        "swap" => return candidate_kind == "swap" || candidate_kind == "trade_like_unclassified",
        "add_liquidity" => return candidate_kind == "add_liquidity",
        "remove_liquidity" => return candidate_kind == "remove_liquidity",
        "claim_fee" => return candidate_kind == "claim_fee",
        "claim_reward" | "reward" => return candidate_kind == "claim_reward",
        "position_open" | "open_position" => return candidate_kind == "position_open",
        "position_close" | "close_position" => return candidate_kind == "position_close",
        "pool_create" | "create_pool" => {
            return candidate_kind == "create_pool" || candidate_kind == "initialize_pool";
        },
        "pool_admin" | "admin" | "config" | "authority" => {
            return candidate_kind == "pool_admin" || candidate_kind == "lock_liquidity";
        },
        "create_lock_escrow" => return candidate_kind == "create_lock_escrow",
        "lock_liquidity" | "lock" => return candidate_kind == "lock_liquidity",
        "burn" => return candidate_kind == "burn",
        "mint" => return candidate_kind == "mint",
        "transfer" => return candidate_kind == "transfer",
        "close_account" => return candidate_kind == "close_account",
        "token_account_create" => return candidate_kind == "token_account_create",
        "wrap_sol" => return candidate_kind == "wrap_sol",
        "unwrap_sol" => return candidate_kind == "unwrap_sol",
        "stake" => return candidate_kind == "stake",
        "unstake" => return candidate_kind == "unstake",
        "order_place" => return candidate_kind == "order_place",
        "order_cancel" => return candidate_kind == "order_cancel",
        "order_fill" => return candidate_kind == "order_fill",
        "settle_funds" => return candidate_kind == "settle_funds",
        "consume_events" => return candidate_kind == "consume_events",
        "market_create" => return candidate_kind == "market_create",
        "open_orders_create" => return candidate_kind == "open_orders_create",
        "open_orders_close" => return candidate_kind == "open_orders_close",
        "close_market" => return candidate_kind == "close_market",
        _ => return false,
    }
}

fn candidate_kind_is_explicit_surface(candidate_kind: &str) -> bool {
    return candidate_kind == "swap"
        || candidate_kind == "trade_like_unclassified"
        || candidate_kind == "add_liquidity"
        || candidate_kind == "remove_liquidity"
        || candidate_kind == "claim_fee"
        || candidate_kind == "claim_reward"
        || candidate_kind == "position_open"
        || candidate_kind == "position_close"
        || candidate_kind == "pool_admin"
        || candidate_kind == "create_pool"
        || candidate_kind == "initialize_pool"
        || candidate_kind == "create_lock_escrow"
        || candidate_kind == "lock_liquidity"
        || candidate_kind == "burn"
        || candidate_kind == "mint"
        || candidate_kind == "transfer"
        || candidate_kind == "close_account"
        || candidate_kind == "token_account_create"
        || candidate_kind == "wrap_sol"
        || candidate_kind == "unwrap_sol"
        || candidate_kind == "stake"
        || candidate_kind == "unstake"
        || candidate_kind == "order_place"
        || candidate_kind == "order_cancel"
        || candidate_kind == "order_fill"
        || candidate_kind == "settle_funds"
        || candidate_kind == "consume_events"
        || candidate_kind == "market_create"
        || candidate_kind == "open_orders_create"
        || candidate_kind == "open_orders_close"
        || candidate_kind == "close_market";
}

fn candidate_is_unknown_non_swap_candidate(candidate: &crate::OnchainDexPairCandidateDto) -> bool {
    if candidate.candidate_kind == "swap" || candidate_is_known_trade_like_surface(candidate) {
        return false;
    }
    if candidate_kind_is_explicit_surface(candidate.candidate_kind.as_str()) {
        return false;
    }
    if candidate.candidate_kind == "unclassified_instruction"
        || candidate.candidate_kind == "program_activity"
        || candidate.candidate_kind == "liquidity_or_position"
        || candidate.candidate_kind == "non_swap_activity"
        || candidate.candidate_kind == "upstream_git_instruction"
    {
        return true;
    }
    return candidate.instruction_data_prefix.is_some() && candidate.instruction_name.is_none();
}

fn candidate_is_non_swap_audit_candidate(candidate: &crate::OnchainDexPairCandidateDto) -> bool {
    if candidate.candidate_kind == "swap" || candidate_is_known_trade_like_surface(candidate) {
        return false;
    }
    if candidate.candidate_kind == "unclassified_instruction"
        || candidate.candidate_kind == "program_activity"
        || candidate.candidate_kind == "liquidity_or_position"
        || candidate.candidate_kind == "non_swap_activity"
        || candidate.candidate_kind == "add_liquidity"
        || candidate.candidate_kind == "remove_liquidity"
        || candidate.candidate_kind == "claim_fee"
        || candidate.candidate_kind == "claim_reward"
        || candidate.candidate_kind == "position_open"
        || candidate.candidate_kind == "position_close"
        || candidate.candidate_kind == "pool_admin"
        || candidate.candidate_kind == "create_pool"
        || candidate.candidate_kind == "initialize_pool"
        || candidate.candidate_kind == "create_lock_escrow"
        || candidate.candidate_kind == "lock_liquidity"
        || candidate.candidate_kind == "burn"
        || candidate.candidate_kind == "mint"
        || candidate.candidate_kind == "transfer"
        || candidate.candidate_kind == "close_account"
        || candidate.candidate_kind == "token_account_create"
        || candidate.candidate_kind == "wrap_sol"
        || candidate.candidate_kind == "unwrap_sol"
        || candidate.candidate_kind == "stake"
        || candidate.candidate_kind == "unstake"
        || candidate.candidate_kind == "order_place"
        || candidate.candidate_kind == "order_cancel"
        || candidate.candidate_kind == "order_fill"
        || candidate.candidate_kind == "settle_funds"
        || candidate.candidate_kind == "consume_events"
        || candidate.candidate_kind == "market_create"
        || candidate.candidate_kind == "open_orders_create"
        || candidate.candidate_kind == "open_orders_close"
        || candidate.candidate_kind == "close_market"
        || candidate.candidate_kind == "upstream_git_instruction"
    {
        return true;
    }
    return candidate.instruction_data_prefix.is_some();
}

fn candidate_is_known_trade_like_surface(candidate: &crate::OnchainDexPairCandidateDto) -> bool {
    if candidate.candidate_kind == "swap" || candidate.candidate_kind == "trade_like_unclassified" {
        return true;
    }
    if candidate.program_id == "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG" {
        if let Some(prefix) = &candidate.instruction_data_prefix {
            if prefix == "EVM9wLnauu9H41Gf" {
                return true;
            }
            if prefix.starts_with("TGq5We4Uqk") || prefix.starts_with("PgQWtn8ozi") {
                return true;
            }
        }
    }
    return false;
}

fn candidate_identity_key(candidate: &crate::OnchainDexPairCandidateDto) -> std::string::String {
    return format!(
        "{}:{}:{}:{}",
        candidate.signature,
        option_i64_key(candidate.instruction_index),
        option_i64_key(candidate.inner_instruction_index),
        candidate.instruction_data_prefix.clone().unwrap_or_default()
    );
}

fn option_i64_key(value: std::option::Option<i64>) -> std::string::String {
    match value {
        Some(value) => return value.to_string(),
        None => return "-".to_string(),
    }
}

fn push_rejected_candidate_summary(
    summaries: &mut std::vec::Vec<crate::OnchainDexRejectedCandidateSummaryDto>,
    candidate: &crate::OnchainDexPairCandidateDto,
    rejection_reason: &str,
) {
    for summary in summaries.iter_mut() {
        if summary.candidate_kind == candidate.candidate_kind
            && summary.instruction_data_prefix == candidate.instruction_data_prefix
            && summary.instruction_name == candidate.instruction_name
            && summary.rejection_reason == rejection_reason
        {
            summary.count += 1;
            return;
        }
    }
    summaries.push(crate::OnchainDexRejectedCandidateSummaryDto {
        candidate_kind: candidate.candidate_kind.clone(),
        instruction_data_prefix: candidate.instruction_data_prefix.clone(),
        instruction_name: candidate.instruction_name.clone(),
        rejection_reason: rejection_reason.to_string(),
        count: 1,
    });
}

fn merge_rejected_candidate_summaries(
    target: &mut std::vec::Vec<crate::OnchainDexRejectedCandidateSummaryDto>,
    source: std::vec::Vec<crate::OnchainDexRejectedCandidateSummaryDto>,
) {
    for item in source {
        let mut merged = false;
        for target_item in target.iter_mut() {
            if target_item.candidate_kind == item.candidate_kind
                && target_item.instruction_data_prefix == item.instruction_data_prefix
                && target_item.instruction_name == item.instruction_name
                && target_item.rejection_reason == item.rejection_reason
            {
                target_item.count += item.count;
                merged = true;
                break;
            }
        }
        if !merged {
            target.push(item);
        }
    }
}

fn logs_contain_swap(logs: &[std::string::String]) -> bool {
    for log in logs {
        let lower = log.to_ascii_lowercase();
        if lower.contains("instruction: swap") || lower.contains("swapevent") {
            return true;
        }
    }
    return false;
}

fn text_matches_add_liquidity(lower: &str) -> bool {
    return lower.contains("addliquidity")
        || lower.contains("add liquidity")
        || lower.contains("add_liquidity")
        || lower.contains("increaseliquidity")
        || lower.contains("increase_liquidity")
        || lower.contains("increase liquidity")
        || lower.contains("depositliquidity")
        || lower.contains("deposit liquidity")
        || lower.contains("deposit");
}

fn text_matches_remove_liquidity(lower: &str) -> bool {
    return lower.contains("removeliquidity")
        || lower.contains("remove liquidity")
        || lower.contains("remove_liquidity")
        || lower.contains("decreaseliquidity")
        || lower.contains("decrease_liquidity")
        || lower.contains("decrease liquidity")
        || lower.contains("withdrawliquidity")
        || lower.contains("withdraw liquidity")
        || lower.contains("withdraw");
}

fn text_matches_claim_fee(lower: &str) -> bool {
    return lower.contains("claimfee")
        || lower.contains("claim_fee")
        || lower.contains("claim fee")
        || lower.contains("collectfee")
        || lower.contains("collect_fee")
        || lower.contains("collect fee")
        || lower.contains("claimpositionfee")
        || lower.contains("claim_position_fee")
        || lower.contains("position fee")
        || lower.contains("protocol_fee")
        || lower.contains("creator_fee");
}

fn text_matches_claim_reward(lower: &str) -> bool {
    return lower.contains("claimreward")
        || lower.contains("claim_reward")
        || lower.contains("claim reward")
        || lower.contains("reward");
}

fn text_matches_position_open(lower: &str) -> bool {
    return lower.contains("openposition")
        || lower.contains("open_position")
        || lower.contains("open position")
        || lower.contains("initializeposition")
        || lower.contains("initialize_position")
        || lower.contains("initialize position")
        || lower.contains("createposition")
        || lower.contains("create_position")
        || lower.contains("create position");
}

fn text_matches_position_close(lower: &str) -> bool {
    return lower.contains("closeposition")
        || lower.contains("close_position")
        || lower.contains("close position");
}

fn text_matches_pool_admin(lower: &str) -> bool {
    return lower.contains("setconfig")
        || lower.contains("set_config")
        || lower.contains("set config")
        || lower.contains("updateconfig")
        || lower.contains("update_config")
        || lower.contains("update config")
        || lower.contains("set_parameter")
        || lower.contains("setactivation")
        || lower.contains("set_activation")
        || lower.contains("config")
        || lower.contains("authority")
        || lower.contains("admin");
}

fn text_matches_orderbook_place(lower: &str) -> bool {
    return lower.contains("place_order")
        || lower.contains("place order")
        || lower.contains("placeorder")
        || lower.contains("new_order")
        || lower.contains("new order")
        || lower.contains("placelimitorder")
        || lower.contains("place_limit_order")
        || lower.contains("place_take_order")
        || lower.contains("post_only");
}

fn text_matches_orderbook_cancel(lower: &str) -> bool {
    return lower.contains("cancel_order")
        || lower.contains("cancel order")
        || lower.contains("cancelorder")
        || lower.contains("cancel_all_orders")
        || lower.contains("cancel all orders")
        || lower.contains("cancelallorders");
}

fn text_matches_orderbook_fill(lower: &str) -> bool {
    return lower.contains("fill_log_event")
        || lower.contains("fill log event")
        || lower.contains("filllogevent")
        || lower.contains("total_order_fill_event")
        || lower.contains("total order fill event")
        || lower.contains("order_fill")
        || lower.contains("fill_order")
        || lower.contains("order fill");
}

fn text_matches_settle_funds(lower: &str) -> bool {
    return lower.contains("settle_funds")
        || lower.contains("settle funds")
        || lower.contains("settlefunds")
        || lower.contains("settle_funds_expired");
}

fn text_matches_consume_events(lower: &str) -> bool {
    return lower.contains("consume_events")
        || lower.contains("consume events")
        || lower.contains("consumeevents")
        || lower.contains("consume_given_events");
}

fn text_matches_market_create(lower: &str) -> bool {
    return lower.contains("create_market")
        || lower.contains("create market")
        || lower.contains("createmarket")
        || lower.contains("initialize_market")
        || lower.contains("initialize market")
        || lower.contains("initializemarket");
}

fn text_matches_open_orders_create(lower: &str) -> bool {
    return lower.contains("create_open_orders")
        || lower.contains("create open orders")
        || lower.contains("create_open_orders_account")
        || lower.contains("create_open_orders_indexer")
        || lower.contains("open_orders_create");
}

fn text_matches_open_orders_close(lower: &str) -> bool {
    return lower.contains("close_open_orders")
        || lower.contains("close open orders")
        || lower.contains("close_open_orders_account")
        || lower.contains("close_open_orders_indexer")
        || lower.contains("open_orders_close");
}

fn text_matches_close_market(lower: &str) -> bool {
    return lower.contains("close_market")
        || lower.contains("close market")
        || lower.contains("closemarket");
}

fn text_matches_burn(lower: &str) -> bool {
    return lower.contains("burn")
        || lower.contains("burnchecked")
        || lower.contains("burn_checked")
        || lower.contains("burn token")
        || lower.contains("token burn");
}

fn text_matches_mint(lower: &str) -> bool {
    return lower.contains("mintto")
        || lower.contains("mint_to")
        || lower.contains("mint to")
        || lower.contains("mint token")
        || lower.contains("token mint")
        || lower == "mint"
        || lower.contains("instruction: mint");
}

fn text_matches_transfer(lower: &str) -> bool {
    return lower.contains("transferchecked")
        || lower.contains("transfer_checked")
        || lower.contains("transfer checked")
        || lower.contains("token transfer")
        || lower.contains("transfer token")
        || lower.contains("instruction: transfer");
}

fn text_matches_close_account(lower: &str) -> bool {
    return lower.contains("closeaccount")
        || lower.contains("close_account")
        || lower.contains("close account")
        || lower.contains("close token account")
        || lower.contains("token account close");
}

fn text_matches_token_account_create(lower: &str) -> bool {
    return lower.contains("createtokenaccount")
        || lower.contains("create_token_account")
        || lower.contains("create token account")
        || lower.contains("createassociatedtokenaccount")
        || lower.contains("create_associated_token_account")
        || lower.contains("associated token account")
        || lower.contains("initializeaccount")
        || lower.contains("initialize_account")
        || lower.contains("initialize account");
}

fn text_matches_wrap_sol(lower: &str) -> bool {
    return lower.contains("wrapsol")
        || lower.contains("wrap_sol")
        || lower.contains("wrap sol")
        || lower.contains("wrap native")
        || lower.contains("syncnative")
        || lower.contains("sync_native");
}

fn text_matches_unwrap_sol(lower: &str) -> bool {
    return lower.contains("unwrapsol")
        || lower.contains("unwrap_sol")
        || lower.contains("unwrap sol")
        || lower.contains("unwrap native")
        || lower.contains("close wsol")
        || lower.contains("close wrapped sol");
}

fn text_matches_stake(lower: &str) -> bool {
    if text_matches_unstake(lower) {
        return false;
    }
    return lower.contains("stake")
        || lower.contains("delegate")
        || lower.contains("delegate_stake")
        || lower.contains("depositstake")
        || lower.contains("deposit_stake");
}

fn text_matches_unstake(lower: &str) -> bool {
    return lower.contains("unstake")
        || lower.contains("deactivate")
        || lower.contains("deactivate_stake")
        || lower.contains("withdrawstake")
        || lower.contains("withdraw_stake");
}

fn transaction_failed(transaction: &serde_json::Value) -> bool {
    let err = transaction.get("meta").and_then(|value| return value.get("err"));
    match err {
        Some(err) => return !err.is_null(),
        None => return false,
    }
}

fn extract_log_messages(transaction: &serde_json::Value) -> std::vec::Vec<std::string::String> {
    let mut logs = std::vec::Vec::new();
    let log_values = transaction
        .get("meta")
        .and_then(|value| return value.get("logMessages"))
        .and_then(serde_json::Value::as_array);
    if let Some(log_values) = log_values {
        for log_value in log_values {
            if let Some(log) = log_value.as_str() {
                logs.push(log.to_string());
            }
        }
    }
    return logs;
}

fn sample_logs(logs: &[std::string::String], limit: usize) -> std::vec::Vec<std::string::String> {
    return sample_strings(logs, limit);
}

fn sample_strings(
    values: &[std::string::String],
    limit: usize,
) -> std::vec::Vec<std::string::String> {
    let mut samples = std::vec::Vec::new();
    let mut index = 0usize;
    while index < values.len() && index < limit {
        samples.push(values[index].clone());
        index += 1;
    }
    return samples;
}

fn build_backfill_hint(
    hint_kind: &str,
    address: std::option::Option<&str>,
    signature: &str,
) -> std::string::String {
    match hint_kind {
        "pool" => {
            if let Some(address) = address {
                return format!("Backfill pool address in Demo Pipeline 2: {}", address);
            }
        },
        "token" => {
            if let Some(address) = address {
                return format!("Backfill token mint in Demo Pipeline 2: {}", address);
            }
        },
        "candidate_pool" => {
            if let Some(address) = address {
                return format!(
                    "Candidate pool/state account for inspection/backfill: {} ; signature: {}",
                    address, signature
                );
            }
        },
        _ => {},
    }
    return format!("Inspect/backfill transaction signature: {}", signature);
}

fn clamp_u32(value: u32, min: u32, max: u32) -> u32 {
    if value < min {
        return min;
    }
    if value > max {
        return max;
    }
    return value;
}

fn clamp_usize(value: usize, min: usize, max: usize) -> usize {
    if value < min {
        return min;
    }
    if value > max {
        return max;
    }
    return value;
}

#[cfg(test)]
mod tests {
    #[test]
    fn resolve_known_dex_code_to_program_id() {
        let request = crate::OnchainDexPairDiscoveryRequestDto {
            dex_code: Some("raydium_clmm".to_string()),
            program_id: None,
            signature_source: None,
            source_address: None,
            source_addresses: std::vec::Vec::new(),
            before_signature: None,
            until_signature: None,
            max_pages: 1,
            scan_order: None,
            target_event: None,
            target_instruction_name: None,
            target_discriminator_hex: None,
            exclude_swaps: false,
            include_failed: true,
            http_role: "history_backfill".to_string(),
            signature_limit: 10,
            transaction_limit: 5,
            candidate_limit: 3,
        };
        let resolved = super::resolve_program_id(&request);
        match resolved {
            Ok(resolved) => {
                assert_eq!(resolved.program_id, crate::RAYDIUM_CLMM_PROGRAM_ID);
                assert_eq!(resolved.dex_code, Some("raydium_clmm".to_string()));
            },
            Err(error) => panic!("resolve must succeed: {error}"),
        }
    }

    #[test]
    fn reject_unverified_dex_code_without_program_id() {
        let request = crate::OnchainDexPairDiscoveryRequestDto {
            dex_code: Some("metadao".to_string()),
            program_id: None,
            signature_source: None,
            source_address: None,
            source_addresses: std::vec::Vec::new(),
            before_signature: None,
            until_signature: None,
            max_pages: 1,
            scan_order: None,
            target_event: None,
            target_instruction_name: None,
            target_discriminator_hex: None,
            exclude_swaps: false,
            include_failed: true,
            http_role: "history_backfill".to_string(),
            signature_limit: 10,
            transaction_limit: 5,
            candidate_limit: 3,
        };
        let resolved = super::resolve_program_id(&request);
        assert!(resolved.is_err());
    }

    #[test]
    fn reject_wrong_size_base58_program_id_before_rpc_call() {
        let request = crate::OnchainDexPairDiscoveryRequestDto {
            dex_code: Some("goonfi_v2".to_string()),
            program_id: Some("goonuddtQRrWqqn5FyczVKaie28f3kDkHWkHtURSLE".to_string()),
            signature_source: None,
            source_address: None,
            source_addresses: std::vec::Vec::new(),
            before_signature: None,
            until_signature: None,
            max_pages: 1,
            scan_order: None,
            target_event: None,
            target_instruction_name: None,
            target_discriminator_hex: None,
            exclude_swaps: false,
            include_failed: true,
            http_role: "history_backfill".to_string(),
            signature_limit: 10,
            transaction_limit: 5,
            candidate_limit: 3,
        };
        let normalized_request = super::normalize_request(request);
        let resolved = match super::resolve_program_id(&normalized_request) {
            Ok(resolved) => resolved,
            Err(error) => {
                panic!("explicit program id resolution should not fail before validation: {error}")
            },
        };
        let signature_sources = super::resolve_signature_sources(&normalized_request, &resolved);
        assert!(signature_sources.is_err());
    }

    #[test]
    fn raw_compiled_openbook_v2_instruction_is_resolved_from_program_id_index() {
        let mut data = vec![3_u8, 44_u8, 71_u8, 3_u8, 26_u8, 199_u8, 203_u8, 85_u8];
        data.extend_from_slice(&[0_u8, 1_u8, 2_u8, 3_u8]);
        let data_base58 = bs58::encode(data.as_slice()).into_string();
        let transaction = serde_json::json!({
            "slot": 777,
            "blockTime": 1770000000,
            "transaction": {
                "message": {
                    "accountKeys": [
                        "Payer111111111111111111111111111111111111",
                        crate::OPENBOOK_V2_PROGRAM_ID,
                        "Market11111111111111111111111111111111111",
                        "OpenOrders1111111111111111111111111111111"
                    ],
                    "instructions": [
                        {
                            "programIdIndex": 1,
                            "accounts": [2, 3],
                            "data": data_base58
                        }
                    ]
                }
            },
            "meta": {
                "err": null,
                "logMessages": []
            }
        });
        let extraction = super::extract_candidates_from_transaction(
            "sig-openbook_v2-raw",
            &transaction,
            crate::OPENBOOK_V2_PROGRAM_ID,
            Some("openbook_v2".to_string()),
            Some("order_place"),
            None,
            None,
        );
        assert_eq!(extraction.extracted_candidate_count, 1);
        assert_eq!(extraction.target_rejected_candidate_count, 0);
        assert_eq!(extraction.candidates.len(), 1);
        assert_eq!(extraction.candidates[0].candidate_kind, "order_place".to_string());
        assert_eq!(extraction.candidates[0].instruction_name, Some("place_take_order".to_string()));
        assert_eq!(extraction.candidates[0].confidence, "high".to_string());
        assert_eq!(
            extraction.candidates[0].account_samples,
            vec![
                "Market11111111111111111111111111111111111".to_string(),
                "OpenOrders1111111111111111111111111111111".to_string(),
            ]
        );
    }

    #[test]
    fn loaded_address_indices_are_available_to_demo3_raw_instruction_resolution() {
        let transaction = serde_json::json!({
            "transaction": {
                "message": {
                    "accountKeys": [
                        "Payer111111111111111111111111111111111111"
                    ],
                    "instructions": [
                        {
                            "programIdIndex": 1,
                            "accounts": [2],
                            "data": "11111111111"
                        }
                    ]
                }
            },
            "meta": {
                "loadedAddresses": {
                    "writable": [crate::OPENBOOK_V2_PROGRAM_ID],
                    "readonly": ["Readonly1111111111111111111111111111111111"]
                }
            }
        });
        let account_keys = super::extract_transaction_account_keys(&transaction);
        let instructions =
            super::extract_onchain_instructions(&transaction, account_keys.as_slice());
        assert_eq!(instructions.len(), 1);
        assert_eq!(instructions[0].program_id, Some(crate::OPENBOOK_V2_PROGRAM_ID.to_string()));
        assert_eq!(
            instructions[0].accounts,
            vec!["Readonly1111111111111111111111111111111111".to_string()]
        );
    }

    #[test]
    fn known_meteora_damm_v2_trade_like_prefix_is_not_returned_as_non_swap_audit() {
        let candidate = crate::OnchainDexPairCandidateDto {
            signature: "sig".to_string(),
            slot: None,
            block_time: None,
            failed: false,
            program_id: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG".to_string(),
            dex_code: Some("meteora_damm_v2".to_string()),
            candidate_kind: "unclassified_instruction".to_string(),
            confidence: "medium".to_string(),
            instruction_index: Some(4),
            inner_instruction_index: Some(2),
            instruction_name: None,
            instruction_data_prefix: Some("EVM9wLnauu9H41Gf".to_string()),
            instruction_discriminator_hex: None,
            pool_address: None,
            token_a_mint: None,
            token_b_mint: None,
            verified_pool_address: None,
            observed_token_mints: std::vec::Vec::new(),
            token_balance_deltas: std::vec::Vec::new(),
            candidate_pool_accounts: std::vec::Vec::new(),
            candidate_token_vault_accounts: std::vec::Vec::new(),
            candidate_program_accounts: std::vec::Vec::new(),
            account_samples: std::vec::Vec::new(),
            log_samples: std::vec::Vec::new(),
            backfill_hint: "hint".to_string(),
        };
        assert!(!super::candidate_matches_target_event(
            &candidate,
            Some("unclassified_instruction")
        ));
        assert!(!super::candidate_matches_target_event(&candidate, Some("audit_non_swap_like")));
        assert!(super::candidate_matches_target_event(&candidate, Some("swap")));
    }

    #[test]
    fn non_trade_liquidity_candidate_remains_available_for_non_swap_audit() {
        let candidate = make_candidate("add_liquidity", "AddLiquidity");
        assert!(super::candidate_matches_target_event(&candidate, Some("audit_non_swap_like")));
    }

    #[test]
    fn heuristic_swap_instruction_name_wins_over_mixed_liquidity_logs() {
        let logs = vec![
            "Program log: Instruction: AddLiquidity".to_string(),
            "Program log: deposit".to_string(),
        ];
        let instruction_name = "Swap".to_string();
        let kind =
            super::infer_candidate_kind(Some(&instruction_name), logs.as_slice(), true, false);
        assert_eq!(kind, "swap".to_string());
    }

    #[test]
    fn target_swap_rejects_explicit_liquidity_candidate_from_mixed_logs() {
        let mut candidate = make_candidate("add_liquidity", "AddLiquidity");
        candidate.log_samples.push("Program log: Instruction: Swap".to_string());
        assert!(!super::candidate_matches_target_event(&candidate, Some("swap")));
    }

    #[test]
    fn target_add_liquidity_rejects_explicit_swap_candidate_from_mixed_logs() {
        let mut candidate = make_candidate("swap", "Swap");
        candidate.log_samples.push("Program log: Instruction: AddLiquidity".to_string());
        assert!(!super::candidate_matches_target_event(&candidate, Some("add_liquidity")));
    }

    #[test]
    fn meteora_damm_v1_claim_fee_candidate_is_decoded_for_demo3() {
        let mut data = super::DEMO3_DAMM_V1_CLAIM_FEE.to_vec();
        data.extend_from_slice(&500_u64.to_le_bytes());
        let instruction = super::OnchainInstructionCandidate {
            instruction_index: Some(2),
            inner_instruction_index: None,
            program_id: Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
            accounts: vec![
                "Pool111".to_string(),
                "LpMint111".to_string(),
                "LockEscrow111".to_string(),
                "Owner111".to_string(),
                "SourceTokens111".to_string(),
                "EscrowVault111".to_string(),
                "TokenProgram111".to_string(),
                "ATokenVault111".to_string(),
                "BTokenVault111".to_string(),
                "AVault111".to_string(),
                "BVault111".to_string(),
                "AVaultLp111".to_string(),
                "BVaultLp111".to_string(),
                "AVaultLpMint111".to_string(),
                "BVaultLpMint111".to_string(),
                "UserA111".to_string(),
                "UserB111".to_string(),
                "VaultProgram111".to_string(),
            ],
            data: Some(bs58::encode(data.as_slice()).into_string()),
            parsed: None,
        };
        let decoded = super::decode_meteora_damm_v1_candidate(
            "sig111",
            Some(42),
            Some(1234),
            false,
            crate::METEORA_DAMM_V1_PROGRAM_ID,
            Some("meteora_damm_v1".to_string()),
            &instruction,
            &[],
        );
        let candidate = match decoded {
            Some(candidate) => candidate,
            None => panic!("candidate must decode"),
        };
        assert_eq!(candidate.candidate_kind, "claim_fee".to_string());
        assert_eq!(candidate.pool_address, Some("Pool111".to_string()));
        assert_eq!(candidate.verified_pool_address, Some("Pool111".to_string()));
        assert!(super::candidate_matches_target_event(&candidate, Some("claim_fee")));
    }

    #[test]
    fn split_target_event_filter_keeps_multiple_targets() {
        let targets =
            super::split_target_event_filter(Some("claim_fee, remove-liquidity;pool_create all"));
        assert_eq!(
            targets,
            vec![
                "claim_fee".to_string(),
                "remove_liquidity".to_string(),
                "pool_create".to_string(),
            ]
        );
    }

    #[test]
    fn split_target_event_filter_normalizes_extended_non_swap_aliases() {
        let targets = super::split_target_event_filter(Some(
            "token-burn mint_to transfer_checked close-token-account create-token-account wrap unwrap delegate-stake withdraw-stake",
        ));
        assert_eq!(
            targets,
            vec![
                "burn".to_string(),
                "mint".to_string(),
                "transfer".to_string(),
                "close_account".to_string(),
                "token_account_create".to_string(),
                "wrap_sol".to_string(),
                "unwrap_sol".to_string(),
                "stake".to_string(),
                "unstake".to_string(),
            ]
        );
    }

    #[test]
    fn resolve_multiple_source_addresses_from_single_field() {
        let request = crate::OnchainDexPairDiscoveryRequestDto {
            dex_code: Some("meteora_damm_v1".to_string()),
            program_id: Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
            signature_source: Some("address".to_string()),
            source_address: Some(
                "BCXjm4FfSoquZQJV5Wcje1g1pSHW2hFMU9wDE98Nyatb, EYAVndnE1Fm88iat97rWKYcEARNrgHA47v6vnjTzvw7w".to_string(),
            ),
            source_addresses: std::vec::Vec::new(),
            before_signature: None,
            until_signature: None,
            max_pages: 2,
            scan_order: Some("oldest_first".to_string()),
            target_event: Some("claim_fee,remove_liquidity".to_string()),
            target_instruction_name: None,
            target_discriminator_hex: None,
            exclude_swaps: true,
            include_failed: true,
            http_role: "history_backfill".to_string(),
            signature_limit: 10,
            transaction_limit: 5,
            candidate_limit: 3,
        };
        let normalized = super::normalize_request(request);
        assert_eq!(normalized.max_pages, 2);
        assert_eq!(normalized.scan_order, Some("oldest_first".to_string()));
        assert_eq!(normalized.target_event, Some("claim_fee,remove_liquidity".to_string()));
        let resolved = super::ResolvedDiscoveryTarget {
            dex_code: Some("meteora_damm_v1".to_string()),
            program_id: crate::METEORA_DAMM_V1_PROGRAM_ID.to_string(),
        };
        let sources = super::resolve_signature_sources(&normalized, &resolved);
        match sources {
            Ok(sources) => {
                assert_eq!(sources.len(), 2);
                assert_eq!(
                    sources[0].address,
                    "BCXjm4FfSoquZQJV5Wcje1g1pSHW2hFMU9wDE98Nyatb".to_string()
                );
                assert_eq!(
                    sources[1].address,
                    "EYAVndnE1Fm88iat97rWKYcEARNrgHA47v6vnjTzvw7w".to_string()
                );
            },
            Err(error) => panic!("multi source resolution must succeed: {error}"),
        }
    }

    fn make_candidate(
        candidate_kind: &str,
        instruction_name: &str,
    ) -> crate::OnchainDexPairCandidateDto {
        return crate::OnchainDexPairCandidateDto {
            signature: "sig".to_string(),
            slot: None,
            block_time: None,
            failed: false,
            program_id: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG".to_string(),
            dex_code: Some("meteora_damm_v2".to_string()),
            candidate_kind: candidate_kind.to_string(),
            confidence: "medium".to_string(),
            instruction_index: Some(1),
            inner_instruction_index: None,
            instruction_name: Some(instruction_name.to_string()),
            instruction_data_prefix: Some("prefix".to_string()),
            instruction_discriminator_hex: None,
            pool_address: None,
            token_a_mint: None,
            token_b_mint: None,
            verified_pool_address: None,
            observed_token_mints: std::vec::Vec::new(),
            token_balance_deltas: std::vec::Vec::new(),
            candidate_pool_accounts: std::vec::Vec::new(),
            candidate_token_vault_accounts: std::vec::Vec::new(),
            candidate_program_accounts: std::vec::Vec::new(),
            account_samples: std::vec::Vec::new(),
            log_samples: std::vec::Vec::new(),
            backfill_hint: "hint".to_string(),
        };
    }

    #[test]
    fn maps_upstream_registry_entry_names_to_candidate_kinds() {
        assert_eq!(
            super::upstream_registry_entry_name_to_candidate_kind("add_liquidity"),
            "add_liquidity"
        );
        assert_eq!(super::upstream_registry_entry_name_to_candidate_kind("swap_base_in"), "swap");
        assert_eq!(
            super::upstream_registry_entry_name_to_candidate_kind("claim_protocol_fee"),
            "claim_fee"
        );
        assert_eq!(
            super::upstream_registry_entry_name_to_candidate_kind("close_open_orders_account"),
            "open_orders_close"
        );
        assert_eq!(
            super::upstream_registry_entry_name_to_candidate_kind("unknown_surface"),
            "upstream_git_instruction"
        );
    }

    #[test]
    fn target_filter_accepts_open_orders_close_candidates() {
        let candidate = make_candidate("open_orders_close", "CloseOpenOrdersAccount");
        assert!(super::candidate_matches_target_event(&candidate, Some("open_orders_close")));
        assert!(!super::candidate_matches_target_event(&candidate, Some("unknown_non_swap")));
        assert!(super::candidate_matches_target_event(&candidate, Some("audit_non_swap_like")));
        assert!(!super::candidate_matches_target_event(&candidate, Some("close_market")));
    }
    #[test]
    fn target_filter_rejects_known_surface_for_unknown_non_swap() {
        let candidate = make_candidate("claim_fee", "CollectProtocolFee");
        assert!(!super::candidate_matches_target_event(&candidate, Some("unknown_non_swap")));
        assert!(super::candidate_matches_target_event(&candidate, Some("audit_non_swap_like")));
    }

    #[test]
    fn target_filter_accepts_unclassified_instruction_for_unknown_non_swap() {
        let candidate = make_candidate("unclassified_instruction", "UnknownInstruction");
        assert!(super::candidate_matches_target_event(&candidate, Some("unknown_non_swap")));
    }

    #[test]
    fn target_instruction_filters_match_name_and_discriminator() {
        let mut candidate = make_candidate("remove_liquidity", "raydium_cpmm.withdraw");
        candidate.instruction_discriminator_hex = Some("b712469c946da122".to_string());
        assert!(super::candidate_matches_instruction_filters(
            &candidate,
            Some("withdraw"),
            Some("b712469c946da122")
        ));
        assert!(!super::candidate_matches_instruction_filters(
            &candidate,
            Some("deposit"),
            Some("b712469c946da122")
        ));
        assert!(!super::candidate_matches_instruction_filters(
            &candidate,
            Some("withdraw"),
            Some("f223c68952e1f2b6")
        ));
    }
}
