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

//! Meteora DLMM transaction decoder.
//!
//! This first decoder version is intentionally conservative. It only emits
//! decoded events when the projected instruction or transaction logs expose
//! clear DLMM create/swap hints.

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY: [u8; 8] = [0xb5, 0x9d, 0x59, 0x43, 0x8f, 0xb6, 0x34, 0x48];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY2: [u8; 8] = [0xe4, 0xa2, 0x4e, 0x1c, 0x46, 0xdb, 0x74, 0x73];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_STRATEGY: [u8; 8] =
    [0x07, 0x03, 0x96, 0x7f, 0x94, 0x28, 0x3d, 0xc8];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_STRATEGY2: [u8; 8] =
    [0x03, 0xdd, 0x95, 0xda, 0x6f, 0x8d, 0x76, 0xd5];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_STRATEGY_ONE_SIDE: [u8; 8] =
    [0x29, 0x05, 0xee, 0xaf, 0x64, 0xe1, 0x06, 0xcd];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_WEIGHT: [u8; 8] =
    [0x1c, 0x8c, 0xee, 0x63, 0xe7, 0xa2, 0x15, 0x95];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_WEIGHT2: [u8; 8] =
    [0xd1, 0x3b, 0x3f, 0x5b, 0x6f, 0xc8, 0x99, 0xe4];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_ONE_SIDE: [u8; 8] =
    [0x5e, 0x9b, 0x67, 0x97, 0x46, 0x5f, 0xdc, 0xa5];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_ONE_SIDE_PRECISE: [u8; 8] =
    [0xa1, 0xc2, 0x67, 0x54, 0xab, 0x47, 0xfa, 0x9a];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY_ONE_SIDE_PRECISE2: [u8; 8] =
    [0x21, 0x33, 0xa3, 0xc9, 0x75, 0x62, 0x7d, 0xe7];

const DLMM_DISCRIMINATOR_CANCEL_LIMIT_ORDER: [u8; 8] =
    [0x84, 0x9c, 0x84, 0x1f, 0x43, 0x28, 0xe8, 0x61];

const DLMM_DISCRIMINATOR_CLAIM_FEE: [u8; 8] = [0xa9, 0x20, 0x4f, 0x89, 0x88, 0xe8, 0x46, 0x89];

const DLMM_DISCRIMINATOR_CLAIM_FEE2: [u8; 8] = [0x70, 0xbf, 0x65, 0xab, 0x1c, 0x90, 0x7f, 0xbb];

const DLMM_DISCRIMINATOR_CLAIM_REWARD: [u8; 8] = [0x95, 0x5f, 0xb5, 0xf2, 0x5e, 0x5a, 0x9e, 0xa2];

const DLMM_DISCRIMINATOR_CLAIM_REWARD2: [u8; 8] = [0xbe, 0x03, 0x7f, 0x77, 0xb2, 0x57, 0x9d, 0xb7];

const DLMM_DISCRIMINATOR_CLOSE_BIN_ARRAY: [u8; 8] =
    [0x44, 0xae, 0x58, 0x50, 0xb5, 0xcc, 0x13, 0xe0];

const DLMM_DISCRIMINATOR_CLOSE_CLAIM_FEE_OPERATOR_ACCOUNT: [u8; 8] =
    [0xb8, 0xd5, 0x58, 0x1f, 0xb3, 0x65, 0x82, 0x24];

const DLMM_DISCRIMINATOR_CLOSE_LIMIT_ORDER_IF_EMPTY: [u8; 8] =
    [0x39, 0x7c, 0x24, 0x9b, 0x7e, 0xf9, 0x5d, 0xab];

const DLMM_DISCRIMINATOR_CLOSE_OPERATOR_ACCOUNT: [u8; 8] =
    [0xab, 0x09, 0xd5, 0x4a, 0x78, 0x17, 0x03, 0x1d];

const DLMM_DISCRIMINATOR_CLOSE_POSITION: [u8; 8] = [0x7b, 0x86, 0x51, 0x00, 0x31, 0x44, 0x62, 0x62];

const DLMM_DISCRIMINATOR_CLOSE_POSITION2: [u8; 8] =
    [0xae, 0x5a, 0x23, 0x73, 0xba, 0x28, 0x93, 0xe2];

const DLMM_DISCRIMINATOR_CLOSE_POSITION_IF_EMPTY: [u8; 8] =
    [0x3b, 0x7c, 0xd4, 0x76, 0x5b, 0x98, 0x6e, 0x9d];

const DLMM_DISCRIMINATOR_CLOSE_PRESET_PARAMETER: [u8; 8] =
    [0x04, 0x94, 0x91, 0x64, 0x86, 0x1a, 0xb5, 0x3d];

const DLMM_DISCRIMINATOR_CLOSE_PRESET_PARAMETER2: [u8; 8] =
    [0x27, 0x19, 0x5f, 0x6b, 0x74, 0x11, 0x73, 0x1c];

const DLMM_DISCRIMINATOR_CLOSE_TOKEN_BADGE: [u8; 8] =
    [0x6c, 0x92, 0x56, 0x6e, 0xb3, 0xfe, 0x0a, 0x68];

const DLMM_DISCRIMINATOR_CREATE_OPERATOR_ACCOUNT: [u8; 8] =
    [0xdd, 0x40, 0xf6, 0x95, 0xf0, 0x99, 0xe5, 0xa3];

const DLMM_DISCRIMINATOR_DECREASE_POSITION_LENGTH: [u8; 8] =
    [0xc2, 0xdb, 0x88, 0x20, 0x19, 0x60, 0x69, 0x25];

const DLMM_DISCRIMINATOR_FOR_IDL_TYPE_GENERATION_DO_NOT_CALL: [u8; 8] =
    [0xb4, 0x69, 0x45, 0x50, 0x5f, 0x32, 0x49, 0x6c];

const DLMM_DISCRIMINATOR_FUND_REWARD: [u8; 8] = [0xbc, 0x32, 0xf9, 0xa5, 0x5d, 0x97, 0x26, 0x3f];

const DLMM_DISCRIMINATOR_GO_TO_A_BIN: [u8; 8] = [0x92, 0x48, 0xae, 0xe0, 0x28, 0xfd, 0x54, 0xae];

const DLMM_DISCRIMINATOR_INCREASE_ORACLE_LENGTH: [u8; 8] =
    [0xbe, 0x3d, 0x7d, 0x57, 0x67, 0x4f, 0x9e, 0xad];

const DLMM_DISCRIMINATOR_INCREASE_POSITION_LENGTH: [u8; 8] =
    [0x50, 0x53, 0x75, 0xd3, 0x42, 0x0d, 0x21, 0x95];

const DLMM_DISCRIMINATOR_INCREASE_POSITION_LENGTH2: [u8; 8] =
    [0xff, 0xd2, 0xcc, 0x47, 0x73, 0x89, 0xe1, 0x71];

const DLMM_DISCRIMINATOR_INITIALIZE_BIN_ARRAY: [u8; 8] =
    [0x23, 0x56, 0x13, 0xb9, 0x4e, 0xd4, 0x4b, 0xd3];

const DLMM_DISCRIMINATOR_INITIALIZE_BIN_ARRAY_BITMAP_EXTENSION: [u8; 8] =
    [0x2f, 0x9d, 0xe2, 0xb4, 0x0c, 0xf0, 0x21, 0x47];

const DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR: [u8; 8] =
    [0x2e, 0x27, 0x29, 0x87, 0x6f, 0xb7, 0xc8, 0x40];

const DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR2: [u8; 8] =
    [0xf3, 0x49, 0x81, 0x7e, 0x33, 0x13, 0xf1, 0x6b];

const DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR: [u8; 8] =
    [0x2d, 0x9a, 0xed, 0xd2, 0xdd, 0x0f, 0xa6, 0x5c];

const DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR2: [u8; 8] =
    [0x49, 0x3b, 0x24, 0x78, 0xed, 0x53, 0x6c, 0xc6];

const DLMM_DISCRIMINATOR_INITIALIZE_PERMISSION_LB_PAIR: [u8; 8] =
    [0x6c, 0x66, 0xd5, 0x55, 0xfb, 0x03, 0x35, 0x15];

const DLMM_DISCRIMINATOR_INITIALIZE_POSITION: [u8; 8] =
    [0xdb, 0xc0, 0xea, 0x47, 0xbe, 0xbf, 0x66, 0x50];

const DLMM_DISCRIMINATOR_INITIALIZE_POSITION2: [u8; 8] =
    [0x8f, 0x13, 0xf2, 0x91, 0xd5, 0x0f, 0x68, 0x73];

const DLMM_DISCRIMINATOR_INITIALIZE_POSITION_BY_OPERATOR: [u8; 8] =
    [0xfb, 0xbd, 0xbe, 0xf4, 0x75, 0xfe, 0x23, 0x94];

const DLMM_DISCRIMINATOR_INITIALIZE_POSITION_PDA: [u8; 8] =
    [0x2e, 0x52, 0x7d, 0x92, 0x55, 0x8d, 0xe4, 0x99];

const DLMM_DISCRIMINATOR_INITIALIZE_PRESET_PARAMETER: [u8; 8] =
    [0x42, 0xbc, 0x47, 0xd3, 0x62, 0x6d, 0x0e, 0xba];

const DLMM_DISCRIMINATOR_INITIALIZE_PRESET_PARAMETER_V2: [u8; 8] =
    [0x75, 0xc7, 0x3e, 0x67, 0x06, 0x8e, 0x1f, 0xcb];

const DLMM_DISCRIMINATOR_INITIALIZE_REWARD: [u8; 8] =
    [0x5f, 0x87, 0xc0, 0xc4, 0xf2, 0x81, 0xe6, 0x44];

const DLMM_DISCRIMINATOR_INITIALIZE_TOKEN_BADGE: [u8; 8] =
    [0xfd, 0x4d, 0xcd, 0x5f, 0x1b, 0xe0, 0x59, 0xdf];

const DLMM_DISCRIMINATOR_PLACE_LIMIT_ORDER: [u8; 8] =
    [0x6c, 0xb0, 0x21, 0xba, 0x92, 0xe5, 0x01, 0xc5];

const DLMM_DISCRIMINATOR_REBALANCE_LIQUIDITY: [u8; 8] =
    [0x5c, 0x04, 0xb0, 0xc1, 0x77, 0xb9, 0x53, 0x09];

const DLMM_DISCRIMINATOR_REMOVE_ALL_LIQUIDITY: [u8; 8] =
    [0x0a, 0x33, 0x3d, 0x23, 0x70, 0x69, 0x18, 0x55];

const DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY: [u8; 8] =
    [0x50, 0x55, 0xd1, 0x48, 0x18, 0xce, 0xb1, 0x6c];

const DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY2: [u8; 8] =
    [0xe6, 0xd7, 0x52, 0x7f, 0xf1, 0x65, 0xe3, 0x92];

const DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY_BY_RANGE: [u8; 8] =
    [0x1a, 0x52, 0x66, 0x98, 0xf0, 0x4a, 0x69, 0x1a];

const DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY_BY_RANGE2: [u8; 8] =
    [0xcc, 0x02, 0xc3, 0x91, 0x35, 0x91, 0x91, 0xcd];

const DLMM_DISCRIMINATOR_SET_ACTIVATION_POINT: [u8; 8] =
    [0x5b, 0xf9, 0x0f, 0xa5, 0x1a, 0x81, 0xfe, 0x7d];

const DLMM_DISCRIMINATOR_SET_PAIR_STATUS: [u8; 8] =
    [0x43, 0xf8, 0xe7, 0x89, 0x9a, 0x95, 0xd9, 0xae];

const DLMM_DISCRIMINATOR_SET_PAIR_STATUS_PERMISSIONLESS: [u8; 8] =
    [0x4e, 0x3b, 0x98, 0xd3, 0x46, 0xb7, 0x2e, 0xd0];

const DLMM_DISCRIMINATOR_SET_PERMISSIONLESS_OPERATION_BITS: [u8; 8] =
    [0x54, 0x3a, 0xcb, 0x8b, 0xa3, 0x51, 0xbe, 0xba];

const DLMM_DISCRIMINATOR_SET_PRE_ACTIVATION_DURATION: [u8; 8] =
    [0xa5, 0x3d, 0xc9, 0xf4, 0x82, 0x9f, 0x16, 0x64];

const DLMM_DISCRIMINATOR_SET_PRE_ACTIVATION_SWAP_ADDRESS: [u8; 8] =
    [0x39, 0x8b, 0x2f, 0x7b, 0xd8, 0x50, 0xdf, 0x0a];

const DLMM_DISCRIMINATOR_SWAP: [u8; 8] = [0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8];

const DLMM_DISCRIMINATOR_SWAP2: [u8; 8] = [0x41, 0x4b, 0x3f, 0x4c, 0xeb, 0x5b, 0x5b, 0x88];

const DLMM_DISCRIMINATOR_SWAP_EXACT_OUT: [u8; 8] = [0xfa, 0x49, 0x65, 0x21, 0x26, 0xcf, 0x4b, 0xb8];

const DLMM_DISCRIMINATOR_SWAP_EXACT_OUT2: [u8; 8] =
    [0x2b, 0xd7, 0xf7, 0x84, 0x89, 0x3c, 0xf3, 0x51];

const DLMM_DISCRIMINATOR_SWAP_WITH_PRICE_IMPACT: [u8; 8] =
    [0x38, 0xad, 0xe6, 0xd0, 0xad, 0xe4, 0x9c, 0xcd];

const DLMM_DISCRIMINATOR_SWAP_WITH_PRICE_IMPACT2: [u8; 8] =
    [0x4a, 0x62, 0xc0, 0xd6, 0xb1, 0x33, 0x4b, 0x33];

const DLMM_DISCRIMINATOR_UPDATE_BASE_FEE_PARAMETERS: [u8; 8] =
    [0x4b, 0xa8, 0xdf, 0xa1, 0x10, 0xc3, 0x03, 0x2f];

const DLMM_DISCRIMINATOR_UPDATE_DYNAMIC_FEE_PARAMETERS: [u8; 8] =
    [0x5c, 0xa1, 0x2e, 0xf6, 0xff, 0xbd, 0x16, 0x16];

const DLMM_DISCRIMINATOR_UPDATE_FEES_AND_REWARD2: [u8; 8] =
    [0x20, 0x8e, 0xb8, 0x9a, 0x67, 0x41, 0xb8, 0x58];

const DLMM_DISCRIMINATOR_UPDATE_FEES_AND_REWARDS: [u8; 8] =
    [0x9a, 0xe6, 0xfa, 0x0d, 0xec, 0xd1, 0x4b, 0xdf];

const DLMM_DISCRIMINATOR_UPDATE_POSITION_OPERATOR: [u8; 8] =
    [0xca, 0xb8, 0x67, 0x8f, 0xb4, 0xbf, 0x74, 0xd9];

const DLMM_DISCRIMINATOR_UPDATE_REWARD_DURATION: [u8; 8] =
    [0x8a, 0xae, 0xc4, 0xa9, 0xd5, 0xeb, 0xfe, 0x6b];

const DLMM_DISCRIMINATOR_UPDATE_REWARD_FUNDER: [u8; 8] =
    [0xd3, 0x1c, 0x30, 0x20, 0xd7, 0xa0, 0x23, 0x17];

const DLMM_DISCRIMINATOR_WITHDRAW_INELIGIBLE_REWARD: [u8; 8] =
    [0x94, 0xce, 0x2a, 0xc3, 0xf7, 0x31, 0x67, 0x08];

const DLMM_DISCRIMINATOR_WITHDRAW_PROTOCOL_FEE: [u8; 8] =
    [0x9e, 0xc9, 0x9e, 0xbd, 0x21, 0x5d, 0xa2, 0x67];

const DLMM_DISCRIMINATOR_ZAP_PROTOCOL_FEE: [u8; 8] =
    [0xd5, 0x9b, 0xbb, 0x22, 0x38, 0xb6, 0x5b, 0xf0];

const DLMM_ANCHOR_SELF_CPI_LOG_SELECTOR: [u8; 8] = [0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d];

const DLMM_EVENT_DISCRIMINATOR_SWAP: [u8; 8] = [0x51, 0x6c, 0xe3, 0xbe, 0xcd, 0xd0, 0x0a, 0xc4];

const DLMM_EVENT_DISCRIMINATOR_SWAP2_EVT: [u8; 8] =
    [0x2e, 0x74, 0x52, 0xd7, 0x94, 0x1b, 0x54, 0x4d];

const DLMM_EVENT_DISCRIMINATOR_LB_PAIR_CREATE: [u8; 8] =
    [0xb9, 0x4a, 0xfc, 0x7d, 0x1b, 0xd7, 0xbc, 0x6f];

const DLMM_EVENT_DISCRIMINATOR_ADD_LIQUIDITY: [u8; 8] =
    [0x1f, 0x5e, 0x7d, 0x5a, 0xe3, 0x34, 0x3d, 0xba];

const DLMM_EVENT_DISCRIMINATOR_REMOVE_LIQUIDITY: [u8; 8] =
    [0x74, 0xf4, 0x61, 0xe8, 0x67, 0x1f, 0x98, 0x3a];

const DLMM_EVENT_DISCRIMINATOR_CLAIM_FEE: [u8; 8] =
    [0x4b, 0x7a, 0x9a, 0x30, 0x8c, 0x4a, 0x7b, 0xa3];

const DLMM_EVENT_DISCRIMINATOR_CLAIM_REWARD: [u8; 8] =
    [0x94, 0x74, 0x86, 0xcc, 0x16, 0xab, 0x55, 0x5f];

const DLMM_EVENT_DISCRIMINATOR_CLAIM_FEE2: [u8; 8] =
    [0xe8, 0xab, 0xf2, 0x61, 0x3a, 0x4d, 0x23, 0x2d];

const DLMM_EVENT_DISCRIMINATOR_CLAIM_REWARD2: [u8; 8] =
    [0x1b, 0x8f, 0xf4, 0x21, 0x50, 0x2b, 0x6e, 0x92];

const DLMM_EVENT_DISCRIMINATOR_COMPOSITION_FEE: [u8; 8] =
    [0x80, 0x97, 0x7b, 0x6a, 0x11, 0x66, 0x71, 0x8e];

const DLMM_EVENT_DISCRIMINATOR_INITIALIZE_REWARD: [u8; 8] =
    [0xd3, 0x99, 0x58, 0x3e, 0x95, 0x3c, 0xb1, 0x46];

const DLMM_EVENT_DISCRIMINATOR_WITHDRAW_INELIGIBLE_REWARD: [u8; 8] =
    [0xe7, 0xbd, 0x41, 0x95, 0x66, 0xd7, 0x9a, 0xf4];

const DLMM_EVENT_DISCRIMINATOR_PLACE_LIMIT_ORDER_EVT: [u8; 8] =
    [0x2b, 0x4f, 0x1b, 0xa9, 0xf4, 0x1c, 0xe1, 0x3f];

const DLMM_EVENT_DISCRIMINATOR_CANCEL_LIMIT_ORDER_EVT: [u8; 8] =
    [0x83, 0xea, 0xc2, 0x85, 0x09, 0x0e, 0xbd, 0xd1];

const DLMM_EVENT_DISCRIMINATOR_CLOSE_LIMIT_ORDER_EVT: [u8; 8] =
    [0x8e, 0x87, 0x08, 0x4c, 0x5c, 0x3f, 0x76, 0x53];

const DLMM_EVENT_DISCRIMINATOR_FEE_PARAMETER_UPDATE: [u8; 8] =
    [0x30, 0x4c, 0xf1, 0x75, 0x90, 0xd7, 0xf2, 0x2c];

const DLMM_EVENT_DISCRIMINATOR_DYNAMIC_FEE_PARAMETER_UPDATE: [u8; 8] =
    [0x58, 0x58, 0xb2, 0x87, 0xc2, 0x92, 0x5b, 0xf3];

const DLMM_EVENT_DISCRIMINATOR_FUND_REWARD: [u8; 8] =
    [0xf6, 0xe4, 0x3a, 0x82, 0x91, 0xaa, 0x4f, 0xcc];

const DLMM_EVENT_DISCRIMINATOR_POSITION_CREATE: [u8; 8] =
    [0x90, 0x8e, 0xfc, 0x54, 0x9d, 0x35, 0x25, 0x79];

const DLMM_EVENT_DISCRIMINATOR_POSITION_CLOSE: [u8; 8] =
    [0xff, 0xc4, 0x10, 0x6b, 0x1c, 0xca, 0x35, 0x80];

/// Decoded Meteora DLMM create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmCreatePoolDecoded {
    /// 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 DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional preset/config account.
    pub config_account: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmSwapDecoded {
    /// 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,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Trade side relative to normalized base, when inferable.
    pub trade_side: crate::SwapTradeSide,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional reserve X token account.
    pub reserve_x_account: std::option::Option<std::string::String>,
    /// Optional reserve Y token account.
    pub reserve_y_account: std::option::Option<std::string::String>,
    /// Optional user token-in account.
    pub user_token_in_account: std::option::Option<std::string::String>,
    /// Optional user token-out account.
    pub user_token_out_account: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM liquidity lifecycle event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmLiquidityDecoded {
    /// 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,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional actor wallet or owner account.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional decoded base/token-X amount.
    pub base_amount_raw: std::option::Option<std::string::String>,
    /// Optional decoded quote/token-Y amount.
    pub quote_amount_raw: std::option::Option<std::string::String>,
    /// Optional decoded liquidity amount.
    pub liquidity_amount_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM pool lifecycle event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmPoolLifecycleDecoded {
    /// 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,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM fee collection event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmFeeDecoded {
    /// 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,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional actor wallet or owner account.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional fee token mint.
    pub fee_token_mint: std::option::Option<std::string::String>,
    /// Optional raw fee amount as decimal text.
    pub fee_amount_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM reward or emission event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmRewardDecoded {
    /// 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,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional actor wallet, owner or funder account.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional reward token mint.
    pub reward_token_mint: std::option::Option<std::string::String>,
    /// Optional raw reward amount as decimal text.
    pub reward_amount_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum MeteoraDlmmDecodedEvent {
    /// DLMM pair/pool creation.
    CreatePool(MeteoraDlmmCreatePoolDecoded),
    /// DLMM swap.
    Swap(MeteoraDlmmSwapDecoded),
    /// DLMM liquidity lifecycle event.
    Liquidity(MeteoraDlmmLiquidityDecoded),
    /// DLMM pool lifecycle event that is not the canonical create-pool event.
    PoolLifecycle(MeteoraDlmmPoolLifecycleDecoded),
    /// DLMM fee collection event.
    Fee(MeteoraDlmmFeeDecoded),
    /// DLMM reward or emission event.
    Reward(MeteoraDlmmRewardDecoded),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MeteoraDlmmInstructionKind {
    CreatePool,
    Swap,
    LiquidityAdd,
    LiquidityRemove,
    LiquidityChange,
    PositionOpen,
    PositionClose,
    PositionUpdate,
    PoolLifecycle,
    Fee,
    Reward,
    Admin,
    Orderbook,
    Unknown,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MeteoraDlmmInstructionName {
    AddLiquidity,
    AddLiquidity2,
    AddLiquidityByStrategy,
    AddLiquidityByStrategy2,
    AddLiquidityByStrategyOneSide,
    AddLiquidityByWeight,
    AddLiquidityByWeight2,
    AddLiquidityOneSide,
    AddLiquidityOneSidePrecise,
    AddLiquidityOneSidePrecise2,
    CancelLimitOrder,
    ClaimFee,
    ClaimFee2,
    ClaimReward,
    ClaimReward2,
    CloseBinArray,
    CloseClaimFeeOperatorAccount,
    CloseLimitOrderIfEmpty,
    CloseOperatorAccount,
    ClosePosition,
    ClosePosition2,
    ClosePositionIfEmpty,
    ClosePresetParameter,
    ClosePresetParameter2,
    CloseTokenBadge,
    CreateOperatorAccount,
    DecreasePositionLength,
    ForIdlTypeGenerationDoNotCall,
    FundReward,
    GoToABin,
    IncreaseOracleLength,
    IncreasePositionLength,
    IncreasePositionLength2,
    InitializeBinArray,
    InitializeBinArrayBitmapExtension,
    InitializeCustomizablePermissionlessLbPair,
    InitializeCustomizablePermissionlessLbPair2,
    InitializeLbPair,
    InitializeLbPair2,
    InitializePermissionLbPair,
    InitializePosition,
    InitializePosition2,
    InitializePositionByOperator,
    InitializePositionPda,
    InitializePresetParameter,
    InitializePresetParameterV2,
    InitializeReward,
    InitializeTokenBadge,
    PlaceLimitOrder,
    RebalanceLiquidity,
    RemoveAllLiquidity,
    RemoveLiquidity,
    RemoveLiquidity2,
    RemoveLiquidityByRange,
    RemoveLiquidityByRange2,
    SetActivationPoint,
    SetPairStatus,
    SetPairStatusPermissionless,
    SetPermissionlessOperationBits,
    SetPreActivationDuration,
    SetPreActivationSwapAddress,
    Swap,
    Swap2,
    SwapExactOut,
    SwapExactOut2,
    SwapWithPriceImpact,
    SwapWithPriceImpact2,
    UpdateBaseFeeParameters,
    UpdateDynamicFeeParameters,
    UpdateFeesAndReward2,
    UpdateFeesAndRewards,
    UpdatePositionOperator,
    UpdateRewardDuration,
    UpdateRewardFunder,
    WithdrawIneligibleReward,
    WithdrawProtocolFee,
    ZapProtocolFee,
    Unknown,
}

impl MeteoraDlmmInstructionName {
    fn as_str(&self) -> &'static str {
        match self {
            Self::AddLiquidity => return "add_liquidity",
            Self::AddLiquidity2 => return "add_liquidity2",
            Self::AddLiquidityByStrategy => return "add_liquidity_by_strategy",
            Self::AddLiquidityByStrategy2 => return "add_liquidity_by_strategy2",
            Self::AddLiquidityByStrategyOneSide => return "add_liquidity_by_strategy_one_side",
            Self::AddLiquidityByWeight => return "add_liquidity_by_weight",
            Self::AddLiquidityByWeight2 => return "add_liquidity_by_weight2",
            Self::AddLiquidityOneSide => return "add_liquidity_one_side",
            Self::AddLiquidityOneSidePrecise => return "add_liquidity_one_side_precise",
            Self::AddLiquidityOneSidePrecise2 => return "add_liquidity_one_side_precise2",
            Self::CancelLimitOrder => return "cancel_limit_order",
            Self::ClaimFee => return "claim_fee",
            Self::ClaimFee2 => return "claim_fee2",
            Self::ClaimReward => return "claim_reward",
            Self::ClaimReward2 => return "claim_reward2",
            Self::CloseBinArray => return "close_bin_array",
            Self::CloseClaimFeeOperatorAccount => return "close_claim_fee_operator_account",
            Self::CloseLimitOrderIfEmpty => return "close_limit_order_if_empty",
            Self::CloseOperatorAccount => return "close_operator_account",
            Self::ClosePosition => return "close_position",
            Self::ClosePosition2 => return "close_position2",
            Self::ClosePositionIfEmpty => return "close_position_if_empty",
            Self::ClosePresetParameter => return "close_preset_parameter",
            Self::ClosePresetParameter2 => return "close_preset_parameter2",
            Self::CloseTokenBadge => return "close_token_badge",
            Self::CreateOperatorAccount => return "create_operator_account",
            Self::DecreasePositionLength => return "decrease_position_length",
            Self::ForIdlTypeGenerationDoNotCall => return "for_idl_type_generation_do_not_call",
            Self::FundReward => return "fund_reward",
            Self::GoToABin => return "go_to_a_bin",
            Self::IncreaseOracleLength => return "increase_oracle_length",
            Self::IncreasePositionLength => return "increase_position_length",
            Self::IncreasePositionLength2 => return "increase_position_length2",
            Self::InitializeBinArray => return "initialize_bin_array",
            Self::InitializeBinArrayBitmapExtension => {
                return "initialize_bin_array_bitmap_extension";
            },
            Self::InitializeCustomizablePermissionlessLbPair => {
                return "initialize_customizable_permissionless_lb_pair";
            },
            Self::InitializeCustomizablePermissionlessLbPair2 => {
                return "initialize_customizable_permissionless_lb_pair2";
            },
            Self::InitializeLbPair => return "initialize_lb_pair",
            Self::InitializeLbPair2 => return "initialize_lb_pair2",
            Self::InitializePermissionLbPair => return "initialize_permission_lb_pair",
            Self::InitializePosition => return "initialize_position",
            Self::InitializePosition2 => return "initialize_position2",
            Self::InitializePositionByOperator => return "initialize_position_by_operator",
            Self::InitializePositionPda => return "initialize_position_pda",
            Self::InitializePresetParameter => return "initialize_preset_parameter",
            Self::InitializePresetParameterV2 => return "initialize_preset_parameter_v2",
            Self::InitializeReward => return "initialize_reward",
            Self::InitializeTokenBadge => return "initialize_token_badge",
            Self::PlaceLimitOrder => return "place_limit_order",
            Self::RebalanceLiquidity => return "rebalance_liquidity",
            Self::RemoveAllLiquidity => return "remove_all_liquidity",
            Self::RemoveLiquidity => return "remove_liquidity",
            Self::RemoveLiquidity2 => return "remove_liquidity2",
            Self::RemoveLiquidityByRange => return "remove_liquidity_by_range",
            Self::RemoveLiquidityByRange2 => return "remove_liquidity_by_range2",
            Self::SetActivationPoint => return "set_activation_point",
            Self::SetPairStatus => return "set_pair_status",
            Self::SetPairStatusPermissionless => return "set_pair_status_permissionless",
            Self::SetPermissionlessOperationBits => return "set_permissionless_operation_bits",
            Self::SetPreActivationDuration => return "set_pre_activation_duration",
            Self::SetPreActivationSwapAddress => return "set_pre_activation_swap_address",
            Self::Swap => return "swap",
            Self::Swap2 => return "swap2",
            Self::SwapExactOut => return "swap_exact_out",
            Self::SwapExactOut2 => return "swap_exact_out2",
            Self::SwapWithPriceImpact => return "swap_with_price_impact",
            Self::SwapWithPriceImpact2 => return "swap_with_price_impact2",
            Self::UpdateBaseFeeParameters => return "update_base_fee_parameters",
            Self::UpdateDynamicFeeParameters => return "update_dynamic_fee_parameters",
            Self::UpdateFeesAndReward2 => return "update_fees_and_reward2",
            Self::UpdateFeesAndRewards => return "update_fees_and_rewards",
            Self::UpdatePositionOperator => return "update_position_operator",
            Self::UpdateRewardDuration => return "update_reward_duration",
            Self::UpdateRewardFunder => return "update_reward_funder",
            Self::WithdrawIneligibleReward => return "withdraw_ineligible_reward",
            Self::WithdrawProtocolFee => return "withdraw_protocol_fee",
            Self::ZapProtocolFee => return "zap_protocol_fee",
            Self::Unknown => return "unknown",
        }
    }

    fn kind(&self) -> MeteoraDlmmInstructionKind {
        match self {
            Self::AddLiquidity
            | Self::AddLiquidity2
            | Self::AddLiquidityByStrategy
            | Self::AddLiquidityByStrategy2
            | Self::AddLiquidityByStrategyOneSide
            | Self::AddLiquidityByWeight
            | Self::AddLiquidityByWeight2
            | Self::AddLiquidityOneSide
            | Self::AddLiquidityOneSidePrecise
            | Self::AddLiquidityOneSidePrecise2 => return MeteoraDlmmInstructionKind::LiquidityAdd,
            Self::CancelLimitOrder | Self::CloseLimitOrderIfEmpty | Self::PlaceLimitOrder => {
                return MeteoraDlmmInstructionKind::Orderbook;
            },
            Self::ClaimFee | Self::ClaimFee2 | Self::WithdrawProtocolFee | Self::ZapProtocolFee => {
                return MeteoraDlmmInstructionKind::Fee;
            },
            Self::ClaimReward
            | Self::ClaimReward2
            | Self::FundReward
            | Self::InitializeReward
            | Self::WithdrawIneligibleReward => return MeteoraDlmmInstructionKind::Reward,
            Self::CloseBinArray
            | Self::GoToABin
            | Self::IncreaseOracleLength
            | Self::InitializeBinArray
            | Self::InitializeBinArrayBitmapExtension => {
                return MeteoraDlmmInstructionKind::PoolLifecycle;
            },
            Self::CloseClaimFeeOperatorAccount
            | Self::CloseOperatorAccount
            | Self::ClosePresetParameter
            | Self::ClosePresetParameter2
            | Self::CloseTokenBadge
            | Self::CreateOperatorAccount
            | Self::InitializePresetParameter
            | Self::InitializePresetParameterV2
            | Self::InitializeTokenBadge
            | Self::SetActivationPoint
            | Self::SetPairStatus
            | Self::SetPairStatusPermissionless
            | Self::SetPermissionlessOperationBits
            | Self::SetPreActivationDuration
            | Self::SetPreActivationSwapAddress
            | Self::UpdateBaseFeeParameters
            | Self::UpdateDynamicFeeParameters
            | Self::UpdateFeesAndReward2
            | Self::UpdateFeesAndRewards
            | Self::UpdatePositionOperator
            | Self::UpdateRewardDuration
            | Self::UpdateRewardFunder => return MeteoraDlmmInstructionKind::Admin,
            Self::ClosePosition | Self::ClosePosition2 | Self::ClosePositionIfEmpty => {
                return MeteoraDlmmInstructionKind::PositionClose;
            },
            Self::DecreasePositionLength
            | Self::IncreasePositionLength
            | Self::IncreasePositionLength2 => return MeteoraDlmmInstructionKind::PositionUpdate,
            Self::ForIdlTypeGenerationDoNotCall => {
                return MeteoraDlmmInstructionKind::PoolLifecycle;
            },
            Self::InitializeCustomizablePermissionlessLbPair
            | Self::InitializeCustomizablePermissionlessLbPair2
            | Self::InitializeLbPair
            | Self::InitializeLbPair2
            | Self::InitializePermissionLbPair => return MeteoraDlmmInstructionKind::CreatePool,
            Self::InitializePosition
            | Self::InitializePosition2
            | Self::InitializePositionByOperator
            | Self::InitializePositionPda => return MeteoraDlmmInstructionKind::PositionOpen,
            Self::RebalanceLiquidity => return MeteoraDlmmInstructionKind::LiquidityChange,
            Self::RemoveAllLiquidity
            | Self::RemoveLiquidity
            | Self::RemoveLiquidity2
            | Self::RemoveLiquidityByRange
            | Self::RemoveLiquidityByRange2 => return MeteoraDlmmInstructionKind::LiquidityRemove,
            Self::Swap
            | Self::Swap2
            | Self::SwapExactOut
            | Self::SwapExactOut2
            | Self::SwapWithPriceImpact
            | Self::SwapWithPriceImpact2 => return MeteoraDlmmInstructionKind::Swap,
            Self::Unknown => return MeteoraDlmmInstructionKind::Unknown,
        }
    }
}

/// Meteora DLMM decoder.
#[derive(Debug, Clone, Default)]
pub struct MeteoraDlmmDecoder;

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

    /// Decodes one projected transaction into zero or more Meteora DLMM events.
    pub fn decode_transaction(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::MeteoraDlmmDecodedEvent>, crate::Error> {
        let transaction_id = match transaction.id {
            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 = match instruction.program_id.as_deref() {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id != crate::METEORA_DLMM_PROGRAM_ID {
                continue;
            }
            let instruction_id = match instruction.id {
                Some(instruction_id) => instruction_id,
                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 let Some(data) = instruction_data.as_ref() {
                let anchor_event = decode_anchor_non_swap_event(
                    transaction_id,
                    instruction_id,
                    transaction.signature.as_str(),
                    program_id,
                    instruction,
                    data.as_slice(),
                    instructions,
                );
                if let Some(anchor_event) = anchor_event {
                    decoded_events.push(anchor_event);
                    continue;
                }
            }
            let instruction_name = classify_instruction_name(
                parsed_json.as_ref(),
                instruction.parsed_type.as_deref(),
                instruction_data.as_deref(),
                &log_messages,
            );
            let instruction_kind = instruction_name.kind();
            if instruction_kind == MeteoraDlmmInstructionKind::Unknown
                || instruction_name == MeteoraDlmmInstructionName::ForIdlTypeGenerationDoNotCall
            {
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::PositionClose {
                let position_account = extract_account(&accounts, 0);
                let actor_wallet = extract_account(&accounts, 1);
                let rent_receiver = extract_account(&accounts, 2);
                let event_authority = extract_account(&accounts, 3);
                let program_account = extract_account(&accounts, 4);
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "proofStatus": "upstream_git_local_corpus_observed",
                    "position": position_account,
                    "actorWallet": actor_wallet,
                    "rentReceiver": rent_receiver,
                    "eventAuthority": event_authority,
                    "programAccount": program_account
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::PoolLifecycle(
                    crate::MeteoraDlmmPoolLifecycleDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account: None,
                        token_a_mint: None,
                        token_b_mint: None,
                        payload_json,
                    },
                ));
                continue;
            }
            let pool_account =
                resolve_dlmm_pool_account(instruction_name, parsed_json.as_ref(), &accounts);
            let token_a_mint =
                resolve_dlmm_token_x_mint(instruction_name, parsed_json.as_ref(), &accounts);
            let token_b_mint =
                resolve_dlmm_token_y_mint(instruction_name, parsed_json.as_ref(), &accounts);
            let config_account =
                resolve_dlmm_config_account(instruction_name, parsed_json.as_ref(), &accounts);
            if instruction_kind == MeteoraDlmmInstructionKind::CreatePool {
                if pool_account.is_none() || token_a_mint.is_none() || token_b_mint.is_none() {
                    continue;
                }
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": "create_pool",
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": "create_pool",
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "configAccount": config_account
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::CreatePool(
                    crate::MeteoraDlmmCreatePoolDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        config_account,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::Swap {
                if pool_account.is_none() || token_a_mint.is_none() || token_b_mint.is_none() {
                    continue;
                }
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let reserve_x_account = resolve_dlmm_reserve_x_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let reserve_y_account = resolve_dlmm_reserve_y_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let user_token_in_account = resolve_dlmm_user_token_in_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let user_token_out_account = resolve_dlmm_user_token_out_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let trade_side = infer_trade_side(parsed_json.as_ref());
                let anchor_swap_event =
                    find_best_anchor_swap_event_for_instruction(instruction, instructions);
                let anchor_amount_in_raw =
                    extract_json_string_field(anchor_swap_event.as_ref(), "amountInRaw");
                let anchor_amount_out_raw =
                    extract_json_string_field(anchor_swap_event.as_ref(), "amountOutRaw");
                let anchor_fee_raw =
                    extract_json_string_field(anchor_swap_event.as_ref(), "feeRaw");
                let anchor_protocol_fee_raw =
                    extract_json_string_field(anchor_swap_event.as_ref(), "protocolFeeRaw");
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": "swap",
                    "skipTradeReason": serde_json::Value::Null,
                    "skipCandleReason": serde_json::Value::Null,
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "reserveXAccount": reserve_x_account,
                    "reserveYAccount": reserve_y_account,
                    "userTokenInAccount": user_token_in_account,
                    "userTokenOutAccount": user_token_out_account,
                    "tradeSide": format!("{:?}", trade_side),
                    "amountIn": anchor_amount_in_raw,
                    "amountOut": anchor_amount_out_raw,
                    "feeAmountRaw": anchor_fee_raw,
                    "protocolFeeAmountRaw": anchor_protocol_fee_raw,
                    "anchorSwapEvent": anchor_swap_event
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::Swap(
                    crate::MeteoraDlmmSwapDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        trade_side,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        reserve_x_account,
                        reserve_y_account,
                        user_token_in_account,
                        user_token_out_account,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::LiquidityAdd
                || instruction_kind == MeteoraDlmmInstructionKind::LiquidityRemove
                || instruction_kind == MeteoraDlmmInstructionKind::LiquidityChange
                || instruction_kind == MeteoraDlmmInstructionKind::PositionOpen
                || instruction_kind == MeteoraDlmmInstructionKind::PositionUpdate
            {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let actor_wallet =
                    resolve_dlmm_actor_wallet(instruction_name, parsed_json.as_ref(), &accounts);
                let base_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "baseAmountRaw",
                        "base_amount_raw",
                        "tokenXAmount",
                        "token_x_amount",
                        "amountX",
                        "amount_x",
                    ],
                );
                let quote_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "quoteAmountRaw",
                        "quote_amount_raw",
                        "tokenYAmount",
                        "token_y_amount",
                        "amountY",
                        "amount_y",
                    ],
                );
                let liquidity_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "liquidity",
                        "liquidityAmount",
                        "liquidity_amount",
                        "binLiquidity",
                        "bin_liquidity",
                    ],
                );
                let range_from_bin_id = read_i32_string_from_instruction_data(
                    instruction_name,
                    instruction_data.as_deref(),
                    8,
                );
                let range_to_bin_id = read_i32_string_from_instruction_data(
                    instruction_name,
                    instruction_data.as_deref(),
                    12,
                );
                let range_bps_to_remove = read_u16_string_from_instruction_data(
                    instruction_name,
                    instruction_data.as_deref(),
                    16,
                );
                let liquidity_position_account =
                    resolve_dlmm_liquidity_position_account(instruction_name, &accounts);
                let bin_array_bitmap_extension = extract_account(&accounts, 2);
                let user_token_x_account = extract_account(&accounts, 3);
                let user_token_y_account = extract_account(&accounts, 4);
                let reserve_x_account = extract_account(&accounts, 5);
                let reserve_y_account = extract_account(&accounts, 6);
                let event_authority_account =
                    resolve_dlmm_liquidity_event_authority_account(instruction_name, &accounts);
                let program_account =
                    resolve_dlmm_liquidity_program_account(instruction_name, &accounts);
                let proof_status = resolve_dlmm_instruction_proof_status(instruction_name);
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "actorWallet": actor_wallet,
                    "baseAmountRaw": base_amount_raw,
                    "quoteAmountRaw": quote_amount_raw,
                    "liquidityAmountRaw": liquidity_amount_raw,
                    "materializationSource": "dlmm_instruction_liquidity_materialization",
                    "rangeFromBinId": range_from_bin_id,
                    "rangeToBinId": range_to_bin_id,
                    "rangeBpsToRemove": range_bps_to_remove,
                    "positionAccount": liquidity_position_account,
                    "binArrayBitmapExtension": bin_array_bitmap_extension,
                    "userTokenXAccount": user_token_x_account,
                    "userTokenYAccount": user_token_y_account,
                    "reserveXAccount": reserve_x_account,
                    "reserveYAccount": reserve_y_account,
                    "eventAuthority": event_authority_account,
                    "programAccount": program_account,
                    "proofStatus": proof_status
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::Liquidity(
                    crate::MeteoraDlmmLiquidityDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        actor_wallet,
                        base_amount_raw,
                        quote_amount_raw,
                        liquidity_amount_raw,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::Fee {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let actor_wallet =
                    resolve_dlmm_actor_wallet(instruction_name, parsed_json.as_ref(), &accounts);
                let fee_token_mint = extract_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "feeTokenMint",
                        "fee_token_mint",
                        "tokenMint",
                        "token_mint",
                        "mint",
                        "quoteMint",
                        "quote_mint",
                    ],
                );
                let fee_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "feeAmountRaw",
                        "fee_amount_raw",
                        "feeAmount",
                        "fee_amount",
                        "protocolFeeAmount",
                        "protocol_fee_amount",
                        "amount",
                    ],
                );
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "actorWallet": actor_wallet,
                    "feeTokenMint": fee_token_mint,
                    "feeAmountRaw": fee_amount_raw,
                    "materializationSource": "dlmm_instruction_fee_materialization"
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::Fee(
                    crate::MeteoraDlmmFeeDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        actor_wallet,
                        fee_token_mint,
                        fee_amount_raw,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::Reward {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let actor_wallet =
                    resolve_dlmm_actor_wallet(instruction_name, parsed_json.as_ref(), &accounts);
                let reward_token_mint = resolve_dlmm_reward_token_mint(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let reward_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "rewardAmountRaw",
                        "reward_amount_raw",
                        "rewardAmount",
                        "reward_amount",
                        "totalReward",
                        "total_reward",
                        "amount",
                    ],
                );
                let reward_index = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &["rewardIndex", "reward_index", "index"],
                );
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "actorWallet": actor_wallet,
                    "rewardIndex": reward_index,
                    "rewardTokenMint": reward_token_mint,
                    "rewardAmountRaw": reward_amount_raw,
                    "materializationSource": "dlmm_instruction_reward_materialization"
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::Reward(
                    crate::MeteoraDlmmRewardDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        actor_wallet,
                        reward_token_mint,
                        reward_amount_raw,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::Admin
                || instruction_kind == MeteoraDlmmInstructionKind::Orderbook
            {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let actor_wallet =
                    resolve_dlmm_actor_wallet(instruction_name, parsed_json.as_ref(), &accounts);
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "actorWallet": actor_wallet,
                    "materializationTarget": if instruction_kind == MeteoraDlmmInstructionKind::Orderbook { "orderbook" } else { "pool_admin" },
                    "skipTradeReason": "dlmm_instruction_not_a_verified_fill",
                    "skipCandleReason": "dlmm_instruction_not_a_verified_fill"
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::PoolLifecycle(
                    crate::MeteoraDlmmPoolLifecycleDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::PoolLifecycle {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::PoolLifecycle(
                    crate::MeteoraDlmmPoolLifecycleDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        payload_json,
                    },
                ));
            }
        }
        return Ok(decoded_events);
    }
}

fn decode_anchor_non_swap_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if !is_dlmm_anchor_self_cpi_log(data) {
        return None;
    }
    if data.len() < 16 {
        return None;
    }
    let event_discriminator = read_8_bytes(data, 8);
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_LB_PAIR_CREATE {
        return decode_anchor_lb_pair_create_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_ADD_LIQUIDITY {
        return decode_anchor_liquidity_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "meteora_dlmm.add_liquidity_event",
            "add_liquidity_event",
            "1f5e7d5ae3343dba",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_REMOVE_LIQUIDITY {
        return decode_anchor_liquidity_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "meteora_dlmm.remove_liquidity_event",
            "remove_liquidity_event",
            "74f461e8671f983a",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_CLAIM_FEE {
        return decode_anchor_claim_fee_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "claim_fee_event",
            "4b7a9a308c4a7ba3",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_CLAIM_FEE2 {
        return decode_anchor_claim_fee_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "claim_fee2_event",
            "e8abf2613a4d232d",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_CLAIM_REWARD {
        return decode_anchor_claim_reward_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "claim_reward_event",
            "947486cc16ab555f",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_CLAIM_REWARD2 {
        return decode_anchor_claim_reward_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "claim_reward2_event",
            "1b8ff421502b6e92",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_FUND_REWARD {
        return decode_anchor_fund_reward_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_POSITION_CREATE {
        return decode_anchor_position_create_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_POSITION_CLOSE {
        return decode_anchor_position_close_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_COMPOSITION_FEE {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "80977b6a1166718e",
            "composition_fee_event",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_INITIALIZE_REWARD {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "d399583e953cb146",
            "initialize_reward_event",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_WITHDRAW_INELIGIBLE_REWARD {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "e7bd419566d79af4",
            "withdraw_ineligible_reward_event",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_PLACE_LIMIT_ORDER_EVT {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "2b4f1ba9f41ce13f",
            "place_limit_order_evt",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_CANCEL_LIMIT_ORDER_EVT {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "83eac285090ebdd1",
            "cancel_limit_order_evt",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_CLOSE_LIMIT_ORDER_EVT {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "8e87084c5c3f7653",
            "close_limit_order_evt",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_FEE_PARAMETER_UPDATE {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "304cf17590d7f22c",
            "fee_parameter_update_event",
            instructions,
        );
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_DYNAMIC_FEE_PARAMETER_UPDATE {
        return decode_anchor_generic_event(
            transaction_id,
            instruction_id,
            signature,
            program_id,
            instruction,
            data,
            "5858b287c2925bf3",
            "dynamic_fee_parameter_update_event",
            instructions,
        );
    }
    let event_discriminator_hex = match first_8_bytes_hex(&event_discriminator) {
        Some(event_discriminator_hex) => event_discriminator_hex,
        None => return None,
    };
    let anchor_event_name =
        match dlmm_anchor_event_name_from_discriminator_hex(event_discriminator_hex.as_str()) {
            Some(anchor_event_name) => anchor_event_name,
            None => return None,
        };
    return decode_anchor_generic_event(
        transaction_id,
        instruction_id,
        signature,
        program_id,
        instruction,
        data,
        event_discriminator_hex.as_str(),
        anchor_event_name,
        instructions,
    );
}

fn dlmm_anchor_event_name_from_discriminator_hex(
    event_discriminator_hex: &str,
) -> std::option::Option<&'static str> {
    match event_discriminator_hex {
        "1f5e7d5ae3343dba" => return Some("add_liquidity_event"),
        "83eac285090ebdd1" => return Some("cancel_limit_order_evt"),
        "4b7a9a308c4a7ba3" => return Some("claim_fee_event"),
        "e8abf2613a4d232d" => return Some("claim_fee2_event"),
        "947486cc16ab555f" => return Some("claim_reward_event"),
        "1b8ff421502b6e92" => return Some("claim_reward2_event"),
        "8e87084c5c3f7653" => return Some("close_limit_order_evt"),
        "80977b6a1166718e" => return Some("composition_fee_event"),
        "3476eb55aca90f80" => return Some("decrease_position_length_event"),
        "5858b287c2925bf3" => return Some("dynamic_fee_parameter_update_event"),
        "304cf17590d7f22c" => return Some("fee_parameter_update_event"),
        "f6e43a8291aa4fcc" => return Some("fund_reward_event"),
        "3b8a4c448a83b043" => return Some("go_to_a_bin_event"),
        "63f91179a69ccfd7" => return Some("increase_observation_event"),
        "9def2acc1e38df2e" => return Some("increase_position_length_event"),
        "d399583e953cb146" => return Some("initialize_reward_event"),
        "b94afc7d1bd7bc6f" => return Some("lb_pair_create_event"),
        "2b4f1ba9f41ce13f" => return Some("place_limit_order_evt"),
        "ffc4106b1cca3580" => return Some("position_close_event"),
        "908efc549d352579" => return Some("position_create_event"),
        "006d75b33d5bc7c8" => return Some("rebalancing_event"),
        "74f461e8671f983a" => return Some("remove_liquidity_event"),
        "c3e593f51d7d30a8" => return Some("set_position_permissionless_operation_bits_evt"),
        "516ce3becdd00ac4" => return Some("swap_event"),
        "2e7452d7941b544d" => return Some("swap2_evt"),
        "85d642e0400c07bf" => return Some("update_position_lock_release_point_event"),
        "277330ccf62f4239" => return Some("update_position_operator_event"),
        "dff5e099311da3ac" => return Some("update_reward_duration_event"),
        "e0b2ae4afca555b4" => return Some("update_reward_funder_event"),
        "e7bd419566d79af4" => return Some("withdraw_ineligible_reward_event"),
        _ => return None,
    }
}

fn decode_anchor_generic_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    event_discriminator_hex: &str,
    anchor_event_name: &str,
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    let event_kind = format!("meteora_dlmm.{}", anchor_event_name);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let pool_account =
        match resolve_anchor_parent_pool_account(parent_name, parent_accounts.as_slice()) {
            Some(pool_account) => Some(pool_account),
            None => extract_anchor_pool_account_from_payload(data),
        };
    let actor_wallet = extract_anchor_actor_from_payload(data);
    let token_x_mint = resolve_anchor_parent_token_x_mint(parent_name, parent_accounts.as_slice());
    let token_y_mint = resolve_anchor_parent_token_y_mint(parent_name, parent_accounts.as_slice());
    let reward_mint = resolve_anchor_parent_reward_mint(parent_name, parent_accounts.as_slice());
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": anchor_event_name,
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": anchor_event_name,
        "anchorEventDiscriminatorHex": event_discriminator_hex,
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "local_idl_discriminator_observed_synthetic_materialization",
        "poolAccount": pool_account,
        "actorWallet": actor_wallet,
        "tokenXMint": token_x_mint,
        "tokenYMint": token_y_mint,
        "rewardTokenMint": reward_mint,
        "materializationSource": "dlmm_anchor_generic_materialization",
        "skipTradeReason": "dlmm_anchor_event_not_a_verified_instruction_fill",
        "skipCandleReason": "dlmm_anchor_event_not_a_verified_instruction_fill"
    });
    if crate::is_dex_fee_event_kind(event_kind.as_str()) {
        return Some(crate::MeteoraDlmmDecodedEvent::Fee(crate::MeteoraDlmmFeeDecoded {
            transaction_id,
            instruction_id,
            signature: signature.to_string(),
            program_id: program_id.to_string(),
            event_kind,
            pool_account,
            actor_wallet,
            fee_token_mint: token_x_mint,
            fee_amount_raw: None,
            payload_json,
        }));
    }
    if crate::is_dex_reward_event_kind(event_kind.as_str()) {
        return Some(crate::MeteoraDlmmDecodedEvent::Reward(crate::MeteoraDlmmRewardDecoded {
            transaction_id,
            instruction_id,
            signature: signature.to_string(),
            program_id: program_id.to_string(),
            event_kind,
            pool_account,
            actor_wallet,
            reward_token_mint: reward_mint,
            reward_amount_raw: None,
            payload_json,
        }));
    }
    if crate::is_dex_liquidity_event_kind(event_kind.as_str()) {
        return Some(crate::MeteoraDlmmDecodedEvent::Liquidity(
            crate::MeteoraDlmmLiquidityDecoded {
                transaction_id,
                instruction_id,
                signature: signature.to_string(),
                program_id: program_id.to_string(),
                event_kind,
                pool_account,
                token_a_mint: token_x_mint,
                token_b_mint: token_y_mint,
                actor_wallet,
                base_amount_raw: None,
                quote_amount_raw: None,
                liquidity_amount_raw: None,
                payload_json,
            },
        ));
    }
    return Some(crate::MeteoraDlmmDecodedEvent::PoolLifecycle(
        crate::MeteoraDlmmPoolLifecycleDecoded {
            transaction_id,
            instruction_id,
            signature: signature.to_string(),
            program_id: program_id.to_string(),
            event_kind,
            pool_account,
            token_a_mint: token_x_mint,
            token_b_mint: token_y_mint,
            payload_json,
        },
    ));
}

fn extract_anchor_pool_account_from_payload(
    data: &[u8],
) -> std::option::Option<std::string::String> {
    if data.len() < 48 {
        return None;
    }
    return read_pubkey_string(data, 16);
}

fn extract_anchor_actor_from_payload(data: &[u8]) -> std::option::Option<std::string::String> {
    if data.len() < 80 {
        return None;
    }
    return read_pubkey_string(data, 48);
}

fn find_parent_dlmm_instruction<'a>(
    instruction: &crate::ChainInstructionDto,
    instructions: &'a [crate::ChainInstructionDto],
) -> std::option::Option<&'a crate::ChainInstructionDto> {
    if let Some(parent_instruction_id) = instruction.parent_instruction_id {
        for candidate in instructions {
            if candidate.id == Some(parent_instruction_id) {
                return Some(candidate);
            }
        }
    }
    for candidate in instructions {
        if candidate.inner_instruction_index.is_some() {
            continue;
        }
        if candidate.instruction_index != instruction.instruction_index {
            continue;
        }
        if candidate.program_id.as_deref() != Some(crate::METEORA_DLMM_PROGRAM_ID) {
            continue;
        }
        return Some(candidate);
    }
    return None;
}

fn parent_dlmm_instruction_name(
    parent: std::option::Option<&crate::ChainInstructionDto>,
) -> MeteoraDlmmInstructionName {
    let parent = match parent {
        Some(parent) => parent,
        None => return MeteoraDlmmInstructionName::Unknown,
    };
    let parent_data_result = decode_instruction_data_json(parent.data_json.as_ref());
    let parent_data = match parent_data_result {
        Ok(parent_data) => parent_data,
        Err(_) => None,
    };
    return classify_instruction_name(
        None,
        parent.parsed_type.as_deref(),
        parent_data.as_deref(),
        &[],
    );
}

fn parent_dlmm_accounts(
    parent: std::option::Option<&crate::ChainInstructionDto>,
) -> std::vec::Vec<std::string::String> {
    let parent = match parent {
        Some(parent) => parent,
        None => return std::vec::Vec::new(),
    };
    let parsed = parse_accounts_json(parent.accounts_json.as_str());
    match parsed {
        Ok(parsed) => return parsed,
        Err(_) => return std::vec::Vec::new(),
    }
}

fn resolve_anchor_parent_token_x_mint(
    parent_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match parent_name {
        MeteoraDlmmInstructionName::ClaimFee => return extract_account(accounts, 9),
        MeteoraDlmmInstructionName::ClaimFee2 => return extract_account(accounts, 7),
        MeteoraDlmmInstructionName::WithdrawProtocolFee => return extract_account(accounts, 3),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidity2
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity2 => return extract_account(accounts, 7),
        MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => return extract_account(accounts, 6),
        _ => return None,
    }
}

fn resolve_anchor_parent_token_y_mint(
    parent_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match parent_name {
        MeteoraDlmmInstructionName::ClaimFee => return extract_account(accounts, 10),
        MeteoraDlmmInstructionName::ClaimFee2 => return extract_account(accounts, 8),
        MeteoraDlmmInstructionName::WithdrawProtocolFee => return extract_account(accounts, 4),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidity2
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity2 => return extract_account(accounts, 8),
        MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => return extract_account(accounts, 7),
        _ => return None,
    }
}

fn resolve_anchor_parent_reward_mint(
    parent_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match parent_name {
        MeteoraDlmmInstructionName::ClaimReward => return extract_account(accounts, 6),
        MeteoraDlmmInstructionName::ClaimReward2 => return extract_account(accounts, 4),
        MeteoraDlmmInstructionName::FundReward
        | MeteoraDlmmInstructionName::InitializeReward
        | MeteoraDlmmInstructionName::WithdrawIneligibleReward => {
            return extract_account(accounts, 2);
        },
        _ => return None,
    }
}

fn resolve_anchor_parent_pool_account(
    parent_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match parent_name {
        MeteoraDlmmInstructionName::ClosePosition => return extract_account(accounts, 1),
        MeteoraDlmmInstructionName::InitializePosition
        | MeteoraDlmmInstructionName::InitializePosition2
        | MeteoraDlmmInstructionName::InitializePositionByOperator
        | MeteoraDlmmInstructionName::InitializePositionPda => return extract_account(accounts, 2),
        _ => return extract_account(accounts, 0),
    }
}

fn amount_is_non_zero(amount: &std::option::Option<std::string::String>) -> bool {
    let amount = match amount {
        Some(amount) => amount.trim(),
        None => return false,
    };
    if amount.is_empty() {
        return false;
    }
    let parsed = amount.parse::<u128>();
    match parsed {
        Ok(parsed) => return parsed > 0,
        Err(_) => return true,
    }
}

fn build_fee_amount_leg(
    token_mint: &std::option::Option<std::string::String>,
    amount_raw: &std::option::Option<std::string::String>,
    amount_source: &str,
    source_account: std::option::Option<std::string::String>,
    destination_account: std::option::Option<std::string::String>,
) -> std::option::Option<serde_json::Value> {
    if !amount_is_non_zero(amount_raw) {
        return None;
    }
    let token_mint = match token_mint {
        Some(token_mint) => token_mint,
        None => return None,
    };
    let amount_raw = match amount_raw {
        Some(amount_raw) => amount_raw,
        None => return None,
    };
    let mut object = serde_json::Map::new();
    object.insert("tokenMint".to_string(), serde_json::Value::String(token_mint.clone()));
    object.insert("amountRaw".to_string(), serde_json::Value::String(amount_raw.clone()));
    object.insert("amountSource".to_string(), serde_json::Value::String(amount_source.to_string()));
    if let Some(source_account) = source_account {
        object.insert("sourceAccount".to_string(), serde_json::Value::String(source_account));
    }
    if let Some(destination_account) = destination_account {
        object.insert(
            "destinationAccount".to_string(),
            serde_json::Value::String(destination_account),
        );
    }
    return Some(serde_json::Value::Object(object));
}

fn build_dual_fee_amount_legs(
    token_x_mint: &std::option::Option<std::string::String>,
    fee_x: &std::option::Option<std::string::String>,
    token_y_mint: &std::option::Option<std::string::String>,
    fee_y: &std::option::Option<std::string::String>,
    amount_source: &str,
) -> serde_json::Value {
    let mut legs = std::vec::Vec::new();
    let x_leg = build_fee_amount_leg(token_x_mint, fee_x, amount_source, None, None);
    if let Some(x_leg) = x_leg {
        legs.push(x_leg);
    }
    let y_leg = build_fee_amount_leg(token_y_mint, fee_y, amount_source, None, None);
    if let Some(y_leg) = y_leg {
        legs.push(y_leg);
    }
    return serde_json::Value::Array(legs);
}

fn decode_anchor_lb_pair_create_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 114 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let bin_step = read_u16_string(data, 48);
    let token_x = read_pubkey_string(data, 50);
    let token_y = read_pubkey_string(data, 82);
    let event_kind = "meteora_dlmm.lb_pair_create_event".to_string();
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": "lb_pair_create_event",
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": "lb_pair_create_event",
        "anchorEventDiscriminatorHex": "b94afc7d1bd7bc6f",
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "lbPair": lb_pair,
        "poolAccount": lb_pair,
        "binStep": bin_step,
        "tokenX": token_x,
        "tokenY": token_y,
        "tokenAMint": token_x,
        "tokenBMint": token_y
    });
    return Some(crate::MeteoraDlmmDecodedEvent::PoolLifecycle(
        crate::MeteoraDlmmPoolLifecycleDecoded {
            transaction_id,
            instruction_id,
            signature: signature.to_string(),
            program_id: program_id.to_string(),
            event_kind,
            pool_account: lb_pair,
            token_a_mint: token_x,
            token_b_mint: token_y,
            payload_json,
        },
    ));
}

fn decode_anchor_liquidity_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    event_kind_text: &str,
    anchor_event_name: &str,
    event_discriminator_hex: &str,
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 132 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let from = read_pubkey_string(data, 48);
    let position = read_pubkey_string(data, 80);
    let amount_x = read_u64_string(data, 112);
    let amount_y = read_u64_string(data, 120);
    let active_bin_id = read_i32_string(data, 128);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let token_x_mint = resolve_anchor_parent_token_x_mint(parent_name, parent_accounts.as_slice());
    let token_y_mint = resolve_anchor_parent_token_y_mint(parent_name, parent_accounts.as_slice());
    let event_kind = event_kind_text.to_string();
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": anchor_event_name,
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": anchor_event_name,
        "anchorEventDiscriminatorHex": event_discriminator_hex,
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "lbPair": lb_pair,
        "poolAccount": lb_pair,
        "from": from,
        "actorWallet": from,
        "position": position,
        "tokenAMint": token_x_mint,
        "tokenBMint": token_y_mint,
        "amounts": [amount_x.clone(), amount_y.clone()],
        "baseAmountRaw": amount_x,
        "quoteAmountRaw": amount_y,
        "activeBinId": active_bin_id,
        "materializationSource": "anchor_liquidity_event_amounts_with_parent_accounts"
    });
    return Some(crate::MeteoraDlmmDecodedEvent::Liquidity(crate::MeteoraDlmmLiquidityDecoded {
        transaction_id,
        instruction_id,
        signature: signature.to_string(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account: lb_pair,
        token_a_mint: token_x_mint,
        token_b_mint: token_y_mint,
        actor_wallet: from,
        base_amount_raw: amount_x,
        quote_amount_raw: amount_y,
        liquidity_amount_raw: None,
        payload_json,
    }));
}

fn decode_anchor_claim_fee_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    anchor_event_name: &str,
    event_discriminator_hex: &str,
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 128 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let position = read_pubkey_string(data, 48);
    let owner = read_pubkey_string(data, 80);
    let fee_x = read_u64_string(data, 112);
    let fee_y = read_u64_string(data, 120);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let token_x_mint = resolve_anchor_parent_token_x_mint(parent_name, parent_accounts.as_slice());
    let token_y_mint = resolve_anchor_parent_token_y_mint(parent_name, parent_accounts.as_slice());
    let fee_amount_legs = build_dual_fee_amount_legs(
        &token_x_mint,
        &fee_x,
        &token_y_mint,
        &fee_y,
        "anchor_claim_fee_event_amounts",
    );
    let event_kind = format!("meteora_dlmm.{}", anchor_event_name);
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": anchor_event_name,
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": anchor_event_name,
        "anchorEventDiscriminatorHex": event_discriminator_hex,
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "lbPair": lb_pair,
        "poolAccount": lb_pair,
        "position": position,
        "owner": owner,
        "actorWallet": owner,
        "tokenXMint": token_x_mint,
        "tokenYMint": token_y_mint,
        "feeXRaw": fee_x,
        "feeYRaw": fee_y,
        "feeAmountLegs": fee_amount_legs,
        "materializationSource": "anchor_claim_fee_event_amounts_with_parent_accounts"
    });
    return Some(crate::MeteoraDlmmDecodedEvent::Fee(crate::MeteoraDlmmFeeDecoded {
        transaction_id,
        instruction_id,
        signature: signature.to_string(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account: lb_pair,
        actor_wallet: owner,
        fee_token_mint: None,
        fee_amount_raw: None,
        payload_json,
    }));
}

fn decode_anchor_claim_reward_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    anchor_event_name: &str,
    event_discriminator_hex: &str,
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 128 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let position = read_pubkey_string(data, 48);
    let owner = read_pubkey_string(data, 80);
    let reward_index = read_u64_string(data, 112);
    let total_reward = read_u64_string(data, 120);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let reward_mint = resolve_anchor_parent_reward_mint(parent_name, parent_accounts.as_slice());
    let event_kind = format!("meteora_dlmm.{}", anchor_event_name);
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": anchor_event_name,
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": anchor_event_name,
        "anchorEventDiscriminatorHex": event_discriminator_hex,
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "lbPair": lb_pair,
        "poolAccount": lb_pair,
        "position": position,
        "owner": owner,
        "actorWallet": owner,
        "rewardIndex": reward_index,
        "rewardTokenMint": reward_mint,
        "totalRewardRaw": total_reward,
        "rewardAmountRaw": total_reward,
        "materializationSource": "anchor_claim_reward_event_amount_with_parent_accounts"
    });
    return Some(crate::MeteoraDlmmDecodedEvent::Reward(crate::MeteoraDlmmRewardDecoded {
        transaction_id,
        instruction_id,
        signature: signature.to_string(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account: lb_pair,
        actor_wallet: owner,
        reward_token_mint: reward_mint,
        reward_amount_raw: total_reward,
        payload_json,
    }));
}

fn decode_anchor_fund_reward_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 96 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let funder = read_pubkey_string(data, 48);
    let reward_index = read_u64_string(data, 80);
    let amount = read_u64_string(data, 88);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let reward_mint = resolve_anchor_parent_reward_mint(parent_name, parent_accounts.as_slice());
    let event_kind = "meteora_dlmm.fund_reward_event".to_string();
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": "fund_reward_event",
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": "fund_reward_event",
        "anchorEventDiscriminatorHex": "f6e43a8291aa4fcc",
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "lbPair": lb_pair,
        "poolAccount": lb_pair,
        "funder": funder,
        "actorWallet": funder,
        "rewardIndex": reward_index,
        "rewardTokenMint": reward_mint,
        "amount": amount,
        "rewardAmountRaw": amount,
        "materializationSource": "anchor_fund_reward_event_amount_with_parent_accounts"
    });
    return Some(crate::MeteoraDlmmDecodedEvent::Reward(crate::MeteoraDlmmRewardDecoded {
        transaction_id,
        instruction_id,
        signature: signature.to_string(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account: lb_pair,
        actor_wallet: funder,
        reward_token_mint: reward_mint,
        reward_amount_raw: amount,
        payload_json,
    }));
}

fn decode_anchor_position_create_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 112 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let position = read_pubkey_string(data, 48);
    let owner = read_pubkey_string(data, 80);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let parent_pool_account =
        resolve_anchor_parent_pool_account(parent_name, parent_accounts.as_slice());
    let event_kind = "meteora_dlmm.position_create_event".to_string();
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": "position_create_event",
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": "position_create_event",
        "anchorEventDiscriminatorHex": "908efc549d352579",
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "lbPair": lb_pair,
        "poolAccount": lb_pair,
        "position": position,
        "owner": owner,
        "actorWallet": owner,
        "parentPoolAccount": parent_pool_account,
        "materializationSource": "anchor_position_create_event"
    });
    return Some(crate::MeteoraDlmmDecodedEvent::Liquidity(crate::MeteoraDlmmLiquidityDecoded {
        transaction_id,
        instruction_id,
        signature: signature.to_string(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account: lb_pair,
        token_a_mint: None,
        token_b_mint: None,
        actor_wallet: owner,
        base_amount_raw: None,
        quote_amount_raw: None,
        liquidity_amount_raw: None,
        payload_json,
    }));
}

fn decode_anchor_position_close_event(
    transaction_id: i64,
    instruction_id: i64,
    signature: &str,
    program_id: &str,
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<crate::MeteoraDlmmDecodedEvent> {
    if data.len() < 80 {
        return None;
    }
    let position = read_pubkey_string(data, 16);
    let owner = read_pubkey_string(data, 48);
    let parent = find_parent_dlmm_instruction(instruction, instructions);
    let parent_name = parent_dlmm_instruction_name(parent);
    let parent_accounts = parent_dlmm_accounts(parent);
    let parent_pool_account =
        resolve_anchor_parent_pool_account(parent_name, parent_accounts.as_slice());
    let event_kind = "meteora_dlmm.position_close_event".to_string();
    let payload_json = serde_json::json!({
        "decoder": "meteora_dlmm",
        "eventKind": "position_close_event",
        "decodedInstructionName": "anchor_self_cpi_log",
        "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
        "signature": signature,
        "instructionId": instruction_id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "stackHeight": instruction.stack_height,
        "anchorSelfCpiLog": true,
        "anchorEventName": "position_close_event",
        "anchorEventDiscriminatorHex": "ffc4106b1cca3580",
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "proofStatus": "upstream_git_layout_unverified",
        "position": position,
        "owner": owner,
        "actorWallet": owner,
        "parentPoolAccount": parent_pool_account,
        "materializationSource": "anchor_position_create_event"
    });
    return Some(crate::MeteoraDlmmDecodedEvent::Liquidity(crate::MeteoraDlmmLiquidityDecoded {
        transaction_id,
        instruction_id,
        signature: signature.to_string(),
        program_id: program_id.to_string(),
        event_kind,
        pool_account: parent_pool_account,
        token_a_mint: None,
        token_b_mint: None,
        actor_wallet: owner,
        base_amount_raw: None,
        quote_amount_raw: None,
        liquidity_amount_raw: None,
        payload_json,
    }));
}

fn find_best_anchor_swap_event_for_instruction(
    instruction: &crate::ChainInstructionDto,
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<serde_json::Value> {
    let instruction_id = match instruction.id {
        Some(instruction_id) => instruction_id,
        None => return None,
    };
    let parent_instruction_id = instruction.parent_instruction_id;
    let next_dlmm_business_instruction_id =
        find_next_dlmm_business_instruction_id(instruction_id, parent_instruction_id, instructions);
    let mut legacy_swap_event = None;
    let mut swap2_event = None;
    for candidate in instructions {
        let candidate_id = match candidate.id {
            Some(candidate_id) => candidate_id,
            None => continue,
        };
        if candidate_id <= instruction_id {
            continue;
        }
        if let Some(next_instruction_id) = next_dlmm_business_instruction_id {
            if candidate_id >= next_instruction_id {
                continue;
            }
        }
        if candidate.parent_instruction_id != parent_instruction_id {
            continue;
        }
        let candidate_program_id = match candidate.program_id.as_deref() {
            Some(candidate_program_id) => candidate_program_id,
            None => continue,
        };
        if candidate_program_id != crate::METEORA_DLMM_PROGRAM_ID {
            continue;
        }
        let candidate_data_result = decode_instruction_data_json(candidate.data_json.as_ref());
        let candidate_data = match candidate_data_result {
            Ok(candidate_data) => candidate_data,
            Err(_) => continue,
        };
        let candidate_data = match candidate_data {
            Some(candidate_data) => candidate_data,
            None => continue,
        };
        let decoded_event = decode_anchor_swap_event(candidate, candidate_data.as_slice());
        let decoded_event = match decoded_event {
            Some(decoded_event) => decoded_event,
            None => continue,
        };
        let event_name =
            decoded_event.get("anchorEventName").and_then(|value| return value.as_str());
        if event_name == Some("swap2_evt") {
            swap2_event = Some(decoded_event);
            continue;
        }
        if event_name == Some("swap") {
            legacy_swap_event = Some(decoded_event);
            continue;
        }
    }
    if swap2_event.is_some() {
        return swap2_event;
    }
    return legacy_swap_event;
}

fn find_next_dlmm_business_instruction_id(
    instruction_id: i64,
    parent_instruction_id: std::option::Option<i64>,
    instructions: &[crate::ChainInstructionDto],
) -> std::option::Option<i64> {
    let mut next_instruction_id = None;
    for candidate in instructions {
        let candidate_id = match candidate.id {
            Some(candidate_id) => candidate_id,
            None => continue,
        };
        if candidate_id <= instruction_id {
            continue;
        }
        if candidate.parent_instruction_id != parent_instruction_id {
            continue;
        }
        let candidate_program_id = match candidate.program_id.as_deref() {
            Some(candidate_program_id) => candidate_program_id,
            None => continue,
        };
        if candidate_program_id != crate::METEORA_DLMM_PROGRAM_ID {
            continue;
        }
        let candidate_data_result = decode_instruction_data_json(candidate.data_json.as_ref());
        let candidate_data = match candidate_data_result {
            Ok(candidate_data) => candidate_data,
            Err(_) => continue,
        };
        let candidate_data = match candidate_data {
            Some(candidate_data) => candidate_data,
            None => continue,
        };
        if is_dlmm_anchor_self_cpi_log(candidate_data.as_slice()) {
            continue;
        }
        let candidate_instruction_name =
            classify_instruction_name_from_data(Some(candidate_data.as_slice()));
        if candidate_instruction_name.kind() == MeteoraDlmmInstructionKind::Unknown {
            continue;
        }
        match next_instruction_id {
            Some(existing_id) => {
                if candidate_id < existing_id {
                    next_instruction_id = Some(candidate_id);
                }
            },
            None => next_instruction_id = Some(candidate_id),
        }
    }
    return next_instruction_id;
}

fn decode_anchor_swap_event(
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
) -> std::option::Option<serde_json::Value> {
    if !is_dlmm_anchor_self_cpi_log(data) {
        return None;
    }
    if data.len() < 16 {
        return None;
    }
    let event_discriminator = read_8_bytes(data, 8);
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_SWAP2_EVT {
        return decode_anchor_swap2_evt(instruction, data);
    }
    if event_discriminator == DLMM_EVENT_DISCRIMINATOR_SWAP {
        return decode_anchor_swap(instruction, data);
    }
    return None;
}

fn decode_anchor_swap(
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
) -> std::option::Option<serde_json::Value> {
    if data.len() < 145 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let from = read_pubkey_string(data, 48);
    let start_bin_id = read_i32_string(data, 80);
    let end_bin_id = read_i32_string(data, 84);
    let amount_in = read_u64_string(data, 88);
    let amount_out = read_u64_string(data, 96);
    let swap_for_y = read_bool(data, 104);
    let fee = read_u64_string(data, 105);
    let protocol_fee = read_u64_string(data, 113);
    let fee_bps = read_u128_string(data, 121);
    let host_fee = read_u64_string(data, 137);
    return Some(serde_json::json!({
        "anchorEventName": "swap",
        "anchorEventDiscriminatorHex": "516ce3becdd00ac4",
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "lbPair": lb_pair,
        "from": from,
        "startBinId": start_bin_id,
        "endBinId": end_bin_id,
        "amountInRaw": amount_in,
        "amountOutRaw": amount_out,
        "swapForY": swap_for_y,
        "feeRaw": fee,
        "protocolFeeRaw": protocol_fee,
        "feeBpsRaw": fee_bps,
        "hostFeeRaw": host_fee
    }));
}

fn decode_anchor_swap2_evt(
    instruction: &crate::ChainInstructionDto,
    data: &[u8],
) -> std::option::Option<serde_json::Value> {
    if data.len() < 163 {
        return None;
    }
    let lb_pair = read_pubkey_string(data, 16);
    let from = read_pubkey_string(data, 48);
    let start_bin_id = read_i32_string(data, 80);
    let end_bin_id = read_i32_string(data, 84);
    let swap_for_y = read_bool(data, 88);
    let fee_bps = read_u128_string(data, 89);
    let amount_in = read_u64_string(data, 105);
    let amount_left = read_u64_string(data, 113);
    let amount_out = read_u64_string(data, 121);
    let mm_fee = read_u64_string(data, 129);
    let protocol_fee = read_u64_string(data, 137);
    let limit_order_fee = read_u64_string(data, 145);
    let host_fee = read_u64_string(data, 153);
    let fees_on_input = read_bool(data, 161);
    let fees_on_token_x = read_bool(data, 162);
    return Some(serde_json::json!({
        "anchorEventName": "swap2_evt",
        "anchorEventDiscriminatorHex": "2e7452d7941b544d",
        "anchorEventPayloadSize": data.len().saturating_sub(8),
        "instructionId": instruction.id,
        "instructionIndex": instruction.instruction_index,
        "innerInstructionIndex": instruction.inner_instruction_index,
        "parentInstructionId": instruction.parent_instruction_id,
        "lbPair": lb_pair,
        "from": from,
        "startBinId": start_bin_id,
        "endBinId": end_bin_id,
        "swapForY": swap_for_y,
        "feeBpsRaw": fee_bps,
        "amountInRaw": amount_in,
        "amountLeftRaw": amount_left,
        "amountOutRaw": amount_out,
        "feeRaw": mm_fee,
        "mmFeeRaw": mm_fee,
        "protocolFeeRaw": protocol_fee,
        "limitOrderFeeRaw": limit_order_fee,
        "hostFeeRaw": host_fee,
        "feesOnInput": fees_on_input,
        "feesOnTokenX": fees_on_token_x
    }));
}

fn is_dlmm_anchor_self_cpi_log(data: &[u8]) -> bool {
    if data.len() < 8 {
        return false;
    }
    let discriminator = read_8_bytes(data, 0);
    return discriminator == DLMM_ANCHOR_SELF_CPI_LOG_SELECTOR;
}

fn read_8_bytes(data: &[u8], offset: usize) -> [u8; 8] {
    let mut bytes = [0_u8; 8];
    if data.len() < offset + 8 {
        return bytes;
    }
    let mut index = 0_usize;
    while index < 8 {
        bytes[index] = data[offset + index];
        index += 1;
    }
    return bytes;
}

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

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

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

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

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

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

fn extract_json_string_field(
    value: std::option::Option<&serde_json::Value>,
    key: &str,
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value,
        None => return None,
    };
    let field = match value.get(key) {
        Some(field) => field,
        None => return None,
    };
    if let Some(text) = field.as_str() {
        return Some(text.to_string());
    }
    if let Some(number) = field.as_u64() {
        return Some(number.to_string());
    }
    if let Some(number) = field.as_i64() {
        return Some(number.to_string());
    }
    return None;
}

fn classify_instruction_name_from_normalized_text(normalized: &str) -> MeteoraDlmmInstructionName {
    match normalized {
        "add_liquidity" => return MeteoraDlmmInstructionName::AddLiquidity,
        "add_liquidity2" => return MeteoraDlmmInstructionName::AddLiquidity2,
        "add_liquidity_by_strategy" => return MeteoraDlmmInstructionName::AddLiquidityByStrategy,
        "add_liquidity_by_strategy2" => return MeteoraDlmmInstructionName::AddLiquidityByStrategy2,
        "add_liquidity_by_strategy_one_side" => {
            return MeteoraDlmmInstructionName::AddLiquidityByStrategyOneSide;
        },
        "add_liquidity_by_weight" => return MeteoraDlmmInstructionName::AddLiquidityByWeight,
        "add_liquidity_by_weight2" => return MeteoraDlmmInstructionName::AddLiquidityByWeight2,
        "add_liquidity_one_side" => return MeteoraDlmmInstructionName::AddLiquidityOneSide,
        "add_liquidity_one_side_precise" => {
            return MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise;
        },
        "add_liquidity_one_side_precise2" => {
            return MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise2;
        },
        "cancel_limit_order" => return MeteoraDlmmInstructionName::CancelLimitOrder,
        "claim_fee" => return MeteoraDlmmInstructionName::ClaimFee,
        "claim_fee2" => return MeteoraDlmmInstructionName::ClaimFee2,
        "claim_reward" => return MeteoraDlmmInstructionName::ClaimReward,
        "claim_reward2" => return MeteoraDlmmInstructionName::ClaimReward2,
        "close_bin_array" => return MeteoraDlmmInstructionName::CloseBinArray,
        "close_claim_fee_operator_account" => {
            return MeteoraDlmmInstructionName::CloseClaimFeeOperatorAccount;
        },
        "close_limit_order_if_empty" => return MeteoraDlmmInstructionName::CloseLimitOrderIfEmpty,
        "close_operator_account" => return MeteoraDlmmInstructionName::CloseOperatorAccount,
        "close_position" => return MeteoraDlmmInstructionName::ClosePosition,
        "close_position2" => return MeteoraDlmmInstructionName::ClosePosition2,
        "close_position_if_empty" => return MeteoraDlmmInstructionName::ClosePositionIfEmpty,
        "close_preset_parameter" => return MeteoraDlmmInstructionName::ClosePresetParameter,
        "close_preset_parameter2" => return MeteoraDlmmInstructionName::ClosePresetParameter2,
        "close_token_badge" => return MeteoraDlmmInstructionName::CloseTokenBadge,
        "create_operator_account" => return MeteoraDlmmInstructionName::CreateOperatorAccount,
        "decrease_position_length" => return MeteoraDlmmInstructionName::DecreasePositionLength,
        "for_idl_type_generation_do_not_call" => {
            return MeteoraDlmmInstructionName::ForIdlTypeGenerationDoNotCall;
        },
        "fund_reward" => return MeteoraDlmmInstructionName::FundReward,
        "go_to_a_bin" => return MeteoraDlmmInstructionName::GoToABin,
        "increase_oracle_length" => return MeteoraDlmmInstructionName::IncreaseOracleLength,
        "increase_position_length" => return MeteoraDlmmInstructionName::IncreasePositionLength,
        "increase_position_length2" => return MeteoraDlmmInstructionName::IncreasePositionLength2,
        "initialize_bin_array" => return MeteoraDlmmInstructionName::InitializeBinArray,
        "initialize_bin_array_bitmap_extension" => {
            return MeteoraDlmmInstructionName::InitializeBinArrayBitmapExtension;
        },
        "initialize_customizable_permissionless_lb_pair" => {
            return MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair;
        },
        "initialize_customizable_permissionless_lb_pair2" => {
            return MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2;
        },
        "initialize_lb_pair" => return MeteoraDlmmInstructionName::InitializeLbPair,
        "initialize_lb_pair2" => return MeteoraDlmmInstructionName::InitializeLbPair2,
        "initialize_permission_lb_pair" => {
            return MeteoraDlmmInstructionName::InitializePermissionLbPair;
        },
        "initialize_position" => return MeteoraDlmmInstructionName::InitializePosition,
        "initialize_position2" => return MeteoraDlmmInstructionName::InitializePosition2,
        "initialize_position_by_operator" => {
            return MeteoraDlmmInstructionName::InitializePositionByOperator;
        },
        "initialize_position_pda" => return MeteoraDlmmInstructionName::InitializePositionPda,
        "initialize_preset_parameter" => {
            return MeteoraDlmmInstructionName::InitializePresetParameter;
        },
        "initialize_preset_parameter_v2" => {
            return MeteoraDlmmInstructionName::InitializePresetParameterV2;
        },
        "initialize_reward" => return MeteoraDlmmInstructionName::InitializeReward,
        "initialize_token_badge" => return MeteoraDlmmInstructionName::InitializeTokenBadge,
        "place_limit_order" => return MeteoraDlmmInstructionName::PlaceLimitOrder,
        "rebalance_liquidity" => return MeteoraDlmmInstructionName::RebalanceLiquidity,
        "remove_all_liquidity" => return MeteoraDlmmInstructionName::RemoveAllLiquidity,
        "remove_liquidity" => return MeteoraDlmmInstructionName::RemoveLiquidity,
        "remove_liquidity2" => return MeteoraDlmmInstructionName::RemoveLiquidity2,
        "remove_liquidity_by_range" => return MeteoraDlmmInstructionName::RemoveLiquidityByRange,
        "remove_liquidity_by_range2" => return MeteoraDlmmInstructionName::RemoveLiquidityByRange2,
        "set_activation_point" => return MeteoraDlmmInstructionName::SetActivationPoint,
        "set_pair_status" => return MeteoraDlmmInstructionName::SetPairStatus,
        "set_pair_status_permissionless" => {
            return MeteoraDlmmInstructionName::SetPairStatusPermissionless;
        },
        "set_permissionless_operation_bits" => {
            return MeteoraDlmmInstructionName::SetPermissionlessOperationBits;
        },
        "set_pre_activation_duration" => {
            return MeteoraDlmmInstructionName::SetPreActivationDuration;
        },
        "set_pre_activation_swap_address" => {
            return MeteoraDlmmInstructionName::SetPreActivationSwapAddress;
        },
        "swap" => return MeteoraDlmmInstructionName::Swap,
        "swap2" => return MeteoraDlmmInstructionName::Swap2,
        "swap_exact_out" => return MeteoraDlmmInstructionName::SwapExactOut,
        "swap_exact_out2" => return MeteoraDlmmInstructionName::SwapExactOut2,
        "swap_with_price_impact" => return MeteoraDlmmInstructionName::SwapWithPriceImpact,
        "swap_with_price_impact2" => return MeteoraDlmmInstructionName::SwapWithPriceImpact2,
        "update_base_fee_parameters" => return MeteoraDlmmInstructionName::UpdateBaseFeeParameters,
        "update_dynamic_fee_parameters" => {
            return MeteoraDlmmInstructionName::UpdateDynamicFeeParameters;
        },
        "update_fees_and_reward2" => return MeteoraDlmmInstructionName::UpdateFeesAndReward2,
        "update_fees_and_rewards" => return MeteoraDlmmInstructionName::UpdateFeesAndRewards,
        "update_position_operator" => return MeteoraDlmmInstructionName::UpdatePositionOperator,
        "update_reward_duration" => return MeteoraDlmmInstructionName::UpdateRewardDuration,
        "update_reward_funder" => return MeteoraDlmmInstructionName::UpdateRewardFunder,
        "withdraw_ineligible_reward" => {
            return MeteoraDlmmInstructionName::WithdrawIneligibleReward;
        },
        "withdraw_protocol_fee" => return MeteoraDlmmInstructionName::WithdrawProtocolFee,
        "zap_protocol_fee" => return MeteoraDlmmInstructionName::ZapProtocolFee,
        _ => return MeteoraDlmmInstructionName::Unknown,
    }
}

fn normalize_dlmm_instruction_text(value: &str) -> std::string::String {
    let mut normalized = std::string::String::new();
    let mut previous_was_separator = false;
    for character in value.chars() {
        if character == '_' || character == '-' || character == ' ' {
            if !previous_was_separator && !normalized.is_empty() {
                normalized.push('_');
            }
            previous_was_separator = true;
            continue;
        }
        if character.is_ascii_uppercase() {
            if !previous_was_separator && !normalized.is_empty() {
                normalized.push('_');
            }
            normalized.push(character.to_ascii_lowercase());
            previous_was_separator = false;
            continue;
        }
        normalized.push(character.to_ascii_lowercase());
        previous_was_separator = false;
    }
    return normalized.trim_matches('_').to_string();
}

fn classify_instruction_name(
    parsed_json: std::option::Option<&serde_json::Value>,
    parsed_type: std::option::Option<&str>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> MeteoraDlmmInstructionName {
    let from_data = classify_instruction_name_from_data(instruction_data);
    if from_data != MeteoraDlmmInstructionName::Unknown {
        return from_data;
    }
    if instruction_data.is_some() {
        return MeteoraDlmmInstructionName::Unknown;
    }
    if let Some(parsed_type) = parsed_type {
        let normalized = normalize_dlmm_instruction_text(parsed_type);
        let parsed_name = classify_instruction_name_from_normalized_text(normalized.as_str());
        if parsed_name != MeteoraDlmmInstructionName::Unknown {
            return parsed_name;
        }
    }
    if let Some(parsed_json) = parsed_json {
        if let Some(parsed_name) = extract_string_by_candidate_keys(
            Some(parsed_json),
            &["type", "instruction", "instructionName", "instruction_name", "name"],
        ) {
            let normalized = normalize_dlmm_instruction_text(parsed_name.as_str());
            let parsed_name = classify_instruction_name_from_normalized_text(normalized.as_str());
            if parsed_name != MeteoraDlmmInstructionName::Unknown {
                return parsed_name;
            }
        }
    }
    for log_message in log_messages {
        if !log_message.contains("Instruction:") {
            continue;
        }
        let instruction_text = match log_message.split("Instruction:").nth(1) {
            Some(value) => value.trim(),
            None => continue,
        };
        let normalized = normalize_dlmm_instruction_text(instruction_text);
        let log_name = classify_instruction_name_from_normalized_text(normalized.as_str());
        if log_name != MeteoraDlmmInstructionName::Unknown {
            return log_name;
        }
    }
    return MeteoraDlmmInstructionName::Unknown;
}

fn classify_instruction_name_from_data(
    instruction_data: std::option::Option<&[u8]>,
) -> MeteoraDlmmInstructionName {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return MeteoraDlmmInstructionName::Unknown,
    };
    if instruction_data.len() < 8 {
        return MeteoraDlmmInstructionName::Unknown;
    }
    let discriminator = read_8_bytes(instruction_data, 0);
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY {
        return MeteoraDlmmInstructionName::AddLiquidity;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY2 {
        return MeteoraDlmmInstructionName::AddLiquidity2;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_STRATEGY {
        return MeteoraDlmmInstructionName::AddLiquidityByStrategy;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_STRATEGY2 {
        return MeteoraDlmmInstructionName::AddLiquidityByStrategy2;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_STRATEGY_ONE_SIDE {
        return MeteoraDlmmInstructionName::AddLiquidityByStrategyOneSide;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_WEIGHT {
        return MeteoraDlmmInstructionName::AddLiquidityByWeight;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_BY_WEIGHT2 {
        return MeteoraDlmmInstructionName::AddLiquidityByWeight2;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_ONE_SIDE {
        return MeteoraDlmmInstructionName::AddLiquidityOneSide;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_ONE_SIDE_PRECISE {
        return MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY_ONE_SIDE_PRECISE2 {
        return MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise2;
    }
    if discriminator == DLMM_DISCRIMINATOR_CANCEL_LIMIT_ORDER {
        return MeteoraDlmmInstructionName::CancelLimitOrder;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLAIM_FEE {
        return MeteoraDlmmInstructionName::ClaimFee;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLAIM_FEE2 {
        return MeteoraDlmmInstructionName::ClaimFee2;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLAIM_REWARD {
        return MeteoraDlmmInstructionName::ClaimReward;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLAIM_REWARD2 {
        return MeteoraDlmmInstructionName::ClaimReward2;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_BIN_ARRAY {
        return MeteoraDlmmInstructionName::CloseBinArray;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_CLAIM_FEE_OPERATOR_ACCOUNT {
        return MeteoraDlmmInstructionName::CloseClaimFeeOperatorAccount;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_LIMIT_ORDER_IF_EMPTY {
        return MeteoraDlmmInstructionName::CloseLimitOrderIfEmpty;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_OPERATOR_ACCOUNT {
        return MeteoraDlmmInstructionName::CloseOperatorAccount;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_POSITION {
        return MeteoraDlmmInstructionName::ClosePosition;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_POSITION2 {
        return MeteoraDlmmInstructionName::ClosePosition2;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_POSITION_IF_EMPTY {
        return MeteoraDlmmInstructionName::ClosePositionIfEmpty;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_PRESET_PARAMETER {
        return MeteoraDlmmInstructionName::ClosePresetParameter;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_PRESET_PARAMETER2 {
        return MeteoraDlmmInstructionName::ClosePresetParameter2;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLOSE_TOKEN_BADGE {
        return MeteoraDlmmInstructionName::CloseTokenBadge;
    }
    if discriminator == DLMM_DISCRIMINATOR_CREATE_OPERATOR_ACCOUNT {
        return MeteoraDlmmInstructionName::CreateOperatorAccount;
    }
    if discriminator == DLMM_DISCRIMINATOR_DECREASE_POSITION_LENGTH {
        return MeteoraDlmmInstructionName::DecreasePositionLength;
    }
    if discriminator == DLMM_DISCRIMINATOR_FOR_IDL_TYPE_GENERATION_DO_NOT_CALL {
        return MeteoraDlmmInstructionName::ForIdlTypeGenerationDoNotCall;
    }
    if discriminator == DLMM_DISCRIMINATOR_FUND_REWARD {
        return MeteoraDlmmInstructionName::FundReward;
    }
    if discriminator == DLMM_DISCRIMINATOR_GO_TO_A_BIN {
        return MeteoraDlmmInstructionName::GoToABin;
    }
    if discriminator == DLMM_DISCRIMINATOR_INCREASE_ORACLE_LENGTH {
        return MeteoraDlmmInstructionName::IncreaseOracleLength;
    }
    if discriminator == DLMM_DISCRIMINATOR_INCREASE_POSITION_LENGTH {
        return MeteoraDlmmInstructionName::IncreasePositionLength;
    }
    if discriminator == DLMM_DISCRIMINATOR_INCREASE_POSITION_LENGTH2 {
        return MeteoraDlmmInstructionName::IncreasePositionLength2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_BIN_ARRAY {
        return MeteoraDlmmInstructionName::InitializeBinArray;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_BIN_ARRAY_BITMAP_EXTENSION {
        return MeteoraDlmmInstructionName::InitializeBinArrayBitmapExtension;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR {
        return MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR2 {
        return MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR {
        return MeteoraDlmmInstructionName::InitializeLbPair;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR2 {
        return MeteoraDlmmInstructionName::InitializeLbPair2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_PERMISSION_LB_PAIR {
        return MeteoraDlmmInstructionName::InitializePermissionLbPair;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_POSITION {
        return MeteoraDlmmInstructionName::InitializePosition;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_POSITION2 {
        return MeteoraDlmmInstructionName::InitializePosition2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_POSITION_BY_OPERATOR {
        return MeteoraDlmmInstructionName::InitializePositionByOperator;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_POSITION_PDA {
        return MeteoraDlmmInstructionName::InitializePositionPda;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_PRESET_PARAMETER {
        return MeteoraDlmmInstructionName::InitializePresetParameter;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_PRESET_PARAMETER_V2 {
        return MeteoraDlmmInstructionName::InitializePresetParameterV2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_REWARD {
        return MeteoraDlmmInstructionName::InitializeReward;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_TOKEN_BADGE {
        return MeteoraDlmmInstructionName::InitializeTokenBadge;
    }
    if discriminator == DLMM_DISCRIMINATOR_PLACE_LIMIT_ORDER {
        return MeteoraDlmmInstructionName::PlaceLimitOrder;
    }
    if discriminator == DLMM_DISCRIMINATOR_REBALANCE_LIQUIDITY {
        return MeteoraDlmmInstructionName::RebalanceLiquidity;
    }
    if discriminator == DLMM_DISCRIMINATOR_REMOVE_ALL_LIQUIDITY {
        return MeteoraDlmmInstructionName::RemoveAllLiquidity;
    }
    if discriminator == DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY {
        return MeteoraDlmmInstructionName::RemoveLiquidity;
    }
    if discriminator == DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY2 {
        return MeteoraDlmmInstructionName::RemoveLiquidity2;
    }
    if discriminator == DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY_BY_RANGE {
        return MeteoraDlmmInstructionName::RemoveLiquidityByRange;
    }
    if discriminator == DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY_BY_RANGE2 {
        return MeteoraDlmmInstructionName::RemoveLiquidityByRange2;
    }
    if discriminator == DLMM_DISCRIMINATOR_SET_ACTIVATION_POINT {
        return MeteoraDlmmInstructionName::SetActivationPoint;
    }
    if discriminator == DLMM_DISCRIMINATOR_SET_PAIR_STATUS {
        return MeteoraDlmmInstructionName::SetPairStatus;
    }
    if discriminator == DLMM_DISCRIMINATOR_SET_PAIR_STATUS_PERMISSIONLESS {
        return MeteoraDlmmInstructionName::SetPairStatusPermissionless;
    }
    if discriminator == DLMM_DISCRIMINATOR_SET_PERMISSIONLESS_OPERATION_BITS {
        return MeteoraDlmmInstructionName::SetPermissionlessOperationBits;
    }
    if discriminator == DLMM_DISCRIMINATOR_SET_PRE_ACTIVATION_DURATION {
        return MeteoraDlmmInstructionName::SetPreActivationDuration;
    }
    if discriminator == DLMM_DISCRIMINATOR_SET_PRE_ACTIVATION_SWAP_ADDRESS {
        return MeteoraDlmmInstructionName::SetPreActivationSwapAddress;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP {
        return MeteoraDlmmInstructionName::Swap;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP2 {
        return MeteoraDlmmInstructionName::Swap2;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_EXACT_OUT {
        return MeteoraDlmmInstructionName::SwapExactOut;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_EXACT_OUT2 {
        return MeteoraDlmmInstructionName::SwapExactOut2;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_WITH_PRICE_IMPACT {
        return MeteoraDlmmInstructionName::SwapWithPriceImpact;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_WITH_PRICE_IMPACT2 {
        return MeteoraDlmmInstructionName::SwapWithPriceImpact2;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_BASE_FEE_PARAMETERS {
        return MeteoraDlmmInstructionName::UpdateBaseFeeParameters;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_DYNAMIC_FEE_PARAMETERS {
        return MeteoraDlmmInstructionName::UpdateDynamicFeeParameters;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_FEES_AND_REWARD2 {
        return MeteoraDlmmInstructionName::UpdateFeesAndReward2;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_FEES_AND_REWARDS {
        return MeteoraDlmmInstructionName::UpdateFeesAndRewards;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_POSITION_OPERATOR {
        return MeteoraDlmmInstructionName::UpdatePositionOperator;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_REWARD_DURATION {
        return MeteoraDlmmInstructionName::UpdateRewardDuration;
    }
    if discriminator == DLMM_DISCRIMINATOR_UPDATE_REWARD_FUNDER {
        return MeteoraDlmmInstructionName::UpdateRewardFunder;
    }
    if discriminator == DLMM_DISCRIMINATOR_WITHDRAW_INELIGIBLE_REWARD {
        return MeteoraDlmmInstructionName::WithdrawIneligibleReward;
    }
    if discriminator == DLMM_DISCRIMINATOR_WITHDRAW_PROTOCOL_FEE {
        return MeteoraDlmmInstructionName::WithdrawProtocolFee;
    }
    if discriminator == DLMM_DISCRIMINATOR_ZAP_PROTOCOL_FEE {
        return MeteoraDlmmInstructionName::ZapProtocolFee;
    }
    return MeteoraDlmmInstructionName::Unknown;
}

fn resolve_dlmm_pool_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "lbPair",
            "lb_pair",
            "lbPairAccount",
            "pair",
            "pairAccount",
            "pool",
            "poolAccount",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializePermissionLbPair => {
            return extract_account(accounts, 1);
        },
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2
        | MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2
        | MeteoraDlmmInstructionName::InitializeBinArray
        | MeteoraDlmmInstructionName::InitializeBinArrayBitmapExtension
        | MeteoraDlmmInstructionName::CloseBinArray
        | MeteoraDlmmInstructionName::GoToABin
        | MeteoraDlmmInstructionName::IncreaseOracleLength
        | MeteoraDlmmInstructionName::UpdateFeesAndRewards
        | MeteoraDlmmInstructionName::UpdateFeesAndReward2
        | MeteoraDlmmInstructionName::UpdateBaseFeeParameters
        | MeteoraDlmmInstructionName::UpdateDynamicFeeParameters
        | MeteoraDlmmInstructionName::SetPairStatus
        | MeteoraDlmmInstructionName::SetPairStatusPermissionless
        | MeteoraDlmmInstructionName::SetActivationPoint
        | MeteoraDlmmInstructionName::SetPreActivationDuration
        | MeteoraDlmmInstructionName::SetPreActivationSwapAddress
        | MeteoraDlmmInstructionName::SetPermissionlessOperationBits
        | MeteoraDlmmInstructionName::UpdateRewardDuration
        | MeteoraDlmmInstructionName::UpdateRewardFunder
        | MeteoraDlmmInstructionName::FundReward
        | MeteoraDlmmInstructionName::InitializeReward
        | MeteoraDlmmInstructionName::ClaimReward
        | MeteoraDlmmInstructionName::ClaimReward2
        | MeteoraDlmmInstructionName::WithdrawIneligibleReward
        | MeteoraDlmmInstructionName::ClaimFee
        | MeteoraDlmmInstructionName::ClaimFee2
        | MeteoraDlmmInstructionName::WithdrawProtocolFee => return extract_account(accounts, 0),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidity2
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::AddLiquidityByStrategyOneSide
        | MeteoraDlmmInstructionName::AddLiquidityByWeight
        | MeteoraDlmmInstructionName::AddLiquidityByWeight2
        | MeteoraDlmmInstructionName::AddLiquidityOneSide
        | MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise
        | MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise2
        | MeteoraDlmmInstructionName::RemoveAllLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity2
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2
        | MeteoraDlmmInstructionName::RebalanceLiquidity
        | MeteoraDlmmInstructionName::ClosePosition
        | MeteoraDlmmInstructionName::ClosePosition2 => return extract_account(accounts, 1),
        MeteoraDlmmInstructionName::InitializePosition
        | MeteoraDlmmInstructionName::InitializePosition2 => return extract_account(accounts, 2),
        MeteoraDlmmInstructionName::InitializePositionByOperator
        | MeteoraDlmmInstructionName::InitializePositionPda => return extract_account(accounts, 3),
        _ => return None,
    }
}

fn resolve_dlmm_token_x_mint(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "tokenXMint",
            "token_x_mint",
            "tokenMintX",
            "token_mint_x",
            "mintX",
            "mint_x",
            "tokenAMint",
            "token_a_mint",
            "baseMint",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializePermissionLbPair => {
            return extract_account(accounts, 3);
        },
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2 => {
            return extract_account(accounts, 2);
        },
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => return extract_account(accounts, 6),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidity2
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::AddLiquidityByStrategyOneSide
        | MeteoraDlmmInstructionName::AddLiquidityByWeight
        | MeteoraDlmmInstructionName::AddLiquidityByWeight2
        | MeteoraDlmmInstructionName::AddLiquidityOneSide
        | MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise
        | MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise2
        | MeteoraDlmmInstructionName::RemoveAllLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity2
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2
        | MeteoraDlmmInstructionName::RebalanceLiquidity => return extract_account(accounts, 7),
        MeteoraDlmmInstructionName::ClaimFee => return extract_account(accounts, 9),
        MeteoraDlmmInstructionName::ClaimFee2 => return extract_account(accounts, 7),
        MeteoraDlmmInstructionName::WithdrawProtocolFee => return extract_account(accounts, 3),
        MeteoraDlmmInstructionName::ZapProtocolFee => return extract_account(accounts, 2),
        _ => return None,
    }
}

fn resolve_dlmm_token_y_mint(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "tokenYMint",
            "token_y_mint",
            "tokenMintY",
            "token_mint_y",
            "mintY",
            "mint_y",
            "tokenBMint",
            "token_b_mint",
            "quoteMint",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializePermissionLbPair => {
            return extract_account(accounts, 4);
        },
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2 => {
            return extract_account(accounts, 3);
        },
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => return extract_account(accounts, 7),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidity2
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::AddLiquidityByStrategyOneSide
        | MeteoraDlmmInstructionName::AddLiquidityByWeight
        | MeteoraDlmmInstructionName::AddLiquidityByWeight2
        | MeteoraDlmmInstructionName::AddLiquidityOneSide
        | MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise
        | MeteoraDlmmInstructionName::AddLiquidityOneSidePrecise2
        | MeteoraDlmmInstructionName::RemoveAllLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidity2
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2
        | MeteoraDlmmInstructionName::RebalanceLiquidity => return extract_account(accounts, 8),
        MeteoraDlmmInstructionName::ClaimFee => return extract_account(accounts, 10),
        MeteoraDlmmInstructionName::ClaimFee2 => return extract_account(accounts, 8),
        MeteoraDlmmInstructionName::WithdrawProtocolFee => return extract_account(accounts, 4),
        _ => return None,
    }
}

fn resolve_dlmm_reserve_x_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["reserveX", "reserve_x", "reserveXAccount", "reserve_x_account"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => {
            return extract_account(accounts, 2);
        },
        _ => return None,
    }
}

fn resolve_dlmm_reserve_y_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["reserveY", "reserve_y", "reserveYAccount", "reserve_y_account"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => {
            return extract_account(accounts, 3);
        },
        _ => return None,
    }
}

fn resolve_dlmm_user_token_in_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["userTokenIn", "user_token_in", "userTokenInAccount", "user_token_in_account"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2 => {
            return extract_account(accounts, 4);
        },
        _ => return None,
    }
}

fn resolve_dlmm_user_token_out_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "userTokenOut",
            "user_token_out",
            "userTokenOutAccount",
            "user_token_out_account",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapWithPriceImpact2
        | MeteoraDlmmInstructionName::SwapExactOut2 => {
            return extract_account(accounts, 5);
        },
        _ => return None,
    }
}

fn resolve_dlmm_config_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "presetParameter",
            "preset_parameter",
            "presetParameter2",
            "config",
            "poolConfig",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair => {
            return extract_account(accounts, 7);
        },
        MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2 => {
            return None;
        },
        _ => return None,
    }
}

fn resolve_dlmm_actor_wallet(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["owner", "payer", "sender", "user", "authority", "liquidityProvider"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2 => {
            return extract_account(accounts, 9);
        },
        MeteoraDlmmInstructionName::AddLiquidityByWeight => {
            return extract_account(accounts, 11);
        },
        MeteoraDlmmInstructionName::InitializePosition => {
            return extract_account(accounts, 3);
        },
        _ => return None,
    }
}

fn resolve_dlmm_reward_token_mint(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "rewardTokenMint",
            "reward_token_mint",
            "rewardMint",
            "reward_mint",
            "tokenMint",
            "token_mint",
            "mint",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializeReward
        | MeteoraDlmmInstructionName::FundReward
        | MeteoraDlmmInstructionName::WithdrawIneligibleReward => {
            return extract_account(accounts, 2);
        },
        MeteoraDlmmInstructionName::ClaimReward => return extract_account(accounts, 6),
        MeteoraDlmmInstructionName::ClaimReward2 => return extract_account(accounts, 4),
        _ => return None,
    }
}

fn read_i32_string_from_instruction_data(
    instruction_name: MeteoraDlmmInstructionName,
    instruction_data: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    if instruction_name != MeteoraDlmmInstructionName::RemoveLiquidityByRange2 {
        return None;
    }
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return None,
    };
    return read_i32_string(instruction_data, offset);
}

fn read_u16_string_from_instruction_data(
    instruction_name: MeteoraDlmmInstructionName,
    instruction_data: std::option::Option<&[u8]>,
    offset: usize,
) -> std::option::Option<std::string::String> {
    if instruction_name != MeteoraDlmmInstructionName::RemoveLiquidityByRange2 {
        return None;
    }
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return None,
    };
    return read_u16_string(instruction_data, offset);
}

fn resolve_dlmm_liquidity_position_account(
    instruction_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match instruction_name {
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::AddLiquidityByWeight
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2 => {
            return extract_account(accounts, 0);
        },
        _ => return None,
    }
}

fn resolve_dlmm_liquidity_event_authority_account(
    instruction_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match instruction_name {
        MeteoraDlmmInstructionName::AddLiquidityByWeight => return extract_account(accounts, 14),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2 => {
            return extract_account(accounts, 12);
        },
        _ => return None,
    }
}

fn resolve_dlmm_liquidity_program_account(
    instruction_name: MeteoraDlmmInstructionName,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    match instruction_name {
        MeteoraDlmmInstructionName::AddLiquidityByWeight => return extract_account(accounts, 15),
        MeteoraDlmmInstructionName::AddLiquidity
        | MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::RemoveLiquidity
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2 => {
            return extract_account(accounts, 13);
        },
        _ => return None,
    }
}

fn resolve_dlmm_instruction_proof_status(
    instruction_name: MeteoraDlmmInstructionName,
) -> &'static str {
    match instruction_name {
        MeteoraDlmmInstructionName::AddLiquidityByStrategy2
        | MeteoraDlmmInstructionName::AddLiquidityByWeight
        | MeteoraDlmmInstructionName::RemoveLiquidityByRange2 => {
            return "upstream_git_local_corpus_observed";
        },
        _ => return "decoded_from_instruction_discriminator_or_local_hint",
    }
}

fn extract_amount_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,
    };
    if let Some(text) = extract_string_by_candidate_keys(Some(value), candidate_keys) {
        return Some(text);
    }
    return extract_number_by_candidate_keys(Some(value), candidate_keys);
}

fn extract_number_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,
    };
    match value {
        serde_json::Value::Object(object) => {
            for candidate_key in candidate_keys {
                if let Some(candidate) = object.get(*candidate_key) {
                    if let Some(number) = candidate.as_i64() {
                        return Some(number.to_string());
                    }
                    if let Some(number) = candidate.as_u64() {
                        return Some(number.to_string());
                    }
                    if let Some(number) = candidate.as_f64() {
                        return Some(number.to_string());
                    }
                }
            }
            for nested in object.values() {
                let result = extract_number_by_candidate_keys(Some(nested), candidate_keys);
                if result.is_some() {
                    return result;
                }
            }
        },
        serde_json::Value::Array(values) => {
            for nested in values {
                let result = extract_number_by_candidate_keys(Some(nested), candidate_keys);
                if result.is_some() {
                    return result;
                }
            }
        },
        _ => {},
    }
    return None;
}

fn infer_trade_side(parsed_json: std::option::Option<&serde_json::Value>) -> crate::SwapTradeSide {
    let side = extract_string_by_candidate_keys(
        parsed_json,
        &["tradeSide", "trade_side", "side", "swapSide", "swap_side"],
    );
    match side.as_deref() {
        Some("BuyBase") => return crate::SwapTradeSide::BuyBase,
        Some("buy") => return crate::SwapTradeSide::BuyBase,
        Some("BUY") => return crate::SwapTradeSide::BuyBase,
        Some("SellBase") => return crate::SwapTradeSide::SellBase,
        Some("sell") => return crate::SwapTradeSide::SellBase,
        Some("SELL") => return crate::SwapTradeSide::SellBase,
        _ => return crate::SwapTradeSide::Unknown,
    }
}

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

fn parse_accounts_json(
    accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
    let parsed_result = serde_json::from_str::<serde_json::Value>(accounts_json);
    let parsed = match parsed_result {
        Ok(parsed) => parsed,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DLMM accounts_json: {}",
                error
            )));
        },
    };
    let array = match parsed.as_array() {
        Some(array) => array,
        None => return Ok(std::vec::Vec::new()),
    };
    let mut accounts = std::vec::Vec::new();
    for item in array {
        if let Some(text) = item.as_str() {
            accounts.push(text.to_string());
            continue;
        }
        if let Some(pubkey) = item.get("pubkey").and_then(|value| return value.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 parsed_result = serde_json::from_str::<serde_json::Value>(parsed_json);
    match parsed_result {
        Ok(parsed) => return Ok(Some(parsed)),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DLMM parsed_json: {}",
                error
            )));
        },
    }
}

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

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,
    };
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            if let Some(found) = object.get(*candidate_key) {
                if let Some(text) = found.as_str() {
                    return Some(text.to_string());
                }
                if let Some(number) = found.as_u64() {
                    return Some(number.to_string());
                }
                if let Some(number) = found.as_i64() {
                    return Some(number.to_string());
                }
            }
        }
        for nested in object.values() {
            let nested_result = extract_string_by_candidate_keys(Some(nested), candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    if let Some(array) = value.as_array() {
        for nested in array {
            let nested_result = extract_string_by_candidate_keys(Some(nested), candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

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 parsed_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    let parsed = match parsed_result {
        Ok(parsed) => parsed,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DLMM data_json: {}",
                error
            )));
        },
    };
    if let serde_json::Value::String(base58_text) = parsed {
        let decoded_result = decode_base58(base58_text.as_str());
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => return Err(error),
        };
        return Ok(Some(decoded));
    }
    return Ok(None);
}

fn decode_base58(input: &str) -> Result<std::vec::Vec<u8>, crate::Error> {
    let decoded_result = bs58::decode(input).into_vec();
    match decoded_result {
        Ok(decoded) => return Ok(decoded),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot decode Meteora DLMM instruction data from base58: {}",
                error
            )));
        },
    }
}

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],
    ));
}

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

    fn make_create_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            401,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "DlmmPair111",
                "DlmmTokenX111",
                crate::WSOL_MINT_ID,
                "DlmmPreset111"
            ])
            .to_string(),
            None,
            Some("initialize_lb_pair".to_string()),
            Some(
                serde_json::json!({
                    "info": {
                        "lbPair": "DlmmPair111",
                        "tokenXMint": "DlmmTokenX111",
                        "tokenYMint": crate::WSOL_MINT_ID,
                        "presetParameter": "DlmmPreset111"
                    }
                })
                .to_string(),
            ),
        );
        dto.id = Some(402);
        return dto;
    }

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

    fn make_swap_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            403,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!(["DlmmPairSwap111", "DlmmSwapTokenX111", crate::WSOL_MINT_ID])
                .to_string(),
            None,
            Some("swap".to_string()),
            Some(
                serde_json::json!({
                    "info": {
                        "lbPair": "DlmmPairSwap111",
                        "tokenXMint": "DlmmSwapTokenX111",
                        "tokenYMint": crate::WSOL_MINT_ID,
                        "tradeSide": "buy"
                    }
                })
                .to_string(),
            ),
        );
        dto.id = Some(404);
        return dto;
    }

    #[test]
    fn meteora_dlmm_create_pool_is_detected() {
        let decoder = crate::MeteoraDlmmDecoder::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::MeteoraDlmmDecodedEvent::CreatePool(event) => {
                assert_eq!(event.transaction_id, 401);
                assert_eq!(event.instruction_id, 402);
                assert_eq!(event.pool_account, Some("DlmmPair111".to_string()));
                assert_eq!(event.token_a_mint, Some("DlmmTokenX111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert_eq!(event.config_account, Some("DlmmPreset111".to_string()));
            },
            crate::MeteoraDlmmDecodedEvent::Swap(_) => {
                panic!("unexpected swap event");
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(_)
            | crate::MeteoraDlmmDecodedEvent::PoolLifecycle(_)
            | crate::MeteoraDlmmDecodedEvent::Fee(_)
            | crate::MeteoraDlmmDecodedEvent::Reward(_) => {
                panic!("unexpected non-trade event");
            },
        }
    }

    #[test]
    fn meteora_dlmm_swap_is_detected() {
        let decoder = crate::MeteoraDlmmDecoder::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::MeteoraDlmmDecodedEvent::Swap(event) => {
                assert_eq!(event.transaction_id, 403);
                assert_eq!(event.instruction_id, 404);
                assert_eq!(event.pool_account, Some("DlmmPairSwap111".to_string()));
                assert_eq!(event.token_a_mint, Some("DlmmSwapTokenX111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert_eq!(event.trade_side, crate::SwapTradeSide::BuyBase);
            },
            crate::MeteoraDlmmDecodedEvent::CreatePool(_) => {
                panic!("unexpected create event");
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(_)
            | crate::MeteoraDlmmDecodedEvent::PoolLifecycle(_)
            | crate::MeteoraDlmmDecodedEvent::Fee(_)
            | crate::MeteoraDlmmDecodedEvent::Reward(_) => {
                panic!("unexpected non-trade event");
            },
        }
    }

    #[test]
    fn meteora_dlmm_initialize_bin_array_hint_is_decoded_as_pool_lifecycle() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = make_swap_instruction();
        instruction.parsed_type = Some("initialize_bin_array".to_string());
        instruction.parsed_json = None;
        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::MeteoraDlmmDecodedEvent::PoolLifecycle(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.initialize_bin_array");
                assert_eq!(event.pool_account, Some("DlmmPairSwap111".to_string()));
            },
            _ => panic!("expected pool lifecycle event"),
        }
    }

    #[test]
    fn meteora_dlmm_swap2_discriminator_is_detected() {
        let instruction_data = [0x41, 0x4b, 0x3f, 0x4c, 0xeb, 0x5b, 0x5b, 0x88, 0x01, 0x02, 0x03];
        let name = super::classify_instruction_name_from_data(Some(&instruction_data));
        assert_eq!(name, super::MeteoraDlmmInstructionName::Swap2);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::Swap);
    }

    #[test]
    fn meteora_dlmm_swap_accounts_are_mapped_from_upstream_git_layout() {
        let accounts = vec![
            "LbPair111".to_string(),
            "Bitmap111".to_string(),
            "ReserveX111".to_string(),
            "ReserveY111".to_string(),
            "UserTokenIn111".to_string(),
            "UserTokenOut111".to_string(),
            "TokenXMint111".to_string(),
            "TokenYMint111".to_string(),
        ];
        let pool = super::resolve_dlmm_pool_account(
            super::MeteoraDlmmInstructionName::Swap2,
            None,
            &accounts,
        );
        let token_x = super::resolve_dlmm_token_x_mint(
            super::MeteoraDlmmInstructionName::Swap2,
            None,
            &accounts,
        );
        let token_y = super::resolve_dlmm_token_y_mint(
            super::MeteoraDlmmInstructionName::Swap2,
            None,
            &accounts,
        );
        assert_eq!(pool, Some("LbPair111".to_string()));
        assert_eq!(token_x, Some("TokenXMint111".to_string()));
        assert_eq!(token_y, Some("TokenYMint111".to_string()));
    }

    #[test]
    fn meteora_dlmm_real_swap2_base58_discriminator_is_decoded() {
        let data_json = "\"fx9RHbGFfZ8bH9v4Jv5SQRfuGUq9kGrWfmiZ99\"".to_string();
        let decoded_result = super::decode_instruction_data_json(Some(&data_json));
        let decoded = match decoded_result {
            Ok(Some(decoded)) => decoded,
            Ok(None) => panic!("expected decoded data"),
            Err(error) => panic!("decode must succeed: {}", error),
        };
        let name = super::classify_instruction_name_from_data(Some(decoded.as_slice()));
        assert_eq!(name, super::MeteoraDlmmInstructionName::Swap2);
    }

    #[test]
    fn meteora_dlmm_inner_swap2_instruction_is_not_skipped() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let mut transaction = crate::ChainTransactionDto::new(
            "sig-meteora_dlmm-inner-swap2".to_string(),
            Some(888103),
            Some(1779400003),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 888103,
                "meta": {
                    "logMessages": []
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        transaction.id = Some(405);
        let mut instruction = crate::ChainInstructionDto::new(
            405,
            Some(404),
            3,
            Some(14),
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(2),
            serde_json::json!([
                "LbPair111",
                "Bitmap111",
                "ReserveX111",
                "ReserveY111",
                "UserTokenIn111",
                "UserTokenOut111",
                "TokenXMint111",
                "TokenYMint111",
                "Oracle111",
                "HostFee111",
                "User111",
                crate::SPL_TOKEN_PROGRAM_ID,
                crate::SPL_TOKEN_PROGRAM_ID,
                "EventAuthority111".to_string(),
                crate::METEORA_DLMM_PROGRAM_ID,
                "BinArray111",
                "BinArray222"
            ])
            .to_string(),
            Some("\"fx9RHbGFfZ8bH9v4Jv5SQRfuGUq9kGrWfmiZ99\"".to_string()),
            None,
            None,
        );
        instruction.id = Some(406);
        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::MeteoraDlmmDecodedEvent::Swap(event) => {
                assert_eq!(event.transaction_id, 405);
                assert_eq!(event.instruction_id, 406);
                assert_eq!(event.pool_account, Some("LbPair111".to_string()));
                assert_eq!(event.token_a_mint, Some("TokenXMint111".to_string()));
                assert_eq!(event.token_b_mint, Some("TokenYMint111".to_string()));
                assert_eq!(event.reserve_x_account, Some("ReserveX111".to_string()));
                assert_eq!(event.reserve_y_account, Some("ReserveY111".to_string()));
                assert_eq!(event.user_token_in_account, Some("UserTokenIn111".to_string()));
                assert_eq!(event.user_token_out_account, Some("UserTokenOut111".to_string()));
            },
            crate::MeteoraDlmmDecodedEvent::CreatePool(_) => {
                panic!("unexpected create event");
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(_)
            | crate::MeteoraDlmmDecodedEvent::PoolLifecycle(_)
            | crate::MeteoraDlmmDecodedEvent::Fee(_)
            | crate::MeteoraDlmmDecodedEvent::Reward(_) => {
                panic!("unexpected non-trade event");
            },
        }
    }

    #[test]
    fn meteora_dlmm_initialize_position_discriminator_is_non_trade_position_open() {
        let instruction_data = [0xdb, 0xc0, 0xea, 0x47, 0xbe, 0xbf, 0x66, 0x50, 0x01, 0x02, 0x03];
        let log_messages = vec!["Program log: Instruction: Swap".to_string()];
        let name =
            super::classify_instruction_name(None, None, Some(&instruction_data), &log_messages);
        assert_eq!(name, super::MeteoraDlmmInstructionName::InitializePosition);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::PositionOpen);
    }

    #[test]
    fn meteora_dlmm_add_remove_liquidity_discriminators_are_non_trade_liquidity() {
        let add_data = [0xb5, 0x9d, 0x59, 0x43, 0x8f, 0xb6, 0x34, 0x48, 0x01];
        let remove_data = [0x50, 0x55, 0xd1, 0x48, 0x18, 0xce, 0xb1, 0x6c, 0x01];
        let add_name = super::classify_instruction_name_from_data(Some(&add_data));
        let remove_name = super::classify_instruction_name_from_data(Some(&remove_data));
        assert_eq!(add_name, super::MeteoraDlmmInstructionName::AddLiquidity);
        assert_eq!(add_name.kind(), super::MeteoraDlmmInstructionKind::LiquidityAdd);
        assert_eq!(remove_name, super::MeteoraDlmmInstructionName::RemoveLiquidity);
        assert_eq!(remove_name.kind(), super::MeteoraDlmmInstructionKind::LiquidityRemove);
    }

    #[test]
    fn meteora_dlmm_initialize_bin_array_is_pool_lifecycle() {
        let data = [0x23, 0x56, 0x13, 0xb9, 0x4e, 0xd4, 0x4b, 0xd3, 0x01];
        let name = super::classify_instruction_name_from_data(Some(&data));
        assert_eq!(name, super::MeteoraDlmmInstructionName::InitializeBinArray);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::PoolLifecycle);
    }

    #[test]
    fn meteora_dlmm_add_liquidity_is_decoded_as_non_trade_event() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = crate::ChainInstructionDto::new(
            403,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "Position111",
                "DlmmPairSwap111",
                "Bitmap111",
                "UserTokenX111",
                "UserTokenY111",
                "ReserveX111",
                "ReserveY111",
                "DlmmSwapTokenX111",
                crate::WSOL_MINT_ID,
                "Owner111"
            ])
            .to_string(),
            Some("\"3K5citUwVB6uv\"".to_string()),
            None,
            None,
        );
        instruction.id = Some(405);
        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::MeteoraDlmmDecodedEvent::Liquidity(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.add_liquidity");
                assert_eq!(event.pool_account, Some("DlmmPairSwap111".to_string()));
                assert_eq!(event.token_a_mint, Some("DlmmSwapTokenX111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert_eq!(event.actor_wallet, Some("Owner111".to_string()));
            },
            _ => panic!("expected liquidity event"),
        }
    }

    #[test]
    fn meteora_dlmm_claim_fee2_discriminator_is_decoded_as_fee_event() {
        let instruction_data = [0x70, 0xbf, 0x65, 0xab, 0x1c, 0x90, 0x7f, 0xbb, 0x01];
        let name = super::classify_instruction_name_from_data(Some(&instruction_data));
        assert_eq!(name, super::MeteoraDlmmInstructionName::ClaimFee2);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::Fee);

        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = crate::ChainInstructionDto::new(
            403,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "DlmmPairFee111",
                "Position111",
                "BinArrayLower111",
                "BinArrayUpper111",
                "Owner111",
                "ReserveX111",
                "ReserveY111",
                "UserTokenX111",
                "UserTokenY111"
            ])
            .to_string(),
            Some("\"2SEn7GSqXFVhr\"".to_string()),
            None,
            None,
        );
        instruction.id = Some(407);
        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::MeteoraDlmmDecodedEvent::Fee(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.claim_fee2");
                assert_eq!(event.pool_account, Some("DlmmPairFee111".to_string()));
            },
            _ => panic!("expected fee event"),
        }
    }

    #[test]
    fn meteora_dlmm_close_position_if_empty_is_pool_lifecycle() {
        let instruction_data = [0x3b, 0x7c, 0xd4, 0x76, 0x5b, 0x98, 0x6e, 0x9d, 0x01];
        let name = super::classify_instruction_name_from_data(Some(&instruction_data));
        assert_eq!(name, super::MeteoraDlmmInstructionName::ClosePositionIfEmpty);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::PositionClose);

        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = crate::ChainInstructionDto::new(
            403,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "Position111",
                "Sender111",
                "RentReceiver111",
                "EventAuthority111",
                crate::METEORA_DLMM_PROGRAM_ID
            ])
            .to_string(),
            Some("\"kvDj9kL1jgdn\"".to_string()),
            None,
            None,
        );
        instruction.id = Some(408);
        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::MeteoraDlmmDecodedEvent::PoolLifecycle(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.close_position_if_empty");
                assert_eq!(event.pool_account, None);
                assert_eq!(
                    event.payload_json.get("position").and_then(serde_json::Value::as_str),
                    Some("Position111")
                );
            },
            _ => panic!("expected pool lifecycle event"),
        }
    }

    #[test]
    fn meteora_dlmm_unknown_data_discriminator_does_not_fallback_to_global_swap_logs() {
        let instruction_data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x01, 0x02, 0x03];
        let log_messages = vec!["Program log: Instruction: Swap".to_string()];
        let name =
            super::classify_instruction_name(None, None, Some(&instruction_data), &log_messages);
        assert_eq!(name, super::MeteoraDlmmInstructionName::Unknown);
    }

    #[test]
    fn meteora_dlmm_classifies_observed_initialize_preset_parameter_v2_discriminator() {
        let instruction_data =
            [0x75, 0xc7, 0x3e, 0x67, 0x06, 0x8e, 0x1f, 0xcb, 0x64, 0x00, 0x10, 0x27];
        let log_messages =
            vec!["Program log: Instruction: InitializePresetParameterV2".to_string()];
        let name =
            super::classify_instruction_name(None, None, Some(&instruction_data), &log_messages);
        assert_eq!(name, super::MeteoraDlmmInstructionName::InitializePresetParameterV2);
        assert_eq!(name.as_str(), "initialize_preset_parameter_v2");
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::Admin);
    }

    #[test]
    fn meteora_dlmm_all_local_idl_instruction_discriminators_are_classified() {
        let cases: &[(&str, [u8; 8])] = &[
            ("add_liquidity", [0xb5, 0x9d, 0x59, 0x43, 0x8f, 0xb6, 0x34, 0x48]),
            ("add_liquidity2", [0xe4, 0xa2, 0x4e, 0x1c, 0x46, 0xdb, 0x74, 0x73]),
            ("add_liquidity_by_strategy", [0x07, 0x03, 0x96, 0x7f, 0x94, 0x28, 0x3d, 0xc8]),
            ("add_liquidity_by_strategy2", [0x03, 0xdd, 0x95, 0xda, 0x6f, 0x8d, 0x76, 0xd5]),
            (
                "add_liquidity_by_strategy_one_side",
                [0x29, 0x05, 0xee, 0xaf, 0x64, 0xe1, 0x06, 0xcd],
            ),
            ("add_liquidity_by_weight", [0x1c, 0x8c, 0xee, 0x63, 0xe7, 0xa2, 0x15, 0x95]),
            ("add_liquidity_by_weight2", [0xd1, 0x3b, 0x3f, 0x5b, 0x6f, 0xc8, 0x99, 0xe4]),
            ("add_liquidity_one_side", [0x5e, 0x9b, 0x67, 0x97, 0x46, 0x5f, 0xdc, 0xa5]),
            (
                "add_liquidity_one_side_precise",
                [0xa1, 0xc2, 0x67, 0x54, 0xab, 0x47, 0xfa, 0x9a],
            ),
            (
                "add_liquidity_one_side_precise2",
                [0x21, 0x33, 0xa3, 0xc9, 0x75, 0x62, 0x7d, 0xe7],
            ),
            ("cancel_limit_order", [0x84, 0x9c, 0x84, 0x1f, 0x43, 0x28, 0xe8, 0x61]),
            ("claim_fee", [0xa9, 0x20, 0x4f, 0x89, 0x88, 0xe8, 0x46, 0x89]),
            ("claim_fee2", [0x70, 0xbf, 0x65, 0xab, 0x1c, 0x90, 0x7f, 0xbb]),
            ("claim_reward", [0x95, 0x5f, 0xb5, 0xf2, 0x5e, 0x5a, 0x9e, 0xa2]),
            ("claim_reward2", [0xbe, 0x03, 0x7f, 0x77, 0xb2, 0x57, 0x9d, 0xb7]),
            ("close_bin_array", [0x44, 0xae, 0x58, 0x50, 0xb5, 0xcc, 0x13, 0xe0]),
            (
                "close_claim_fee_operator_account",
                [0xb8, 0xd5, 0x58, 0x1f, 0xb3, 0x65, 0x82, 0x24],
            ),
            ("close_limit_order_if_empty", [0x39, 0x7c, 0x24, 0x9b, 0x7e, 0xf9, 0x5d, 0xab]),
            ("close_operator_account", [0xab, 0x09, 0xd5, 0x4a, 0x78, 0x17, 0x03, 0x1d]),
            ("close_position", [0x7b, 0x86, 0x51, 0x00, 0x31, 0x44, 0x62, 0x62]),
            ("close_position2", [0xae, 0x5a, 0x23, 0x73, 0xba, 0x28, 0x93, 0xe2]),
            ("close_position_if_empty", [0x3b, 0x7c, 0xd4, 0x76, 0x5b, 0x98, 0x6e, 0x9d]),
            ("close_preset_parameter", [0x04, 0x94, 0x91, 0x64, 0x86, 0x1a, 0xb5, 0x3d]),
            ("close_preset_parameter2", [0x27, 0x19, 0x5f, 0x6b, 0x74, 0x11, 0x73, 0x1c]),
            ("close_token_badge", [0x6c, 0x92, 0x56, 0x6e, 0xb3, 0xfe, 0x0a, 0x68]),
            ("create_operator_account", [0xdd, 0x40, 0xf6, 0x95, 0xf0, 0x99, 0xe5, 0xa3]),
            ("decrease_position_length", [0xc2, 0xdb, 0x88, 0x20, 0x19, 0x60, 0x69, 0x25]),
            (
                "for_idl_type_generation_do_not_call",
                [0xb4, 0x69, 0x45, 0x50, 0x5f, 0x32, 0x49, 0x6c],
            ),
            ("fund_reward", [0xbc, 0x32, 0xf9, 0xa5, 0x5d, 0x97, 0x26, 0x3f]),
            ("go_to_a_bin", [0x92, 0x48, 0xae, 0xe0, 0x28, 0xfd, 0x54, 0xae]),
            ("increase_oracle_length", [0xbe, 0x3d, 0x7d, 0x57, 0x67, 0x4f, 0x9e, 0xad]),
            ("increase_position_length", [0x50, 0x53, 0x75, 0xd3, 0x42, 0x0d, 0x21, 0x95]),
            ("increase_position_length2", [0xff, 0xd2, 0xcc, 0x47, 0x73, 0x89, 0xe1, 0x71]),
            ("initialize_bin_array", [0x23, 0x56, 0x13, 0xb9, 0x4e, 0xd4, 0x4b, 0xd3]),
            (
                "initialize_bin_array_bitmap_extension",
                [0x2f, 0x9d, 0xe2, 0xb4, 0x0c, 0xf0, 0x21, 0x47],
            ),
            (
                "initialize_customizable_permissionless_lb_pair",
                [0x2e, 0x27, 0x29, 0x87, 0x6f, 0xb7, 0xc8, 0x40],
            ),
            (
                "initialize_customizable_permissionless_lb_pair2",
                [0xf3, 0x49, 0x81, 0x7e, 0x33, 0x13, 0xf1, 0x6b],
            ),
            ("initialize_lb_pair", [0x2d, 0x9a, 0xed, 0xd2, 0xdd, 0x0f, 0xa6, 0x5c]),
            ("initialize_lb_pair2", [0x49, 0x3b, 0x24, 0x78, 0xed, 0x53, 0x6c, 0xc6]),
            (
                "initialize_permission_lb_pair",
                [0x6c, 0x66, 0xd5, 0x55, 0xfb, 0x03, 0x35, 0x15],
            ),
            ("initialize_position", [0xdb, 0xc0, 0xea, 0x47, 0xbe, 0xbf, 0x66, 0x50]),
            ("initialize_position2", [0x8f, 0x13, 0xf2, 0x91, 0xd5, 0x0f, 0x68, 0x73]),
            (
                "initialize_position_by_operator",
                [0xfb, 0xbd, 0xbe, 0xf4, 0x75, 0xfe, 0x23, 0x94],
            ),
            ("initialize_position_pda", [0x2e, 0x52, 0x7d, 0x92, 0x55, 0x8d, 0xe4, 0x99]),
            ("initialize_preset_parameter", [0x42, 0xbc, 0x47, 0xd3, 0x62, 0x6d, 0x0e, 0xba]),
            ("initialize_reward", [0x5f, 0x87, 0xc0, 0xc4, 0xf2, 0x81, 0xe6, 0x44]),
            ("initialize_token_badge", [0xfd, 0x4d, 0xcd, 0x5f, 0x1b, 0xe0, 0x59, 0xdf]),
            ("place_limit_order", [0x6c, 0xb0, 0x21, 0xba, 0x92, 0xe5, 0x01, 0xc5]),
            ("rebalance_liquidity", [0x5c, 0x04, 0xb0, 0xc1, 0x77, 0xb9, 0x53, 0x09]),
            ("remove_all_liquidity", [0x0a, 0x33, 0x3d, 0x23, 0x70, 0x69, 0x18, 0x55]),
            ("remove_liquidity", [0x50, 0x55, 0xd1, 0x48, 0x18, 0xce, 0xb1, 0x6c]),
            ("remove_liquidity2", [0xe6, 0xd7, 0x52, 0x7f, 0xf1, 0x65, 0xe3, 0x92]),
            ("remove_liquidity_by_range", [0x1a, 0x52, 0x66, 0x98, 0xf0, 0x4a, 0x69, 0x1a]),
            ("remove_liquidity_by_range2", [0xcc, 0x02, 0xc3, 0x91, 0x35, 0x91, 0x91, 0xcd]),
            ("set_activation_point", [0x5b, 0xf9, 0x0f, 0xa5, 0x1a, 0x81, 0xfe, 0x7d]),
            ("set_pair_status", [0x43, 0xf8, 0xe7, 0x89, 0x9a, 0x95, 0xd9, 0xae]),
            (
                "set_pair_status_permissionless",
                [0x4e, 0x3b, 0x98, 0xd3, 0x46, 0xb7, 0x2e, 0xd0],
            ),
            (
                "set_permissionless_operation_bits",
                [0x54, 0x3a, 0xcb, 0x8b, 0xa3, 0x51, 0xbe, 0xba],
            ),
            ("set_pre_activation_duration", [0xa5, 0x3d, 0xc9, 0xf4, 0x82, 0x9f, 0x16, 0x64]),
            (
                "set_pre_activation_swap_address",
                [0x39, 0x8b, 0x2f, 0x7b, 0xd8, 0x50, 0xdf, 0x0a],
            ),
            ("swap", [0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8]),
            ("swap2", [0x41, 0x4b, 0x3f, 0x4c, 0xeb, 0x5b, 0x5b, 0x88]),
            ("swap_exact_out", [0xfa, 0x49, 0x65, 0x21, 0x26, 0xcf, 0x4b, 0xb8]),
            ("swap_exact_out2", [0x2b, 0xd7, 0xf7, 0x84, 0x89, 0x3c, 0xf3, 0x51]),
            ("swap_with_price_impact", [0x38, 0xad, 0xe6, 0xd0, 0xad, 0xe4, 0x9c, 0xcd]),
            ("swap_with_price_impact2", [0x4a, 0x62, 0xc0, 0xd6, 0xb1, 0x33, 0x4b, 0x33]),
            ("update_base_fee_parameters", [0x4b, 0xa8, 0xdf, 0xa1, 0x10, 0xc3, 0x03, 0x2f]),
            (
                "update_dynamic_fee_parameters",
                [0x5c, 0xa1, 0x2e, 0xf6, 0xff, 0xbd, 0x16, 0x16],
            ),
            ("update_fees_and_reward2", [0x20, 0x8e, 0xb8, 0x9a, 0x67, 0x41, 0xb8, 0x58]),
            ("update_fees_and_rewards", [0x9a, 0xe6, 0xfa, 0x0d, 0xec, 0xd1, 0x4b, 0xdf]),
            ("update_position_operator", [0xca, 0xb8, 0x67, 0x8f, 0xb4, 0xbf, 0x74, 0xd9]),
            ("update_reward_duration", [0x8a, 0xae, 0xc4, 0xa9, 0xd5, 0xeb, 0xfe, 0x6b]),
            ("update_reward_funder", [0xd3, 0x1c, 0x30, 0x20, 0xd7, 0xa0, 0x23, 0x17]),
            ("withdraw_ineligible_reward", [0x94, 0xce, 0x2a, 0xc3, 0xf7, 0x31, 0x67, 0x08]),
            ("withdraw_protocol_fee", [0x9e, 0xc9, 0x9e, 0xbd, 0x21, 0x5d, 0xa2, 0x67]),
            ("zap_protocol_fee", [0xd5, 0x9b, 0xbb, 0x22, 0x38, 0xb6, 0x5b, 0xf0]),
        ];
        assert_eq!(cases.len(), 76);
        for (expected_name, discriminator) in cases {
            let decoded_name = super::classify_instruction_name_from_data(Some(discriminator));
            assert_eq!(decoded_name.as_str(), *expected_name);
            assert_ne!(decoded_name.kind(), super::MeteoraDlmmInstructionKind::Unknown);
        }
    }

    #[test]
    fn meteora_dlmm_all_local_idl_anchor_event_discriminators_are_named() {
        let cases: &[(&str, &str)] = &[
            ("add_liquidity_event", "1f5e7d5ae3343dba"),
            ("cancel_limit_order_evt", "83eac285090ebdd1"),
            ("claim_fee_event", "4b7a9a308c4a7ba3"),
            ("claim_fee2_event", "e8abf2613a4d232d"),
            ("claim_reward_event", "947486cc16ab555f"),
            ("claim_reward2_event", "1b8ff421502b6e92"),
            ("close_limit_order_evt", "8e87084c5c3f7653"),
            ("composition_fee_event", "80977b6a1166718e"),
            ("decrease_position_length_event", "3476eb55aca90f80"),
            ("dynamic_fee_parameter_update_event", "5858b287c2925bf3"),
            ("fee_parameter_update_event", "304cf17590d7f22c"),
            ("fund_reward_event", "f6e43a8291aa4fcc"),
            ("go_to_a_bin_event", "3b8a4c448a83b043"),
            ("increase_observation_event", "63f91179a69ccfd7"),
            ("increase_position_length_event", "9def2acc1e38df2e"),
            ("initialize_reward_event", "d399583e953cb146"),
            ("lb_pair_create_event", "b94afc7d1bd7bc6f"),
            ("place_limit_order_evt", "2b4f1ba9f41ce13f"),
            ("position_close_event", "ffc4106b1cca3580"),
            ("position_create_event", "908efc549d352579"),
            ("rebalancing_event", "006d75b33d5bc7c8"),
            ("remove_liquidity_event", "74f461e8671f983a"),
            ("set_position_permissionless_operation_bits_evt", "c3e593f51d7d30a8"),
            ("swap_event", "516ce3becdd00ac4"),
            ("swap2_evt", "2e7452d7941b544d"),
            ("update_position_lock_release_point_event", "85d642e0400c07bf"),
            ("update_position_operator_event", "277330ccf62f4239"),
            ("update_reward_duration_event", "dff5e099311da3ac"),
            ("update_reward_funder_event", "e0b2ae4afca555b4"),
            ("withdraw_ineligible_reward_event", "e7bd419566d79af4"),
        ];
        assert_eq!(cases.len(), 30);
        for (expected_name, discriminator_hex) in cases {
            let decoded_name =
                super::dlmm_anchor_event_name_from_discriminator_hex(discriminator_hex);
            assert_eq!(decoded_name, Some(*expected_name));
        }
    }

    fn synthetic_data_json(bytes: &[u8]) -> std::string::String {
        return serde_json::Value::String(bs58::encode(bytes).into_string()).to_string();
    }

    fn synthetic_instruction_data(discriminator: [u8; 8]) -> std::vec::Vec<u8> {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&discriminator);
        data.push(0_u8);
        return data;
    }

    fn append_synthetic_pubkey(data: &mut std::vec::Vec<u8>, seed: u8) {
        data.extend_from_slice(&[seed; 32]);
    }

    fn append_u64(data: &mut std::vec::Vec<u8>, amount: u64) {
        data.extend_from_slice(&amount.to_le_bytes());
    }

    fn synthetic_claim_fee_event_data(
        event_discriminator: [u8; 8],
        fee_x: u64,
        fee_y: u64,
    ) -> std::vec::Vec<u8> {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&super::DLMM_ANCHOR_SELF_CPI_LOG_SELECTOR);
        data.extend_from_slice(&event_discriminator);
        append_synthetic_pubkey(&mut data, 1);
        append_synthetic_pubkey(&mut data, 2);
        append_synthetic_pubkey(&mut data, 3);
        append_u64(&mut data, fee_x);
        append_u64(&mut data, fee_y);
        return data;
    }

    fn synthetic_claim_reward_event_data(
        event_discriminator: [u8; 8],
        reward_index: u64,
        reward_amount: u64,
    ) -> std::vec::Vec<u8> {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&super::DLMM_ANCHOR_SELF_CPI_LOG_SELECTOR);
        data.extend_from_slice(&event_discriminator);
        append_synthetic_pubkey(&mut data, 4);
        append_synthetic_pubkey(&mut data, 5);
        append_synthetic_pubkey(&mut data, 6);
        append_u64(&mut data, reward_index);
        append_u64(&mut data, reward_amount);
        return data;
    }

    fn synthetic_transaction(signature: &str, transaction_id: i64) -> crate::ChainTransactionDto {
        let mut transaction = crate::ChainTransactionDto::new(
            signature.to_string(),
            Some(888157),
            Some(1779400057),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 888157,
                "meta": { "logMessages": [] },
                "transaction": { "message": { "instructions": [] } }
            })
            .to_string(),
        );
        transaction.id = Some(transaction_id);
        return transaction;
    }

    #[test]
    fn meteora_dlmm_claim_fee_anchor_event_materializes_dual_fee_legs() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = synthetic_transaction("sig-meteora-dlmm-claim-fee-anchor", 701);
        let mut parent = crate::ChainInstructionDto::new(
            701,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "LbPairFee111",
                "Position111",
                "ReserveX111",
                "ReserveY111",
                "Sender111",
                "ReserveX111",
                "ReserveY111",
                "UserTokenX111",
                "UserTokenY111",
                "TokenXMint111",
                "TokenYMint111"
            ])
            .to_string(),
            Some(synthetic_data_json(&synthetic_instruction_data(
                super::DLMM_DISCRIMINATOR_CLAIM_FEE,
            ))),
            Some("claim_fee".to_string()),
            None,
        );
        parent.id = Some(702);
        let event_data =
            synthetic_claim_fee_event_data(super::DLMM_EVENT_DISCRIMINATOR_CLAIM_FEE, 11, 22);
        let mut child = crate::ChainInstructionDto::new(
            701,
            Some(702),
            0,
            Some(0),
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(2),
            serde_json::json!([]).to_string(),
            Some(synthetic_data_json(&event_data)),
            None,
            None,
        );
        child.id = Some(703);
        let decoded_result = decoder.decode_transaction(&transaction, &[parent, child]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        let mut found = false;
        for event in decoded.iter() {
            if let crate::MeteoraDlmmDecodedEvent::Fee(fee_event) = event {
                if fee_event.event_kind != "meteora_dlmm.claim_fee_event" {
                    continue;
                }
                found = true;
                assert!(fee_event.payload_json.get("skipFeeReason").is_none());
                let legs = fee_event.payload_json.get("feeAmountLegs");
                let legs = match legs.and_then(serde_json::Value::as_array) {
                    Some(legs) => legs,
                    None => panic!("expected feeAmountLegs"),
                };
                assert_eq!(legs.len(), 2);
                assert_eq!(
                    legs[0].get("tokenMint").and_then(serde_json::Value::as_str),
                    Some("TokenXMint111")
                );
                assert_eq!(
                    legs[0].get("amountRaw").and_then(serde_json::Value::as_str),
                    Some("11")
                );
                assert_eq!(
                    legs[1].get("tokenMint").and_then(serde_json::Value::as_str),
                    Some("TokenYMint111")
                );
                assert_eq!(
                    legs[1].get("amountRaw").and_then(serde_json::Value::as_str),
                    Some("22")
                );
            }
        }
        assert!(found);
    }

    #[test]
    fn meteora_dlmm_claim_reward_anchor_event_materializes_reward_mint() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = synthetic_transaction("sig-meteora-dlmm-claim-reward-anchor", 704);
        let mut parent = crate::ChainInstructionDto::new(
            704,
            None,
            1,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "LbPairReward111",
                "Position111",
                "BinArray111",
                "Sender111",
                "ReserveReward111",
                "UserReward111",
                "RewardMint111"
            ])
            .to_string(),
            Some(synthetic_data_json(&synthetic_instruction_data(
                super::DLMM_DISCRIMINATOR_CLAIM_REWARD,
            ))),
            Some("claim_reward".to_string()),
            None,
        );
        parent.id = Some(705);
        let event_data =
            synthetic_claim_reward_event_data(super::DLMM_EVENT_DISCRIMINATOR_CLAIM_REWARD, 2, 333);
        let mut child = crate::ChainInstructionDto::new(
            704,
            Some(705),
            1,
            Some(0),
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(2),
            serde_json::json!([]).to_string(),
            Some(synthetic_data_json(&event_data)),
            None,
            None,
        );
        child.id = Some(706);
        let decoded_result = decoder.decode_transaction(&transaction, &[parent, child]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        let mut found = false;
        for event in decoded.iter() {
            if let crate::MeteoraDlmmDecodedEvent::Reward(reward_event) = event {
                if reward_event.event_kind != "meteora_dlmm.claim_reward_event" {
                    continue;
                }
                found = true;
                assert!(reward_event.payload_json.get("skipRewardReason").is_none());
                assert_eq!(reward_event.reward_token_mint, Some("RewardMint111".to_string()));
                assert_eq!(reward_event.reward_amount_raw, Some("333".to_string()));
            }
        }
        assert!(found);
    }

    #[test]
    fn meteora_dlmm_place_limit_order_instruction_materializes_orderbook_target() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = synthetic_transaction("sig-meteora-dlmm-place-limit-order", 707);
        let mut instruction = crate::ChainInstructionDto::new(
            707,
            None,
            2,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "LbPairOrder111",
                "BinArrayBitmap111",
                "Reserve111",
                "TokenMint111",
                "LimitOrder111",
                "Payer111",
                "Owner111",
                "TokenProgram111",
                "Sender111"
            ])
            .to_string(),
            Some(synthetic_data_json(&synthetic_instruction_data(
                super::DLMM_DISCRIMINATOR_PLACE_LIMIT_ORDER,
            ))),
            Some("place_limit_order".to_string()),
            None,
        );
        instruction.id = Some(708);
        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::MeteoraDlmmDecodedEvent::PoolLifecycle(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.place_limit_order");
                assert_eq!(event.payload_json.get("skipOrderbookReason"), None);
                assert_eq!(
                    event
                        .payload_json
                        .get("materializationTarget")
                        .and_then(serde_json::Value::as_str),
                    Some("orderbook"),
                );
                assert!(event.payload_json.get("skipTradeReason").is_some());
                assert!(event.payload_json.get("skipCandleReason").is_some());
            },
            _ => panic!("expected materialized orderbook lifecycle event"),
        }
    }

    #[test]
    fn meteora_dlmm_idl_generation_sentinel_does_not_emit_decoded_only_event() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = synthetic_transaction("sig-meteora-dlmm-idl-sentinel", 709);
        let mut instruction = crate::ChainInstructionDto::new(
            709,
            None,
            3,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora_dlmm".to_string()),
            Some(1),
            serde_json::json!([]).to_string(),
            Some(synthetic_data_json(&synthetic_instruction_data(
                super::DLMM_DISCRIMINATOR_FOR_IDL_TYPE_GENERATION_DO_NOT_CALL,
            ))),
            Some("for_idl_type_generation_do_not_call".to_string()),
            None,
        );
        instruction.id = Some(710);
        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(), 0);
    }
}
