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

//! Meteora DAMM v1 transaction decoder.

const DAMM_V1_DISCRIMINATOR_INITIALIZE_POOL: [u8; 8] =
    [0x5f, 0xb4, 0x0a, 0xac, 0x54, 0xae, 0xe8, 0x28];
const DAMM_V1_DISCRIMINATOR_INITIALIZE_POOL_WITH_CONFIG_LEGACY: [u8; 8] =
    [0x49, 0xfe, 0x76, 0xf3, 0xab, 0xc4, 0x4c, 0xd0];
const DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG: [u8; 8] =
    [0x07, 0xa6, 0x8a, 0xab, 0xce, 0xab, 0xec, 0xf4];
const DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG2: [u8; 8] =
    [0x30, 0x95, 0xdc, 0x82, 0x3d, 0x0b, 0x09, 0xb2];
const DAMM_V1_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_CP_POOL: [u8; 8] =
    [0x91, 0x18, 0xac, 0xc2, 0xdb, 0x7d, 0x03, 0xbe];
const DAMM_V1_DISCRIMINATOR_SWAP: [u8; 8] = [0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8];
const DAMM_V1_DISCRIMINATOR_ADD_BALANCE_LIQUIDITY: [u8; 8] =
    [0xa8, 0xe3, 0x32, 0x3e, 0xbd, 0xab, 0x54, 0xb0];
const DAMM_V1_DISCRIMINATOR_ADD_IMBALANCE_LIQUIDITY: [u8; 8] =
    [0x4f, 0x23, 0x7a, 0x54, 0xad, 0x0f, 0x5d, 0xbf];
const DAMM_V1_DISCRIMINATOR_BOOTSTRAP_LIQUIDITY: [u8; 8] =
    [0x04, 0xe4, 0xd7, 0x47, 0xe1, 0xfd, 0x77, 0xce];
const DAMM_V1_DISCRIMINATOR_REMOVE_BALANCE_LIQUIDITY: [u8; 8] =
    [0x85, 0x6d, 0x2c, 0xb3, 0x38, 0xee, 0x72, 0x21];
const DAMM_V1_DISCRIMINATOR_REMOVE_LIQUIDITY_SINGLE_SIDE: [u8; 8] =
    [0x54, 0x54, 0xb1, 0x42, 0xfe, 0xb9, 0x0a, 0xfb];
const DAMM_V1_DISCRIMINATOR_CLAIM_FEE: [u8; 8] = [0xa9, 0x20, 0x4f, 0x89, 0x88, 0xe8, 0x46, 0x89];
const DAMM_V1_DISCRIMINATOR_CREATE_LOCK_ESCROW: [u8; 8] =
    [0x36, 0x57, 0xa5, 0x13, 0x45, 0xe3, 0xda, 0xe0];
const DAMM_V1_DISCRIMINATOR_LOCK: [u8; 8] = [0x15, 0x13, 0xd0, 0x2b, 0xed, 0x3e, 0xff, 0x57];

const DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONED_POOL: [u8; 8] =
    [0x4d, 0x55, 0xb2, 0x9d, 0x32, 0x30, 0xd4, 0x7e];
const DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_POOL: [u8; 8] =
    [0x76, 0xad, 0x29, 0x9d, 0xad, 0x48, 0x61, 0x67];
const DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_POOL_WITH_FEE_TIER: [u8; 8] =
    [0x06, 0x87, 0x44, 0x93, 0xe5, 0x52, 0xa9, 0x71];
const DAMM_V1_DISCRIMINATOR_ENABLE_OR_DISABLE_POOL: [u8; 8] =
    [0x80, 0x06, 0xe4, 0x83, 0x37, 0xa1, 0x34, 0xa9];
const DAMM_V1_DISCRIMINATOR_SET_POOL_FEES: [u8; 8] =
    [0x66, 0x2c, 0x9e, 0x36, 0xcd, 0x25, 0x7e, 0x4e];
const DAMM_V1_DISCRIMINATOR_OVERRIDE_CURVE_PARAM: [u8; 8] =
    [0x62, 0x56, 0xcc, 0x33, 0x5e, 0x47, 0x45, 0xbb];
const DAMM_V1_DISCRIMINATOR_GET_POOL_INFO: [u8; 8] =
    [0x09, 0x30, 0xdc, 0x65, 0x16, 0xf0, 0x4e, 0xc8];
const DAMM_V1_DISCRIMINATOR_CREATE_MINT_METADATA: [u8; 8] =
    [0x0d, 0x46, 0xa8, 0x29, 0xfa, 0x64, 0x94, 0x5a];
const DAMM_V1_DISCRIMINATOR_CREATE_CONFIG: [u8; 8] =
    [0xc9, 0xcf, 0xf3, 0x72, 0x4b, 0x6f, 0x2f, 0xbd];
const DAMM_V1_DISCRIMINATOR_CLOSE_CONFIG: [u8; 8] =
    [0x91, 0x09, 0x48, 0x9d, 0x5f, 0x7d, 0x3d, 0x55];
const DAMM_V1_DISCRIMINATOR_UPDATE_ACTIVATION_POINT: [u8; 8] =
    [0x96, 0x3e, 0x7d, 0xdb, 0xab, 0xdc, 0x1a, 0xed];
const DAMM_V1_DISCRIMINATOR_WITHDRAW_PROTOCOL_FEES: [u8; 8] =
    [0x0b, 0x44, 0xa5, 0x62, 0x12, 0xd0, 0x86, 0x49];
const DAMM_V1_DISCRIMINATOR_SET_WHITELISTED_VAULT: [u8; 8] =
    [0x0c, 0x94, 0x5e, 0x2a, 0x37, 0x39, 0x53, 0xf7];
const DAMM_V1_DISCRIMINATOR_PARTNER_CLAIM_FEE: [u8; 8] =
    [0x39, 0x35, 0xb0, 0x1e, 0x7b, 0x46, 0x34, 0x40];
const DAMM_V1_EVENT_DISCRIMINATOR_ADD_LIQUIDITY: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x1f, 0x5e, 0x7d, 0x5a, 0xe3, 0x34, 0x3d, 0xba,
];
const DAMM_V1_EVENT_DISCRIMINATOR_REMOVE_LIQUIDITY: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x74, 0xf4, 0x61, 0xe8, 0x67, 0x1f, 0x98, 0x3a,
];
const DAMM_V1_EVENT_DISCRIMINATOR_BOOTSTRAP_LIQUIDITY: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x79, 0x7f, 0x26, 0x88, 0x5c, 0x37, 0x0e, 0xf7,
];
const DAMM_V1_EVENT_DISCRIMINATOR_SWAP: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x51, 0x6c, 0xe3, 0xbe, 0xcd, 0xd0, 0x0a, 0xc4,
];
const DAMM_V1_EVENT_DISCRIMINATOR_SET_POOL_FEES: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xf5, 0x1a, 0xc6, 0xa4, 0x58, 0x12, 0x4b, 0x09,
];
const DAMM_V1_EVENT_DISCRIMINATOR_POOL_INFO: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xcf, 0x14, 0x57, 0x61, 0xfb, 0xd4, 0xea, 0x2d,
];
const DAMM_V1_EVENT_DISCRIMINATOR_TRANSFER_ADMIN: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xe4, 0xa9, 0x83, 0xf4, 0x3d, 0x38, 0x41, 0xfe,
];
const DAMM_V1_EVENT_DISCRIMINATOR_OVERRIDE_CURVE_PARAM: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xf7, 0x14, 0xa5, 0xf8, 0x4b, 0x05, 0x36, 0xf6,
];
const DAMM_V1_EVENT_DISCRIMINATOR_POOL_CREATED: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xca, 0x2c, 0x29, 0x58, 0x68, 0xdc, 0x9d, 0x52,
];
const DAMM_V1_EVENT_DISCRIMINATOR_POOL_ENABLED: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x02, 0x97, 0x12, 0x53, 0xcc, 0x86, 0x5c, 0xbf,
];
const DAMM_V1_EVENT_DISCRIMINATOR_MIGRATE_FEE_ACCOUNT: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xdf, 0xea, 0xe8, 0x1a, 0xfc, 0x69, 0xb4, 0x7d,
];
const DAMM_V1_EVENT_DISCRIMINATOR_CREATE_LOCK_ESCROW: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x4a, 0x5e, 0x6a, 0x8d, 0x31, 0x11, 0x62, 0x6d,
];
const DAMM_V1_EVENT_DISCRIMINATOR_LOCK: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xdc, 0xb7, 0x43, 0xd7, 0x99, 0xcf, 0x38, 0xea,
];
const DAMM_V1_EVENT_DISCRIMINATOR_CLAIM_FEE: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x4b, 0x7a, 0x9a, 0x30, 0x8c, 0x4a, 0x7b, 0xa3,
];
const DAMM_V1_EVENT_DISCRIMINATOR_CREATE_CONFIG: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xc7, 0x98, 0x0a, 0x13, 0x27, 0x27, 0x9d, 0x68,
];
const DAMM_V1_EVENT_DISCRIMINATOR_CLOSE_CONFIG: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xf9, 0xb5, 0x6c, 0x59, 0x04, 0x96, 0x5a, 0xae,
];
const DAMM_V1_EVENT_DISCRIMINATOR_WITHDRAW_PROTOCOL_FEES: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x1e, 0xf0, 0xcf, 0xc4, 0x8b, 0xef, 0x4f, 0x1c,
];
const DAMM_V1_EVENT_DISCRIMINATOR_PARTNER_CLAIM_FEES: [u8; 16] = [
    0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x87, 0x83, 0x0a, 0x5e, 0x77, 0xd1, 0xca, 0x30,
];

/// Decoded Meteora DAMM v1 create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDammV1CreatePoolDecoded {
    /// 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 pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token A mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token B mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional LP mint.
    pub lp_mint: std::option::Option<std::string::String>,
    /// Optional config account.
    pub config_account: std::option::Option<std::string::String>,
    /// Optional creator / payer.
    pub creator: std::option::Option<std::string::String>,
    /// Whether the create path used an explicit config.
    pub used_config: bool,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DAMM v1 swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDammV1SwapDecoded {
    /// 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 relative to normalized base.
    pub trade_side: crate::SwapTradeSide,
    /// Optional pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token A mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token B mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DAMM v1 liquidity event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDammV1LiquidityDecoded {
    /// 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,
    /// Persisted event kind.
    pub event_kind: std::string::String,
    /// Optional pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token A mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token B mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional LP mint.
    pub lp_mint: std::option::Option<std::string::String>,
    /// Optional actor wallet.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional decoded base/token-A amount.
    pub base_amount_raw: std::option::Option<std::string::String>,
    /// Optional decoded quote/token-B amount.
    pub quote_amount_raw: std::option::Option<std::string::String>,
    /// Optional decoded LP amount.
    pub lp_amount_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DAMM v1 fee event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDammV1FeeDecoded {
    /// 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,
    /// Persisted event kind.
    pub event_kind: std::string::String,
    /// Optional pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional LP mint.
    pub lp_mint: std::option::Option<std::string::String>,
    /// Optional actor or fee owner wallet.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional fee amount when the instruction provides an exact claimed amount.
    pub fee_amount_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DAMM v1 pool lifecycle event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDammV1PoolLifecycleDecoded {
    /// 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,
    /// Persisted event kind.
    pub event_kind: std::string::String,
    /// Optional pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token A mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token B mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional LP mint.
    pub lp_mint: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DAMM v1 pool administration event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDammV1PoolAdminDecoded {
    /// 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,
    /// Persisted event kind.
    pub event_kind: std::string::String,
    /// Optional pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional actor wallet.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional administration action.
    pub admin_action: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DAMM v1 event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum MeteoraDammV1DecodedEvent {
    /// Pool creation.
    CreatePool(MeteoraDammV1CreatePoolDecoded),
    /// Swap.
    Swap(MeteoraDammV1SwapDecoded),
    /// Liquidity add/remove event.
    Liquidity(MeteoraDammV1LiquidityDecoded),
    /// Fee claim event.
    Fee(MeteoraDammV1FeeDecoded),
    /// Pool lifecycle event.
    PoolLifecycle(MeteoraDammV1PoolLifecycleDecoded),
    /// Pool administration event.
    PoolAdmin(MeteoraDammV1PoolAdminDecoded),
}

/// Meteora DAMM v1 decoder.
#[derive(Debug, Clone, Default)]
pub struct MeteoraDammV1Decoder;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MeteoraDammV1InstructionKind {
    InitializePermissionedPool,
    InitializePermissionlessPool,
    InitializePermissionlessPoolWithFeeTier,
    CreatePoolLegacy,
    CreatePoolWithConfigLegacy,
    InitializePermissionlessConstantProductPoolWithConfig,
    InitializePermissionlessConstantProductPoolWithConfig2,
    InitializeCustomizablePermissionlessConstantProductPool,
    Swap,
    AddBalanceLiquidity,
    AddImbalanceLiquidity,
    BootstrapLiquidity,
    RemoveBalanceLiquidity,
    RemoveLiquiditySingleSide,
    ClaimFee,
    CreateLockEscrow,
    Lock,
    EnableOrDisablePool,
    SetPoolFees,
    OverrideCurveParam,
    GetPoolInfo,
    CreateMintMetadata,
    CreateConfig,
    CloseConfig,
    UpdateActivationPoint,
    WithdrawProtocolFees,
    SetWhitelistedVault,
    PartnerClaimFee,
    AddLiquidityEvent,
    RemoveLiquidityEvent,
    BootstrapLiquidityEvent,
    SwapEvent,
    SetPoolFeesEvent,
    PoolInfoEvent,
    TransferAdminEvent,
    OverrideCurveParamEvent,
    PoolCreatedEvent,
    PoolEnabledEvent,
    MigrateFeeAccountEvent,
    CreateLockEscrowEvent,
    LockEvent,
    ClaimFeeEvent,
    CreateConfigEvent,
    CloseConfigEvent,
    WithdrawProtocolFeesEvent,
    PartnerClaimFeesEvent,
    Unknown,
}

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

    /// Decodes one projected transaction into zero or more Meteora DAMM v1 events.
    pub fn decode_transaction(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::MeteoraDammV1DecodedEvent>, 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
                )));
            },
        };
        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 mut decoded_events = std::vec::Vec::new();
        for instruction in instructions {
            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::METEORA_DAMM_V1_PROGRAM_ID {
                continue;
            }
            if instruction.id.is_none() {
                continue;
            }
            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 parsed_json_result = parse_optional_parsed_json(instruction.parsed_json.as_ref());
            let parsed_json = match parsed_json_result {
                Ok(parsed_json) => parsed_json,
                Err(error) => return Err(error),
            };
            let instruction_data_result =
                decode_instruction_data_json(instruction.data_json.as_ref());
            let instruction_data = match instruction_data_result {
                Ok(instruction_data) => instruction_data,
                Err(error) => return Err(error),
            };
            if instruction.parent_instruction_id.is_some() && instruction_data.is_none() {
                continue;
            }
            let instruction_kind = classify_instruction_kind(
                parsed_json.as_ref(),
                instruction_data.as_deref(),
                &log_messages,
            );
            if is_create_pool_kind(instruction_kind) {
                let event = build_create_pool_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                    instruction_kind,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::CreatePool(event));
                continue;
            }
            if instruction_kind == MeteoraDammV1InstructionKind::Swap {
                let event_result = build_swap_event(
                    transaction_id,
                    transaction,
                    instructions,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                );
                let event = match event_result {
                    Ok(event) => event,
                    Err(error) => return Err(error),
                };
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::Swap(event));
                continue;
            }
            if is_liquidity_kind(instruction_kind) {
                let event = build_liquidity_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                    instruction_kind,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::Liquidity(event));
                continue;
            }
            if instruction_kind == MeteoraDammV1InstructionKind::ClaimFee {
                let event = build_claim_fee_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::Fee(event));
                continue;
            }
            if instruction_kind == MeteoraDammV1InstructionKind::CreateLockEscrow {
                let event = build_create_lock_escrow_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::PoolLifecycle(event));
                continue;
            }
            if instruction_kind == MeteoraDammV1InstructionKind::Lock {
                let event = build_lock_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::PoolAdmin(event));
                continue;
            }
            if is_upstream_git_fee_kind(instruction_kind) {
                let event = build_upstream_git_fee_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                    instruction_kind,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::Fee(event));
                continue;
            }
            if is_upstream_git_pool_lifecycle_kind(instruction_kind) {
                let event = build_upstream_git_pool_lifecycle_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                    instruction_kind,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::PoolLifecycle(event));
                continue;
            }
            if is_upstream_git_pool_admin_kind(instruction_kind) {
                let event = build_upstream_git_pool_admin_event(
                    transaction_id,
                    transaction,
                    instruction,
                    program_id.as_str(),
                    &accounts,
                    parsed_json.as_ref(),
                    instruction_data.as_deref(),
                    &log_messages,
                    instruction_kind,
                );
                decoded_events.push(crate::MeteoraDammV1DecodedEvent::PoolAdmin(event));
            }
        }
        return Ok(decoded_events);
    }
}

#[allow(clippy::too_many_arguments)]
fn build_create_pool_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> crate::MeteoraDammV1CreatePoolDecoded {
    let pool_account = create_pool_pool_account(parsed_json, accounts, instruction_kind);
    let token_a_mint = create_pool_token_a_mint(parsed_json, accounts, instruction_kind);
    let token_b_mint = create_pool_token_b_mint(parsed_json, accounts, instruction_kind);
    let lp_mint = create_pool_lp_mint(accounts, instruction_kind);
    let config_account = create_pool_config_account(parsed_json, accounts, instruction_kind);
    let creator = create_pool_creator(parsed_json, accounts, instruction_kind);
    let used_config = matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy
            | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
            | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2
    );
    let decoded_instruction =
        decoded_create_pool_instruction_payload(instruction_data, instruction_kind);
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": "meteora_damm_v1.create_pool",
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return first_8_bytes_hex(data)),
        "classifiedInstructionKind": instruction_kind_code(instruction_kind),
        "upstreamInstructionName": upstream_git_instruction_name(instruction_kind),
        "proofStatus": proof_status_for_instruction(instruction_kind),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": create_pool_account_roles(accounts, instruction_kind),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "tokenAMint": token_a_mint,
        "tokenBMint": token_b_mint,
        "lpMint": lp_mint,
        "configAccount": config_account,
        "creator": creator,
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1CreatePoolDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        pool_account,
        token_a_mint,
        token_b_mint,
        lp_mint,
        config_account,
        creator,
        used_config,
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_swap_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instructions: &[crate::ChainInstructionDto],
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> Result<crate::MeteoraDammV1SwapDecoded, crate::Error> {
    let pool_account = extract_string_by_candidate_keys(
        parsed_json,
        &["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
    )
    .or_else(|| return extract_account(accounts, 0));
    let token_a_mint = extract_string_by_candidate_keys(
        parsed_json,
        &["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0", "coinMint"],
    );
    let token_b_mint = extract_string_by_candidate_keys(
        parsed_json,
        &["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1", "pcMint"],
    );
    let decoded_amounts_result =
        crate::meteora_swap_amount_inference::infer_meteora_swap_amounts_from_inner_transfers(
            transaction,
            instructions,
            instruction,
            pool_account.as_deref(),
        );
    let inner_transfer_amounts = match decoded_amounts_result {
        Ok(decoded_amounts) => decoded_amounts,
        Err(error) => return Err(error),
    };
    let mut amount_resolution_source = "none";
    let decoded_amounts = match inner_transfer_amounts {
        Some(decoded_amounts) => {
            amount_resolution_source = "flattened_cpi_pool_transfer_window";
            Some(decoded_amounts)
        },
        None => {
            let event_log_amounts_result =
                crate::meteora_swap_amount_inference::infer_meteora_damm_v1_swap_amounts_from_event_log(
                    transaction,
                    accounts,
                    log_messages,
                    pool_account.as_deref(),
                );
            match event_log_amounts_result {
                Ok(event_log_amounts) => match event_log_amounts {
                    Some(event_log_amounts) => {
                        amount_resolution_source = "meteora_damm_v1_swap_event_log";
                        Some(event_log_amounts)
                    },
                    None => None,
                },
                Err(error) => return Err(error),
            }
        },
    };
    let fallback_trade_side = infer_trade_side(log_messages);
    let trade_side = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => decoded_amounts.trade_side,
        None => fallback_trade_side,
    };
    let effective_token_a_mint = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => Some(decoded_amounts.base_token_mint.clone()),
        None => token_a_mint.clone(),
    };
    let effective_token_b_mint = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => Some(decoded_amounts.quote_token_mint.clone()),
        None => token_b_mint.clone(),
    };
    let base_vault = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => decoded_amounts.base_vault_address.clone(),
        None => None,
    };
    let quote_vault = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => decoded_amounts.quote_vault_address.clone(),
        None => None,
    };
    let base_amount_raw = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => serde_json::Value::String(decoded_amounts.base_amount_raw.clone()),
        None => serde_json::Value::Null,
    };
    let quote_amount_raw = match decoded_amounts.as_ref() {
        Some(decoded_amounts) => {
            serde_json::Value::String(decoded_amounts.quote_amount_raw.clone())
        },
        None => serde_json::Value::Null,
    };
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": "swap",
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return first_8_bytes_hex(data)),
        "classifiedInstructionKind": "swap",
        "upstreamInstructionName": "Swap",
        "proofStatus": proof_status_for_instruction(MeteoraDammV1InstructionKind::Swap),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": swap_account_roles(accounts),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "tokenAMint": effective_token_a_mint.clone(),
        "tokenBMint": effective_token_b_mint.clone(),
        "inputTokenAccount": extract_account(accounts, 1),
        "outputTokenAccount": extract_account(accounts, 2),
        "baseVault": base_vault,
        "quoteVault": quote_vault,
        "baseAmountRaw": base_amount_raw,
        "quoteAmountRaw": quote_amount_raw,
        "amountResolutionSource": amount_resolution_source,
        "tradeSide": format!("{:?}", trade_side),
        "decodedInstruction": decoded_swap_instruction_payload(instruction_data)
    });
    return Ok(crate::MeteoraDammV1SwapDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        trade_side,
        pool_account,
        token_a_mint: effective_token_a_mint,
        token_b_mint: effective_token_b_mint,
        payload_json,
    });
}

#[allow(clippy::too_many_arguments)]
fn build_liquidity_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> crate::MeteoraDammV1LiquidityDecoded {
    let event_kind = liquidity_event_kind(instruction_kind).to_string();
    let pool_account = extract_account(accounts, 0);
    let lp_mint = extract_account(accounts, 1);
    let actor_wallet = liquidity_actor_wallet(accounts, instruction_kind);
    let decoded_instruction =
        decoded_liquidity_instruction_payload(instruction_data, instruction_kind);
    let base_amount_raw =
        decoded_instruction_amount_string(&decoded_instruction, "tokenAAmountRaw");
    let quote_amount_raw =
        decoded_instruction_amount_string(&decoded_instruction, "tokenBAmountRaw");
    let lp_amount_raw = decoded_instruction_amount_string(&decoded_instruction, "lpAmountRaw");
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return first_8_bytes_hex(data)),
        "classifiedInstructionKind": instruction_kind_code(instruction_kind),
        "upstreamInstructionName": upstream_git_instruction_name(instruction_kind),
        "proofStatus": proof_status_for_instruction(instruction_kind),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": liquidity_account_roles(accounts, instruction_kind),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "lpMint": lp_mint,
        "actorWallet": actor_wallet,
        "baseAmountRaw": base_amount_raw,
        "quoteAmountRaw": quote_amount_raw,
        "lpAmountRaw": lp_amount_raw,
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1LiquidityDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        token_a_mint: None,
        token_b_mint: None,
        lp_mint,
        actor_wallet,
        base_amount_raw,
        quote_amount_raw,
        lp_amount_raw,
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_claim_fee_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> crate::MeteoraDammV1FeeDecoded {
    let event_kind = "meteora_damm_v1.claim_fee".to_string();
    let pool_account = extract_account(accounts, 0);
    let lp_mint = extract_account(accounts, 1);
    let actor_wallet = extract_account(accounts, 3);
    let decoded_instruction = decoded_claim_fee_instruction_payload(instruction_data);
    let max_amount_raw = decoded_instruction_amount_string(&decoded_instruction, "maxAmountRaw");
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return first_8_bytes_hex(data)),
        "classifiedInstructionKind": "claim_fee",
        "upstreamInstructionName": "ClaimFee",
        "proofStatus": proof_status_for_instruction(MeteoraDammV1InstructionKind::ClaimFee),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": claim_fee_account_roles(accounts),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "lpMint": lp_mint,
        "owner": actor_wallet,
        "actorWallet": actor_wallet,
        "maxAmountRaw": max_amount_raw,
        "feeAmountRaw": serde_json::Value::Null,
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1FeeDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        lp_mint,
        actor_wallet,
        fee_amount_raw: None,
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_create_lock_escrow_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> crate::MeteoraDammV1PoolLifecycleDecoded {
    let event_kind = "meteora_damm_v1.create_lock_escrow".to_string();
    let pool_account = extract_account(accounts, 0);
    let lp_mint = extract_account(accounts, 3);
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return first_8_bytes_hex(data)),
        "classifiedInstructionKind": "create_lock_escrow",
        "upstreamInstructionName": "CreateLockEscrow",
        "proofStatus": proof_status_for_instruction(MeteoraDammV1InstructionKind::CreateLockEscrow),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": create_lock_escrow_account_roles(accounts),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "lpMint": lp_mint,
        "lockEscrow": extract_account(accounts, 1),
        "owner": extract_account(accounts, 2),
        "payer": extract_account(accounts, 4),
        "decodedInstruction": {}
    });
    return crate::MeteoraDammV1PoolLifecycleDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        token_a_mint: None,
        token_b_mint: None,
        lp_mint,
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_lock_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> crate::MeteoraDammV1PoolAdminDecoded {
    let event_kind = "meteora_damm_v1.lock_liquidity".to_string();
    let pool_account = extract_account(accounts, 0);
    let actor_wallet = extract_account(accounts, 3);
    let decoded_instruction = decoded_lock_instruction_payload(instruction_data);
    let max_amount_raw = decoded_instruction_amount_string(&decoded_instruction, "maxAmountRaw");
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return first_8_bytes_hex(data)),
        "classifiedInstructionKind": "lock_liquidity",
        "upstreamInstructionName": "Lock",
        "proofStatus": proof_status_for_instruction(MeteoraDammV1InstructionKind::Lock),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": lock_account_roles(accounts),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "lpMint": extract_account(accounts, 1),
        "lockEscrow": extract_account(accounts, 2),
        "owner": actor_wallet,
        "actorWallet": actor_wallet,
        "adminAction": "lock_liquidity",
        "maxAmountRaw": max_amount_raw,
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1PoolAdminDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        actor_wallet,
        admin_action: Some("lock_liquidity".to_string()),
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_upstream_git_fee_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> crate::MeteoraDammV1FeeDecoded {
    let event_kind = fee_event_kind(instruction_kind).to_string();
    let pool_account = upstream_git_pool_account(accounts, instruction_data, instruction_kind);
    let lp_mint = upstream_git_lp_mint(accounts, instruction_kind);
    let actor_wallet = upstream_git_actor_wallet(accounts, instruction_data, instruction_kind);
    let decoded_instruction =
        decoded_upstream_git_instruction_payload(instruction_data, instruction_kind);
    let fee_amount_raw = decoded_instruction_amount_string(&decoded_instruction, "feeAmountRaw")
        .or_else(|| {
            return decoded_instruction_amount_string(&decoded_instruction, "protocolAFeeRaw");
        })
        .or_else(|| return decoded_instruction_amount_string(&decoded_instruction, "feeARaw"))
        .or_else(|| return decoded_instruction_amount_string(&decoded_instruction, "amountRaw"));
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return discriminator_hex_for_kind(data, instruction_kind)),
        "classifiedInstructionKind": instruction_kind_code(instruction_kind),
        "upstreamInstructionName": upstream_git_instruction_name(instruction_kind),
        "proofStatus": proof_status_for_instruction(instruction_kind),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": upstream_git_account_roles(accounts, instruction_kind),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "lpMint": lp_mint,
        "actorWallet": actor_wallet,
        "feeAmountRaw": fee_amount_raw,
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1FeeDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        lp_mint,
        actor_wallet,
        fee_amount_raw,
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_upstream_git_pool_lifecycle_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> crate::MeteoraDammV1PoolLifecycleDecoded {
    let event_kind = pool_lifecycle_event_kind(instruction_kind).to_string();
    let decoded_instruction =
        decoded_upstream_git_instruction_payload(instruction_data, instruction_kind);
    let pool_account = upstream_git_pool_account(accounts, instruction_data, instruction_kind);
    let token_a_mint = upstream_git_token_a_mint(accounts, instruction_data, instruction_kind);
    let token_b_mint = upstream_git_token_b_mint(accounts, instruction_data, instruction_kind);
    let lp_mint = upstream_git_lp_mint(accounts, instruction_kind);
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return discriminator_hex_for_kind(data, instruction_kind)),
        "classifiedInstructionKind": instruction_kind_code(instruction_kind),
        "upstreamInstructionName": upstream_git_instruction_name(instruction_kind),
        "proofStatus": proof_status_for_instruction(instruction_kind),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": upstream_git_account_roles(accounts, instruction_kind),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "tokenAMint": token_a_mint,
        "tokenBMint": token_b_mint,
        "lpMint": lp_mint,
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1PoolLifecycleDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        token_a_mint,
        token_b_mint,
        lp_mint,
        payload_json,
    };
}

#[allow(clippy::too_many_arguments)]
fn build_upstream_git_pool_admin_event(
    transaction_id: i64,
    transaction: &crate::ChainTransactionDto,
    instruction: &crate::ChainInstructionDto,
    program_id: &str,
    accounts: &[std::string::String],
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> crate::MeteoraDammV1PoolAdminDecoded {
    let event_kind = pool_admin_event_kind(instruction_kind).to_string();
    let pool_account = upstream_git_pool_account(accounts, instruction_data, instruction_kind);
    let actor_wallet = upstream_git_actor_wallet(accounts, instruction_data, instruction_kind);
    let admin_action = Some(instruction_kind_code(instruction_kind).to_string());
    let decoded_instruction =
        decoded_upstream_git_instruction_payload(instruction_data, instruction_kind);
    let payload_json = serde_json::json!({
        "decoder": "meteora_damm_v1",
        "eventKind": event_kind,
        "dataDiscriminatorHex": instruction_data
            .and_then(|data| return discriminator_hex_for_kind(data, instruction_kind)),
        "classifiedInstructionKind": instruction_kind_code(instruction_kind),
        "upstreamInstructionName": upstream_git_instruction_name(instruction_kind),
        "proofStatus": proof_status_for_instruction(instruction_kind),
        "signature": transaction.signature,
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "innerInstruction": instruction.inner_instruction_index.is_some(),
        "accounts": accounts,
        "accountRoles": upstream_git_account_roles(accounts, instruction_kind),
        "parsed": parsed_json,
        "logMessages": log_messages,
        "poolAccount": pool_account,
        "actorWallet": actor_wallet,
        "adminAction": admin_action.clone(),
        "decodedInstruction": decoded_instruction
    });
    return crate::MeteoraDammV1PoolAdminDecoded {
        transaction_id,
        instruction_id: instruction_id_or_zero(instruction),
        signature: transaction.signature.clone(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account,
        actor_wallet,
        admin_action,
        payload_json,
    };
}

fn instruction_id_or_zero(instruction: &crate::ChainInstructionDto) -> i64 {
    match instruction.id {
        Some(instruction_id) => return instruction_id,
        None => return 0,
    }
}

fn classify_instruction_kind(
    parsed_json: std::option::Option<&serde_json::Value>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> MeteoraDammV1InstructionKind {
    let data_kind = classify_instruction_kind_from_data(instruction_data);
    if data_kind != MeteoraDammV1InstructionKind::Unknown {
        return data_kind;
    }
    if instruction_data_has_full_discriminator(instruction_data) {
        return MeteoraDammV1InstructionKind::Unknown;
    }
    let parsed_instruction_name = extract_string_by_candidate_keys(
        parsed_json,
        &["instruction", "instructionName", "type", "name"],
    );
    if let Some(parsed_instruction_name) = parsed_instruction_name {
        let normalized = normalize_text(parsed_instruction_name.as_str());
        if normalized.contains("initializepermissionlessconstantproductpoolwithconfig2") {
            return MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2;
        }
        if normalized.contains("initializepermissionlessconstantproductpoolwithconfig") {
            return MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig;
        }
        if normalized.contains("initializecustomizablepermissionlessconstantproductpool") {
            return MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool;
        }
        if normalized.contains("initializepoolwithconfig") {
            return MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy;
        }
        if normalized.contains("initializepool") {
            return MeteoraDammV1InstructionKind::CreatePoolLegacy;
        }
        if normalized == "swap" {
            return MeteoraDammV1InstructionKind::Swap;
        }
    }
    if value_contains_any_key(parsed_json, &["poolConfig", "ammConfig", "tradeFeeConfig"]) {
        return MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy;
    }
    if log_messages_contain_keyword(log_messages, "initialize_pool_with_config")
        || log_messages_contain_keyword(log_messages, "initializepoolwithconfig")
    {
        return MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy;
    }
    if log_messages_contain_keyword(log_messages, "initialize_pool")
        || log_messages_contain_keyword(log_messages, "initializepool")
    {
        return MeteoraDammV1InstructionKind::CreatePoolLegacy;
    }
    if log_messages_contain_keyword(log_messages, "swap") {
        return MeteoraDammV1InstructionKind::Swap;
    }
    return MeteoraDammV1InstructionKind::Unknown;
}

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

fn classify_instruction_kind_from_data(
    instruction_data: std::option::Option<&[u8]>,
) -> MeteoraDammV1InstructionKind {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return MeteoraDammV1InstructionKind::Unknown,
    };
    if instruction_data.len() < 8 {
        return MeteoraDammV1InstructionKind::Unknown;
    }
    if instruction_data.len() >= 16 {
        let event_discriminator = first_16_bytes(instruction_data);
        let event_kind = classify_event_kind_from_data(event_discriminator);
        if event_kind != MeteoraDammV1InstructionKind::Unknown {
            return event_kind;
        }
    }
    let discriminator = first_8_bytes(instruction_data);
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_POOL_WITH_CONFIG_LEGACY {
        return MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_POOL {
        return MeteoraDammV1InstructionKind::CreatePoolLegacy;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG {
        return MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG2 {
        return MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_CP_POOL {
        return MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_SWAP {
        return MeteoraDammV1InstructionKind::Swap;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_ADD_BALANCE_LIQUIDITY {
        return MeteoraDammV1InstructionKind::AddBalanceLiquidity;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_ADD_IMBALANCE_LIQUIDITY {
        return MeteoraDammV1InstructionKind::AddImbalanceLiquidity;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_BOOTSTRAP_LIQUIDITY {
        return MeteoraDammV1InstructionKind::BootstrapLiquidity;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_REMOVE_BALANCE_LIQUIDITY {
        return MeteoraDammV1InstructionKind::RemoveBalanceLiquidity;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_REMOVE_LIQUIDITY_SINGLE_SIDE {
        return MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_CLAIM_FEE {
        return MeteoraDammV1InstructionKind::ClaimFee;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_CREATE_LOCK_ESCROW {
        return MeteoraDammV1InstructionKind::CreateLockEscrow;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_LOCK {
        return MeteoraDammV1InstructionKind::Lock;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONED_POOL {
        return MeteoraDammV1InstructionKind::InitializePermissionedPool;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_POOL {
        return MeteoraDammV1InstructionKind::InitializePermissionlessPool;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_POOL_WITH_FEE_TIER {
        return MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_ENABLE_OR_DISABLE_POOL {
        return MeteoraDammV1InstructionKind::EnableOrDisablePool;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_SET_POOL_FEES {
        return MeteoraDammV1InstructionKind::SetPoolFees;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_OVERRIDE_CURVE_PARAM {
        return MeteoraDammV1InstructionKind::OverrideCurveParam;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_GET_POOL_INFO {
        return MeteoraDammV1InstructionKind::GetPoolInfo;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_CREATE_MINT_METADATA {
        return MeteoraDammV1InstructionKind::CreateMintMetadata;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_CREATE_CONFIG {
        return MeteoraDammV1InstructionKind::CreateConfig;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_CLOSE_CONFIG {
        return MeteoraDammV1InstructionKind::CloseConfig;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_UPDATE_ACTIVATION_POINT {
        return MeteoraDammV1InstructionKind::UpdateActivationPoint;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_WITHDRAW_PROTOCOL_FEES {
        return MeteoraDammV1InstructionKind::WithdrawProtocolFees;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_SET_WHITELISTED_VAULT {
        return MeteoraDammV1InstructionKind::SetWhitelistedVault;
    }
    if discriminator == DAMM_V1_DISCRIMINATOR_PARTNER_CLAIM_FEE {
        return MeteoraDammV1InstructionKind::PartnerClaimFee;
    }
    return MeteoraDammV1InstructionKind::Unknown;
}

fn classify_event_kind_from_data(discriminator: [u8; 16]) -> MeteoraDammV1InstructionKind {
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_ADD_LIQUIDITY {
        return MeteoraDammV1InstructionKind::AddLiquidityEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_REMOVE_LIQUIDITY {
        return MeteoraDammV1InstructionKind::RemoveLiquidityEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_BOOTSTRAP_LIQUIDITY {
        return MeteoraDammV1InstructionKind::BootstrapLiquidityEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_SWAP {
        return MeteoraDammV1InstructionKind::SwapEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_SET_POOL_FEES {
        return MeteoraDammV1InstructionKind::SetPoolFeesEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_POOL_INFO {
        return MeteoraDammV1InstructionKind::PoolInfoEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_TRANSFER_ADMIN {
        return MeteoraDammV1InstructionKind::TransferAdminEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_OVERRIDE_CURVE_PARAM {
        return MeteoraDammV1InstructionKind::OverrideCurveParamEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_POOL_CREATED {
        return MeteoraDammV1InstructionKind::PoolCreatedEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_POOL_ENABLED {
        return MeteoraDammV1InstructionKind::PoolEnabledEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_MIGRATE_FEE_ACCOUNT {
        return MeteoraDammV1InstructionKind::MigrateFeeAccountEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_CREATE_LOCK_ESCROW {
        return MeteoraDammV1InstructionKind::CreateLockEscrowEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_LOCK {
        return MeteoraDammV1InstructionKind::LockEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_CLAIM_FEE {
        return MeteoraDammV1InstructionKind::ClaimFeeEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_CREATE_CONFIG {
        return MeteoraDammV1InstructionKind::CreateConfigEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_CLOSE_CONFIG {
        return MeteoraDammV1InstructionKind::CloseConfigEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_WITHDRAW_PROTOCOL_FEES {
        return MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent;
    }
    if discriminator == DAMM_V1_EVENT_DISCRIMINATOR_PARTNER_CLAIM_FEES {
        return MeteoraDammV1InstructionKind::PartnerClaimFeesEvent;
    }
    return MeteoraDammV1InstructionKind::Unknown;
}

fn is_create_pool_kind(instruction_kind: MeteoraDammV1InstructionKind) -> bool {
    return matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::InitializePermissionedPool
            | MeteoraDammV1InstructionKind::InitializePermissionlessPool
            | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier
            | MeteoraDammV1InstructionKind::CreatePoolLegacy
            | MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy
            | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
            | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2
            | MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool
            | MeteoraDammV1InstructionKind::PoolCreatedEvent
    );
}

fn is_liquidity_kind(instruction_kind: MeteoraDammV1InstructionKind) -> bool {
    return matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::AddBalanceLiquidity
            | MeteoraDammV1InstructionKind::AddImbalanceLiquidity
            | MeteoraDammV1InstructionKind::BootstrapLiquidity
            | MeteoraDammV1InstructionKind::RemoveBalanceLiquidity
            | MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide
            | MeteoraDammV1InstructionKind::AddLiquidityEvent
            | MeteoraDammV1InstructionKind::RemoveLiquidityEvent
            | MeteoraDammV1InstructionKind::BootstrapLiquidityEvent
    );
}

fn liquidity_event_kind(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::AddBalanceLiquidity
        | MeteoraDammV1InstructionKind::AddImbalanceLiquidity
        | MeteoraDammV1InstructionKind::BootstrapLiquidity
        | MeteoraDammV1InstructionKind::AddLiquidityEvent
        | MeteoraDammV1InstructionKind::BootstrapLiquidityEvent => {
            return "meteora_damm_v1.add_liquidity";
        },
        MeteoraDammV1InstructionKind::RemoveBalanceLiquidity
        | MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide
        | MeteoraDammV1InstructionKind::RemoveLiquidityEvent => {
            return "meteora_damm_v1.remove_liquidity";
        },
        _ => return "meteora_damm_v1.liquidity_unknown",
    }
}

fn is_upstream_git_fee_kind(instruction_kind: MeteoraDammV1InstructionKind) -> bool {
    return matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::WithdrawProtocolFees
            | MeteoraDammV1InstructionKind::PartnerClaimFee
            | MeteoraDammV1InstructionKind::ClaimFeeEvent
            | MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent
            | MeteoraDammV1InstructionKind::PartnerClaimFeesEvent
    );
}

fn is_upstream_git_pool_lifecycle_kind(instruction_kind: MeteoraDammV1InstructionKind) -> bool {
    return matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::CreateMintMetadata
            | MeteoraDammV1InstructionKind::CreateLockEscrowEvent
            | MeteoraDammV1InstructionKind::PoolCreatedEvent
    );
}

fn is_upstream_git_pool_admin_kind(instruction_kind: MeteoraDammV1InstructionKind) -> bool {
    return matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::EnableOrDisablePool
            | MeteoraDammV1InstructionKind::SetPoolFees
            | MeteoraDammV1InstructionKind::OverrideCurveParam
            | MeteoraDammV1InstructionKind::GetPoolInfo
            | MeteoraDammV1InstructionKind::CreateConfig
            | MeteoraDammV1InstructionKind::CloseConfig
            | MeteoraDammV1InstructionKind::UpdateActivationPoint
            | MeteoraDammV1InstructionKind::SetWhitelistedVault
            | MeteoraDammV1InstructionKind::SwapEvent
            | MeteoraDammV1InstructionKind::SetPoolFeesEvent
            | MeteoraDammV1InstructionKind::PoolInfoEvent
            | MeteoraDammV1InstructionKind::TransferAdminEvent
            | MeteoraDammV1InstructionKind::OverrideCurveParamEvent
            | MeteoraDammV1InstructionKind::PoolEnabledEvent
            | MeteoraDammV1InstructionKind::MigrateFeeAccountEvent
            | MeteoraDammV1InstructionKind::LockEvent
            | MeteoraDammV1InstructionKind::CreateConfigEvent
            | MeteoraDammV1InstructionKind::CloseConfigEvent
    );
}

fn fee_event_kind(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::WithdrawProtocolFees
        | MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent => {
            return "meteora_damm_v1.withdraw_protocol_fees";
        },
        MeteoraDammV1InstructionKind::PartnerClaimFee
        | MeteoraDammV1InstructionKind::PartnerClaimFeesEvent => {
            return "meteora_damm_v1.partner_claim_fee";
        },
        MeteoraDammV1InstructionKind::ClaimFeeEvent => return "meteora_damm_v1.claim_fee_event",
        _ => return "meteora_damm_v1.fee_event",
    }
}

fn pool_lifecycle_event_kind(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::CreateMintMetadata => {
            return "meteora_damm_v1.create_mint_metadata";
        },
        MeteoraDammV1InstructionKind::CreateLockEscrowEvent => {
            return "meteora_damm_v1.create_lock_escrow_event";
        },
        MeteoraDammV1InstructionKind::PoolCreatedEvent => {
            return "meteora_damm_v1.create_pool_event";
        },
        _ => return "meteora_damm_v1.pool_lifecycle_event",
    }
}

fn pool_admin_event_kind(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::EnableOrDisablePool
        | MeteoraDammV1InstructionKind::PoolEnabledEvent => {
            return "meteora_damm_v1.set_pool_enabled";
        },
        MeteoraDammV1InstructionKind::SetPoolFees
        | MeteoraDammV1InstructionKind::SetPoolFeesEvent => {
            return "meteora_damm_v1.set_pool_fees";
        },
        MeteoraDammV1InstructionKind::OverrideCurveParam
        | MeteoraDammV1InstructionKind::OverrideCurveParamEvent => {
            return "meteora_damm_v1.override_curve_param";
        },
        MeteoraDammV1InstructionKind::GetPoolInfo | MeteoraDammV1InstructionKind::PoolInfoEvent => {
            return "meteora_damm_v1.pool_info";
        },
        MeteoraDammV1InstructionKind::CreateConfig
        | MeteoraDammV1InstructionKind::CreateConfigEvent => return "meteora_damm_v1.create_config",
        MeteoraDammV1InstructionKind::CloseConfig
        | MeteoraDammV1InstructionKind::CloseConfigEvent => return "meteora_damm_v1.close_config",
        MeteoraDammV1InstructionKind::UpdateActivationPoint => {
            return "meteora_damm_v1.update_activation_point";
        },
        MeteoraDammV1InstructionKind::SetWhitelistedVault => {
            return "meteora_damm_v1.set_whitelisted_vault";
        },
        MeteoraDammV1InstructionKind::TransferAdminEvent => return "meteora_damm_v1.transfer_admin",
        MeteoraDammV1InstructionKind::MigrateFeeAccountEvent => {
            return "meteora_damm_v1.migrate_fee_account";
        },
        MeteoraDammV1InstructionKind::LockEvent => return "meteora_damm_v1.lock_liquidity_event",
        MeteoraDammV1InstructionKind::SwapEvent => return "meteora_damm_v1.upstream_git_event_swap",
        _ => return "meteora_damm_v1.pool_admin",
    }
}

fn instruction_kind_code(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionedPool => {
            return "initialize_permissioned_pool";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessPool => {
            return "initialize_permissionless_pool";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return "initialize_permissionless_pool_with_fee_tier";
        },
        MeteoraDammV1InstructionKind::CreatePoolLegacy => return "initialize_pool_legacy",
        MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy => {
            return "initialize_pool_with_config_legacy";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig => {
            return "initialize_permissionless_constant_product_pool_with_config";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return "initialize_permissionless_constant_product_pool_with_config2";
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return "initialize_customizable_permissionless_constant_product_pool";
        },
        MeteoraDammV1InstructionKind::Swap => return "swap",
        MeteoraDammV1InstructionKind::AddBalanceLiquidity => return "add_balance_liquidity",
        MeteoraDammV1InstructionKind::AddImbalanceLiquidity => return "add_imbalance_liquidity",
        MeteoraDammV1InstructionKind::BootstrapLiquidity => return "bootstrap_liquidity",
        MeteoraDammV1InstructionKind::RemoveBalanceLiquidity => return "remove_balance_liquidity",
        MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide => {
            return "remove_liquidity_single_side";
        },
        MeteoraDammV1InstructionKind::ClaimFee => return "claim_fee",
        MeteoraDammV1InstructionKind::CreateLockEscrow => return "create_lock_escrow",
        MeteoraDammV1InstructionKind::Lock => return "lock",
        MeteoraDammV1InstructionKind::EnableOrDisablePool => return "enable_or_disable_pool",
        MeteoraDammV1InstructionKind::SetPoolFees => return "set_pool_fees",
        MeteoraDammV1InstructionKind::OverrideCurveParam => return "override_curve_param",
        MeteoraDammV1InstructionKind::GetPoolInfo => return "get_pool_info",
        MeteoraDammV1InstructionKind::CreateMintMetadata => return "create_mint_metadata",
        MeteoraDammV1InstructionKind::CreateConfig => return "create_config",
        MeteoraDammV1InstructionKind::CloseConfig => return "close_config",
        MeteoraDammV1InstructionKind::UpdateActivationPoint => return "update_activation_point",
        MeteoraDammV1InstructionKind::WithdrawProtocolFees => return "withdraw_protocol_fees",
        MeteoraDammV1InstructionKind::SetWhitelistedVault => return "set_whitelisted_vault",
        MeteoraDammV1InstructionKind::PartnerClaimFee => return "partner_claim_fee",
        MeteoraDammV1InstructionKind::AddLiquidityEvent => return "add_liquidity_event",
        MeteoraDammV1InstructionKind::RemoveLiquidityEvent => return "remove_liquidity_event",
        MeteoraDammV1InstructionKind::BootstrapLiquidityEvent => return "bootstrap_liquidity_event",
        MeteoraDammV1InstructionKind::SwapEvent => return "swap_event",
        MeteoraDammV1InstructionKind::SetPoolFeesEvent => return "set_pool_fees_event",
        MeteoraDammV1InstructionKind::PoolInfoEvent => return "pool_info_event",
        MeteoraDammV1InstructionKind::TransferAdminEvent => return "transfer_admin_event",
        MeteoraDammV1InstructionKind::OverrideCurveParamEvent => {
            return "override_curve_param_event";
        },
        MeteoraDammV1InstructionKind::PoolCreatedEvent => return "pool_created_event",
        MeteoraDammV1InstructionKind::PoolEnabledEvent => return "pool_enabled_event",
        MeteoraDammV1InstructionKind::MigrateFeeAccountEvent => return "migrate_fee_account_event",
        MeteoraDammV1InstructionKind::CreateLockEscrowEvent => return "create_lock_escrow_event",
        MeteoraDammV1InstructionKind::LockEvent => return "lock_event",
        MeteoraDammV1InstructionKind::ClaimFeeEvent => return "claim_fee_event",
        MeteoraDammV1InstructionKind::CreateConfigEvent => return "create_config_event",
        MeteoraDammV1InstructionKind::CloseConfigEvent => return "close_config_event",
        MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent => {
            return "withdraw_protocol_fees_event";
        },
        MeteoraDammV1InstructionKind::PartnerClaimFeesEvent => return "partner_claim_fees_event",
        MeteoraDammV1InstructionKind::Unknown => return "unknown",
    }
}

fn upstream_git_instruction_name(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionedPool => {
            return "InitializePermissionedPool";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessPool => {
            return "InitializePermissionlessPool";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return "InitializePermissionlessPoolWithFeeTier";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig => {
            return "InitializePermissionlessConstantProductPoolWithConfig";
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return "InitializePermissionlessConstantProductPoolWithConfig2";
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return "InitializeCustomizablePermissionlessConstantProductPool";
        },
        MeteoraDammV1InstructionKind::AddBalanceLiquidity => return "AddBalanceLiquidity",
        MeteoraDammV1InstructionKind::AddImbalanceLiquidity => return "AddImbalanceLiquidity",
        MeteoraDammV1InstructionKind::BootstrapLiquidity => return "BootstrapLiquidity",
        MeteoraDammV1InstructionKind::RemoveBalanceLiquidity => return "RemoveBalanceLiquidity",
        MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide => {
            return "RemoveLiquiditySingleSide";
        },
        MeteoraDammV1InstructionKind::ClaimFee => return "ClaimFee",
        MeteoraDammV1InstructionKind::CreateLockEscrow => return "CreateLockEscrow",
        MeteoraDammV1InstructionKind::Lock => return "Lock",
        MeteoraDammV1InstructionKind::Swap => return "Swap",
        MeteoraDammV1InstructionKind::CreatePoolLegacy => return "InitializePool",
        MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy => {
            return "InitializePoolWithConfig";
        },
        MeteoraDammV1InstructionKind::EnableOrDisablePool => return "EnableOrDisablePool",
        MeteoraDammV1InstructionKind::SetPoolFees => return "SetPoolFees",
        MeteoraDammV1InstructionKind::OverrideCurveParam => return "OverrideCurveParam",
        MeteoraDammV1InstructionKind::GetPoolInfo => return "GetPoolInfo",
        MeteoraDammV1InstructionKind::CreateMintMetadata => return "CreateMintMetadata",
        MeteoraDammV1InstructionKind::CreateConfig => return "CreateConfig",
        MeteoraDammV1InstructionKind::CloseConfig => return "CloseConfig",
        MeteoraDammV1InstructionKind::UpdateActivationPoint => return "UpdateActivationPoint",
        MeteoraDammV1InstructionKind::WithdrawProtocolFees => return "WithdrawProtocolFees",
        MeteoraDammV1InstructionKind::SetWhitelistedVault => return "SetWhitelistedVault",
        MeteoraDammV1InstructionKind::PartnerClaimFee => return "PartnerClaimFee",
        MeteoraDammV1InstructionKind::AddLiquidityEvent => return "AddLiquidityEvent",
        MeteoraDammV1InstructionKind::RemoveLiquidityEvent => return "RemoveLiquidityEvent",
        MeteoraDammV1InstructionKind::BootstrapLiquidityEvent => return "BootstrapLiquidityEvent",
        MeteoraDammV1InstructionKind::SwapEvent => return "SwapEvent",
        MeteoraDammV1InstructionKind::SetPoolFeesEvent => return "SetPoolFeesEvent",
        MeteoraDammV1InstructionKind::PoolInfoEvent => return "PoolInfoEvent",
        MeteoraDammV1InstructionKind::TransferAdminEvent => return "TransferAdminEvent",
        MeteoraDammV1InstructionKind::OverrideCurveParamEvent => return "OverrideCurveParamEvent",
        MeteoraDammV1InstructionKind::PoolCreatedEvent => return "PoolCreatedEvent",
        MeteoraDammV1InstructionKind::PoolEnabledEvent => return "PoolEnabledEvent",
        MeteoraDammV1InstructionKind::MigrateFeeAccountEvent => return "MigrateFeeAccountEvent",
        MeteoraDammV1InstructionKind::CreateLockEscrowEvent => return "CreateLockEscrowEvent",
        MeteoraDammV1InstructionKind::LockEvent => return "LockEvent",
        MeteoraDammV1InstructionKind::ClaimFeeEvent => return "ClaimFeeEvent",
        MeteoraDammV1InstructionKind::CreateConfigEvent => return "CreateConfigEvent",
        MeteoraDammV1InstructionKind::CloseConfigEvent => return "CloseConfigEvent",
        MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent => {
            return "WithdrawProtocolFeesEvent";
        },
        MeteoraDammV1InstructionKind::PartnerClaimFeesEvent => return "PartnerClaimFeesEvent",
        MeteoraDammV1InstructionKind::Unknown => return "Unknown",
    }
}

fn proof_status_for_instruction(instruction_kind: MeteoraDammV1InstructionKind) -> &'static str {
    match instruction_kind {
        MeteoraDammV1InstructionKind::CreatePoolLegacy
        | MeteoraDammV1InstructionKind::CreatePoolWithConfigLegacy => {
            return "legacy_local_decoder_instruction";
        },
        MeteoraDammV1InstructionKind::Swap
        | MeteoraDammV1InstructionKind::AddBalanceLiquidity
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2
        | MeteoraDammV1InstructionKind::RemoveBalanceLiquidity
        | MeteoraDammV1InstructionKind::ClaimFee
        | MeteoraDammV1InstructionKind::CreateLockEscrow
        | MeteoraDammV1InstructionKind::Lock => {
            return "upstream_git_local_corpus_observed";
        },
        MeteoraDammV1InstructionKind::Unknown => return "unclassified_local_corpus_instruction",
        _ => return "upstream_git_mapped_unverified",
    }
}

fn create_pool_pool_account(
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
    _instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    return extract_string_by_candidate_keys(
        parsed_json,
        &["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
    )
    .or_else(|| return extract_account(accounts, 0));
}

fn create_pool_token_a_mint(
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    let parsed = extract_string_by_candidate_keys(
        parsed_json,
        &["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0", "coinMint"],
    );
    if parsed.is_some() {
        return parsed;
    }
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return extract_account(accounts, 3);
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return extract_account(accounts, 2);
        },
        MeteoraDammV1InstructionKind::InitializePermissionedPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return extract_account(accounts, 2);
        },
        _ => return extract_account(accounts, 1),
    }
}

fn create_pool_token_b_mint(
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    let parsed = extract_string_by_candidate_keys(
        parsed_json,
        &["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1", "pcMint"],
    );
    if parsed.is_some() {
        return parsed;
    }
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return extract_account(accounts, 4);
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return extract_account(accounts, 3);
        },
        MeteoraDammV1InstructionKind::InitializePermissionedPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return extract_account(accounts, 3);
        },
        _ => return extract_account(accounts, 2),
    }
}

fn create_pool_lp_mint(
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return extract_account(accounts, 2);
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool
        | MeteoraDammV1InstructionKind::InitializePermissionedPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return extract_account(accounts, 1);
        },
        _ => return None,
    }
}

fn create_pool_config_account(
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    let parsed = extract_string_by_candidate_keys(
        parsed_json,
        &["config", "poolConfig", "ammConfig", "tradeFeeConfig"],
    );
    if parsed.is_some() {
        return parsed;
    }
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return extract_account(accounts, 1);
        },
        _ => return extract_account(accounts, 3),
    }
}

fn create_pool_creator(
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    let parsed =
        extract_string_by_candidate_keys(parsed_json, &["creator", "payer", "user", "owner"]);
    if parsed.is_some() {
        return parsed;
    }
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return extract_account(accounts, 18);
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return extract_account(accounts, 17);
        },
        MeteoraDammV1InstructionKind::InitializePermissionedPool => {
            return extract_account(accounts, 15);
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return extract_account(accounts, 17);
        },
        _ => return extract_account(accounts, 4),
    }
}

fn liquidity_actor_wallet(
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide => {
            return extract_account(accounts, 12);
        },
        _ => return extract_account(accounts, 13),
    }
}

fn decoded_create_pool_instruction_payload(
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> serde_json::Value {
    let data = match instruction_data {
        Some(data) => data,
        None => return serde_json::Value::Null,
    };
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return serde_json::json!({
                "tokenAAmountRaw": u64_le_string_at(data, 8),
                "tokenBAmountRaw": u64_le_string_at(data, 16)
            });
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return serde_json::json!({
                "tokenAAmountRaw": u64_le_string_at(data, 8),
                "tokenBAmountRaw": u64_le_string_at(data, 16),
                "activationPointRaw": option_u64_le_string_at(data, 24)
            });
        },
        _ => return serde_json::Value::Null,
    }
}

fn decoded_swap_instruction_payload(
    instruction_data: std::option::Option<&[u8]>,
) -> serde_json::Value {
    let data = match instruction_data {
        Some(data) => data,
        None => return serde_json::Value::Null,
    };
    return serde_json::json!({
        "inAmountRaw": u64_le_string_at(data, 8),
        "minimumOutAmountRaw": u64_le_string_at(data, 16)
    });
}

fn decoded_liquidity_instruction_payload(
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> serde_json::Value {
    let data = match instruction_data {
        Some(data) => data,
        None => return serde_json::Value::Null,
    };
    match instruction_kind {
        MeteoraDammV1InstructionKind::AddBalanceLiquidity => {
            return serde_json::json!({
                "lpAmountRaw": u64_le_string_at(data, 8),
                "maximumTokenAAmountRaw": u64_le_string_at(data, 16),
                "maximumTokenBAmountRaw": u64_le_string_at(data, 24)
            });
        },
        MeteoraDammV1InstructionKind::AddImbalanceLiquidity => {
            return serde_json::json!({
                "minimumLpAmountRaw": u64_le_string_at(data, 8),
                "tokenAAmountRaw": u64_le_string_at(data, 16),
                "tokenBAmountRaw": u64_le_string_at(data, 24),
                "lpAmountRaw": serde_json::Value::Null
            });
        },
        MeteoraDammV1InstructionKind::BootstrapLiquidity => {
            return serde_json::json!({
                "tokenAAmountRaw": u64_le_string_at(data, 8),
                "tokenBAmountRaw": u64_le_string_at(data, 16),
                "lpAmountRaw": serde_json::Value::Null
            });
        },
        MeteoraDammV1InstructionKind::RemoveBalanceLiquidity => {
            return serde_json::json!({
                "lpAmountRaw": u64_le_string_at(data, 8),
                "minimumTokenAOutAmountRaw": u64_le_string_at(data, 16),
                "minimumTokenBOutAmountRaw": u64_le_string_at(data, 24),
                "tokenAAmountRaw": serde_json::Value::Null,
                "tokenBAmountRaw": serde_json::Value::Null
            });
        },
        MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide => {
            return serde_json::json!({
                "lpAmountRaw": u64_le_string_at(data, 8),
                "minimumOutAmountRaw": u64_le_string_at(data, 16),
                "tokenAAmountRaw": serde_json::Value::Null,
                "tokenBAmountRaw": serde_json::Value::Null
            });
        },
        _ => return serde_json::Value::Null,
    }
}

fn decoded_claim_fee_instruction_payload(
    instruction_data: std::option::Option<&[u8]>,
) -> serde_json::Value {
    let data = match instruction_data {
        Some(data) => data,
        None => return serde_json::Value::Null,
    };
    return serde_json::json!({
        "maxAmountRaw": u64_le_string_at(data, 8)
    });
}

fn decoded_lock_instruction_payload(
    instruction_data: std::option::Option<&[u8]>,
) -> serde_json::Value {
    let data = match instruction_data {
        Some(data) => data,
        None => return serde_json::Value::Null,
    };
    return serde_json::json!({
        "maxAmountRaw": u64_le_string_at(data, 8)
    });
}

fn decoded_instruction_amount_string(
    value: &serde_json::Value,
    key: &str,
) -> std::option::Option<std::string::String> {
    let object = match value.as_object() {
        Some(object) => object,
        None => return None,
    };
    let raw = match object.get(key) {
        Some(raw) => raw,
        None => return None,
    };
    match raw {
        serde_json::Value::String(text) => return Some(text.clone()),
        serde_json::Value::Number(number) => return Some(number.to_string()),
        _ => return None,
    }
}

fn u64_le_string_at(data: &[u8], offset: usize) -> serde_json::Value {
    let number = u64_le_at(data, offset);
    match number {
        Some(number) => return serde_json::Value::String(number.to_string()),
        None => return serde_json::Value::Null,
    }
}

fn option_u64_le_string_at(data: &[u8], offset: usize) -> serde_json::Value {
    if data.len() <= offset {
        return serde_json::Value::Null;
    }
    let tag = data[offset];
    if tag == 0 {
        return serde_json::Value::Null;
    }
    if tag == 1 {
        return u64_le_string_at(data, offset + 1);
    }
    return serde_json::Value::Null;
}

fn u64_le_at(data: &[u8], offset: usize) -> std::option::Option<u64> {
    let end = match offset.checked_add(8) {
        Some(end) => end,
        None => return None,
    };
    if data.len() < end {
        return None;
    }
    let bytes = [
        data[offset],
        data[offset + 1],
        data[offset + 2],
        data[offset + 3],
        data[offset + 4],
        data[offset + 5],
        data[offset + 6],
        data[offset + 7],
    ];
    return Some(u64::from_le_bytes(bytes));
}

fn first_8_bytes(bytes: &[u8]) -> [u8; 8] {
    return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
}

fn first_16_bytes(bytes: &[u8]) -> [u8; 16] {
    return [
        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
        bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
    ];
}

fn create_pool_account_roles(
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> serde_json::Value {
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig
        | MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2 => {
            return named_accounts_json(
                accounts,
                &[
                    ("pool", 0),
                    ("config", 1),
                    ("lpMint", 2),
                    ("tokenAMint", 3),
                    ("tokenBMint", 4),
                    ("aVault", 5),
                    ("bVault", 6),
                    ("aTokenVault", 7),
                    ("bTokenVault", 8),
                    ("aVaultLpMint", 9),
                    ("bVaultLpMint", 10),
                    ("aVaultLp", 11),
                    ("bVaultLp", 12),
                    ("payerTokenA", 13),
                    ("payerTokenB", 14),
                    ("payerPoolLp", 15),
                    ("protocolTokenAFee", 16),
                    ("protocolTokenBFee", 17),
                    ("payer", 18),
                    ("rent", 19),
                    ("mintMetadata", 20),
                    ("metadataProgram", 21),
                    ("vaultProgram", 22),
                    ("tokenProgram", 23),
                    ("associatedTokenProgram", 24),
                    ("systemProgram", 25),
                ],
            );
        },
        MeteoraDammV1InstructionKind::InitializeCustomizablePermissionlessConstantProductPool => {
            return named_accounts_json(
                accounts,
                &[
                    ("pool", 0),
                    ("lpMint", 1),
                    ("tokenAMint", 2),
                    ("tokenBMint", 3),
                    ("aVault", 4),
                    ("bVault", 5),
                    ("aTokenVault", 6),
                    ("bTokenVault", 7),
                    ("aVaultLpMint", 8),
                    ("bVaultLpMint", 9),
                    ("aVaultLp", 10),
                    ("bVaultLp", 11),
                    ("payerTokenA", 12),
                    ("payerTokenB", 13),
                    ("payerPoolLp", 14),
                    ("protocolTokenAFee", 15),
                    ("protocolTokenBFee", 16),
                    ("payer", 17),
                    ("rent", 18),
                    ("mintMetadata", 19),
                    ("metadataProgram", 20),
                    ("vaultProgram", 21),
                    ("tokenProgram", 22),
                    ("associatedTokenProgram", 23),
                    ("systemProgram", 24),
                ],
            );
        },
        _ => {
            return named_accounts_json(
                accounts,
                &[("pool", 0), ("tokenAMint", 1), ("tokenBMint", 2)],
            );
        },
    }
}

fn swap_account_roles(accounts: &[std::string::String]) -> serde_json::Value {
    return named_accounts_json(
        accounts,
        &[
            ("pool", 0),
            ("userSourceToken", 1),
            ("userDestinationToken", 2),
            ("aVault", 3),
            ("bVault", 4),
            ("aTokenVault", 5),
            ("bTokenVault", 6),
            ("aVaultLpMint", 7),
            ("bVaultLpMint", 8),
            ("aVaultLp", 9),
            ("bVaultLp", 10),
            ("protocolTokenFee", 11),
            ("user", 12),
            ("vaultProgram", 13),
            ("tokenProgram", 14),
        ],
    );
}

fn liquidity_account_roles(
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> serde_json::Value {
    if instruction_kind == MeteoraDammV1InstructionKind::RemoveLiquiditySingleSide {
        return named_accounts_json(
            accounts,
            &[
                ("pool", 0),
                ("lpMint", 1),
                ("userPoolLp", 2),
                ("aVaultLp", 3),
                ("bVaultLp", 4),
                ("aVault", 5),
                ("bVault", 6),
                ("aVaultLpMint", 7),
                ("bVaultLpMint", 8),
                ("aTokenVault", 9),
                ("bTokenVault", 10),
                ("userDestinationToken", 11),
                ("user", 12),
                ("vaultProgram", 13),
                ("tokenProgram", 14),
            ],
        );
    }
    return named_accounts_json(
        accounts,
        &[
            ("pool", 0),
            ("lpMint", 1),
            ("userPoolLp", 2),
            ("aVaultLp", 3),
            ("bVaultLp", 4),
            ("aVault", 5),
            ("bVault", 6),
            ("aVaultLpMint", 7),
            ("bVaultLpMint", 8),
            ("aTokenVault", 9),
            ("bTokenVault", 10),
            ("userAToken", 11),
            ("userBToken", 12),
            ("user", 13),
            ("vaultProgram", 14),
            ("tokenProgram", 15),
        ],
    );
}

fn claim_fee_account_roles(accounts: &[std::string::String]) -> serde_json::Value {
    return named_accounts_json(
        accounts,
        &[
            ("pool", 0),
            ("lpMint", 1),
            ("lockEscrow", 2),
            ("owner", 3),
            ("sourceTokens", 4),
            ("escrowVault", 5),
            ("tokenProgram", 6),
            ("aTokenVault", 7),
            ("bTokenVault", 8),
            ("aVault", 9),
            ("bVault", 10),
            ("aVaultLp", 11),
            ("bVaultLp", 12),
            ("aVaultLpMint", 13),
            ("bVaultLpMint", 14),
            ("userAToken", 15),
            ("userBToken", 16),
            ("vaultProgram", 17),
        ],
    );
}

fn create_lock_escrow_account_roles(accounts: &[std::string::String]) -> serde_json::Value {
    return named_accounts_json(
        accounts,
        &[
            ("pool", 0),
            ("lockEscrow", 1),
            ("owner", 2),
            ("lpMint", 3),
            ("payer", 4),
            ("systemProgram", 5),
        ],
    );
}

fn lock_account_roles(accounts: &[std::string::String]) -> serde_json::Value {
    return named_accounts_json(
        accounts,
        &[
            ("pool", 0),
            ("lpMint", 1),
            ("lockEscrow", 2),
            ("owner", 3),
            ("sourceTokens", 4),
            ("escrowVault", 5),
            ("tokenProgram", 6),
            ("aVault", 7),
            ("bVault", 8),
            ("aVaultLp", 9),
            ("bVaultLp", 10),
            ("aVaultLpMint", 11),
            ("bVaultLpMint", 12),
        ],
    );
}

fn named_accounts_json(
    accounts: &[std::string::String],
    roles: &[(&str, usize)],
) -> serde_json::Value {
    let mut object = serde_json::Map::new();
    for (role, index) in roles {
        let account = extract_account(accounts, *index);
        match account {
            Some(account) => {
                object.insert((*role).to_string(), serde_json::Value::String(account));
            },
            None => {
                object.insert((*role).to_string(), serde_json::Value::Null);
            },
        }
    }
    return serde_json::Value::Object(object);
}

fn discriminator_hex_for_kind(
    bytes: &[u8],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    if is_upstream_git_event_kind(instruction_kind) && bytes.len() >= 16 {
        return Some(format!(
            "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
            bytes[0],
            bytes[1],
            bytes[2],
            bytes[3],
            bytes[4],
            bytes[5],
            bytes[6],
            bytes[7],
            bytes[8],
            bytes[9],
            bytes[10],
            bytes[11],
            bytes[12],
            bytes[13],
            bytes[14],
            bytes[15],
        ));
    }
    return first_8_bytes_hex(bytes);
}

fn is_upstream_git_event_kind(instruction_kind: MeteoraDammV1InstructionKind) -> bool {
    return matches!(
        instruction_kind,
        MeteoraDammV1InstructionKind::AddLiquidityEvent
            | MeteoraDammV1InstructionKind::RemoveLiquidityEvent
            | MeteoraDammV1InstructionKind::BootstrapLiquidityEvent
            | MeteoraDammV1InstructionKind::SwapEvent
            | MeteoraDammV1InstructionKind::SetPoolFeesEvent
            | MeteoraDammV1InstructionKind::PoolInfoEvent
            | MeteoraDammV1InstructionKind::TransferAdminEvent
            | MeteoraDammV1InstructionKind::OverrideCurveParamEvent
            | MeteoraDammV1InstructionKind::PoolCreatedEvent
            | MeteoraDammV1InstructionKind::PoolEnabledEvent
            | MeteoraDammV1InstructionKind::MigrateFeeAccountEvent
            | MeteoraDammV1InstructionKind::CreateLockEscrowEvent
            | MeteoraDammV1InstructionKind::LockEvent
            | MeteoraDammV1InstructionKind::ClaimFeeEvent
            | MeteoraDammV1InstructionKind::CreateConfigEvent
            | MeteoraDammV1InstructionKind::CloseConfigEvent
            | MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent
            | MeteoraDammV1InstructionKind::PartnerClaimFeesEvent
    );
}

fn upstream_git_pool_account(
    accounts: &[std::string::String],
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::PoolCreatedEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32 + 32 + 32 + 1);
        },
        MeteoraDammV1InstructionKind::BootstrapLiquidityEvent => {
            return pubkey_base58_at(instruction_data, 16 + 8 + 8 + 8);
        },
        MeteoraDammV1InstructionKind::SetPoolFeesEvent => {
            return pubkey_base58_at(instruction_data, 16 + 8 + 8 + 8 + 8);
        },
        MeteoraDammV1InstructionKind::TransferAdminEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32 + 32);
        },
        MeteoraDammV1InstructionKind::OverrideCurveParamEvent => {
            return pubkey_base58_at(instruction_data, 16 + 8 + 8);
        },
        MeteoraDammV1InstructionKind::PoolEnabledEvent => {
            return pubkey_base58_at(instruction_data, 16);
        },
        MeteoraDammV1InstructionKind::MigrateFeeAccountEvent => {
            return pubkey_base58_at(instruction_data, 16);
        },
        MeteoraDammV1InstructionKind::CreateLockEscrowEvent => {
            return pubkey_base58_at(instruction_data, 16);
        },
        MeteoraDammV1InstructionKind::LockEvent => return pubkey_base58_at(instruction_data, 16),
        MeteoraDammV1InstructionKind::ClaimFeeEvent => {
            return pubkey_base58_at(instruction_data, 16);
        },
        MeteoraDammV1InstructionKind::CreateConfig
        | MeteoraDammV1InstructionKind::CreateConfigEvent
        | MeteoraDammV1InstructionKind::CloseConfig
        | MeteoraDammV1InstructionKind::CloseConfigEvent => return None,
        _ => return extract_account(accounts, 0),
    }
}

fn upstream_git_token_a_mint(
    accounts: &[std::string::String],
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::PoolCreatedEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32);
        },
        MeteoraDammV1InstructionKind::InitializePermissionedPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return extract_account(accounts, 2);
        },
        _ => return None,
    }
}

fn upstream_git_token_b_mint(
    accounts: &[std::string::String],
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::PoolCreatedEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32 + 32);
        },
        MeteoraDammV1InstructionKind::InitializePermissionedPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return extract_account(accounts, 3);
        },
        _ => return None,
    }
}

fn upstream_git_lp_mint(
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionedPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier
        | MeteoraDammV1InstructionKind::CreateMintMetadata => return extract_account(accounts, 1),
        MeteoraDammV1InstructionKind::ClaimFeeEvent
        | MeteoraDammV1InstructionKind::WithdrawProtocolFees
        | MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent
        | MeteoraDammV1InstructionKind::PartnerClaimFee
        | MeteoraDammV1InstructionKind::PartnerClaimFeesEvent => return None,
        _ => return extract_account(accounts, 1),
    }
}

fn upstream_git_actor_wallet(
    accounts: &[std::string::String],
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    match instruction_kind {
        MeteoraDammV1InstructionKind::CreateConfig | MeteoraDammV1InstructionKind::CloseConfig => {
            return extract_account(accounts, 1);
        },
        MeteoraDammV1InstructionKind::EnableOrDisablePool
        | MeteoraDammV1InstructionKind::OverrideCurveParam
        | MeteoraDammV1InstructionKind::UpdateActivationPoint
        | MeteoraDammV1InstructionKind::SetWhitelistedVault => return extract_account(accounts, 1),
        MeteoraDammV1InstructionKind::SetPoolFees => return extract_account(accounts, 1),
        MeteoraDammV1InstructionKind::PartnerClaimFee => return extract_account(accounts, 7),
        MeteoraDammV1InstructionKind::ClaimFeeEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32);
        },
        MeteoraDammV1InstructionKind::PartnerClaimFeesEvent => {
            return pubkey_base58_at(instruction_data, 16 + 8 + 8);
        },
        MeteoraDammV1InstructionKind::TransferAdminEvent => {
            return pubkey_base58_at(instruction_data, 16);
        },
        MeteoraDammV1InstructionKind::CreateLockEscrowEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32);
        },
        MeteoraDammV1InstructionKind::LockEvent => {
            return pubkey_base58_at(instruction_data, 16 + 32);
        },
        _ => return extract_account(accounts, 1),
    }
}

fn decoded_upstream_git_instruction_payload(
    instruction_data: std::option::Option<&[u8]>,
    instruction_kind: MeteoraDammV1InstructionKind,
) -> serde_json::Value {
    let data = match instruction_data {
        Some(data) => data,
        None => return serde_json::Value::Null,
    };
    match instruction_kind {
        MeteoraDammV1InstructionKind::EnableOrDisablePool => {
            return serde_json::json!({ "enabled": bool_at(data, 8) });
        },
        MeteoraDammV1InstructionKind::UpdateActivationPoint => {
            return serde_json::json!({ "newActivationPointRaw": u64_le_string_at(data, 8) });
        },
        MeteoraDammV1InstructionKind::SetWhitelistedVault => {
            return serde_json::json!({ "whitelistedVault": pubkey_base58_at(Some(data), 8) });
        },
        MeteoraDammV1InstructionKind::PartnerClaimFee => {
            return serde_json::json!({
                "maxAmountARaw": u64_le_string_at(data, 8),
                "maxAmountBRaw": u64_le_string_at(data, 16),
            });
        },
        MeteoraDammV1InstructionKind::WithdrawProtocolFees => {
            return serde_json::json!({});
        },
        MeteoraDammV1InstructionKind::AddLiquidityEvent
        | MeteoraDammV1InstructionKind::BootstrapLiquidityEvent => {
            return serde_json::json!({
                "lpAmountRaw": u64_le_string_at(data, 16),
                "tokenAAmountRaw": u64_le_string_at(data, 24),
                "tokenBAmountRaw": u64_le_string_at(data, 32),
                "pool": bootstrap_event_pool_for_payload(data, instruction_kind),
            });
        },
        MeteoraDammV1InstructionKind::RemoveLiquidityEvent => {
            return serde_json::json!({
                "lpAmountRaw": u64_le_string_at(data, 16),
                "tokenAAmountRaw": u64_le_string_at(data, 24),
                "tokenBAmountRaw": u64_le_string_at(data, 32),
            });
        },
        MeteoraDammV1InstructionKind::SwapEvent => {
            return serde_json::json!({
                "inAmountRaw": u64_le_string_at(data, 16),
                "outAmountRaw": u64_le_string_at(data, 24),
                "tradeFeeRaw": u64_le_string_at(data, 32),
                "protocolFeeRaw": u64_le_string_at(data, 40),
                "hostFeeRaw": u64_le_string_at(data, 48),
            });
        },
        MeteoraDammV1InstructionKind::SetPoolFeesEvent => {
            return serde_json::json!({
                "tradeFeeNumeratorRaw": u64_le_string_at(data, 16),
                "tradeFeeDenominatorRaw": u64_le_string_at(data, 24),
                "protocolTradeFeeNumeratorRaw": u64_le_string_at(data, 32),
                "protocolTradeFeeDenominatorRaw": u64_le_string_at(data, 40),
                "pool": pubkey_base58_at(Some(data), 48),
            });
        },
        MeteoraDammV1InstructionKind::PoolInfoEvent => {
            return serde_json::json!({
                "tokenAAmountRaw": u64_le_string_at(data, 16),
                "tokenBAmountRaw": u64_le_string_at(data, 24),
                "virtualPriceRawBytes": bytes_hex_at(data, 32, 8),
                "currentTimestampRaw": u64_le_string_at(data, 40),
            });
        },
        MeteoraDammV1InstructionKind::PoolEnabledEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "enabled": bool_at(data, 48),
            });
        },
        MeteoraDammV1InstructionKind::LockEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "owner": pubkey_base58_at(Some(data), 48),
                "amountRaw": u64_le_string_at(data, 80),
            });
        },
        MeteoraDammV1InstructionKind::ClaimFeeEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "owner": pubkey_base58_at(Some(data), 48),
                "amountRaw": u64_le_string_at(data, 80),
                "feeAmountRaw": u64_le_string_at(data, 80),
                "aFeeRaw": u64_le_string_at(data, 88),
                "bFeeRaw": u64_le_string_at(data, 96),
            });
        },
        MeteoraDammV1InstructionKind::WithdrawProtocolFeesEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "protocolAFeeRaw": u64_le_string_at(data, 48),
                "protocolBFeeRaw": u64_le_string_at(data, 56),
                "protocolAFeeOwner": pubkey_base58_at(Some(data), 64),
                "protocolBFeeOwner": pubkey_base58_at(Some(data), 96),
            });
        },
        MeteoraDammV1InstructionKind::PartnerClaimFeesEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "feeARaw": u64_le_string_at(data, 48),
                "feeBRaw": u64_le_string_at(data, 56),
                "partner": pubkey_base58_at(Some(data), 64),
            });
        },
        MeteoraDammV1InstructionKind::PoolCreatedEvent => {
            return serde_json::json!({
                "lpMint": pubkey_base58_at(Some(data), 16),
                "tokenAMint": pubkey_base58_at(Some(data), 48),
                "tokenBMint": pubkey_base58_at(Some(data), 80),
                "poolTypeRaw": u8_at(data, 112),
                "pool": pubkey_base58_at(Some(data), 113),
            });
        },
        MeteoraDammV1InstructionKind::CreateConfigEvent => {
            return serde_json::json!({
                "tradeFeeNumeratorRaw": u64_le_string_at(data, 16),
                "protocolTradeFeeNumeratorRaw": u64_le_string_at(data, 24),
                "config": pubkey_base58_at(Some(data), 32),
            });
        },
        MeteoraDammV1InstructionKind::CloseConfigEvent => {
            return serde_json::json!({ "config": pubkey_base58_at(Some(data), 16) });
        },
        MeteoraDammV1InstructionKind::CreateLockEscrowEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "owner": pubkey_base58_at(Some(data), 48),
            });
        },
        MeteoraDammV1InstructionKind::TransferAdminEvent => {
            return serde_json::json!({
                "admin": pubkey_base58_at(Some(data), 16),
                "newAdmin": pubkey_base58_at(Some(data), 48),
                "pool": pubkey_base58_at(Some(data), 80),
            });
        },
        MeteoraDammV1InstructionKind::OverrideCurveParamEvent => {
            return serde_json::json!({
                "newAmpRaw": u64_le_string_at(data, 16),
                "updatedTimestampRaw": u64_le_string_at(data, 24),
                "pool": pubkey_base58_at(Some(data), 32),
            });
        },
        MeteoraDammV1InstructionKind::MigrateFeeAccountEvent => {
            return serde_json::json!({
                "pool": pubkey_base58_at(Some(data), 16),
                "newAdminTokenAFee": pubkey_base58_at(Some(data), 48),
                "newAdminTokenBFee": pubkey_base58_at(Some(data), 80),
                "tokenAAmountRaw": u64_le_string_at(data, 112),
                "tokenBAmountRaw": u64_le_string_at(data, 120),
            });
        },
        _ => return serde_json::json!({}),
    }
}

fn upstream_git_account_roles(
    accounts: &[std::string::String],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> serde_json::Value {
    match instruction_kind {
        MeteoraDammV1InstructionKind::InitializePermissionedPool => {
            return named_accounts_json(
                accounts,
                &[
                    ("pool", 0),
                    ("lpMint", 1),
                    ("tokenAMint", 2),
                    ("tokenBMint", 3),
                    ("admin", 15),
                    ("feeOwner", 16),
                ],
            );
        },
        MeteoraDammV1InstructionKind::InitializePermissionlessPool
        | MeteoraDammV1InstructionKind::InitializePermissionlessPoolWithFeeTier => {
            return named_accounts_json(
                accounts,
                &[
                    ("pool", 0),
                    ("lpMint", 1),
                    ("tokenAMint", 2),
                    ("tokenBMint", 3),
                    ("payer", 17),
                    ("feeOwner", 18),
                ],
            );
        },
        MeteoraDammV1InstructionKind::EnableOrDisablePool
        | MeteoraDammV1InstructionKind::OverrideCurveParam
        | MeteoraDammV1InstructionKind::UpdateActivationPoint
        | MeteoraDammV1InstructionKind::SetWhitelistedVault => {
            return named_accounts_json(accounts, &[("pool", 0), ("admin", 1)]);
        },
        MeteoraDammV1InstructionKind::SetPoolFees => {
            return named_accounts_json(accounts, &[("pool", 0), ("feeOperator", 1)]);
        },
        MeteoraDammV1InstructionKind::CreateConfig => {
            return named_accounts_json(
                accounts,
                &[("config", 0), ("admin", 1), ("systemProgram", 2)],
            );
        },
        MeteoraDammV1InstructionKind::CloseConfig => {
            return named_accounts_json(
                accounts,
                &[("config", 0), ("admin", 1), ("rentReceiver", 2)],
            );
        },
        MeteoraDammV1InstructionKind::WithdrawProtocolFees => {
            return named_accounts_json(
                accounts,
                &[
                    ("pool", 0),
                    ("aVaultLp", 1),
                    ("protocolTokenAFee", 2),
                    ("protocolTokenBFee", 3),
                    ("treasuryTokenA", 4),
                    ("treasuryTokenB", 5),
                    ("tokenProgram", 6),
                ],
            );
        },
        MeteoraDammV1InstructionKind::PartnerClaimFee => {
            return named_accounts_json(
                accounts,
                &[
                    ("pool", 0),
                    ("aVaultLp", 1),
                    ("protocolTokenAFee", 2),
                    ("protocolTokenBFee", 3),
                    ("partnerTokenA", 4),
                    ("partnerTokenB", 5),
                    ("tokenProgram", 6),
                    ("partnerAuthority", 7),
                ],
            );
        },
        _ => return named_accounts_json(accounts, &[]),
    }
}

fn bootstrap_event_pool_for_payload(
    data: &[u8],
    instruction_kind: MeteoraDammV1InstructionKind,
) -> std::option::Option<std::string::String> {
    if instruction_kind == MeteoraDammV1InstructionKind::BootstrapLiquidityEvent {
        return pubkey_base58_at(Some(data), 40);
    }
    return None;
}

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

fn bool_at(data: &[u8], offset: usize) -> serde_json::Value {
    if data.len() <= offset {
        return serde_json::Value::Null;
    }
    return serde_json::Value::Bool(data[offset] != 0);
}

fn u8_at(data: &[u8], offset: usize) -> serde_json::Value {
    if data.len() <= offset {
        return serde_json::Value::Null;
    }
    return serde_json::Value::Number(serde_json::Number::from(data[offset]));
}

fn bytes_hex_at(data: &[u8], offset: usize, len: usize) -> serde_json::Value {
    let end = match offset.checked_add(len) {
        Some(end) => end,
        None => return serde_json::Value::Null,
    };
    if data.len() < end {
        return serde_json::Value::Null;
    }
    let mut text = std::string::String::new();
    for byte in &data[offset..end] {
        text.push_str(format!("{:02x}", byte).as_str());
    }
    return serde_json::Value::String(text);
}

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_text(keyword);
    for log_message in log_messages {
        let log_normalized = normalize_text(log_message.as_str());
        if log_normalized.contains(keyword_normalized.as_str()) {
            return true;
        }
    }
    return false;
}

fn normalize_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;
}

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());
            continue;
        }
        if let Some(pubkey) = value.get("pubkey").and_then(|nested| return nested.as_str()) {
            accounts.push(pubkey.to_string());
        }
    }
    return Ok(accounts);
}

fn parse_optional_parsed_json(
    parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
    let parsed_json = match parsed_json {
        Some(parsed_json) => parsed_json,
        None => return Ok(None),
    };
    let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
    match value_result {
        Ok(value) => return Ok(Some(value)),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse instruction parsed_json '{}': {}",
                parsed_json, error
            )));
        },
    }
}

fn decode_instruction_data_json(
    data_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<std::vec::Vec<u8>>, crate::Error> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return Ok(None),
    };
    let value_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    let value = match value_result {
        Ok(value) => value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DAMM v1 data_json: {}",
                error
            )));
        },
    };
    if let serde_json::Value::String(base58_text) = value {
        let decoded_result = bs58::decode(base58_text.as_str()).into_vec();
        match decoded_result {
            Ok(decoded) => return Ok(Some(decoded)),
            Err(error) => {
                return Err(crate::Error::Json(format!(
                    "cannot decode Meteora DAMM v1 instruction data from base58: {}",
                    error
                )));
            },
        }
    }
    return Ok(None);
}

fn first_8_bytes_hex(bytes: &[u8]) -> std::option::Option<std::string::String> {
    if bytes.len() < 8 {
        return None;
    }
    return Some(format!(
        "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
    ));
}

fn extract_string_by_candidate_keys(
    value: std::option::Option<&serde_json::Value>,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value,
        None => return None,
    };
    return extract_string_by_candidate_keys_inner(value, candidate_keys);
}

fn extract_string_by_candidate_keys_inner(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let direct_option = object.get(*candidate_key);
            if let Some(direct) = direct_option {
                let direct_text_option = direct.as_str();
                if let Some(direct_text) = direct_text_option {
                    return Some(direct_text.to_string());
                }
            }
        }
        for nested_value in object.values() {
            let nested_result =
                extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
        return None;
    }
    if let Some(array) = value.as_array() {
        for nested_value in array {
            let nested_result =
                extract_string_by_candidate_keys_inner(nested_value, candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

fn value_contains_any_key(
    value: std::option::Option<&serde_json::Value>,
    candidate_keys: &[&str],
) -> bool {
    let value = match value {
        Some(value) => value,
        None => return false,
    };
    return value_contains_any_key_inner(value, candidate_keys);
}

fn value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            if object.contains_key(*candidate_key) {
                return true;
            }
        }
        for nested_value in object.values() {
            if value_contains_any_key_inner(nested_value, candidate_keys) {
                return true;
            }
        }
        return false;
    }
    if let Some(array) = value.as_array() {
        for nested_value in array {
            if value_contains_any_key_inner(nested_value, candidate_keys) {
                return true;
            }
        }
    }
    return false;
}

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 infer_trade_side(log_messages: &[std::string::String]) -> crate::SwapTradeSide {
    if log_messages_contain_keyword(log_messages, "buy") {
        return crate::SwapTradeSide::BuyBase;
    }
    if log_messages_contain_keyword(log_messages, "sell") {
        return crate::SwapTradeSide::SellBase;
    }
    return crate::SwapTradeSide::Unknown;
}

#[cfg(test)]
mod tests {
    fn make_create_transaction() -> crate::ChainTransactionDto {
        let mut dto = crate::ChainTransactionDto::new(
            "sig-meteora_damm_v1-create-1".to_string(),
            Some(890001),
            Some(1779500001),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 890001,
                "meta": {
                    "logMessages": [
                        "Program log: Instruction: InitializePoolWithConfig"
                    ]
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        dto.id = Some(501);
        return dto;
    }

    fn make_create_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            501,
            None,
            0,
            None,
            Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
            Some("meteora_damm_v1".to_string()),
            Some(1),
            serde_json::json!([
                "DammV1Pool111",
                "DammV1TokenA111",
                crate::WSOL_MINT_ID,
                "DammV1Config111",
                "DammV1Creator111"
            ])
            .to_string(),
            None,
            None,
            Some(
                serde_json::json!({
                    "info": {
                        "instruction": "initialize_pool_with_config",
                        "pool": "DammV1Pool111",
                        "tokenAMint": "DammV1TokenA111",
                        "tokenBMint": crate::WSOL_MINT_ID,
                        "config": "DammV1Config111",
                        "creator": "DammV1Creator111"
                    }
                })
                .to_string(),
            ),
        );
        dto.id = Some(502);
        return dto;
    }

    fn make_swap_transaction() -> crate::ChainTransactionDto {
        let mut dto = crate::ChainTransactionDto::new(
            "sig-meteora_damm_v1-swap-1".to_string(),
            Some(890002),
            Some(1779500002),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 890002,
                "meta": {
                    "logMessages": [
                        "Program log: Instruction: Swap"
                    ]
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        dto.id = Some(503);
        return dto;
    }

    fn make_swap_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            503,
            None,
            0,
            None,
            Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
            Some("meteora_damm_v1".to_string()),
            Some(1),
            serde_json::json!(["DammV1SwapPool111", "DammV1SwapTokenA111", crate::WSOL_MINT_ID])
                .to_string(),
            None,
            None,
            Some(
                serde_json::json!({
                    "info": {
                        "instruction": "swap",
                        "pool": "DammV1SwapPool111",
                        "tokenAMint": "DammV1SwapTokenA111",
                        "tokenBMint": crate::WSOL_MINT_ID
                    }
                })
                .to_string(),
            ),
        );
        dto.id = Some(504);
        return dto;
    }

    fn instruction_with_data(
        id: i64,
        transaction_id: i64,
        accounts: serde_json::Value,
        data: &[u8],
    ) -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            transaction_id,
            None,
            0,
            None,
            Some(crate::METEORA_DAMM_V1_PROGRAM_ID.to_string()),
            Some("meteora_damm_v1".to_string()),
            Some(1),
            accounts.to_string(),
            Some(format!("\"{}\"", bs58::encode(data).into_string())),
            None,
            None,
        );
        dto.id = Some(id);
        return dto;
    }

    fn data_with_u64(discriminator: [u8; 8], values: &[u64]) -> std::vec::Vec<u8> {
        let mut data = discriminator.to_vec();
        for value in values {
            data.extend_from_slice(&value.to_le_bytes());
        }
        return data;
    }

    #[test]
    fn meteora_damm_v1_create_pool_is_detected() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let transaction = make_create_transaction();
        let instructions = vec![make_create_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::MeteoraDammV1DecodedEvent::CreatePool(event) => {
                assert_eq!(event.transaction_id, 501);
                assert_eq!(event.instruction_id, 502);
                assert_eq!(event.pool_account, Some("DammV1Pool111".to_string()));
                assert_eq!(event.token_a_mint, Some("DammV1TokenA111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert!(event.used_config);
            },
            _ => {
                panic!("unexpected event")
            },
        }
    }

    #[test]
    fn meteora_damm_v1_swap_is_detected() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let transaction = make_swap_transaction();
        let instructions = vec![make_swap_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::MeteoraDammV1DecodedEvent::Swap(event) => {
                assert_eq!(event.transaction_id, 503);
                assert_eq!(event.instruction_id, 504);
                assert_eq!(event.pool_account, Some("DammV1SwapPool111".to_string()));
                assert_eq!(event.token_a_mint, Some("DammV1SwapTokenA111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
            },
            _ => {
                panic!("unexpected event")
            },
        }
    }

    #[test]
    fn meteora_damm_v1_upstream_git_create_pool_with_config_is_detected() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let mut transaction = make_create_transaction();
        transaction.id = Some(601);
        let data = data_with_u64(
            super::DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG,
            &[10, 20],
        );
        let instruction = instruction_with_data(
            602,
            601,
            serde_json::json!([
                "Pool111",
                "Config111",
                "LpMint111",
                "MintA111",
                "MintB111",
                "AVault111",
                "BVault111",
                "ATokenVault111",
                "BTokenVault111",
                "AVaultLpMint111",
                "BVaultLpMint111",
                "AVaultLp111",
                "BVaultLp111",
                "PayerA111",
                "PayerB111",
                "PayerLp111",
                "ProtocolA111",
                "ProtocolB111",
                "Payer111",
                "Rent111",
                "Metadata111",
                "MetadataProgram111",
                "VaultProgram111",
                "TokenProgram111",
                "AtaProgram111",
                "System111"
            ]),
            data.as_slice(),
        );
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDammV1DecodedEvent::CreatePool(event) => {
                assert_eq!(event.pool_account, Some("Pool111".to_string()));
                assert_eq!(event.config_account, Some("Config111".to_string()));
                assert_eq!(event.lp_mint, Some("LpMint111".to_string()));
                assert_eq!(event.token_a_mint, Some("MintA111".to_string()));
                assert_eq!(event.token_b_mint, Some("MintB111".to_string()));
                assert_eq!(event.creator, Some("Payer111".to_string()));
            },
            _ => panic!("unexpected event"),
        }
    }

    #[test]
    fn meteora_damm_v1_remove_balance_liquidity_is_detected() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let transaction = make_swap_transaction();
        let data =
            data_with_u64(super::DAMM_V1_DISCRIMINATOR_REMOVE_BALANCE_LIQUIDITY, &[100, 7, 9]);
        let instruction = instruction_with_data(
            604,
            503,
            serde_json::json!([
                "Pool111",
                "LpMint111",
                "UserLp111",
                "AVaultLp111",
                "BVaultLp111",
                "AVault111",
                "BVault111",
                "AVaultLpMint111",
                "BVaultLpMint111",
                "ATokenVault111",
                "BTokenVault111",
                "UserA111",
                "UserB111",
                "User111",
                "VaultProgram111",
                "TokenProgram111"
            ]),
            data.as_slice(),
        );
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDammV1DecodedEvent::Liquidity(event) => {
                assert_eq!(event.event_kind, "meteora_damm_v1.remove_liquidity");
                assert_eq!(event.pool_account, Some("Pool111".to_string()));
                assert_eq!(event.lp_mint, Some("LpMint111".to_string()));
                assert_eq!(event.lp_amount_raw, Some("100".to_string()));
                assert_eq!(event.actor_wallet, Some("User111".to_string()));
            },
            _ => panic!("unexpected event"),
        }
    }

    #[test]
    fn meteora_damm_v1_claim_fee_is_detected() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let transaction = make_swap_transaction();
        let data = data_with_u64(super::DAMM_V1_DISCRIMINATOR_CLAIM_FEE, &[500]);
        let instruction = instruction_with_data(
            605,
            503,
            serde_json::json!([
                "Pool111",
                "LpMint111",
                "LockEscrow111",
                "Owner111",
                "SourceTokens111",
                "EscrowVault111",
                "TokenProgram111",
                "ATokenVault111",
                "BTokenVault111",
                "AVault111",
                "BVault111",
                "AVaultLp111",
                "BVaultLp111",
                "AVaultLpMint111",
                "BVaultLpMint111",
                "UserA111",
                "UserB111",
                "VaultProgram111"
            ]),
            data.as_slice(),
        );
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDammV1DecodedEvent::Fee(event) => {
                assert_eq!(event.event_kind, "meteora_damm_v1.claim_fee");
                assert_eq!(event.pool_account, Some("Pool111".to_string()));
                assert_eq!(event.actor_wallet, Some("Owner111".to_string()));
            },
            _ => panic!("unexpected event"),
        }
    }

    #[test]
    fn meteora_damm_v1_lock_instructions_are_detected() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let transaction = make_swap_transaction();
        let create_lock_data = super::DAMM_V1_DISCRIMINATOR_CREATE_LOCK_ESCROW.to_vec();
        let lock_data = data_with_u64(super::DAMM_V1_DISCRIMINATOR_LOCK, &[300]);
        let create_lock_instruction = instruction_with_data(
            606,
            503,
            serde_json::json!([
                "Pool111",
                "LockEscrow111",
                "Owner111",
                "LpMint111",
                "Payer111",
                "System111"
            ]),
            create_lock_data.as_slice(),
        );
        let lock_instruction = instruction_with_data(
            607,
            503,
            serde_json::json!([
                "Pool111",
                "LpMint111",
                "LockEscrow111",
                "Owner111",
                "SourceTokens111",
                "EscrowVault111",
                "TokenProgram111",
                "AVault111",
                "BVault111",
                "AVaultLp111",
                "BVaultLp111",
                "AVaultLpMint111",
                "BVaultLpMint111"
            ]),
            lock_data.as_slice(),
        );
        let decoded_result =
            decoder.decode_transaction(&transaction, &[create_lock_instruction, lock_instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 2);
        match &decoded[0] {
            crate::MeteoraDammV1DecodedEvent::PoolLifecycle(event) => {
                assert_eq!(event.event_kind, "meteora_damm_v1.create_lock_escrow");
            },
            _ => panic!("unexpected event"),
        }
        match &decoded[1] {
            crate::MeteoraDammV1DecodedEvent::PoolAdmin(event) => {
                assert_eq!(event.event_kind, "meteora_damm_v1.lock_liquidity");
                assert_eq!(event.actor_wallet, Some("Owner111".to_string()));
            },
            _ => panic!("unexpected event"),
        }
    }

    #[test]
    fn meteora_damm_v1_discriminators_are_detected() {
        let cases = vec![
            (
                super::DAMM_V1_DISCRIMINATOR_SWAP,
                super::MeteoraDammV1InstructionKind::Swap,
            ),
            (
                super::DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG,
                super::MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig,
            ),
            (
                super::DAMM_V1_DISCRIMINATOR_INITIALIZE_PERMISSIONLESS_CP_POOL_WITH_CONFIG2,
                super::MeteoraDammV1InstructionKind::InitializePermissionlessConstantProductPoolWithConfig2,
            ),
            (
                super::DAMM_V1_DISCRIMINATOR_REMOVE_BALANCE_LIQUIDITY,
                super::MeteoraDammV1InstructionKind::RemoveBalanceLiquidity,
            ),
            (
                super::DAMM_V1_DISCRIMINATOR_CLAIM_FEE,
                super::MeteoraDammV1InstructionKind::ClaimFee,
            ),
            (
                super::DAMM_V1_DISCRIMINATOR_LOCK,
                super::MeteoraDammV1InstructionKind::Lock,
            ),
            (
                super::DAMM_V1_DISCRIMINATOR_CREATE_LOCK_ESCROW,
                super::MeteoraDammV1InstructionKind::CreateLockEscrow,
            ),
        ];
        for (discriminator, expected) in cases {
            let mut data = discriminator.to_vec();
            data.push(1);
            let kind = super::classify_instruction_kind_from_data(Some(data.as_slice()));
            assert_eq!(kind, expected);
        }
    }

    #[test]
    fn meteora_damm_v1_unknown_data_discriminator_does_not_fallback_to_global_swap_logs() {
        let data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x01];
        let logs = vec!["Program log: Instruction: Swap".to_string()];
        let kind = super::classify_instruction_kind(None, Some(&data), &logs);
        assert_eq!(kind, super::MeteoraDammV1InstructionKind::Unknown);
    }

    #[test]
    fn meteora_damm_v1_inner_swap_instruction_with_data_is_not_skipped() {
        let decoder = crate::MeteoraDammV1Decoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = make_swap_instruction();
        instruction.parent_instruction_id = Some(500);
        instruction.data_json = Some(format!(
            "\"{}\"",
            bs58::encode(&[0xf8_u8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8, 0x01]).into_string()
        ));
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDammV1DecodedEvent::Swap(event) => {
                assert_eq!(event.pool_account, Some("DammV1SwapPool111".to_string()));
            },
            _ => panic!("unexpected event"),
        }
    }
}
