// file: kb_lib/src/dex/pump_fun.rs

//! Pump.fun bonding-curve transaction decoder.

const PUMP_FUN_BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
const PUMP_FUN_SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
const PUMP_FUN_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR: [u8; 8] = [228, 69, 165, 46, 81, 203, 154, 29];

#[derive(Debug, Clone, Copy)]
enum PumpFunBorshFieldKind {
    U64,
    I64,
    Bool,
    Pubkey,
    String,
    OptionBool,
    OptionU64,
    PubkeyArray(usize),
    ShareholderVec,
}

#[derive(Debug, Clone, Copy)]
struct PumpFunBorshFieldDescriptor {
    name: &'static str,
    kind: PumpFunBorshFieldKind,
}

#[derive(Debug, Clone, Copy)]
struct PumpFunAnchorEventDescriptor {
    event_name: &'static str,
    event_kind: &'static str,
    discriminator: [u8; 8],
    fields: &'static [PumpFunBorshFieldDescriptor],
}

#[derive(Debug, Clone)]
struct PumpFunAnchorEventDecodedPayload {
    event_name: &'static str,
    event_kind: &'static str,
    discriminator_hex: std::string::String,
    payload_size: usize,
    payload_json: serde_json::Value,
}

/// Decoded Pump.fun `create_v2` token event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PumpFunCreateV2TokenDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Optional created mint.
    pub mint: std::option::Option<std::string::String>,
    /// Optional bonding curve account.
    pub bonding_curve: std::option::Option<std::string::String>,
    /// Optional associated bonding curve account.
    pub associated_bonding_curve: std::option::Option<std::string::String>,
    /// Optional creator / user account.
    pub creator: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Pump.fun bonding-curve trade event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PumpFunTradeDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Trade side.
    pub trade_side: crate::SwapTradeSide,
    /// Token mint.
    pub mint: std::option::Option<std::string::String>,
    /// Bonding curve account.
    pub bonding_curve: std::option::Option<std::string::String>,
    /// Associated bonding curve token account.
    pub associated_bonding_curve: std::option::Option<std::string::String>,
    /// User token account.
    pub associated_user: std::option::Option<std::string::String>,
    /// User wallet account.
    pub user: std::option::Option<std::string::String>,
    /// Decoded instruction amount, when available.
    pub amount_raw: std::option::Option<std::string::String>,
    /// Decoded SOL limit/threshold argument, when available.
    pub sol_limit_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Pump.fun instruction audit event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PumpFunInstructionAuditDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Stable Pump.fun instruction name.
    pub instruction_name: std::string::String,
    /// Stable local decoded event kind.
    pub event_kind: std::string::String,
    /// Optional instruction discriminator hex.
    pub discriminator_hex: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Pump.fun event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum PumpFunDecodedEvent {
    /// `create_v2` token creation.
    CreateV2Token(PumpFunCreateV2TokenDecoded),
    /// Buy trade.
    BuyTrade(PumpFunTradeDecoded),
    /// Sell trade.
    SellTrade(PumpFunTradeDecoded),
    /// Audit-only instruction.
    InstructionAudit(PumpFunInstructionAuditDecoded),
}

/// Pump.fun decoder.
#[derive(Debug, Clone, Default)]
pub struct PumpFunDecoder;

impl PumpFunDecoder {
    /// Creates a new decoder.
    pub fn new() -> Self {
        return Self;
    }

    /// Decodes one projected transaction into zero or more Pump.fun events.
    pub fn decode_transaction(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::PumpFunDecodedEvent>, crate::Error> {
        let transaction_id_option = transaction.id;
        let transaction_id = match transaction_id_option {
            Some(transaction_id) => transaction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "chain transaction '{}' has no internal id",
                    transaction.signature
                )));
            },
        };
        if transaction.err_json.is_some() {
            return Ok(std::vec::Vec::new());
        }
        let transaction_json_result =
            serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
        let transaction_json = match transaction_json_result {
            Ok(transaction_json) => transaction_json,
            Err(error) => {
                return Err(crate::Error::Json(format!(
                    "cannot parse transaction_json for signature '{}': {}",
                    transaction.signature, error
                )));
            },
        };
        let log_messages = extract_log_messages(&transaction_json);
        let has_create_v2_log = log_messages_contain_keyword(&log_messages, "create_v2")
            || log_messages_contain_keyword(&log_messages, "createv2");
        let mut decoded_events = std::vec::Vec::new();
        let mut pump_fun_instruction_ids = std::vec::Vec::<i64>::new();
        for instruction in instructions {
            if instruction.parent_instruction_id.is_some() {
                continue;
            }
            let program_id_option = &instruction.program_id;
            let program_id = match program_id_option {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id.as_str() != crate::PUMP_FUN_PROGRAM_ID {
                continue;
            }
            let instruction_id_option = instruction.id;
            let instruction_id = match instruction_id_option {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            pump_fun_instruction_ids.push(instruction_id);
            let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
            let accounts = match accounts_result {
                Ok(accounts) => accounts,
                Err(error) => return Err(error),
            };
            let instruction_data = decode_optional_instruction_data(instruction.data_json.as_ref());
            let discriminator_hex = instruction_discriminator_hex(instruction_data.as_deref());
            let instruction_name = pump_fun_instruction_name(discriminator_hex.as_deref());
            if instruction_name == Some("anchor_self_cpi_log") {
                if let Some(instruction_data) = instruction_data.as_deref() {
                    let anchor_event = decode_pump_fun_anchor_event_data(instruction_data);
                    if let Some(anchor_event) = anchor_event {
                        decoded_events.push(build_pump_fun_anchor_event_decoded(
                            transaction,
                            instruction_id,
                            &anchor_event,
                        ));
                    }
                }
                continue;
            }
            let is_buy = instruction_name == Some("buy")
                || instruction_data_starts_with(
                    instruction_data.as_deref(),
                    &PUMP_FUN_BUY_DISCRIMINATOR,
                );
            let is_sell = instruction_name == Some("sell")
                || instruction_data_starts_with(
                    instruction_data.as_deref(),
                    &PUMP_FUN_SELL_DISCRIMINATOR,
                );
            if is_buy || is_sell {
                if accounts.len() < 7 {
                    continue;
                }
                let amount_raw = extract_u64_argument(instruction_data.as_deref(), 8);
                let sol_limit_raw = extract_u64_argument(instruction_data.as_deref(), 16);
                let mint = extract_account(&accounts, 2);
                let bonding_curve = extract_account(&accounts, 3);
                let associated_bonding_curve = extract_account(&accounts, 4);
                let associated_user = extract_account(&accounts, 5);
                let user = extract_account(&accounts, 6);
                let fee_recipient = extract_account(&accounts, 1);
                let token_program = extract_account(&accounts, 8);
                let creator_vault = extract_account(&accounts, 9);
                let payload_json = serde_json::json!({
                    "decoder": "pump_fun",
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "instructionName": if is_buy { "buy" } else { "sell" },
                    "instructionDiscriminatorHex": discriminator_hex,
                    "accounts": accounts,
                    "logMessages": log_messages,
                    "eventKind": if is_buy { "buy" } else { "sell" },
                    "poolAccount": bonding_curve,
                    "tokenAMint": mint,
                    "tokenBMint": crate::WSOL_MINT_ID,
                    "bondingCurve": bonding_curve,
                    "associatedBondingCurve": associated_bonding_curve,
                    "associatedUser": associated_user,
                    "user": user,
                    "feeRecipient": fee_recipient,
                    "tokenProgram": token_program,
                    "creatorVault": creator_vault,
                    "poolBaseTokenAccount": associated_bonding_curve,
                    "poolQuoteNativeAccount": bonding_curve,
                    "amountRaw": amount_raw,
                    "solLimitRaw": sol_limit_raw,
                    "decodedArguments": decode_pump_fun_instruction_arguments(
                        if is_buy { "buy" } else { "sell" },
                        instruction_data.as_deref()
                    ),
                    "tradeSide": if is_buy { "BuyBase" } else { "SellBase" }
                });
                let event = crate::PumpFunTradeDecoded {
                    transaction_id,
                    instruction_id,
                    signature: transaction.signature.clone(),
                    program_id: program_id.clone(),
                    trade_side: if is_buy {
                        crate::SwapTradeSide::BuyBase
                    } else {
                        crate::SwapTradeSide::SellBase
                    },
                    mint,
                    bonding_curve,
                    associated_bonding_curve,
                    associated_user,
                    user,
                    amount_raw,
                    sol_limit_raw,
                    payload_json,
                };
                if is_buy {
                    decoded_events.push(crate::PumpFunDecodedEvent::BuyTrade(event));
                } else {
                    decoded_events.push(crate::PumpFunDecodedEvent::SellTrade(event));
                }
                continue;
            }
            let should_decode_create_v2 = instruction_name == Some("create_v2")
                || (instruction_name.is_none() && has_create_v2_log);
            if should_decode_create_v2 {
                if accounts.len() < 6 {
                    continue;
                }
                let mint = extract_account(&accounts, 0);
                let bonding_curve = extract_account(&accounts, 2);
                let associated_bonding_curve = extract_account(&accounts, 3);
                let creator = extract_account(&accounts, 5);
                let payload_json = serde_json::json!({
                    "decoder": "pump_fun",
                    "eventKind": "create_v2_token",
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "instructionName": "create_v2",
                    "instructionDiscriminatorHex": discriminator_hex,
                    "accounts": accounts,
                    "logMessages": log_messages,
                    "mint": mint,
                    "bondingCurve": bonding_curve,
                    "associatedBondingCurve": associated_bonding_curve,
                    "creator": creator,
                    "decodedArguments": decode_pump_fun_instruction_arguments(
                        "create_v2",
                        instruction_data.as_deref()
                    )
                });
                decoded_events.push(crate::PumpFunDecodedEvent::CreateV2Token(
                    crate::PumpFunCreateV2TokenDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.clone(),
                        mint,
                        bonding_curve,
                        associated_bonding_curve,
                        creator,
                        payload_json,
                    },
                ));
                continue;
            }
            let instruction_name = match instruction_name {
                Some(instruction_name) => instruction_name,
                None => continue,
            };
            if instruction_name == "anchor_self_cpi_log" {
                continue;
            }
            let event_kind = format!("pump_fun.{}", instruction_name);
            let payload_json = build_instruction_audit_payload(
                transaction,
                instruction,
                instruction_name,
                discriminator_hex.clone(),
                accounts,
                &log_messages,
                instruction_data.as_deref(),
            );
            decoded_events.push(crate::PumpFunDecodedEvent::InstructionAudit(
                crate::PumpFunInstructionAuditDecoded {
                    transaction_id,
                    instruction_id,
                    signature: transaction.signature.clone(),
                    program_id: program_id.clone(),
                    instruction_name: instruction_name.to_string(),
                    event_kind,
                    discriminator_hex,
                    payload_json,
                },
            ));
        }
        let fallback_instruction_id = match pump_fun_instruction_ids.first() {
            Some(instruction_id) => Some(*instruction_id),
            None => None,
        };
        if let Some(instruction_id) = fallback_instruction_id {
            for log_message in &log_messages {
                let encoded = match extract_program_data_base64(log_message.as_str()) {
                    Some(encoded) => encoded,
                    None => continue,
                };
                let anchor_event = match decode_pump_fun_anchor_event_from_base64(encoded) {
                    Some(anchor_event) => anchor_event,
                    None => continue,
                };
                decoded_events.push(build_pump_fun_anchor_event_decoded(
                    transaction,
                    instruction_id,
                    &anchor_event,
                ));
            }
        }
        return Ok(decoded_events);
    }
}

fn decode_optional_instruction_data(
    data_json: std::option::Option<&std::string::String>,
) -> std::option::Option<std::vec::Vec<u8>> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return None,
    };
    let parsed_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    let encoded = match parsed_result {
        Ok(parsed) => match parsed.as_str() {
            Some(text) => text.to_string(),
            None => data_json.clone(),
        },
        Err(_) => data_json.clone(),
    };
    let decode_result = bs58::decode(encoded.as_str()).into_vec();
    match decode_result {
        Ok(decoded) => return Some(decoded),
        Err(_) => return None,
    }
}

fn instruction_data_starts_with(
    instruction_data: std::option::Option<&[u8]>,
    discriminator: &[u8; 8],
) -> bool {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return false,
    };
    if instruction_data.len() < discriminator.len() {
        return false;
    }
    return &instruction_data[0..discriminator.len()] == discriminator;
}

fn extract_u64_argument(
    instruction_data: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return None,
    };
    let end = offset + 8;
    if instruction_data.len() < end {
        return None;
    }
    let mut bytes = [0u8; 8];
    bytes.copy_from_slice(&instruction_data[offset..end]);
    return Some(u64::from_le_bytes(bytes).to_string());
}

fn extract_log_messages(
    transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
    let mut messages = std::vec::Vec::new();
    let meta_option = transaction_json.get("meta");
    let meta = match meta_option {
        Some(meta) => meta,
        None => return messages,
    };
    let logs_option = meta.get("logMessages");
    let logs = match logs_option {
        Some(logs) => logs,
        None => return messages,
    };
    let logs_array_option = logs.as_array();
    let logs_array = match logs_array_option {
        Some(logs_array) => logs_array,
        None => return messages,
    };
    for value in logs_array {
        let text_option = value.as_str();
        if let Some(text) = text_option {
            messages.push(text.to_string());
        }
    }
    return messages;
}

fn log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
    let keyword_normalized = normalize_log_text(keyword);
    for log_message in log_messages {
        let log_normalized = normalize_log_text(log_message.as_str());
        if log_normalized.contains(keyword_normalized.as_str()) {
            return true;
        }
    }
    return false;
}

fn parse_accounts_json(
    accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
    let values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
    let values = match values_result {
        Ok(values) => values,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse instruction accounts_json '{}': {}",
                accounts_json, error
            )));
        },
    };
    let mut accounts = std::vec::Vec::new();
    for value in values {
        let text_option = value.as_str();
        if let Some(text) = text_option {
            accounts.push(text.to_string());
        }
    }
    return Ok(accounts);
}

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

fn instruction_discriminator_hex(
    instruction_data: std::option::Option<&[u8]>,
) -> std::option::Option<std::string::String> {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return None,
    };
    if instruction_data.len() < 8 {
        return None;
    }
    return Some(bytes_to_hex(&instruction_data[0..8]));
}

fn bytes_to_hex(bytes: &[u8]) -> std::string::String {
    let mut text = std::string::String::new();
    for byte in bytes {
        text.push_str(format!("{:02x}", byte).as_str());
    }
    return text;
}

fn pump_fun_instruction_name(
    discriminator_hex: std::option::Option<&str>,
) -> std::option::Option<&'static str> {
    let discriminator_hex = match discriminator_hex {
        Some(discriminator_hex) => discriminator_hex,
        None => return None,
    };
    let name = match discriminator_hex {
        "e445a52e51cb9a1d" => "anchor_self_cpi_log",
        "6f79153828185ed1" => "add_quote_mint",
        "4519ab8e39ef0d04" => "admin_set_creator",
        "08d960e79068c005" => "admin_set_idl_authority",
        "d10b7357d5177ccc" => "admin_update_token_incentives",
        "66063d1201daebea" => "buy",
        "c2ab1c46684d5b2f" => "buy_exact_quote_in_v2",
        "38fc74089edfcd5f" => "buy_exact_sol_in",
        "b817ee6167c5d33d" => "buy_v2",
        "253a237ebe35e4c5" => "claim_cashback",
        "7af3cc415e741d37" => "claim_cashback_v2",
        "1004471ccc01281b" => "claim_token_incentives",
        "f945a4da9667548a" => "close_user_volume_accumulator",
        "1416567bc61cdb84" => "collect_creator_fee",
        "cf118af204221338" => "collect_creator_fee_v2",
        "181ec828051c0777" => "create",
        "d6904cec5f8b31b4" => "create_v2",
        "a572670079cef751" => "distribute_creator_fees",
        "ffcb134ff444089f" => "distribute_creator_fees_v2",
        "ea66c2cb96483ee5" => "extend_account",
        "75e17fca865f4423" => "get_minimum_distributable_fee",
        "5e06ca73ff60e8b7" => "init_user_volume_accumulator",
        "afaf6d1f0d989bed" => "initialize",
        "9beae792ec9ea21e" => "migrate",
        "577c34bf3426d6e8" => "migrate_bonding_curve_creator",
        "bbcb121fceedfe29" => "migrate_v2",
        "b141df2658d19e9b" => "remove_quote_mint",
        "33e685a4017f83ad" => "sell",
        "5df6823ce7e940b2" => "sell_v2",
        "fe94ff70cf8eaaa5" => "set_creator",
        "3da9bcbf99952a61" => "set_mayhem_virtual_params",
        "8a60aed93055c5f6" => "set_metaplex_creator",
        "1beab2349302bb8d" => "set_params",
        "6faca2e87259d58e" => "set_reserved_fee_recipients",
        "6587bf6809581460" => "set_virtual_quote_reserves",
        "561fc057a3574fee" => "sync_user_volume_accumulator",
        "7367e0ffbd5956c3" => "toggle_cashback_enabled",
        "1cffe6f0ac6bcbab" => "toggle_create_v2",
        "01096fd0641fffa3" => "toggle_mayhem_mode",
        "fbe0ab92a01a71e9" => "update_buyback_config",
        "e3b54ac4d01561d5" => "update_global_authority",
        _ => return None,
    };
    return Some(name);
}

fn build_instruction_audit_payload(
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    instruction_name: &str,
    discriminator_hex: std::option::Option<std::string::String>,
    accounts: std::vec::Vec<std::string::String>,
    log_messages: &[std::string::String],
    instruction_data: std::option::Option<&[u8]>,
) -> serde_json::Value {
    let event_kind = format!("pump_fun.{}", instruction_name);
    let decoded_arguments = decode_pump_fun_instruction_arguments(instruction_name, instruction_data);
    let is_trade_instruction = pump_fun_instruction_is_direct_materializable_trade(instruction_name);
    let mut payload = serde_json::json!({
        "decoder": "pump_fun",
        "signature": transaction.signature,
        "slot": transaction.slot,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "instructionName": instruction_name,
        "instructionDiscriminatorHex": discriminator_hex,
        "eventKind": event_kind,
        "programId": crate::PUMP_FUN_PROGRAM_ID,
        "accounts": accounts.clone(),
        "logMessages": log_messages,
        "decodedArguments": decoded_arguments,
        "instructionAuditOnly": true,
        "tradeCandidate": is_trade_instruction,
        "candleCandidate": is_trade_instruction,
        "nonTradeUseful": !is_trade_instruction,
    });
    if let Some(object) = payload.as_object_mut() {
        add_pump_fun_instruction_aliases(instruction_name, &accounts, object);
        if let Some(trade_side) = pump_fun_instruction_trade_side(instruction_name) {
            object.insert(
                "tradeSide".to_string(),
                serde_json::Value::String(trade_side.to_string()),
            );
        }
        if is_trade_instruction {
            add_pump_fun_instruction_program_data_trade_amounts(
                instruction_name,
                log_messages,
                object,
            );
        }
        if pump_fun_instruction_is_anchor_trade_sibling_backed(instruction_name) {
            object.insert(
                "instructionCoveredByAnchorTradeEvent".to_string(),
                serde_json::Value::Bool(true),
            );
            object.insert(
                "skipTradeReason".to_string(),
                serde_json::Value::String(
                    "pump_fun_instruction_covered_by_anchor_trade_event".to_string(),
                ),
            );
            object.insert(
                "skipCandleReason".to_string(),
                serde_json::Value::String(
                    "pump_fun_instruction_covered_by_anchor_trade_event".to_string(),
                ),
            );
        } else if !is_trade_instruction {
            object.insert(
                "skipTradeReason".to_string(),
                serde_json::Value::String("non_trade_event".to_string()),
            );
            object.insert(
                "skipCandleReason".to_string(),
                serde_json::Value::String("non_trade_event".to_string()),
            );
        }
        object.insert(
            "skipCatalogReason".to_string(),
            serde_json::Value::String("pump_fun_instruction_audit_only".to_string()),
        );
    }
    return payload;
}

fn pump_fun_instruction_is_direct_materializable_trade(instruction_name: &str) -> bool {
    match instruction_name {
        "buy_exact_sol_in" => return true,
        _ => return false,
    }
}

fn pump_fun_instruction_is_anchor_trade_sibling_backed(instruction_name: &str) -> bool {
    match instruction_name {
        "buy_exact_quote_in_v2" => return true,
        "buy_v2" => return true,
        "sell_v2" => return true,
        _ => return false,
    }
}

fn pump_fun_instruction_trade_side(
    instruction_name: &str,
) -> std::option::Option<&'static str> {
    match instruction_name {
        "buy_exact_quote_in_v2" => return Some("BuyBase"),
        "buy_exact_sol_in" => return Some("BuyBase"),
        "buy_v2" => return Some("BuyBase"),
        "sell_v2" => return Some("SellBase"),
        _ => return None,
    }
}

fn add_pump_fun_instruction_program_data_trade_amounts(
    instruction_name: &str,
    log_messages: &[std::string::String],
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
) {
    let expected_is_buy = match instruction_name {
        "buy_exact_sol_in" => Some(true),
        _ => None,
    };
    let expected_is_buy = match expected_is_buy {
        Some(expected_is_buy) => expected_is_buy,
        None => return,
    };
    let instruction_mint = pump_fun_object_string(object, &["mint", "tokenMint", "tokenAMint"]);
    let instruction_user = pump_fun_object_string(object, &["user", "actorWallet"]);
    for log_message in log_messages {
        let encoded = match extract_program_data_base64(log_message.as_str()) {
            Some(encoded) => encoded,
            None => continue,
        };
        let anchor_event = match decode_pump_fun_anchor_event_from_base64(encoded) {
            Some(anchor_event) => anchor_event,
            None => continue,
        };
        if anchor_event.event_name != "trade_event" {
            continue;
        }
        let event_object = match anchor_event.payload_json.as_object() {
            Some(event_object) => event_object,
            None => continue,
        };
        let event_is_buy = match event_object.get("is_buy") {
            Some(value) => value.as_bool(),
            None => None,
        };
        match event_is_buy {
            Some(event_is_buy) if event_is_buy == expected_is_buy => {},
            Some(_) => continue,
            None => continue,
        }
        let event_mint = pump_fun_object_string(event_object, &["mint"]);
        if !pump_fun_optional_strings_match(instruction_mint.as_deref(), event_mint.as_deref()) {
            continue;
        }
        let event_user = pump_fun_object_string(event_object, &["user"]);
        if !pump_fun_optional_strings_match(instruction_user.as_deref(), event_user.as_deref()) {
            continue;
        }
        copy_pump_fun_anchor_event_value(event_object, object, "token_amount", "baseAmountRaw");
        copy_pump_fun_anchor_event_value(event_object, object, "sol_amount", "quoteAmountRaw");
        copy_pump_fun_anchor_event_value(event_object, object, "quote_amount", "quoteAmountRaw");
        copy_pump_fun_anchor_event_value(event_object, object, "fee", "feeAmountRaw");
        copy_pump_fun_anchor_event_value(event_object, object, "creator_fee", "creatorFeeAmountRaw");
        copy_pump_fun_anchor_event_value(event_object, object, "cashback", "cashbackAmountRaw");
        object.insert(
            "amountSource".to_string(),
            serde_json::Value::String("pump_fun_program_data_trade_event".to_string()),
        );
        object.insert(
            "canonicalAnchorEventName".to_string(),
            serde_json::Value::String(anchor_event.event_name.to_string()),
        );
        object.insert(
            "canonicalAnchorEventDiscriminatorHex".to_string(),
            serde_json::Value::String(anchor_event.discriminator_hex.clone()),
        );
        return;
    }
}

fn copy_pump_fun_anchor_event_value(
    source: &serde_json::Map<std::string::String, serde_json::Value>,
    target: &mut serde_json::Map<std::string::String, serde_json::Value>,
    source_key: &str,
    target_key: &str,
) {
    if target.contains_key(target_key) {
        return;
    }
    let value = match source.get(source_key) {
        Some(value) => value.clone(),
        None => return,
    };
    target.insert(target_key.to_string(), value);
}

fn pump_fun_object_string(
    object: &serde_json::Map<std::string::String, serde_json::Value>,
    keys: &[&str],
) -> std::option::Option<std::string::String> {
    for key in keys {
        let value = match object.get(*key) {
            Some(value) => value,
            None => continue,
        };
        if let Some(text) = value.as_str() {
            let text = text.trim();
            if !text.is_empty() {
                return Some(text.to_string());
            }
        }
    }
    return None;
}

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


fn add_pump_fun_instruction_aliases(
    instruction_name: &str,
    accounts: &[std::string::String],
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
) {
    if instruction_name == "buy_exact_quote_in_v2"
        || instruction_name == "buy_v2"
        || instruction_name == "sell_v2"
    {
        insert_account_alias(object, accounts, 1, &["mint", "tokenMint", "tokenAMint"]);
        insert_account_alias(object, accounts, 2, &["quoteMint", "tokenBMint"]);
        insert_account_alias(object, accounts, 3, &["baseTokenProgram", "tokenProgram"]);
        insert_account_alias(object, accounts, 4, &["quoteTokenProgram"]);
        insert_account_alias(object, accounts, 5, &["associatedTokenProgram"]);
        insert_account_alias(object, accounts, 6, &["feeRecipient"]);
        insert_account_alias(object, accounts, 7, &["associatedQuoteFeeRecipient"]);
        insert_account_alias(object, accounts, 8, &["buybackFeeRecipient"]);
        insert_account_alias(object, accounts, 9, &["associatedQuoteBuybackFeeRecipient"]);
        insert_account_alias(object, accounts, 10, &["bondingCurve", "poolAccount"]);
        insert_account_alias(
            object,
            accounts,
            11,
            &["associatedBondingCurve", "poolBaseTokenAccount", "lpMint"],
        );
        insert_account_alias(
            object,
            accounts,
            12,
            &["associatedQuoteBondingCurve", "poolQuoteTokenAccount"],
        );
        insert_account_alias(object, accounts, 13, &["user", "actorWallet"]);
        insert_account_alias(object, accounts, 14, &["associatedUser", "associatedUserBase"]);
        insert_account_alias(object, accounts, 15, &["associatedUserQuote"]);
        insert_account_alias(object, accounts, 16, &["creatorVault"]);
        insert_account_alias(object, accounts, 17, &["associatedCreatorVault"]);
        insert_account_alias(object, accounts, 18, &["sharingConfig"]);
        if instruction_name == "sell_v2" {
            insert_account_alias(object, accounts, 19, &["userVolumeAccumulator"]);
            insert_account_alias(object, accounts, 20, &["associatedUserVolumeAccumulator"]);
            insert_account_alias(object, accounts, 21, &["feeConfig"]);
            insert_account_alias(object, accounts, 22, &["feeProgram"]);
            insert_account_alias(object, accounts, 23, &["systemProgram"]);
            insert_account_alias(object, accounts, 24, &["eventAuthority"]);
            insert_account_alias(object, accounts, 25, &["program"]);
        } else {
            insert_account_alias(object, accounts, 19, &["globalVolumeAccumulator"]);
            insert_account_alias(object, accounts, 20, &["userVolumeAccumulator"]);
            insert_account_alias(object, accounts, 21, &["associatedUserVolumeAccumulator"]);
            insert_account_alias(object, accounts, 22, &["feeConfig"]);
            insert_account_alias(object, accounts, 23, &["feeProgram"]);
            insert_account_alias(object, accounts, 24, &["systemProgram"]);
            insert_account_alias(object, accounts, 25, &["eventAuthority"]);
            insert_account_alias(object, accounts, 26, &["program"]);
        }
        let pool_quote_token_account = object
            .get("poolQuoteTokenAccount")
            .cloned()
            .unwrap_or(serde_json::Value::String(crate::PUMP_FUN_PROGRAM_ID.to_string()));
        object.insert("poolQuoteNativeAccount".to_string(), pool_quote_token_account);
        return;
    }
    if instruction_name == "buy_exact_sol_in" {
        insert_account_alias(object, accounts, 2, &["mint", "tokenMint", "tokenAMint"]);
        insert_account_alias(object, accounts, 3, &["bondingCurve", "poolAccount"]);
        insert_account_alias(
            object,
            accounts,
            4,
            &["associatedBondingCurve", "poolBaseTokenAccount", "lpMint"],
        );
        insert_account_alias(object, accounts, 5, &["associatedUser"]);
        insert_account_alias(object, accounts, 6, &["user", "actorWallet"]);
        insert_account_alias(object, accounts, 1, &["feeRecipient"]);
        insert_account_alias(object, accounts, 8, &["tokenProgram"]);
        insert_account_alias(object, accounts, 9, &["creatorVault"]);
        object.insert(
            "tokenBMint".to_string(),
            serde_json::Value::String(crate::WSOL_MINT_ID.to_string()),
        );
        let pool_quote_native_account = object
            .get("bondingCurve")
            .cloned()
            .unwrap_or(serde_json::Value::String(crate::PUMP_FUN_PROGRAM_ID.to_string()));
        object.insert("poolQuoteNativeAccount".to_string(), pool_quote_native_account);
        return;
    }
    if instruction_name == "create" || instruction_name == "create_v2" {
        insert_account_alias(object, accounts, 0, &["mint", "tokenMint", "tokenAMint"]);
        insert_account_alias(object, accounts, 2, &["bondingCurve", "poolAccount"]);
        insert_account_alias(object, accounts, 3, &["associatedBondingCurve", "lpMint"]);
        insert_account_alias(object, accounts, 5, &["creator", "actorWallet"]);
        object.insert(
            "tokenBMint".to_string(),
            serde_json::Value::String(crate::WSOL_MINT_ID.to_string()),
        );
        return;
    }
    if instruction_name.contains("migrate") {
        insert_account_alias(object, accounts, 0, &["actorWallet", "user"]);
        insert_account_alias(object, accounts, 1, &["mint", "tokenMint", "tokenAMint"]);
        insert_account_alias(object, accounts, 2, &["bondingCurve", "poolAccount"]);
        object.insert(
            "tokenBMint".to_string(),
            serde_json::Value::String(crate::WSOL_MINT_ID.to_string()),
        );
        return;
    }
    if instruction_name.contains("creator")
        || instruction_name.contains("fee")
        || instruction_name.contains("cashback")
        || instruction_name.contains("volume_accumulator")
        || instruction_name.contains("quote_mint")
        || instruction_name.contains("reserved_fee_recipients")
        || instruction_name.contains("mayhem")
        || instruction_name.contains("global_authority")
        || instruction_name.contains("buyback")
        || instruction_name.contains("extend_account")
    {
        insert_account_alias(object, accounts, 0, &["actorWallet", "authority", "user"]);
        insert_account_alias(object, accounts, 1, &["mint", "tokenMint", "tokenAMint", "quoteMint"]);
        insert_account_alias(object, accounts, 2, &["bondingCurve", "poolAccount", "relatedAccount"]);
        if instruction_name.contains("fee") {
            clone_json_value(object, "quoteMint", "feeTokenMint");
        }
        if instruction_name.contains("cashback") || instruction_name.contains("volume_accumulator") {
            clone_json_value(object, "mint", "rewardTokenMint");
        }
        object.insert(
            "adminAction".to_string(),
            serde_json::Value::String(instruction_name.to_string()),
        );
    }
}

fn insert_account_alias(
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
    accounts: &[std::string::String],
    index: usize,
    keys: &[&str],
) {
    let value = match accounts.get(index) {
        Some(value) => value,
        None => return,
    };
    for key in keys {
        if object.contains_key(*key) {
            continue;
        }
        object.insert((*key).to_string(), serde_json::Value::String(value.clone()));
    }
}

const PUMP_FUN_ADMIN_SET_CREATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "admin_set_creator_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "old_creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "new_creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_ADMIN_SET_IDL_AUTHORITY_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "idl_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_ADMIN_UPDATE_TOKEN_INCENTIVES_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "start_time",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "end_time",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "day_number",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "token_supply_per_day",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "seconds_in_a_day",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
];

const PUMP_FUN_CLAIM_CASHBACK_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_claimed",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_cashback_earned",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_CLAIM_TOKEN_INCENTIVES_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_claimed_tokens",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "current_sol_volume",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_CLOSE_USER_VOLUME_ACCUMULATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_unclaimed_tokens",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_claimed_tokens",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "current_sol_volume",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "last_update_timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
];

const PUMP_FUN_COLLECT_CREATOR_FEE_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator_fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_COMPLETE_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_COMPLETE_PUMP_AMM_MIGRATION_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint_amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "sol_amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "pool_migration_fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "pool",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_CREATE_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "name",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "symbol",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "uri",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "real_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "token_total_supply",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "token_program",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "is_mayhem_mode",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "is_cashback_enabled",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_quote_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_DISTRIBUTE_CREATOR_FEES_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "sharing_config",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "admin",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "shareholders",
        kind: PumpFunBorshFieldKind::ShareholderVec,
    },
    PumpFunBorshFieldDescriptor {
        name: "distributed",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_EXTEND_ACCOUNT_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "account",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "current_size",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "new_size",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
];

const PUMP_FUN_INIT_USER_VOLUME_ACCUMULATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "payer",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
];

const PUMP_FUN_MIGRATE_BONDING_CURVE_CREATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "sharing_config",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "old_creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "new_creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_MINIMUM_DISTRIBUTABLE_FEE_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "minimum_required",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "distributable_fees",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "can_distribute",
        kind: PumpFunBorshFieldKind::Bool,
    },
];

const PUMP_FUN_RESERVED_FEE_RECIPIENTS_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "reserved_fee_recipient",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "reserved_fee_recipients",
        kind: PumpFunBorshFieldKind::PubkeyArray(7),
    },
];

const PUMP_FUN_SET_CREATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_SET_METAPLEX_CREATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "metadata",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_SET_PARAMS_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "initial_virtual_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "initial_virtual_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "initial_real_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "final_real_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "token_total_supply",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "withdraw_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "enable_migrate",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "pool_migration_fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator_fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "fee_recipients",
        kind: PumpFunBorshFieldKind::PubkeyArray(8),
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "set_creator_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "admin_set_creator_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_SYNC_USER_VOLUME_ACCUMULATOR_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_claimed_tokens_before",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_claimed_tokens_after",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
];

const PUMP_FUN_TRADE_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "sol_amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "token_amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "is_buy",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "user",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "real_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "real_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "fee_recipient",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator_fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator_fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "track_volume",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_unclaimed_tokens",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "total_claimed_tokens",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "current_sol_volume",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "last_update_timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "ix_name",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "mayhem_mode",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "cashback_fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "cashback",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "buyback_fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "buyback_fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "shareholders",
        kind: PumpFunBorshFieldKind::ShareholderVec,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "quote_amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_quote_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "real_quote_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_UPDATE_GLOBAL_AUTHORITY_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "global",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "new_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
];

const PUMP_FUN_UPDATE_MAYHEM_VIRTUAL_PARAMS_EVENT_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "virtual_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "new_virtual_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "new_virtual_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "real_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "real_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_IX_ADD_QUOTE_MINT_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_IX_ADMIN_SET_CREATOR_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_IX_ADMIN_SET_IDL_AUTHORITY_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "idl_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_IX_ADMIN_UPDATE_TOKEN_INCENTIVES_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "start_time",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "end_time",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "seconds_in_a_day",
        kind: PumpFunBorshFieldKind::I64,
    },
    PumpFunBorshFieldDescriptor {
        name: "day_number",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "pump_token_supply_per_day",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_IX_BUY_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "max_sol_cost",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "track_volume",
        kind: PumpFunBorshFieldKind::OptionBool,
    },
];

const PUMP_FUN_IX_BUY_EXACT_QUOTE_IN_V2_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "spendable_quote_in",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "min_tokens_out",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_IX_BUY_EXACT_SOL_IN_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "spendable_sol_in",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "min_tokens_out",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "track_volume",
        kind: PumpFunBorshFieldKind::OptionBool,
    },
];

const PUMP_FUN_IX_BUY_V2_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "max_sol_cost",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_IX_CREATE_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "name",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "symbol",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "uri",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_IX_CREATE_V2_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "name",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "symbol",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "uri",
        kind: PumpFunBorshFieldKind::String,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "is_mayhem_mode",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "is_cashback_enabled",
        kind: PumpFunBorshFieldKind::OptionBool,
    },
];

const PUMP_FUN_IX_DISTRIBUTE_CREATOR_FEES_V2_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "initialize_ata",
        kind: PumpFunBorshFieldKind::Bool,
    }];

const PUMP_FUN_IX_REMOVE_QUOTE_MINT_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_IX_SELL_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "min_sol_output",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_IX_SELL_V2_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "amount",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "min_sol_output",
        kind: PumpFunBorshFieldKind::U64,
    },
];

const PUMP_FUN_IX_SET_CREATOR_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "creator",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_IX_SET_PARAMS_FIELDS: &[PumpFunBorshFieldDescriptor] = &[
    PumpFunBorshFieldDescriptor {
        name: "initial_virtual_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "initial_virtual_sol_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "initial_real_token_reserves",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "token_total_supply",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "withdraw_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "enable_migrate",
        kind: PumpFunBorshFieldKind::Bool,
    },
    PumpFunBorshFieldDescriptor {
        name: "pool_migration_fee",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "creator_fee_basis_points",
        kind: PumpFunBorshFieldKind::U64,
    },
    PumpFunBorshFieldDescriptor {
        name: "set_creator_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
    PumpFunBorshFieldDescriptor {
        name: "admin_set_creator_authority",
        kind: PumpFunBorshFieldKind::Pubkey,
    },
];

const PUMP_FUN_IX_SET_RESERVED_FEE_RECIPIENTS_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "whitelist_pda",
        kind: PumpFunBorshFieldKind::Pubkey,
    }];

const PUMP_FUN_IX_SET_VIRTUAL_QUOTE_RESERVES_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "initial_virtual_quote_reserves",
        kind: PumpFunBorshFieldKind::U64,
    }];

const PUMP_FUN_IX_TOGGLE_CASHBACK_ENABLED_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "enabled",
        kind: PumpFunBorshFieldKind::Bool,
    }];

const PUMP_FUN_IX_TOGGLE_CREATE_V2_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "enabled",
        kind: PumpFunBorshFieldKind::Bool,
    }];

const PUMP_FUN_IX_TOGGLE_MAYHEM_MODE_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "enabled",
        kind: PumpFunBorshFieldKind::Bool,
    }];

const PUMP_FUN_IX_UPDATE_BUYBACK_CONFIG_FIELDS: &[PumpFunBorshFieldDescriptor] =
    &[PumpFunBorshFieldDescriptor {
        name: "buyback_basis_points",
        kind: PumpFunBorshFieldKind::OptionU64,
    }];

fn pump_fun_anchor_event_descriptor(
    discriminator: [u8; 8],
) -> std::option::Option<PumpFunAnchorEventDescriptor> {
    match discriminator {
        [64, 69, 192, 104, 29, 30, 25, 107] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "admin_set_creator_event",
                event_kind: "pump_fun.admin_set_creator_event",
                discriminator: [64, 69, 192, 104, 29, 30, 25, 107],
                fields: PUMP_FUN_ADMIN_SET_CREATOR_EVENT_FIELDS,
            });
        },
        [245, 59, 70, 34, 75, 185, 109, 92] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "admin_set_idl_authority_event",
                event_kind: "pump_fun.admin_set_idl_authority_event",
                discriminator: [245, 59, 70, 34, 75, 185, 109, 92],
                fields: PUMP_FUN_ADMIN_SET_IDL_AUTHORITY_EVENT_FIELDS,
            });
        },
        [147, 250, 108, 120, 247, 29, 67, 222] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "admin_update_token_incentives_event",
                event_kind: "pump_fun.admin_update_token_incentives_event",
                discriminator: [147, 250, 108, 120, 247, 29, 67, 222],
                fields: PUMP_FUN_ADMIN_UPDATE_TOKEN_INCENTIVES_EVENT_FIELDS,
            });
        },
        [226, 214, 246, 33, 7, 242, 147, 229] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "claim_cashback_event",
                event_kind: "pump_fun.claim_cashback_event",
                discriminator: [226, 214, 246, 33, 7, 242, 147, 229],
                fields: PUMP_FUN_CLAIM_CASHBACK_EVENT_FIELDS,
            });
        },
        [79, 172, 246, 49, 205, 91, 206, 232] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "claim_token_incentives_event",
                event_kind: "pump_fun.claim_token_incentives_event",
                discriminator: [79, 172, 246, 49, 205, 91, 206, 232],
                fields: PUMP_FUN_CLAIM_TOKEN_INCENTIVES_EVENT_FIELDS,
            });
        },
        [146, 159, 189, 172, 146, 88, 56, 244] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "close_user_volume_accumulator_event",
                event_kind: "pump_fun.close_user_volume_accumulator_event",
                discriminator: [146, 159, 189, 172, 146, 88, 56, 244],
                fields: PUMP_FUN_CLOSE_USER_VOLUME_ACCUMULATOR_EVENT_FIELDS,
            });
        },
        [122, 2, 127, 1, 14, 191, 12, 175] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "collect_creator_fee_event",
                event_kind: "pump_fun.collect_creator_fee_event",
                discriminator: [122, 2, 127, 1, 14, 191, 12, 175],
                fields: PUMP_FUN_COLLECT_CREATOR_FEE_EVENT_FIELDS,
            });
        },
        [95, 114, 97, 156, 212, 46, 152, 8] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "complete_event",
                event_kind: "pump_fun.complete_event",
                discriminator: [95, 114, 97, 156, 212, 46, 152, 8],
                fields: PUMP_FUN_COMPLETE_EVENT_FIELDS,
            });
        },
        [189, 233, 93, 185, 92, 148, 234, 148] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "complete_pump_amm_migration_event",
                event_kind: "pump_fun.complete_pump_amm_migration_event",
                discriminator: [189, 233, 93, 185, 92, 148, 234, 148],
                fields: PUMP_FUN_COMPLETE_PUMP_AMM_MIGRATION_EVENT_FIELDS,
            });
        },
        [27, 114, 169, 77, 222, 235, 99, 118] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "create_event",
                event_kind: "pump_fun.create_event",
                discriminator: [27, 114, 169, 77, 222, 235, 99, 118],
                fields: PUMP_FUN_CREATE_EVENT_FIELDS,
            });
        },
        [165, 55, 129, 112, 4, 179, 202, 40] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "distribute_creator_fees_event",
                event_kind: "pump_fun.distribute_creator_fees_event",
                discriminator: [165, 55, 129, 112, 4, 179, 202, 40],
                fields: PUMP_FUN_DISTRIBUTE_CREATOR_FEES_EVENT_FIELDS,
            });
        },
        [97, 97, 215, 144, 93, 146, 22, 124] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "extend_account_event",
                event_kind: "pump_fun.extend_account_event",
                discriminator: [97, 97, 215, 144, 93, 146, 22, 124],
                fields: PUMP_FUN_EXTEND_ACCOUNT_EVENT_FIELDS,
            });
        },
        [134, 36, 13, 72, 232, 101, 130, 216] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "init_user_volume_accumulator_event",
                event_kind: "pump_fun.init_user_volume_accumulator_event",
                discriminator: [134, 36, 13, 72, 232, 101, 130, 216],
                fields: PUMP_FUN_INIT_USER_VOLUME_ACCUMULATOR_EVENT_FIELDS,
            });
        },
        [155, 167, 104, 220, 213, 108, 243, 3] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "migrate_bonding_curve_creator_event",
                event_kind: "pump_fun.migrate_bonding_curve_creator_event",
                discriminator: [155, 167, 104, 220, 213, 108, 243, 3],
                fields: PUMP_FUN_MIGRATE_BONDING_CURVE_CREATOR_EVENT_FIELDS,
            });
        },
        [168, 216, 132, 239, 235, 182, 49, 52] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "minimum_distributable_fee_event",
                event_kind: "pump_fun.minimum_distributable_fee_event",
                discriminator: [168, 216, 132, 239, 235, 182, 49, 52],
                fields: PUMP_FUN_MINIMUM_DISTRIBUTABLE_FEE_EVENT_FIELDS,
            });
        },
        [43, 188, 250, 18, 221, 75, 187, 95] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "reserved_fee_recipients_event",
                event_kind: "pump_fun.reserved_fee_recipients_event",
                discriminator: [43, 188, 250, 18, 221, 75, 187, 95],
                fields: PUMP_FUN_RESERVED_FEE_RECIPIENTS_EVENT_FIELDS,
            });
        },
        [237, 52, 123, 37, 245, 251, 72, 210] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "set_creator_event",
                event_kind: "pump_fun.set_creator_event",
                discriminator: [237, 52, 123, 37, 245, 251, 72, 210],
                fields: PUMP_FUN_SET_CREATOR_EVENT_FIELDS,
            });
        },
        [142, 203, 6, 32, 127, 105, 191, 162] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "set_metaplex_creator_event",
                event_kind: "pump_fun.set_metaplex_creator_event",
                discriminator: [142, 203, 6, 32, 127, 105, 191, 162],
                fields: PUMP_FUN_SET_METAPLEX_CREATOR_EVENT_FIELDS,
            });
        },
        [223, 195, 159, 246, 62, 48, 143, 131] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "set_params_event",
                event_kind: "pump_fun.set_params_event",
                discriminator: [223, 195, 159, 246, 62, 48, 143, 131],
                fields: PUMP_FUN_SET_PARAMS_EVENT_FIELDS,
            });
        },
        [197, 122, 167, 124, 116, 81, 91, 255] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "sync_user_volume_accumulator_event",
                event_kind: "pump_fun.sync_user_volume_accumulator_event",
                discriminator: [197, 122, 167, 124, 116, 81, 91, 255],
                fields: PUMP_FUN_SYNC_USER_VOLUME_ACCUMULATOR_EVENT_FIELDS,
            });
        },
        [189, 219, 127, 211, 78, 230, 97, 238] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "trade_event",
                event_kind: "pump_fun.trade_event",
                discriminator: [189, 219, 127, 211, 78, 230, 97, 238],
                fields: PUMP_FUN_TRADE_EVENT_FIELDS,
            });
        },
        [182, 195, 137, 42, 35, 206, 207, 247] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "update_global_authority_event",
                event_kind: "pump_fun.update_global_authority_event",
                discriminator: [182, 195, 137, 42, 35, 206, 207, 247],
                fields: PUMP_FUN_UPDATE_GLOBAL_AUTHORITY_EVENT_FIELDS,
            });
        },
        [117, 123, 228, 182, 161, 168, 220, 214] => {
            return Some(PumpFunAnchorEventDescriptor {
                event_name: "update_mayhem_virtual_params_event",
                event_kind: "pump_fun.update_mayhem_virtual_params_event",
                discriminator: [117, 123, 228, 182, 161, 168, 220, 214],
                fields: PUMP_FUN_UPDATE_MAYHEM_VIRTUAL_PARAMS_EVENT_FIELDS,
            });
        },
        _ => return None,
    }
}

fn pump_fun_instruction_arg_fields(
    instruction_name: &str,
) -> std::option::Option<&'static [PumpFunBorshFieldDescriptor]> {
    match instruction_name {
        "add_quote_mint" => return Some(PUMP_FUN_IX_ADD_QUOTE_MINT_FIELDS),
        "admin_set_creator" => return Some(PUMP_FUN_IX_ADMIN_SET_CREATOR_FIELDS),
        "admin_set_idl_authority" => return Some(PUMP_FUN_IX_ADMIN_SET_IDL_AUTHORITY_FIELDS),
        "admin_update_token_incentives" => {
            return Some(PUMP_FUN_IX_ADMIN_UPDATE_TOKEN_INCENTIVES_FIELDS);
        },
        "buy" => return Some(PUMP_FUN_IX_BUY_FIELDS),
        "buy_exact_quote_in_v2" => return Some(PUMP_FUN_IX_BUY_EXACT_QUOTE_IN_V2_FIELDS),
        "buy_exact_sol_in" => return Some(PUMP_FUN_IX_BUY_EXACT_SOL_IN_FIELDS),
        "buy_v2" => return Some(PUMP_FUN_IX_BUY_V2_FIELDS),
        "create" => return Some(PUMP_FUN_IX_CREATE_FIELDS),
        "create_v2" => return Some(PUMP_FUN_IX_CREATE_V2_FIELDS),
        "distribute_creator_fees_v2" => return Some(PUMP_FUN_IX_DISTRIBUTE_CREATOR_FEES_V2_FIELDS),
        "remove_quote_mint" => return Some(PUMP_FUN_IX_REMOVE_QUOTE_MINT_FIELDS),
        "sell" => return Some(PUMP_FUN_IX_SELL_FIELDS),
        "sell_v2" => return Some(PUMP_FUN_IX_SELL_V2_FIELDS),
        "set_creator" => return Some(PUMP_FUN_IX_SET_CREATOR_FIELDS),
        "set_params" => return Some(PUMP_FUN_IX_SET_PARAMS_FIELDS),
        "set_reserved_fee_recipients" => {
            return Some(PUMP_FUN_IX_SET_RESERVED_FEE_RECIPIENTS_FIELDS);
        },
        "set_virtual_quote_reserves" => return Some(PUMP_FUN_IX_SET_VIRTUAL_QUOTE_RESERVES_FIELDS),
        "toggle_cashback_enabled" => return Some(PUMP_FUN_IX_TOGGLE_CASHBACK_ENABLED_FIELDS),
        "toggle_create_v2" => return Some(PUMP_FUN_IX_TOGGLE_CREATE_V2_FIELDS),
        "toggle_mayhem_mode" => return Some(PUMP_FUN_IX_TOGGLE_MAYHEM_MODE_FIELDS),
        "update_buyback_config" => return Some(PUMP_FUN_IX_UPDATE_BUYBACK_CONFIG_FIELDS),
        _ => return None,
    }
}

fn build_pump_fun_anchor_event_decoded(
    transaction: &crate::ChainTransactionDto,
    instruction_id: i64,
    anchor_event: &PumpFunAnchorEventDecodedPayload,
) -> crate::PumpFunDecodedEvent {
    let transaction_id = match transaction.id {
        Some(transaction_id) => transaction_id,
        None => 0,
    };
    let payload_json =
        build_pump_fun_anchor_event_payload(transaction, instruction_id, anchor_event);
    return crate::PumpFunDecodedEvent::InstructionAudit(crate::PumpFunInstructionAuditDecoded {
        transaction_id,
        instruction_id,
        signature: transaction.signature.clone(),
        program_id: crate::PUMP_FUN_PROGRAM_ID.to_string(),
        instruction_name: anchor_event.event_name.to_string(),
        event_kind: anchor_event.event_kind.to_string(),
        discriminator_hex: Some(anchor_event.discriminator_hex.clone()),
        payload_json,
    });
}

fn build_pump_fun_anchor_event_payload(
    transaction: &crate::ChainTransactionDto,
    instruction_id: i64,
    anchor_event: &PumpFunAnchorEventDecodedPayload,
) -> serde_json::Value {
    let mut object = match anchor_event.payload_json.clone() {
        serde_json::Value::Object(object) => object,
        other => {
            let mut object = serde_json::Map::new();
            object.insert("rawAnchorEventPayload".to_string(), other);
            object
        },
    };
    object.insert("decoder".to_string(), serde_json::Value::String("pump_fun".to_string()));
    object.insert(
        "signature".to_string(),
        serde_json::Value::String(transaction.signature.clone()),
    );
    object.insert(
        "instructionId".to_string(),
        serde_json::Value::Number(serde_json::Number::from(instruction_id)),
    );
    object.insert(
        "eventKind".to_string(),
        serde_json::Value::String(anchor_event.event_kind.to_string()),
    );
    object.insert(
        "anchorEventName".to_string(),
        serde_json::Value::String(anchor_event.event_name.to_string()),
    );
    object.insert(
        "anchorEventDiscriminatorHex".to_string(),
        serde_json::Value::String(anchor_event.discriminator_hex.clone()),
    );
    object.insert(
        "anchorEventPayloadSize".to_string(),
        serde_json::json!(anchor_event.payload_size),
    );
    object.insert(
        "anchorEventSource".to_string(),
        serde_json::Value::String("anchor_program_data_or_self_cpi".to_string()),
    );
    object.insert("protocolName".to_string(), serde_json::Value::String("pump_fun".to_string()));
    object.insert(
        "programId".to_string(),
        serde_json::Value::String(crate::PUMP_FUN_PROGRAM_ID.to_string()),
    );
    add_pump_fun_anchor_event_aliases(anchor_event.event_name, &mut object);
    object.insert("anchorEventAuditOnly".to_string(), serde_json::Value::Bool(true));
    object.insert(
        "skipTradeReason".to_string(),
        serde_json::Value::String("pump_fun_anchor_event_audit_only".to_string()),
    );
    object.insert(
        "skipCandleReason".to_string(),
        serde_json::Value::String("pump_fun_anchor_event_audit_only".to_string()),
    );
    object.insert(
        "skipCatalogReason".to_string(),
        serde_json::Value::String("pump_fun_anchor_event_audit_only".to_string()),
    );
    return crate::enrich_dex_decoded_payload(
        "pump_fun",
        anchor_event.event_kind,
        serde_json::Value::Object(object),
    );
}

fn add_pump_fun_anchor_event_aliases(
    event_name: &str,
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
) {
    clone_json_value(object, "mint", "tokenMint");
    clone_json_value(object, "mint", "tokenAMint");
    clone_json_value(object, "quote_mint", "quoteMint");
    clone_json_value(object, "quote_mint", "tokenBMint");
    clone_json_value(object, "bonding_curve", "poolAccount");
    clone_json_value(object, "pool", "poolAccount");
    clone_json_value(object, "user", "actorWallet");
    clone_json_value(object, "creator", "actorWallet");
    clone_json_value(object, "admin", "actorWallet");
    clone_json_value(object, "authority", "actorWallet");
    clone_json_value(object, "creator_fee", "feeAmountRaw");
    clone_json_value(object, "distributed", "feeAmountRaw");
    clone_json_value(object, "distributable_fees", "feeAmountRaw");
    clone_json_value(object, "amount", "rewardAmountRaw");
    clone_json_value(object, "cashback", "rewardAmountRaw");
    clone_json_value(object, "total_unclaimed_tokens", "rewardAmountRaw");
    clone_json_value(object, "token_amount", "baseAmountRaw");
    clone_json_value(object, "sol_amount", "quoteAmountRaw");
    clone_json_value(object, "quote_amount", "quoteAmountRaw");
    if event_name == "claim_token_incentives_event" || event_name == "claim_cashback_event" {
        object.insert(
            "anchorEventMaterializationRole".to_string(),
            serde_json::Value::String("reward".to_string()),
        );
    }
    if event_name == "collect_creator_fee_event" || event_name == "distribute_creator_fees_event" {
        object.insert(
            "anchorEventMaterializationRole".to_string(),
            serde_json::Value::String("fee".to_string()),
        );
    }
}

fn clone_json_value(
    object: &mut serde_json::Map<std::string::String, serde_json::Value>,
    source_key: &str,
    target_key: &str,
) {
    if object.contains_key(target_key) {
        return;
    }
    let value = match object.get(source_key) {
        Some(value) => value.clone(),
        None => return,
    };
    object.insert(target_key.to_string(), value);
}

fn decode_pump_fun_anchor_event_from_base64(
    encoded: &str,
) -> std::option::Option<PumpFunAnchorEventDecodedPayload> {
    let decoded = match decode_base64_standard(encoded) {
        Some(decoded) => decoded,
        None => return None,
    };
    return decode_pump_fun_anchor_event_data(decoded.as_slice());
}

fn decode_pump_fun_anchor_event_data(
    data: &[u8],
) -> std::option::Option<PumpFunAnchorEventDecodedPayload> {
    let first_discriminator = read_8_byte_array(data, 0);
    if data.len() >= 16 && first_discriminator == Some(PUMP_FUN_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR) {
        return decode_pump_fun_anchor_event_payload(&data[8..]);
    }
    return decode_pump_fun_anchor_event_payload(data);
}

fn decode_pump_fun_anchor_event_payload(
    data: &[u8],
) -> std::option::Option<PumpFunAnchorEventDecodedPayload> {
    let discriminator = match read_8_byte_array(data, 0) {
        Some(discriminator) => discriminator,
        None => return None,
    };
    let descriptor = match pump_fun_anchor_event_descriptor(discriminator) {
        Some(descriptor) => descriptor,
        None => return None,
    };
    let payload_json = if descriptor.event_name == "trade_event" {
        match decode_pump_fun_trade_event_borsh_fields(data, 8, descriptor.fields) {
            Some(payload_json) => payload_json,
            None => return None,
        }
    } else {
        match decode_borsh_fields(data, 8, descriptor.fields) {
            Some(payload_json) => payload_json,
            None => return None,
        }
    };
    return Some(PumpFunAnchorEventDecodedPayload {
        event_name: descriptor.event_name,
        event_kind: descriptor.event_kind,
        discriminator_hex: bytes_to_hex(&descriptor.discriminator),
        payload_size: data.len().saturating_sub(8),
        payload_json,
    });
}

fn decode_pump_fun_trade_event_borsh_fields(
    data: &[u8],
    start_offset: usize,
    fields: &[PumpFunBorshFieldDescriptor],
) -> std::option::Option<serde_json::Value> {
    let mut object = serde_json::Map::new();
    let mut offset = start_offset;
    for field in fields {
        let before_offset = offset;
        let value = match read_borsh_field_value(data, &mut offset, field.kind) {
            Some(value) => value,
            None => {
                offset = before_offset;
                if object.contains_key("ix_name") {
                    object.insert(
                        "layoutTruncatedAfterIxName".to_string(),
                        serde_json::Value::Bool(true),
                    );
                    object.insert(
                        "layoutConsumedBytes".to_string(),
                        serde_json::Value::Number(serde_json::Number::from(offset as u64)),
                    );
                    object.insert(
                        "layoutPayloadBytes".to_string(),
                        serde_json::Value::Number(serde_json::Number::from(data.len() as u64)),
                    );
                    return Some(serde_json::Value::Object(object));
                }
                return None;
            },
        };
        object.insert(field.name.to_string(), value);
    }
    return Some(serde_json::Value::Object(object));
}

fn decode_pump_fun_instruction_arguments(
    instruction_name: &str,
    instruction_data: std::option::Option<&[u8]>,
) -> serde_json::Value {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return serde_json::Value::Object(serde_json::Map::new()),
    };
    let fields = match pump_fun_instruction_arg_fields(instruction_name) {
        Some(fields) => fields,
        None => return serde_json::Value::Object(serde_json::Map::new()),
    };
    let decoded = decode_borsh_fields(instruction_data, 8, fields);
    match decoded {
        Some(decoded) => return decoded,
        None => return serde_json::Value::Object(serde_json::Map::new()),
    }
}

fn decode_borsh_fields(
    data: &[u8],
    start_offset: usize,
    fields: &[PumpFunBorshFieldDescriptor],
) -> std::option::Option<serde_json::Value> {
    let mut object = serde_json::Map::new();
    let mut offset = start_offset;
    for field in fields {
        let value = match read_borsh_field_value(data, &mut offset, field.kind) {
            Some(value) => value,
            None => return None,
        };
        object.insert(field.name.to_string(), value);
    }
    return Some(serde_json::Value::Object(object));
}

fn read_borsh_field_value(
    data: &[u8],
    offset: &mut usize,
    kind: PumpFunBorshFieldKind,
) -> std::option::Option<serde_json::Value> {
    match kind {
        PumpFunBorshFieldKind::U64 => {
            let value = match decode_u64_le_as_string(data, *offset) {
                Some(value) => value,
                None => return None,
            };
            *offset = (*offset).saturating_add(8);
            return Some(serde_json::Value::String(value));
        },
        PumpFunBorshFieldKind::I64 => {
            let value = match decode_i64_le_as_string(data, *offset) {
                Some(value) => value,
                None => return None,
            };
            *offset = (*offset).saturating_add(8);
            return Some(serde_json::Value::String(value));
        },
        PumpFunBorshFieldKind::Bool => {
            let value = match read_bool(data, *offset) {
                Some(value) => value,
                None => return None,
            };
            *offset = (*offset).saturating_add(1);
            return Some(serde_json::Value::Bool(value));
        },
        PumpFunBorshFieldKind::Pubkey => {
            let value = match decode_pubkey32_as_string(data, *offset) {
                Some(value) => value,
                None => return None,
            };
            *offset = (*offset).saturating_add(32);
            return Some(serde_json::Value::String(value));
        },
        PumpFunBorshFieldKind::String => {
            let result = match read_borsh_string(data, *offset) {
                Some(result) => result,
                None => return None,
            };
            *offset = result.1;
            return Some(serde_json::Value::String(result.0));
        },
        PumpFunBorshFieldKind::OptionBool => {
            let value = match read_bool(data, *offset) {
                Some(value) => value,
                None => return None,
            };
            *offset = (*offset).saturating_add(1);
            return Some(serde_json::Value::Bool(value));
        },
        PumpFunBorshFieldKind::OptionU64 => {
            let tag = match read_u8(data, *offset) {
                Some(tag) => tag,
                None => return None,
            };
            *offset = (*offset).saturating_add(1);
            if tag == 0 {
                return Some(serde_json::Value::Null);
            }
            let value = match decode_u64_le_as_string(data, *offset) {
                Some(value) => value,
                None => return None,
            };
            *offset = (*offset).saturating_add(8);
            return Some(serde_json::Value::String(value));
        },
        PumpFunBorshFieldKind::PubkeyArray(count) => {
            let mut values = std::vec::Vec::new();
            let mut index = 0_usize;
            while index < count {
                let value = match decode_pubkey32_as_string(data, *offset) {
                    Some(value) => value,
                    None => return None,
                };
                values.push(serde_json::Value::String(value));
                *offset = (*offset).saturating_add(32);
                index = index.saturating_add(1);
            }
            return Some(serde_json::Value::Array(values));
        },
        PumpFunBorshFieldKind::ShareholderVec => {
            if data.len() < (*offset).saturating_add(4) {
                return None;
            }
            let count = u32::from_le_bytes([
                data[*offset],
                data[(*offset).saturating_add(1)],
                data[(*offset).saturating_add(2)],
                data[(*offset).saturating_add(3)],
            ]) as usize;
            *offset = (*offset).saturating_add(4);
            let mut values = std::vec::Vec::new();
            let mut index = 0_usize;
            while index < count {
                let address = match decode_pubkey32_as_string(data, *offset) {
                    Some(value) => value,
                    None => return None,
                };
                *offset = (*offset).saturating_add(32);
                if data.len() < (*offset).saturating_add(2) {
                    return None;
                }
                let share_bps =
                    u16::from_le_bytes([data[*offset], data[(*offset).saturating_add(1)]]);
                *offset = (*offset).saturating_add(2);
                values.push(serde_json::json!({
                    "address": address,
                    "share_bps": share_bps.to_string()
                }));
                index = index.saturating_add(1);
            }
            return Some(serde_json::Value::Array(values));
        },
    }
}

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

fn read_8_byte_array(data: &[u8], offset: usize) -> std::option::Option<[u8; 8]> {
    if data.len() < offset.saturating_add(8) {
        return None;
    }
    return Some([
        data[offset],
        data[offset + 1],
        data[offset + 2],
        data[offset + 3],
        data[offset + 4],
        data[offset + 5],
        data[offset + 6],
        data[offset + 7],
    ]);
}

fn read_bool(data: &[u8], offset: usize) -> std::option::Option<bool> {
    if data.len() <= offset {
        return None;
    }
    return Some(data[offset] != 0);
}

fn read_borsh_string(
    data: &[u8],
    offset: usize,
) -> std::option::Option<(std::string::String, usize)> {
    if data.len() < offset.saturating_add(4) {
        return None;
    }
    let len =
        u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]]);
    let start = offset.saturating_add(4);
    let end = start.saturating_add(len as usize);
    if data.len() < end {
        return None;
    }
    let text_result = std::str::from_utf8(&data[start..end]);
    let text = match text_result {
        Ok(text) => text,
        Err(_) => return None,
    };
    return Some((text.to_string(), end));
}

fn decode_u64_le_as_string(data: &[u8], offset: usize) -> std::option::Option<std::string::String> {
    if data.len() < offset.saturating_add(8) {
        return None;
    }
    let mut bytes = [0u8; 8];
    bytes.copy_from_slice(&data[offset..offset + 8]);
    return Some(u64::from_le_bytes(bytes).to_string());
}

fn decode_i64_le_as_string(data: &[u8], offset: usize) -> std::option::Option<std::string::String> {
    if data.len() < offset.saturating_add(8) {
        return None;
    }
    let mut bytes = [0u8; 8];
    bytes.copy_from_slice(&data[offset..offset + 8]);
    return Some(i64::from_le_bytes(bytes).to_string());
}

fn decode_pubkey32_as_string(
    data: &[u8],
    offset: usize,
) -> std::option::Option<std::string::String> {
    if data.len() < offset.saturating_add(32) {
        return None;
    }
    return Some(bs58::encode(&data[offset..offset + 32]).into_string());
}

fn extract_program_data_base64(message: &str) -> std::option::Option<&str> {
    let trimmed = message.trim();
    if let Some(rest) = trimmed.strip_prefix("Program data: ") {
        return Some(rest.trim());
    }
    return None;
}

fn decode_base64_standard(encoded: &str) -> std::option::Option<std::vec::Vec<u8>> {
    use base64::Engine as _;
    let decoded = base64::engine::general_purpose::STANDARD.decode(encoded.as_bytes());
    match decoded {
        Ok(decoded) => return Some(decoded),
        Err(_) => return None,
    }
}

fn normalize_log_text(value: &str) -> std::string::String {
    let mut normalized = std::string::String::new();
    for character in value.chars() {
        if character.is_ascii_alphanumeric() {
            normalized.push(character.to_ascii_lowercase());
        }
    }
    return normalized;
}

#[cfg(test)]
mod tests {
    fn make_transaction() -> crate::ChainTransactionDto {
        let mut dto = crate::ChainTransactionDto::new(
            "sig-pump-fun-test-1".to_string(),
            Some(777001),
            Some(1779200001),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 777001,
                "meta": {
                    "logMessages": [
                        "Program log: Instruction: CreateV2"
                    ]
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        dto.id = Some(91);
        return dto;
    }

    fn make_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            91,
            None,
            0,
            None,
            Some(crate::PUMP_FUN_PROGRAM_ID.to_string()),
            Some("pump".to_string()),
            Some(1),
            serde_json::json!([
                "MintCreate111",
                "MintAuthority111",
                "BondingCurve111",
                "AssociatedBondingCurve111",
                "Global111",
                "Creator111",
                "System111",
                "Token2022Program111",
                "AtaProgram111"
            ])
            .to_string(),
            None,
            None,
            None,
        );
        dto.id = Some(17);
        return dto;
    }

    fn make_instruction_with_data(discriminator: [u8; 8]) -> crate::ChainInstructionDto {
        let mut instruction = make_instruction();
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&discriminator);
        let encoded = bs58::encode(data).into_string();
        instruction.data_json = Some(serde_json::json!(encoded).to_string());
        return instruction;
    }

    #[test]
    fn pump_fun_create_v2_is_detected() {
        let decoder = crate::PumpFunDecoder::new();
        let transaction = make_transaction();
        let instructions = vec![make_instruction()];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::PumpFunDecodedEvent::CreateV2Token(event) => {
                assert_eq!(event.transaction_id, 91);
                assert_eq!(event.instruction_id, 17);
                assert_eq!(event.mint, Some("MintCreate111".to_string()));
                assert_eq!(event.bonding_curve, Some("BondingCurve111".to_string()));
                assert_eq!(
                    event.associated_bonding_curve,
                    Some("AssociatedBondingCurve111".to_string())
                );
                assert_eq!(event.creator, Some("Creator111".to_string()));
            },
            crate::PumpFunDecodedEvent::BuyTrade(_) => {
                panic!("unexpected pump_fun buy trade event");
            },
            crate::PumpFunDecodedEvent::SellTrade(_) => {
                panic!("unexpected pump_fun sell trade event");
            },
            crate::PumpFunDecodedEvent::InstructionAudit(_) => {
                panic!("unexpected pump_fun instruction audit event");
            },
        }
    }

    #[test]
    fn pump_fun_create_v2_returns_none_without_expected_log() {
        let decoder = crate::PumpFunDecoder::new();
        let mut transaction = make_transaction();
        transaction.transaction_json = serde_json::json!({
            "slot": 777001,
            "meta": {
                "logMessages": [
                    "Program log: Instruction: Buy"
                ]
            },
            "transaction": {
                "message": {
                    "instructions": []
                }
            }
        })
        .to_string();
        let instructions = vec![make_instruction()];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 0);
    }

    #[test]
    fn pump_fun_buy_v2_is_decoded_as_anchor_trade_sibling_backed_audit() {
        let decoder = crate::PumpFunDecoder::new();
        let mut transaction = make_transaction();
        transaction.transaction_json = serde_json::json!({
            "slot": 777001,
            "meta": {
                "logMessages": [
                    "Program log: Instruction: BuyV2"
                ]
            },
            "transaction": {
                "message": {
                    "instructions": []
                }
            }
        })
        .to_string();
        let mut instruction = make_instruction_with_data([184, 23, 238, 97, 103, 197, 211, 61]);
        instruction.accounts_json = serde_json::json!([
            "Global111",
            "BaseMint111",
            "QuoteMint111",
            "BaseTokenProgram111",
            "QuoteTokenProgram111",
            "AssociatedTokenProgram111",
            "FeeRecipient111",
            "AssociatedQuoteFeeRecipient111",
            "BuybackFeeRecipient111",
            "AssociatedQuoteBuybackFeeRecipient111",
            "BondingCurve111",
            "AssociatedBaseBondingCurve111",
            "AssociatedQuoteBondingCurve111",
            "User111",
            "AssociatedBaseUser111",
            "AssociatedQuoteUser111",
            "CreatorVault111",
            "AssociatedCreatorVault111",
            "SharingConfig111",
            "GlobalVolumeAccumulator111",
            "UserVolumeAccumulator111",
            "AssociatedUserVolumeAccumulator111",
            "FeeConfig111",
            "FeeProgram111",
            "SystemProgram111",
            "EventAuthority111",
            "PumpFunProgram111"
        ])
        .to_string();
        let instructions = vec![instruction];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::PumpFunDecodedEvent::InstructionAudit(event) => {
                assert_eq!(event.instruction_name, "buy_v2");
                assert_eq!(event.event_kind, "pump_fun.buy_v2");
                assert_eq!(event.discriminator_hex, Some("b817ee6167c5d33d".to_string()));
                assert_eq!(
                    event.payload_json.get("instructionAuditOnly"),
                    Some(&serde_json::Value::Bool(true))
                );
                assert_eq!(
                    event.payload_json.get("tradeCandidate"),
                    Some(&serde_json::Value::Bool(false))
                );
                assert_eq!(
                    event.payload_json.get("instructionCoveredByAnchorTradeEvent"),
                    Some(&serde_json::Value::Bool(true))
                );
                assert_eq!(
                    event.payload_json.get("skipTradeReason"),
                    Some(&serde_json::Value::String(
                        "pump_fun_instruction_covered_by_anchor_trade_event".to_string()
                    ))
                );
                assert_eq!(
                    event.payload_json.get("tokenMint"),
                    Some(&serde_json::Value::String("BaseMint111".to_string()))
                );
                assert_eq!(
                    event.payload_json.get("quoteMint"),
                    Some(&serde_json::Value::String("QuoteMint111".to_string()))
                );
                assert_eq!(
                    event.payload_json.get("bondingCurve"),
                    Some(&serde_json::Value::String("BondingCurve111".to_string()))
                );
                assert_eq!(
                    event.payload_json.get("actorWallet"),
                    Some(&serde_json::Value::String("User111".to_string()))
                );
            },
            crate::PumpFunDecodedEvent::CreateV2Token(_) => {
                panic!("unexpected pump_fun create_v2 token event");
            },
            crate::PumpFunDecodedEvent::BuyTrade(_) => {
                panic!("unexpected pump_fun buy trade event");
            },
            crate::PumpFunDecodedEvent::SellTrade(_) => {
                panic!("unexpected pump_fun sell trade event");
            },
        }
    }

    fn append_synthetic_field(data: &mut std::vec::Vec<u8>, kind: super::PumpFunBorshFieldKind) {
        match kind {
            super::PumpFunBorshFieldKind::U64 => data.extend_from_slice(&1234_u64.to_le_bytes()),
            super::PumpFunBorshFieldKind::I64 => data.extend_from_slice(&1234_i64.to_le_bytes()),
                super::PumpFunBorshFieldKind::Bool => data.push(1),
            super::PumpFunBorshFieldKind::Pubkey => data.extend_from_slice(&[7_u8; 32]),
            super::PumpFunBorshFieldKind::String => {
                let text = b"synthetic";
                data.extend_from_slice(&(text.len() as u32).to_le_bytes());
                data.extend_from_slice(text);
            },
            super::PumpFunBorshFieldKind::OptionBool => data.push(1),
            super::PumpFunBorshFieldKind::OptionU64 => {
                data.push(1);
                data.extend_from_slice(&55_u64.to_le_bytes());
            },
            super::PumpFunBorshFieldKind::PubkeyArray(count) => {
                let mut index = 0_usize;
                while index < count {
                    data.extend_from_slice(&[8_u8; 32]);
                    index = index.saturating_add(1);
                }
            },
            super::PumpFunBorshFieldKind::ShareholderVec => {
                data.extend_from_slice(&1_u32.to_le_bytes());
                data.extend_from_slice(&[9_u8; 32]);
                data.extend_from_slice(&100_u16.to_le_bytes());
            },
        }
    }

    fn synthetic_payload(
        discriminator: [u8; 8],
        fields: &[super::PumpFunBorshFieldDescriptor],
    ) -> std::vec::Vec<u8> {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&discriminator);
        for field in fields {
            append_synthetic_field(&mut data, field.kind);
        }
        return data;
    }

    fn make_instruction_with_raw_data(data: std::vec::Vec<u8>) -> crate::ChainInstructionDto {
        let mut instruction = make_instruction();
        instruction.data_json =
            Some(serde_json::json!(bs58::encode(data).into_string()).to_string());
        return instruction;
    }

    #[test]
    fn pump_fun_all_idl_instruction_discriminators_decode_synthetically() {
        let decoder = crate::PumpFunDecoder::new();
        let mut transaction = make_transaction();
        transaction.transaction_json = serde_json::json!({
            "slot": 777001,
            "meta": { "logMessages": ["Program log: Instruction: Synthetic"] },
            "transaction": { "message": { "instructions": [] } }
        })
        .to_string();
        let cases: &[(&str, [u8; 8])] = &[
            ("add_quote_mint", [111, 121, 21, 56, 40, 24, 94, 209]),
            ("admin_set_creator", [69, 25, 171, 142, 57, 239, 13, 4]),
            ("admin_set_idl_authority", [8, 217, 96, 231, 144, 104, 192, 5]),
            ("admin_update_token_incentives", [209, 11, 115, 87, 213, 23, 124, 204]),
            ("buy", [102, 6, 61, 18, 1, 218, 235, 234]),
            ("buy_exact_quote_in_v2", [194, 171, 28, 70, 104, 77, 91, 47]),
            ("buy_exact_sol_in", [56, 252, 116, 8, 158, 223, 205, 95]),
            ("buy_v2", [184, 23, 238, 97, 103, 197, 211, 61]),
            ("claim_cashback", [37, 58, 35, 126, 190, 53, 228, 197]),
            ("claim_cashback_v2", [122, 243, 204, 65, 94, 116, 29, 55]),
            ("claim_token_incentives", [16, 4, 71, 28, 204, 1, 40, 27]),
            ("close_user_volume_accumulator", [249, 69, 164, 218, 150, 103, 84, 138]),
            ("collect_creator_fee", [20, 22, 86, 123, 198, 28, 219, 132]),
            ("collect_creator_fee_v2", [207, 17, 138, 242, 4, 34, 19, 56]),
            ("create", [24, 30, 200, 40, 5, 28, 7, 119]),
            ("create_v2", [214, 144, 76, 236, 95, 139, 49, 180]),
            ("distribute_creator_fees", [165, 114, 103, 0, 121, 206, 247, 81]),
            ("distribute_creator_fees_v2", [255, 203, 19, 79, 244, 68, 8, 159]),
            ("extend_account", [234, 102, 194, 203, 150, 72, 62, 229]),
            ("get_minimum_distributable_fee", [117, 225, 127, 202, 134, 95, 68, 35]),
            ("init_user_volume_accumulator", [94, 6, 202, 115, 255, 96, 232, 183]),
            ("initialize", [175, 175, 109, 31, 13, 152, 155, 237]),
            ("migrate", [155, 234, 231, 146, 236, 158, 162, 30]),
            ("migrate_bonding_curve_creator", [87, 124, 52, 191, 52, 38, 214, 232]),
            ("migrate_v2", [187, 203, 18, 31, 206, 237, 254, 41]),
            ("remove_quote_mint", [177, 65, 223, 38, 88, 209, 158, 155]),
            ("sell", [51, 230, 133, 164, 1, 127, 131, 173]),
            ("sell_v2", [93, 246, 130, 60, 231, 233, 64, 178]),
            ("set_creator", [254, 148, 255, 112, 207, 142, 170, 165]),
            ("set_mayhem_virtual_params", [61, 169, 188, 191, 153, 149, 42, 97]),
            ("set_metaplex_creator", [138, 96, 174, 217, 48, 85, 197, 246]),
            ("set_params", [27, 234, 178, 52, 147, 2, 187, 141]),
            ("set_reserved_fee_recipients", [111, 172, 162, 232, 114, 89, 213, 142]),
            ("set_virtual_quote_reserves", [101, 135, 191, 104, 9, 88, 20, 96]),
            ("sync_user_volume_accumulator", [86, 31, 192, 87, 163, 87, 79, 238]),
            ("toggle_cashback_enabled", [115, 103, 224, 255, 189, 89, 86, 195]),
            ("toggle_create_v2", [28, 255, 230, 240, 172, 107, 203, 171]),
            ("toggle_mayhem_mode", [1, 9, 111, 208, 100, 31, 255, 163]),
            ("update_buyback_config", [251, 224, 171, 146, 160, 26, 113, 233]),
            ("update_global_authority", [227, 181, 74, 196, 208, 21, 97, 213]),
        ];
        for (instruction_name, discriminator) in cases {
            let fields = match super::pump_fun_instruction_arg_fields(instruction_name) {
                Some(fields) => fields,
                None => &[],
            };
            let instruction =
                make_instruction_with_raw_data(synthetic_payload(*discriminator, fields));
            let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
            let decoded = match decoded_result {
                Ok(decoded) => decoded,
                Err(error) => {
                    panic!("{} synthetic decode must succeed: {}", instruction_name, error)
                },
            };
            assert_eq!(decoded.len(), 1, "{} must decode to one local event", instruction_name);
        }
    }

    #[test]
    fn pump_fun_all_idl_anchor_events_decode_synthetically_from_self_cpi() {
        let decoder = crate::PumpFunDecoder::new();
        let transaction = make_transaction();
        let cases: &[(&str, [u8; 8])] = &[
            ("admin_set_creator_event", [64, 69, 192, 104, 29, 30, 25, 107]),
            ("admin_set_idl_authority_event", [245, 59, 70, 34, 75, 185, 109, 92]),
            ("admin_update_token_incentives_event", [147, 250, 108, 120, 247, 29, 67, 222]),
            ("claim_cashback_event", [226, 214, 246, 33, 7, 242, 147, 229]),
            ("claim_token_incentives_event", [79, 172, 246, 49, 205, 91, 206, 232]),
            ("close_user_volume_accumulator_event", [146, 159, 189, 172, 146, 88, 56, 244]),
            ("collect_creator_fee_event", [122, 2, 127, 1, 14, 191, 12, 175]),
            ("complete_event", [95, 114, 97, 156, 212, 46, 152, 8]),
            ("complete_pump_amm_migration_event", [189, 233, 93, 185, 92, 148, 234, 148]),
            ("create_event", [27, 114, 169, 77, 222, 235, 99, 118]),
            ("distribute_creator_fees_event", [165, 55, 129, 112, 4, 179, 202, 40]),
            ("extend_account_event", [97, 97, 215, 144, 93, 146, 22, 124]),
            ("init_user_volume_accumulator_event", [134, 36, 13, 72, 232, 101, 130, 216]),
            ("migrate_bonding_curve_creator_event", [155, 167, 104, 220, 213, 108, 243, 3]),
            ("minimum_distributable_fee_event", [168, 216, 132, 239, 235, 182, 49, 52]),
            ("reserved_fee_recipients_event", [43, 188, 250, 18, 221, 75, 187, 95]),
            ("set_creator_event", [237, 52, 123, 37, 245, 251, 72, 210]),
            ("set_metaplex_creator_event", [142, 203, 6, 32, 127, 105, 191, 162]),
            ("set_params_event", [223, 195, 159, 246, 62, 48, 143, 131]),
            ("sync_user_volume_accumulator_event", [197, 122, 167, 124, 116, 81, 91, 255]),
            ("trade_event", [189, 219, 127, 211, 78, 230, 97, 238]),
            ("update_global_authority_event", [182, 195, 137, 42, 35, 206, 207, 247]),
            ("update_mayhem_virtual_params_event", [117, 123, 228, 182, 161, 168, 220, 214]),
        ];
        for (event_name, discriminator) in cases {
            let descriptor = match super::pump_fun_anchor_event_descriptor(*discriminator) {
                Some(descriptor) => descriptor,
                None => panic!("missing descriptor for {}", event_name),
            };
            let event_data = synthetic_payload(*discriminator, descriptor.fields);
            let mut self_cpi_data = std::vec::Vec::new();
            self_cpi_data.extend_from_slice(&super::PUMP_FUN_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR);
            self_cpi_data.extend_from_slice(&event_data);
            let instruction = make_instruction_with_raw_data(self_cpi_data);
            let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
            let decoded = match decoded_result {
                Ok(decoded) => decoded,
                Err(error) => {
                    panic!("{} synthetic anchor event decode must succeed: {}", event_name, error)
                },
            };
            assert_eq!(decoded.len(), 1, "{} must decode to one local event", event_name);
            match &decoded[0] {
                crate::PumpFunDecodedEvent::InstructionAudit(event) => {
                    assert_eq!(event.event_kind, format!("pump_fun.{}", event_name));
                    assert_eq!(event.instruction_name, *event_name);
                    assert_eq!(
                        event.payload_json.get("anchorEventAuditOnly"),
                        Some(&serde_json::Value::Bool(true))
                    );
                },
                crate::PumpFunDecodedEvent::CreateV2Token(_) => {
                    panic!("unexpected create_v2 token event")
                },
                crate::PumpFunDecodedEvent::BuyTrade(_) => panic!("unexpected buy trade event"),
                crate::PumpFunDecodedEvent::SellTrade(_) => panic!("unexpected sell trade event"),
            }
        }
    }
}
