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

//! Pump Fees fee/config/accounting transaction decoder.

const PUMP_FEES_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR: [u8; 8] = [228, 69, 165, 46, 81, 203, 154, 29];
const PUMP_FEES_CLAIM_SOCIAL_FEE_PDA_DISCRIMINATOR: [u8; 8] =
    [225, 21, 251, 133, 161, 30, 199, 226];
const PUMP_FEES_CLAIM_SOCIAL_FEE_PDA_V2_DISCRIMINATOR: [u8; 8] =
    [17, 77, 240, 134, 58, 188, 53, 149];
const PUMP_FEES_CRANK_DONATION_FEE_PDA_DISCRIMINATOR: [u8; 8] =
    [220, 10, 189, 167, 169, 17, 25, 69];
const PUMP_FEES_CREATE_DONATION_FEE_PDA_DISCRIMINATOR: [u8; 8] =
    [244, 139, 16, 88, 14, 255, 122, 26];
const PUMP_FEES_CREATE_FEE_SHARING_CONFIG_DISCRIMINATOR: [u8; 8] =
    [195, 78, 86, 76, 111, 52, 251, 213];
const PUMP_FEES_CREATE_SOCIAL_FEE_PDA_DISCRIMINATOR: [u8; 8] =
    [144, 224, 59, 211, 78, 248, 202, 220];
const PUMP_FEES_EXTEND_FEE_CONFIG_DISCRIMINATOR: [u8; 8] = [68, 179, 244, 90, 173, 56, 17, 217];
const PUMP_FEES_GET_FEES_DISCRIMINATOR: [u8; 8] = [231, 37, 126, 85, 207, 91, 63, 52];
const PUMP_FEES_INITIALIZE_BUYBACK_DISCRIMINATOR: [u8; 8] = [250, 129, 236, 160, 227, 36, 103, 134];
const PUMP_FEES_INITIALIZE_FEE_CONFIG_DISCRIMINATOR: [u8; 8] = [62, 162, 20, 133, 121, 65, 145, 27];
const PUMP_FEES_INITIALIZE_FEE_PROGRAM_GLOBAL_DISCRIMINATOR: [u8; 8] =
    [35, 215, 130, 84, 233, 56, 124, 167];
const PUMP_FEES_RESET_FEE_SHARING_CONFIG_DISCRIMINATOR: [u8; 8] =
    [10, 2, 182, 95, 16, 127, 129, 186];
const PUMP_FEES_RESET_FEE_SHARING_CONFIG_V2_DISCRIMINATOR: [u8; 8] =
    [169, 245, 17, 209, 94, 91, 248, 128];
const PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY_DISCRIMINATOR: [u8; 8] =
    [18, 233, 158, 39, 185, 207, 58, 104];
const PUMP_FEES_SET_AUTHORITY_DISCRIMINATOR: [u8; 8] = [133, 250, 37, 21, 110, 163, 26, 121];
const PUMP_FEES_SET_CLAIM_RATE_LIMIT_DISCRIMINATOR: [u8; 8] = [185, 211, 159, 174, 212, 49, 88, 4];
const PUMP_FEES_SET_DISABLE_FLAGS_DISCRIMINATOR: [u8; 8] = [194, 217, 112, 35, 114, 222, 51, 190];
const PUMP_FEES_SET_SOCIAL_CLAIM_AUTHORITY_DISCRIMINATOR: [u8; 8] =
    [147, 54, 184, 154, 136, 237, 185, 153];
const PUMP_FEES_SWEEP_BUYBACK_DISCRIMINATOR: [u8; 8] = [138, 33, 204, 38, 207, 161, 159, 226];
const PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY_DISCRIMINATOR: [u8; 8] =
    [202, 10, 75, 200, 164, 34, 210, 96];
const PUMP_FEES_UPDATE_ADMIN_DISCRIMINATOR: [u8; 8] = [161, 176, 40, 213, 60, 184, 179, 228];
const PUMP_FEES_UPDATE_BUYBACK_AUTHORITY_DISCRIMINATOR: [u8; 8] =
    [66, 98, 113, 202, 121, 37, 219, 107];
const PUMP_FEES_UPDATE_BUYBACK_CLAIM_RATE_LIMIT_DISCRIMINATOR: [u8; 8] =
    [186, 95, 135, 190, 255, 199, 137, 170];
const PUMP_FEES_UPDATE_FEE_CONFIG_DISCRIMINATOR: [u8; 8] = [104, 184, 103, 242, 88, 151, 107, 20];
const PUMP_FEES_UPDATE_FEE_SHARES_DISCRIMINATOR: [u8; 8] = [189, 13, 136, 99, 187, 164, 237, 35];
const PUMP_FEES_UPDATE_FEE_SHARES_V2_DISCRIMINATOR: [u8; 8] = [111, 251, 49, 6, 78, 78, 106, 18];
const PUMP_FEES_UPDATE_STABLE_FEE_CONFIG_DISCRIMINATOR: [u8; 8] =
    [107, 169, 100, 179, 134, 155, 146, 221];
const PUMP_FEES_UPSERT_FEE_TIERS_DISCRIMINATOR: [u8; 8] = [227, 23, 150, 12, 77, 86, 94, 4];
const PUMP_FEES_UPSERT_STABLE_FEE_TIERS_DISCRIMINATOR: [u8; 8] =
    [181, 160, 162, 252, 74, 76, 224, 221];
const PUMP_FEES_CREATE_FEE_SHARING_CONFIG_EVENT_DISCRIMINATOR: [u8; 8] =
    [133, 105, 170, 200, 184, 116, 251, 88];
const PUMP_FEES_DONATION_FEE_PDA_CRANKED_DISCRIMINATOR: [u8; 8] =
    [30, 208, 107, 93, 177, 0, 223, 78];
const PUMP_FEES_DONATION_FEE_PDA_CREATED_DISCRIMINATOR: [u8; 8] =
    [94, 20, 137, 239, 35, 77, 225, 235];
const PUMP_FEES_EXTEND_FEE_CONFIG_EVENT_DISCRIMINATOR: [u8; 8] =
    [226, 203, 224, 35, 153, 10, 88, 51];
const PUMP_FEES_INITIALIZE_FEE_CONFIG_EVENT_DISCRIMINATOR: [u8; 8] =
    [89, 138, 244, 230, 10, 56, 226, 126];
const PUMP_FEES_INITIALIZE_FEE_PROGRAM_GLOBAL_EVENT_DISCRIMINATOR: [u8; 8] =
    [40, 233, 156, 78, 95, 0, 8, 199];
const PUMP_FEES_RESET_FEE_SHARING_CONFIG_EVENT_DISCRIMINATOR: [u8; 8] =
    [203, 204, 151, 226, 120, 55, 214, 243];
const PUMP_FEES_SET_AUTHORITY_EVENT_DISCRIMINATOR: [u8; 8] = [18, 175, 132, 66, 208, 201, 87, 242];
const PUMP_FEES_SET_CLAIM_RATE_LIMIT_EVENT_DISCRIMINATOR: [u8; 8] =
    [13, 143, 143, 235, 181, 19, 51, 40];
const PUMP_FEES_SET_DISABLE_FLAGS_EVENT_DISCRIMINATOR: [u8; 8] = [5, 8, 179, 65, 49, 55, 145, 126];
const PUMP_FEES_SET_SOCIAL_CLAIM_AUTHORITY_EVENT_DISCRIMINATOR: [u8; 8] =
    [60, 118, 127, 132, 239, 52, 254, 14];
const PUMP_FEES_SOCIAL_FEE_PDA_CLAIMED_DISCRIMINATOR: [u8; 8] =
    [50, 18, 193, 65, 237, 210, 234, 236];
const PUMP_FEES_SOCIAL_FEE_PDA_CREATED_DISCRIMINATOR: [u8; 8] =
    [183, 183, 218, 147, 24, 124, 137, 169];
const PUMP_FEES_SWEEP_BUYBACK_EVENT_DISCRIMINATOR: [u8; 8] = [43, 56, 42, 214, 153, 57, 166, 137];
const PUMP_FEES_UPDATE_ADMIN_EVENT_DISCRIMINATOR: [u8; 8] = [225, 152, 171, 87, 246, 63, 66, 234];
const PUMP_FEES_UPDATE_FEE_CONFIG_EVENT_DISCRIMINATOR: [u8; 8] =
    [90, 23, 65, 35, 62, 244, 188, 208];
const PUMP_FEES_UPDATE_FEE_SHARES_EVENT_DISCRIMINATOR: [u8; 8] =
    [21, 186, 196, 184, 91, 228, 225, 203];
const PUMP_FEES_UPDATE_STABLE_FEE_CONFIG_EVENT_DISCRIMINATOR: [u8; 8] =
    [94, 5, 43, 237, 103, 147, 232, 245];
const PUMP_FEES_UPSERT_FEE_TIERS_EVENT_DISCRIMINATOR: [u8; 8] =
    [171, 89, 169, 187, 122, 186, 33, 204];
const PUMP_FEES_UPSERT_STABLE_FEE_TIERS_EVENT_DISCRIMINATOR: [u8; 8] =
    [232, 237, 237, 52, 98, 146, 73, 243];

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PumpFeesBorshFieldKind {
    U8,
    U64,
    I64,
    U128,
    Bool,
    Pubkey,
    String,
    OptionBool,
    OptionPubkey,
    Fees,
    ConfigStatus,
    ShareholderVec,
    FeeTierVec,
}

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

#[derive(Debug, Clone, Copy)]
struct PumpFeesInstructionSpec {
    name: &'static str,
    fields: &'static [PumpFeesBorshFieldDescriptor],
}

#[derive(Debug, Clone, Copy)]
struct PumpFeesAnchorEventSpec {
    name: &'static str,
    event_kind: &'static str,
    fields: &'static [PumpFeesBorshFieldDescriptor],
}

const PUMP_FEES_INSTRUCTION_CLAIM_SOCIAL_FEE_PDA_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "user_id",
        kind: PumpFeesBorshFieldKind::String,
    },
    PumpFeesBorshFieldDescriptor {
        name: "platform",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_INSTRUCTION_CLAIM_SOCIAL_FEE_PDA_V2_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "user_id",
        kind: PumpFeesBorshFieldKind::String,
    },
    PumpFeesBorshFieldDescriptor {
        name: "platform",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_INSTRUCTION_CRANK_DONATION_FEE_PDA_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_CREATE_DONATION_FEE_PDA_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_CREATE_FEE_SHARING_CONFIG_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_CREATE_SOCIAL_FEE_PDA_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "user_id",
        kind: PumpFeesBorshFieldKind::String,
    },
    PumpFeesBorshFieldDescriptor {
        name: "platform",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_INSTRUCTION_EXTEND_FEE_CONFIG_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_GET_FEES_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "is_pump_pool",
        kind: PumpFeesBorshFieldKind::Bool,
    },
    PumpFeesBorshFieldDescriptor {
        name: "market_cap_lamports",
        kind: PumpFeesBorshFieldKind::U128,
    },
    PumpFeesBorshFieldDescriptor {
        name: "trade_size_lamports",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "is_new_quote_mint",
        kind: PumpFeesBorshFieldKind::OptionBool,
    },
];

const PUMP_FEES_INSTRUCTION_INITIALIZE_BUYBACK_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "index",
        kind: PumpFeesBorshFieldKind::U8,
    }];

const PUMP_FEES_INSTRUCTION_INITIALIZE_FEE_CONFIG_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_INITIALIZE_FEE_PROGRAM_GLOBAL_FIELDS:
    &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "social_claim_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "disable_flags",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "claim_rate_limit",
        kind: PumpFeesBorshFieldKind::U64,
    },
];

const PUMP_FEES_INSTRUCTION_RESET_FEE_SHARING_CONFIG_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_RESET_FEE_SHARING_CONFIG_V2_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[];

const PUMP_FEES_INSTRUCTION_REVOKE_FEE_SHARING_AUTHORITY_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[];

const PUMP_FEES_INSTRUCTION_SET_AUTHORITY_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "new_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    }];

const PUMP_FEES_INSTRUCTION_SET_CLAIM_RATE_LIMIT_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "claim_rate_limit",
        kind: PumpFeesBorshFieldKind::U64,
    }];

const PUMP_FEES_INSTRUCTION_SET_DISABLE_FLAGS_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "disable_flags",
        kind: PumpFeesBorshFieldKind::U8,
    }];

const PUMP_FEES_INSTRUCTION_SET_SOCIAL_CLAIM_AUTHORITY_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "social_claim_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    }];

const PUMP_FEES_INSTRUCTION_SWEEP_BUYBACK_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "index",
        kind: PumpFeesBorshFieldKind::U8,
    }];

const PUMP_FEES_INSTRUCTION_TRANSFER_FEE_SHARING_AUTHORITY_FIELDS:
    &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_UPDATE_ADMIN_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[];

const PUMP_FEES_INSTRUCTION_UPDATE_BUYBACK_AUTHORITY_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "index",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_INSTRUCTION_UPDATE_BUYBACK_CLAIM_RATE_LIMIT_FIELDS:
    &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "index",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "claim_rate_limit",
        kind: PumpFeesBorshFieldKind::I64,
    },
];

const PUMP_FEES_INSTRUCTION_UPDATE_FEE_CONFIG_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "flat_fees",
        kind: PumpFeesBorshFieldKind::Fees,
    },
];

const PUMP_FEES_INSTRUCTION_UPDATE_FEE_SHARES_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "shareholders",
        kind: PumpFeesBorshFieldKind::ShareholderVec,
    }];

const PUMP_FEES_INSTRUCTION_UPDATE_FEE_SHARES_V2_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "shareholders",
        kind: PumpFeesBorshFieldKind::ShareholderVec,
    }];

const PUMP_FEES_INSTRUCTION_UPDATE_STABLE_FEE_CONFIG_FIELDS: &[PumpFeesBorshFieldDescriptor] =
    &[PumpFeesBorshFieldDescriptor {
        name: "fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    }];

const PUMP_FEES_INSTRUCTION_UPSERT_FEE_TIERS_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "offset",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_INSTRUCTION_UPSERT_STABLE_FEE_TIERS_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "offset",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_EVENT_CREATE_FEE_SHARING_CONFIG_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "bonding_curve",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "pool",
        kind: PumpFeesBorshFieldKind::OptionPubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "sharing_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "initial_shareholders",
        kind: PumpFeesBorshFieldKind::ShareholderVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "status",
        kind: PumpFeesBorshFieldKind::ConfigStatus,
    },
];

const PUMP_FEES_EVENT_DONATION_FEE_PDA_CRANKED_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "signer",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "donation_fee_pda",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "config_id",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "base_mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "creator",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "amount",
        kind: PumpFeesBorshFieldKind::U64,
    },
];

const PUMP_FEES_EVENT_DONATION_FEE_PDA_CREATED_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "created_by",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "donation_fee_pda",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "config_id",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "base_mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "creator",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_EXTEND_FEE_CONFIG_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "fee_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "user",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "current_size",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_size",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
];

const PUMP_FEES_EVENT_INITIALIZE_FEE_CONFIG_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_INITIALIZE_FEE_PROGRAM_GLOBAL_EVENT_FIELDS:
    &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "social_claim_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "disable_flags",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "claim_rate_limit",
        kind: PumpFeesBorshFieldKind::U64,
    },
];

const PUMP_FEES_EVENT_RESET_FEE_SHARING_CONFIG_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "sharing_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "old_admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "old_shareholders",
        kind: PumpFeesBorshFieldKind::ShareholderVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_shareholders",
        kind: PumpFeesBorshFieldKind::ShareholderVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "old_version",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_version",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_EVENT_SET_AUTHORITY_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "old_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_SET_CLAIM_RATE_LIMIT_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "claim_rate_limit",
        kind: PumpFeesBorshFieldKind::U64,
    },
];

const PUMP_FEES_EVENT_SET_DISABLE_FLAGS_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "disable_flags",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_EVENT_SET_SOCIAL_CLAIM_AUTHORITY_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "social_claim_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_SOCIAL_FEE_PDA_CLAIMED_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "user_id",
        kind: PumpFeesBorshFieldKind::String,
    },
    PumpFeesBorshFieldDescriptor {
        name: "platform",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "social_fee_pda",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "recipient",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "social_claim_authority",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "amount_claimed",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "claimable_before",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "lifetime_claimed",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "recipient_balance_before",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "recipient_balance_after",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "quote_mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_SOCIAL_FEE_PDA_CREATED_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "user_id",
        kind: PumpFeesBorshFieldKind::String,
    },
    PumpFeesBorshFieldDescriptor {
        name: "platform",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "social_fee_pda",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "created_by",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_SWEEP_BUYBACK_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "index",
        kind: PumpFeesBorshFieldKind::U8,
    },
    PumpFeesBorshFieldDescriptor {
        name: "sol_amount",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "token_amount",
        kind: PumpFeesBorshFieldKind::U64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "destination",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "buyback_vault",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_UPDATE_ADMIN_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "old_admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
];

const PUMP_FEES_EVENT_UPDATE_FEE_CONFIG_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "flat_fees",
        kind: PumpFeesBorshFieldKind::Fees,
    },
];

const PUMP_FEES_EVENT_UPDATE_FEE_SHARES_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "mint",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "sharing_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "new_shareholders",
        kind: PumpFeesBorshFieldKind::ShareholderVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "version",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_EVENT_UPDATE_STABLE_FEE_CONFIG_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "stable_fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "flat_fees",
        kind: PumpFeesBorshFieldKind::Fees,
    },
];

const PUMP_FEES_EVENT_UPSERT_FEE_TIERS_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "offset",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

const PUMP_FEES_EVENT_UPSERT_STABLE_FEE_TIERS_EVENT_FIELDS: &[PumpFeesBorshFieldDescriptor] = &[
    PumpFeesBorshFieldDescriptor {
        name: "timestamp",
        kind: PumpFeesBorshFieldKind::I64,
    },
    PumpFeesBorshFieldDescriptor {
        name: "admin",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "fee_config",
        kind: PumpFeesBorshFieldKind::Pubkey,
    },
    PumpFeesBorshFieldDescriptor {
        name: "stable_fee_tiers",
        kind: PumpFeesBorshFieldKind::FeeTierVec,
    },
    PumpFeesBorshFieldDescriptor {
        name: "offset",
        kind: PumpFeesBorshFieldKind::U8,
    },
];

#[derive(Debug, Clone)]
struct PumpFeesInstructionData {
    spec: PumpFeesInstructionSpec,
    discriminator_hex: std::string::String,
    args_json: serde_json::Value,
}

#[derive(Debug, Clone)]
struct PumpFeesAnchorEventData {
    spec: PumpFeesAnchorEventSpec,
    discriminator_hex: std::string::String,
    payload_size: usize,
    fields_json: serde_json::Value,
}

/// Decoded Pump Fees instruction or Anchor event retained for coverage/materialization.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PumpFeesInstructionDecoded {
    /// Parent transaction internal id.
    pub transaction_id: i64,
    /// Parent instruction internal id.
    pub instruction_id: i64,
    /// Decoded program id.
    pub program_id: std::string::String,
    /// Normalized event kind.
    pub event_kind: std::string::String,
    /// Best-effort accounting/config account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Best-effort base/subject mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Best-effort quote/fee mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Best-effort secondary account used as lp/config surrogate.
    pub lp_mint: std::option::Option<std::string::String>,
    /// Decoded payload with instruction/event fields and materialization aliases.
    pub payload_json: serde_json::Value,
}

/// Decoded Pump Fees event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum PumpFeesDecodedEvent {
    /// Decoded on-chain instruction.
    Instruction(PumpFeesInstructionDecoded),
    /// Decoded Anchor `Program data` event.
    AnchorEvent(PumpFeesInstructionDecoded),
}

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

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

    /// Decodes one projected transaction into Pump Fees instruction and Anchor events.
    pub fn decode_transaction(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::PumpFeesDecodedEvent>, 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 anchor_events_result = decode_pump_fees_anchor_events(log_messages.as_slice());
        let anchor_events = match anchor_events_result {
            Ok(anchor_events) => anchor_events,
            Err(error) => return Err(error),
        };
        let mut decoded_events = std::vec::Vec::new();
        let mut pump_instruction_links = std::vec::Vec::new();
        for instruction in instructions {
            let program_id = match &instruction.program_id {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id.as_str() != crate::PUMP_FEES_PROGRAM_ID {
                continue;
            }
            let instruction_id = match instruction.id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            let instruction_data_result =
                parse_pump_fees_instruction_data(instruction.data_json.as_ref());
            let instruction_data = match instruction_data_result {
                Ok(instruction_data) => instruction_data,
                Err(error) => return Err(error),
            };
            let instruction_data = match instruction_data {
                Some(instruction_data) => instruction_data,
                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 accounts_by_name = accounts_by_name_json(parsed_json.as_ref(), accounts.as_slice());
            let mut payload = serde_json::Map::new();
            payload
                .insert("decoder".to_string(), serde_json::Value::String("pump_fees".to_string()));
            payload.insert(
                "signature".to_string(),
                serde_json::Value::String(transaction.signature.clone()),
            );
            payload.insert("instructionId".to_string(), serde_json::json!(instruction_id));
            payload.insert(
                "instructionIndex".to_string(),
                serde_json::json!(instruction.instruction_index),
            );
            if let Some(inner_instruction_index) = instruction.inner_instruction_index {
                payload.insert(
                    "innerInstructionIndex".to_string(),
                    serde_json::json!(inner_instruction_index),
                );
            }
            payload.insert("programId".to_string(), serde_json::Value::String(program_id.clone()));
            payload.insert(
                "instructionName".to_string(),
                serde_json::Value::String(instruction_data.spec.name.to_string()),
            );
            payload.insert(
                "discriminatorHex".to_string(),
                serde_json::Value::String(instruction_data.discriminator_hex.clone()),
            );
            payload.insert("decodedArguments".to_string(), instruction_data.args_json.clone());
            payload.insert("accounts".to_string(), serde_json::json!(accounts));
            payload.insert("accountsByName".to_string(), accounts_by_name.clone());
            add_materialization_aliases(
                &mut payload,
                instruction_data.spec.name,
                &instruction_data.args_json,
                &accounts_by_name,
            );
            let payload_json = serde_json::Value::Object(payload);
            let pool_account = payload_string(
                &payload_json,
                &[
                    "feeConfig",
                    "fee_config",
                    "feeProgramGlobal",
                    "fee_program_global",
                    "sharingConfig",
                    "sharing_config",
                    "socialFeePda",
                    "social_fee_pda",
                    "donationFeePda",
                    "donation_fee_pda",
                    "buybackVault",
                    "buyback_vault",
                    "global",
                ],
            );
            let token_a_mint = payload_string(&payload_json, &["mint", "baseMint", "base_mint"]);
            let token_b_mint = match payload_string(&payload_json, &["quoteMint", "quote_mint"]) {
                Some(quote_mint) => Some(quote_mint),
                None => {
                    if event_kind_is_buyback_or_fee(instruction_data.spec.name) {
                        Some(crate::WSOL_MINT_ID.to_string())
                    } else {
                        None
                    }
                },
            };
            let lp_mint = payload_string(
                &payload_json,
                &[
                    "sharingConfig",
                    "sharing_config",
                    "buybackVaultAta",
                    "buyback_vault_ata",
                    "socialFeePda",
                    "social_fee_pda",
                    "donationFeePda",
                    "donation_fee_pda",
                ],
            );
            decoded_events.push(crate::PumpFeesDecodedEvent::Instruction(
                PumpFeesInstructionDecoded {
                    transaction_id,
                    instruction_id,
                    program_id: program_id.clone(),
                    event_kind: format!("pump_fees.{}", instruction_data.spec.name),
                    pool_account,
                    token_a_mint,
                    token_b_mint,
                    lp_mint,
                    payload_json,
                },
            ));
            pump_instruction_links.push((
                instruction.instruction_index,
                instruction_id,
                accounts_by_name,
            ));
        }
        for (event_index, anchor_event) in anchor_events.iter().enumerate() {
            let instruction_id = match instruction_id_for_anchor_event(
                event_index,
                pump_instruction_links.as_slice(),
            ) {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            let accounts_by_name =
                accounts_by_name_for_anchor_event(event_index, pump_instruction_links.as_slice());
            let mut payload = serde_json::Map::new();
            payload
                .insert("decoder".to_string(), serde_json::Value::String("pump_fees".to_string()));
            payload.insert(
                "signature".to_string(),
                serde_json::Value::String(transaction.signature.clone()),
            );
            payload.insert("instructionId".to_string(), serde_json::json!(instruction_id));
            payload.insert("anchorEventIndex".to_string(), serde_json::json!(event_index));
            payload.insert(
                "programId".to_string(),
                serde_json::Value::String(crate::PUMP_FEES_PROGRAM_ID.to_string()),
            );
            payload.insert(
                "eventName".to_string(),
                serde_json::Value::String(anchor_event.spec.name.to_string()),
            );
            payload.insert(
                "discriminatorHex".to_string(),
                serde_json::Value::String(anchor_event.discriminator_hex.clone()),
            );
            payload.insert(
                "anchorEventPayloadSize".to_string(),
                serde_json::json!(anchor_event.payload_size),
            );
            payload.insert("decodedEvent".to_string(), anchor_event.fields_json.clone());
            add_materialization_aliases(
                &mut payload,
                anchor_event.spec.name,
                &anchor_event.fields_json,
                &accounts_by_name,
            );
            let payload_json = serde_json::Value::Object(payload);
            let pool_account = payload_string(
                &payload_json,
                &[
                    "feeConfig",
                    "fee_config",
                    "feeProgramGlobal",
                    "fee_program_global",
                    "sharingConfig",
                    "sharing_config",
                    "socialFeePda",
                    "social_fee_pda",
                    "donationFeePda",
                    "donation_fee_pda",
                    "buybackVault",
                    "buyback_vault",
                    "global",
                ],
            );
            let token_a_mint = payload_string(&payload_json, &["mint", "baseMint", "base_mint"]);
            let token_b_mint = payload_string(&payload_json, &["quoteMint", "quote_mint"]);
            let lp_mint = payload_string(
                &payload_json,
                &[
                    "sharingConfig",
                    "sharing_config",
                    "socialFeePda",
                    "social_fee_pda",
                    "donationFeePda",
                    "donation_fee_pda",
                    "buybackVault",
                    "buyback_vault",
                ],
            );
            decoded_events.push(crate::PumpFeesDecodedEvent::AnchorEvent(
                PumpFeesInstructionDecoded {
                    transaction_id,
                    instruction_id,
                    program_id: crate::PUMP_FEES_PROGRAM_ID.to_string(),
                    event_kind: anchor_event.spec.event_kind.to_string(),
                    pool_account,
                    token_a_mint,
                    token_b_mint,
                    lp_mint,
                    payload_json,
                },
            ));
        }
        return Ok(decoded_events);
    }
}

fn pump_fees_instruction_spec(
    discriminator: [u8; 8],
) -> std::option::Option<PumpFeesInstructionSpec> {
    match discriminator {
        PUMP_FEES_CLAIM_SOCIAL_FEE_PDA_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "claim_social_fee_pda",
                fields: PUMP_FEES_INSTRUCTION_CLAIM_SOCIAL_FEE_PDA_FIELDS,
            });
        },
        PUMP_FEES_CLAIM_SOCIAL_FEE_PDA_V2_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "claim_social_fee_pda_v2",
                fields: PUMP_FEES_INSTRUCTION_CLAIM_SOCIAL_FEE_PDA_V2_FIELDS,
            });
        },
        PUMP_FEES_CRANK_DONATION_FEE_PDA_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "crank_donation_fee_pda",
                fields: PUMP_FEES_INSTRUCTION_CRANK_DONATION_FEE_PDA_FIELDS,
            });
        },
        PUMP_FEES_CREATE_DONATION_FEE_PDA_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "create_donation_fee_pda",
                fields: PUMP_FEES_INSTRUCTION_CREATE_DONATION_FEE_PDA_FIELDS,
            });
        },
        PUMP_FEES_CREATE_FEE_SHARING_CONFIG_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "create_fee_sharing_config",
                fields: PUMP_FEES_INSTRUCTION_CREATE_FEE_SHARING_CONFIG_FIELDS,
            });
        },
        PUMP_FEES_CREATE_SOCIAL_FEE_PDA_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "create_social_fee_pda",
                fields: PUMP_FEES_INSTRUCTION_CREATE_SOCIAL_FEE_PDA_FIELDS,
            });
        },
        PUMP_FEES_EXTEND_FEE_CONFIG_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "extend_fee_config",
                fields: PUMP_FEES_INSTRUCTION_EXTEND_FEE_CONFIG_FIELDS,
            });
        },
        PUMP_FEES_GET_FEES_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "get_fees",
                fields: PUMP_FEES_INSTRUCTION_GET_FEES_FIELDS,
            });
        },
        PUMP_FEES_INITIALIZE_BUYBACK_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "initialize_buyback",
                fields: PUMP_FEES_INSTRUCTION_INITIALIZE_BUYBACK_FIELDS,
            });
        },
        PUMP_FEES_INITIALIZE_FEE_CONFIG_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "initialize_fee_config",
                fields: PUMP_FEES_INSTRUCTION_INITIALIZE_FEE_CONFIG_FIELDS,
            });
        },
        PUMP_FEES_INITIALIZE_FEE_PROGRAM_GLOBAL_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "initialize_fee_program_global",
                fields: PUMP_FEES_INSTRUCTION_INITIALIZE_FEE_PROGRAM_GLOBAL_FIELDS,
            });
        },
        PUMP_FEES_RESET_FEE_SHARING_CONFIG_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "reset_fee_sharing_config",
                fields: PUMP_FEES_INSTRUCTION_RESET_FEE_SHARING_CONFIG_FIELDS,
            });
        },
        PUMP_FEES_RESET_FEE_SHARING_CONFIG_V2_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "reset_fee_sharing_config_v2",
                fields: PUMP_FEES_INSTRUCTION_RESET_FEE_SHARING_CONFIG_V2_FIELDS,
            });
        },
        PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "revoke_fee_sharing_authority",
                fields: PUMP_FEES_INSTRUCTION_REVOKE_FEE_SHARING_AUTHORITY_FIELDS,
            });
        },
        PUMP_FEES_SET_AUTHORITY_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "set_authority",
                fields: PUMP_FEES_INSTRUCTION_SET_AUTHORITY_FIELDS,
            });
        },
        PUMP_FEES_SET_CLAIM_RATE_LIMIT_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "set_claim_rate_limit",
                fields: PUMP_FEES_INSTRUCTION_SET_CLAIM_RATE_LIMIT_FIELDS,
            });
        },
        PUMP_FEES_SET_DISABLE_FLAGS_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "set_disable_flags",
                fields: PUMP_FEES_INSTRUCTION_SET_DISABLE_FLAGS_FIELDS,
            });
        },
        PUMP_FEES_SET_SOCIAL_CLAIM_AUTHORITY_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "set_social_claim_authority",
                fields: PUMP_FEES_INSTRUCTION_SET_SOCIAL_CLAIM_AUTHORITY_FIELDS,
            });
        },
        PUMP_FEES_SWEEP_BUYBACK_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "sweep_buyback",
                fields: PUMP_FEES_INSTRUCTION_SWEEP_BUYBACK_FIELDS,
            });
        },
        PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "transfer_fee_sharing_authority",
                fields: PUMP_FEES_INSTRUCTION_TRANSFER_FEE_SHARING_AUTHORITY_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_ADMIN_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_admin",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_ADMIN_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_BUYBACK_AUTHORITY_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_buyback_authority",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_BUYBACK_AUTHORITY_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_BUYBACK_CLAIM_RATE_LIMIT_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_buyback_claim_rate_limit",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_BUYBACK_CLAIM_RATE_LIMIT_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_FEE_CONFIG_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_fee_config",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_FEE_CONFIG_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_FEE_SHARES_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_fee_shares",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_FEE_SHARES_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_FEE_SHARES_V2_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_fee_shares_v2",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_FEE_SHARES_V2_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_STABLE_FEE_CONFIG_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "update_stable_fee_config",
                fields: PUMP_FEES_INSTRUCTION_UPDATE_STABLE_FEE_CONFIG_FIELDS,
            });
        },
        PUMP_FEES_UPSERT_FEE_TIERS_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "upsert_fee_tiers",
                fields: PUMP_FEES_INSTRUCTION_UPSERT_FEE_TIERS_FIELDS,
            });
        },
        PUMP_FEES_UPSERT_STABLE_FEE_TIERS_DISCRIMINATOR => {
            return Some(PumpFeesInstructionSpec {
                name: "upsert_stable_fee_tiers",
                fields: PUMP_FEES_INSTRUCTION_UPSERT_STABLE_FEE_TIERS_FIELDS,
            });
        },
        _ => return None,
    }
}

fn pump_fees_anchor_event_spec(
    discriminator: [u8; 8],
) -> std::option::Option<PumpFeesAnchorEventSpec> {
    match discriminator {
        PUMP_FEES_CREATE_FEE_SHARING_CONFIG_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "CreateFeeSharingConfigEvent",
                event_kind: "pump_fees.create_fee_sharing_config_event",
                fields: PUMP_FEES_EVENT_CREATE_FEE_SHARING_CONFIG_EVENT_FIELDS,
            });
        },
        PUMP_FEES_DONATION_FEE_PDA_CRANKED_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "DonationFeePdaCranked",
                event_kind: "pump_fees.donation_fee_pda_cranked",
                fields: PUMP_FEES_EVENT_DONATION_FEE_PDA_CRANKED_FIELDS,
            });
        },
        PUMP_FEES_DONATION_FEE_PDA_CREATED_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "DonationFeePdaCreated",
                event_kind: "pump_fees.donation_fee_pda_created",
                fields: PUMP_FEES_EVENT_DONATION_FEE_PDA_CREATED_FIELDS,
            });
        },
        PUMP_FEES_EXTEND_FEE_CONFIG_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "ExtendFeeConfigEvent",
                event_kind: "pump_fees.extend_fee_config_event",
                fields: PUMP_FEES_EVENT_EXTEND_FEE_CONFIG_EVENT_FIELDS,
            });
        },
        PUMP_FEES_INITIALIZE_FEE_CONFIG_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "InitializeFeeConfigEvent",
                event_kind: "pump_fees.initialize_fee_config_event",
                fields: PUMP_FEES_EVENT_INITIALIZE_FEE_CONFIG_EVENT_FIELDS,
            });
        },
        PUMP_FEES_INITIALIZE_FEE_PROGRAM_GLOBAL_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "InitializeFeeProgramGlobalEvent",
                event_kind: "pump_fees.initialize_fee_program_global_event",
                fields: PUMP_FEES_EVENT_INITIALIZE_FEE_PROGRAM_GLOBAL_EVENT_FIELDS,
            });
        },
        PUMP_FEES_RESET_FEE_SHARING_CONFIG_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "ResetFeeSharingConfigEvent",
                event_kind: "pump_fees.reset_fee_sharing_config_event",
                fields: PUMP_FEES_EVENT_RESET_FEE_SHARING_CONFIG_EVENT_FIELDS,
            });
        },
        PUMP_FEES_SET_AUTHORITY_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SetAuthorityEvent",
                event_kind: "pump_fees.set_authority_event",
                fields: PUMP_FEES_EVENT_SET_AUTHORITY_EVENT_FIELDS,
            });
        },
        PUMP_FEES_SET_CLAIM_RATE_LIMIT_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SetClaimRateLimitEvent",
                event_kind: "pump_fees.set_claim_rate_limit_event",
                fields: PUMP_FEES_EVENT_SET_CLAIM_RATE_LIMIT_EVENT_FIELDS,
            });
        },
        PUMP_FEES_SET_DISABLE_FLAGS_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SetDisableFlagsEvent",
                event_kind: "pump_fees.set_disable_flags_event",
                fields: PUMP_FEES_EVENT_SET_DISABLE_FLAGS_EVENT_FIELDS,
            });
        },
        PUMP_FEES_SET_SOCIAL_CLAIM_AUTHORITY_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SetSocialClaimAuthorityEvent",
                event_kind: "pump_fees.set_social_claim_authority_event",
                fields: PUMP_FEES_EVENT_SET_SOCIAL_CLAIM_AUTHORITY_EVENT_FIELDS,
            });
        },
        PUMP_FEES_SOCIAL_FEE_PDA_CLAIMED_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SocialFeePdaClaimed",
                event_kind: "pump_fees.social_fee_pda_claimed",
                fields: PUMP_FEES_EVENT_SOCIAL_FEE_PDA_CLAIMED_FIELDS,
            });
        },
        PUMP_FEES_SOCIAL_FEE_PDA_CREATED_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SocialFeePdaCreated",
                event_kind: "pump_fees.social_fee_pda_created",
                fields: PUMP_FEES_EVENT_SOCIAL_FEE_PDA_CREATED_FIELDS,
            });
        },
        PUMP_FEES_SWEEP_BUYBACK_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "SweepBuybackEvent",
                event_kind: "pump_fees.sweep_buyback_event",
                fields: PUMP_FEES_EVENT_SWEEP_BUYBACK_EVENT_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_ADMIN_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "UpdateAdminEvent",
                event_kind: "pump_fees.update_admin_event",
                fields: PUMP_FEES_EVENT_UPDATE_ADMIN_EVENT_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_FEE_CONFIG_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "UpdateFeeConfigEvent",
                event_kind: "pump_fees.update_fee_config_event",
                fields: PUMP_FEES_EVENT_UPDATE_FEE_CONFIG_EVENT_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_FEE_SHARES_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "UpdateFeeSharesEvent",
                event_kind: "pump_fees.update_fee_shares_event",
                fields: PUMP_FEES_EVENT_UPDATE_FEE_SHARES_EVENT_FIELDS,
            });
        },
        PUMP_FEES_UPDATE_STABLE_FEE_CONFIG_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "UpdateStableFeeConfigEvent",
                event_kind: "pump_fees.update_stable_fee_config_event",
                fields: PUMP_FEES_EVENT_UPDATE_STABLE_FEE_CONFIG_EVENT_FIELDS,
            });
        },
        PUMP_FEES_UPSERT_FEE_TIERS_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "UpsertFeeTiersEvent",
                event_kind: "pump_fees.upsert_fee_tiers_event",
                fields: PUMP_FEES_EVENT_UPSERT_FEE_TIERS_EVENT_FIELDS,
            });
        },
        PUMP_FEES_UPSERT_STABLE_FEE_TIERS_EVENT_DISCRIMINATOR => {
            return Some(PumpFeesAnchorEventSpec {
                name: "UpsertStableFeeTiersEvent",
                event_kind: "pump_fees.upsert_stable_fee_tiers_event",
                fields: PUMP_FEES_EVENT_UPSERT_STABLE_FEE_TIERS_EVENT_FIELDS,
            });
        },
        _ => return None,
    }
}

fn parse_pump_fees_instruction_data(
    data_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<PumpFeesInstructionData>, crate::Error> {
    let data_text = match decode_instruction_data_text(data_json) {
        Some(data_text) => data_text,
        None => return Ok(None),
    };
    let decoded_result = bs58::decode(data_text.trim()).into_vec();
    let decoded = match decoded_result {
        Ok(decoded) => decoded,
        Err(error) => {
            return Err(crate::Error::InvalidState(format!(
                "cannot base58-decode pump_fees instruction data: {}",
                error
            )));
        },
    };
    if decoded.len() < 8 {
        return Ok(None);
    }
    let discriminator = match read_8_byte_array(decoded.as_slice(), 0) {
        Some(discriminator) => discriminator,
        None => return Ok(None),
    };
    let spec = match pump_fees_instruction_spec(discriminator) {
        Some(spec) => spec,
        None => return Ok(None),
    };
    let decoded_fields_result = decode_borsh_fields(&decoded[8..], spec.fields);
    let decoded_fields = match decoded_fields_result {
        Ok(decoded_fields) => decoded_fields,
        Err(error) => {
            tracing::debug!(
                decoder = "pump_fees",
                discriminator_hex = bytes_to_hex(discriminator.as_slice()),
                payload_size = decoded.len().saturating_sub(8),
                error = %error,
                "ignoring malformed pump_fees instruction payload"
            );
            return Ok(None);
        },
    };
    return Ok(Some(PumpFeesInstructionData {
        spec,
        discriminator_hex: bytes_to_hex(discriminator.as_slice()),
        args_json: decoded_fields,
    }));
}

fn decode_pump_fees_anchor_events(
    log_messages: &[std::string::String],
) -> Result<std::vec::Vec<PumpFeesAnchorEventData>, crate::Error> {
    let mut events = std::vec::Vec::new();
    for message in log_messages {
        let encoded = match extract_program_data_base64(message.as_str()) {
            Some(encoded) => encoded,
            None => continue,
        };
        let event_result = decode_pump_fees_anchor_event_from_base64(encoded);
        let event_option = match event_result {
            Ok(event_option) => event_option,
            Err(error) => return Err(error),
        };
        if let Some(event) = event_option {
            events.push(event);
        }
    }
    return Ok(events);
}

fn decode_pump_fees_anchor_event_from_base64(
    encoded: &str,
) -> Result<std::option::Option<PumpFeesAnchorEventData>, crate::Error> {
    let decoded = decode_base64_standard(encoded);
    let decoded = match decoded {
        Some(decoded) => decoded,
        None => return Ok(None),
    };
    let payload = if decoded.len() >= 16
        && read_8_byte_array(decoded.as_slice(), 0)
            == Some(PUMP_FEES_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR)
    {
        &decoded[8..]
    } else {
        decoded.as_slice()
    };
    if payload.len() < 8 {
        return Ok(None);
    }
    let discriminator = match read_8_byte_array(payload, 0) {
        Some(discriminator) => discriminator,
        None => return Ok(None),
    };
    let spec = match pump_fees_anchor_event_spec(discriminator) {
        Some(spec) => spec,
        None => return Ok(None),
    };
    let fields_result = decode_borsh_fields(&payload[8..], spec.fields);
    let fields_json = match fields_result {
        Ok(fields_json) => fields_json,
        Err(error) => {
            tracing::debug!(
                decoder = "pump_fees",
                discriminator_hex = bytes_to_hex(discriminator.as_slice()),
                payload_size = payload.len().saturating_sub(8),
                error = %error,
                "ignoring malformed pump_fees anchor event payload"
            );
            return Ok(None);
        },
    };
    return Ok(Some(PumpFeesAnchorEventData {
        spec,
        discriminator_hex: bytes_to_hex(discriminator.as_slice()),
        payload_size: payload.len().saturating_sub(8),
        fields_json,
    }));
}

fn decode_borsh_fields(
    data: &[u8],
    fields: &[PumpFeesBorshFieldDescriptor],
) -> Result<serde_json::Value, crate::Error> {
    let mut offset = 0_usize;
    let mut object = serde_json::Map::new();
    for field in fields {
        let decoded_result = read_borsh_field_value(data, offset, field.kind);
        let (value, next_offset) = match decoded_result {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        offset = next_offset;
        object.insert(field.name.to_string(), value);
    }
    object.insert("payloadBytesConsumed".to_string(), serde_json::json!(offset));
    object.insert("payloadBytesTotal".to_string(), serde_json::json!(data.len()));
    return Ok(serde_json::Value::Object(object));
}

fn read_borsh_field_value(
    data: &[u8],
    offset: usize,
    kind: PumpFeesBorshFieldKind,
) -> Result<(serde_json::Value, usize), crate::Error> {
    match kind {
        PumpFeesBorshFieldKind::U8 => return read_u8_value(data, offset),
        PumpFeesBorshFieldKind::U64 => return read_u64_value(data, offset),
        PumpFeesBorshFieldKind::I64 => return read_i64_value(data, offset),
        PumpFeesBorshFieldKind::U128 => return read_u128_value(data, offset),
        PumpFeesBorshFieldKind::Bool => return read_bool_value(data, offset),
        PumpFeesBorshFieldKind::Pubkey => return read_pubkey_value(data, offset),
        PumpFeesBorshFieldKind::String => return read_string_value(data, offset),
        PumpFeesBorshFieldKind::OptionBool => return read_option_bool_value(data, offset),
        PumpFeesBorshFieldKind::OptionPubkey => return read_option_pubkey_value(data, offset),
        PumpFeesBorshFieldKind::Fees => return read_fees_value(data, offset),
        PumpFeesBorshFieldKind::ConfigStatus => return read_config_status_value(data, offset),
        PumpFeesBorshFieldKind::ShareholderVec => return read_shareholder_vec_value(data, offset),
        PumpFeesBorshFieldKind::FeeTierVec => return read_fee_tier_vec_value(data, offset),
    }
}

fn read_u8_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    if offset.saturating_add(1) > data.len() {
        return Err(crate::Error::InvalidState("pump_fees u8 field exceeds payload".to_string()));
    }
    return Ok((serde_json::json!(data[offset]), offset.saturating_add(1)));
}

fn read_u16_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    if offset.saturating_add(2) > data.len() {
        return Err(crate::Error::InvalidState("pump_fees u16 field exceeds payload".to_string()));
    }
    let mut bytes = [0_u8; 2];
    bytes.copy_from_slice(&data[offset..offset + 2]);
    return Ok((serde_json::json!(u16::from_le_bytes(bytes)), offset.saturating_add(2)));
}

fn read_u32_raw(data: &[u8], offset: usize) -> Result<(u32, usize), crate::Error> {
    if offset.saturating_add(4) > data.len() {
        return Err(crate::Error::InvalidState("pump_fees u32 field exceeds payload".to_string()));
    }
    let mut bytes = [0_u8; 4];
    bytes.copy_from_slice(&data[offset..offset + 4]);
    return Ok((u32::from_le_bytes(bytes), offset.saturating_add(4)));
}

fn read_u64_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    if offset.saturating_add(8) > data.len() {
        return Err(crate::Error::InvalidState("pump_fees u64 field exceeds payload".to_string()));
    }
    let mut bytes = [0_u8; 8];
    bytes.copy_from_slice(&data[offset..offset + 8]);
    let value = u64::from_le_bytes(bytes);
    return Ok((serde_json::Value::String(value.to_string()), offset.saturating_add(8)));
}

fn read_i64_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    if offset.saturating_add(8) > data.len() {
        return Err(crate::Error::InvalidState("pump_fees i64 field exceeds payload".to_string()));
    }
    let mut bytes = [0_u8; 8];
    bytes.copy_from_slice(&data[offset..offset + 8]);
    let value = i64::from_le_bytes(bytes);
    return Ok((serde_json::Value::String(value.to_string()), offset.saturating_add(8)));
}

fn read_u128_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    if offset.saturating_add(16) > data.len() {
        return Err(crate::Error::InvalidState("pump_fees u128 field exceeds payload".to_string()));
    }
    let mut bytes = [0_u8; 16];
    bytes.copy_from_slice(&data[offset..offset + 16]);
    let value = u128::from_le_bytes(bytes);
    return Ok((serde_json::Value::String(value.to_string()), offset.saturating_add(16)));
}

fn read_bool_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    let value_result = read_u8_value(data, offset);
    let (value, next_offset) = match value_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let numeric = match value.as_u64() {
        Some(numeric) => numeric,
        None => 0,
    };
    return Ok((serde_json::Value::Bool(numeric != 0), next_offset));
}

fn read_pubkey_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    if offset.saturating_add(32) > data.len() {
        return Err(crate::Error::InvalidState(
            "pump_fees pubkey field exceeds payload".to_string(),
        ));
    }
    let encoded = bs58::encode(&data[offset..offset + 32]).into_string();
    return Ok((serde_json::Value::String(encoded), offset.saturating_add(32)));
}

fn read_string_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    let length_result = read_u32_raw(data, offset);
    let (length, mut current_offset) = match length_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let length_usize = length as usize;
    if current_offset.saturating_add(length_usize) > data.len() {
        return Err(crate::Error::InvalidState(
            "pump_fees string field exceeds payload".to_string(),
        ));
    }
    let text_result = std::str::from_utf8(&data[current_offset..current_offset + length_usize]);
    let text = match text_result {
        Ok(text) => text,
        Err(error) => {
            return Err(crate::Error::InvalidState(format!(
                "pump_fees string field is not utf8: {}",
                error
            )));
        },
    };
    current_offset = current_offset.saturating_add(length_usize);
    return Ok((serde_json::Value::String(text.to_string()), current_offset));
}

fn read_option_bool_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    return read_bool_value(data, offset);
}

fn read_option_pubkey_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    let tag_result = read_u8_value(data, offset);
    let (tag_value, mut current_offset) = match tag_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let tag = match tag_value.as_u64() {
        Some(tag) => tag,
        None => 0,
    };
    if tag == 0 {
        return Ok((serde_json::Value::Null, current_offset));
    }
    let pubkey_result = read_pubkey_value(data, current_offset);
    let (pubkey, next_offset) = match pubkey_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    current_offset = next_offset;
    return Ok((pubkey, current_offset));
}

fn read_fees_value(data: &[u8], offset: usize) -> Result<(serde_json::Value, usize), crate::Error> {
    let lp_result = read_u64_value(data, offset);
    let (lp_fee_bps, current_offset) = match lp_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let protocol_result = read_u64_value(data, current_offset);
    let (protocol_fee_bps, current_offset) = match protocol_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let creator_result = read_u64_value(data, current_offset);
    let (creator_fee_bps, current_offset) = match creator_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let mut object = serde_json::Map::new();
    object.insert("lp_fee_bps".to_string(), lp_fee_bps);
    object.insert("protocol_fee_bps".to_string(), protocol_fee_bps);
    object.insert("creator_fee_bps".to_string(), creator_fee_bps);
    return Ok((serde_json::Value::Object(object), current_offset));
}

fn read_config_status_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    let status_result = read_u8_value(data, offset);
    let (status_value, next_offset) = match status_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let status_code = match status_value.as_u64() {
        Some(status_code) => status_code,
        None => 0,
    };
    let status_name = match status_code {
        0 => "paused",
        1 => "active",
        _ => "unknown",
    };
    let mut object = serde_json::Map::new();
    object.insert("code".to_string(), serde_json::json!(status_code));
    object.insert("name".to_string(), serde_json::Value::String(status_name.to_string()));
    return Ok((serde_json::Value::Object(object), next_offset));
}

fn read_shareholder_vec_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    let length_result = read_u32_raw(data, offset);
    let (length, mut current_offset) = match length_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let mut values = std::vec::Vec::new();
    for _ in 0..length {
        let address_result = read_pubkey_value(data, current_offset);
        let (address, next_offset) = match address_result {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        current_offset = next_offset;
        let share_result = read_u16_value(data, current_offset);
        let (share_bps, next_offset) = match share_result {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        current_offset = next_offset;
        let mut object = serde_json::Map::new();
        object.insert("address".to_string(), address);
        object.insert("share_bps".to_string(), share_bps);
        values.push(serde_json::Value::Object(object));
    }
    return Ok((serde_json::Value::Array(values), current_offset));
}

fn read_fee_tier_vec_value(
    data: &[u8],
    offset: usize,
) -> Result<(serde_json::Value, usize), crate::Error> {
    let length_result = read_u32_raw(data, offset);
    let (length, mut current_offset) = match length_result {
        Ok(value) => value,
        Err(error) => return Err(error),
    };
    let mut values = std::vec::Vec::new();
    for _ in 0..length {
        let threshold_result = read_u128_value(data, current_offset);
        let (threshold, next_offset) = match threshold_result {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        current_offset = next_offset;
        let fees_result = read_fees_value(data, current_offset);
        let (fees, next_offset) = match fees_result {
            Ok(value) => value,
            Err(error) => return Err(error),
        };
        current_offset = next_offset;
        let mut object = serde_json::Map::new();
        object.insert("market_cap_lamports_threshold".to_string(), threshold);
        object.insert("fees".to_string(), fees);
        values.push(serde_json::Value::Object(object));
    }
    return Ok((serde_json::Value::Array(values), current_offset));
}

fn add_materialization_aliases(
    payload: &mut serde_json::Map<std::string::String, serde_json::Value>,
    name: &str,
    decoded: &serde_json::Value,
    accounts_by_name: &serde_json::Value,
) {
    copy_payload_alias(payload, decoded, accounts_by_name, "mint", "mint");
    copy_payload_alias(payload, decoded, accounts_by_name, "base_mint", "baseMint");
    copy_payload_alias(payload, decoded, accounts_by_name, "quote_mint", "quoteMint");
    copy_payload_alias(payload, decoded, accounts_by_name, "fee_config", "feeConfig");
    copy_payload_alias(
        payload,
        decoded,
        accounts_by_name,
        "fee_program_global",
        "feeProgramGlobal",
    );
    copy_payload_alias(payload, decoded, accounts_by_name, "sharing_config", "sharingConfig");
    copy_payload_alias(payload, decoded, accounts_by_name, "social_fee_pda", "socialFeePda");
    copy_payload_alias(payload, decoded, accounts_by_name, "donation_fee_pda", "donationFeePda");
    copy_payload_alias(payload, decoded, accounts_by_name, "buyback_vault", "buybackVault");
    copy_payload_alias(payload, decoded, accounts_by_name, "admin", "admin");
    copy_payload_alias(payload, decoded, accounts_by_name, "authority", "authority");
    copy_payload_alias(payload, decoded, accounts_by_name, "recipient", "recipient");
    copy_payload_alias(payload, decoded, accounts_by_name, "destination", "destination");
    copy_payload_alias(payload, decoded, accounts_by_name, "signer", "signer");
    copy_payload_alias(payload, decoded, accounts_by_name, "creator", "creator");
    copy_payload_alias(payload, decoded, accounts_by_name, "new_authority", "newAuthority");
    copy_payload_alias(payload, decoded, accounts_by_name, "new_admin", "newAdmin");
    copy_payload_alias(payload, decoded, accounts_by_name, "amount", "amount");
    copy_payload_alias(payload, decoded, accounts_by_name, "amount_claimed", "amountClaimed");
    copy_payload_alias(payload, decoded, accounts_by_name, "sol_amount", "solAmount");
    copy_payload_alias(payload, decoded, accounts_by_name, "token_amount", "tokenAmount");
    let actor = first_string_value(
        payload,
        &[
            "recipient",
            "destination",
            "signer",
            "authority",
            "admin",
            "creator",
            "newAuthority",
            "newAdmin",
        ],
    );
    if let Some(actor) = actor {
        payload.insert("actorWallet".to_string(), serde_json::Value::String(actor));
    }
    if name.contains("SocialFeePdaClaimed")
        || name == "claim_social_fee_pda"
        || name == "claim_social_fee_pda_v2"
    {
        if let Some(amount) = first_json_value(payload, &["amountClaimed", "amount"]) {
            payload.insert("rewardAmountRaw".to_string(), amount);
        }
        if let Some(mint) = first_string_value(payload, &["quoteMint", "mint"]) {
            payload.insert("rewardTokenMint".to_string(), serde_json::Value::String(mint));
        }
    }
    if name.contains("DonationFee") || name.contains("Buyback") || name == "sweep_buyback" {
        if let Some(amount) = first_json_value(payload, &["amount", "solAmount", "tokenAmount"]) {
            payload.insert("feeAmountRaw".to_string(), amount);
        }
        if let Some(mint) = first_string_value(payload, &["quoteMint", "mint"]) {
            payload.insert("feeTokenMint".to_string(), serde_json::Value::String(mint));
        }
    }
    payload.insert(
        "pumpFeesMaterializationPolicy".to_string(),
        serde_json::Value::String(materialization_policy_for_name(name).to_string()),
    );
}

fn materialization_policy_for_name(name: &str) -> &'static str {
    if name == "get_fees" {
        return "decoded_only_fee_calculation";
    }
    if name.contains("Claimed") || name.contains("Cranked") || name.contains("Sweep") {
        return "materialize_if_successful_fee_or_reward";
    }
    if name.contains("Config")
        || name.contains("Authority")
        || name.contains("Admin")
        || name.contains("Tier")
        || name.contains("Disable")
        || name.contains("Buyback")
        || name.contains("Sharing")
        || name.contains("Created")
        || name.starts_with("create_")
        || name.starts_with("initialize_")
        || name.starts_with("update_")
        || name.starts_with("set_")
        || name.starts_with("reset_")
        || name.starts_with("extend_")
        || name.starts_with("upsert_")
    {
        return "materialize_if_successful_admin_or_lifecycle";
    }
    return "decoded_only_audit";
}

fn event_kind_is_buyback_or_fee(name: &str) -> bool {
    return name.contains("fee") || name.contains("buyback") || name.contains("donation");
}

fn copy_payload_alias(
    payload: &mut serde_json::Map<std::string::String, serde_json::Value>,
    decoded: &serde_json::Value,
    accounts_by_name: &serde_json::Value,
    source_key: &str,
    target_key: &str,
) {
    if payload.get(target_key).is_some() {
        return;
    }
    if let Some(value) = json_value_by_key(decoded, source_key) {
        payload.insert(target_key.to_string(), value.clone());
        return;
    }
    if let Some(value) = json_value_by_key(accounts_by_name, source_key) {
        payload.insert(target_key.to_string(), value.clone());
    }
}

fn json_value_by_key<'a>(
    value: &'a serde_json::Value,
    key: &str,
) -> std::option::Option<&'a serde_json::Value> {
    if let Some(object) = value.as_object() {
        if let Some(found) = object.get(key) {
            return Some(found);
        }
        let camel = snake_to_camel(key);
        if let Some(found) = object.get(camel.as_str()) {
            return Some(found);
        }
    }
    return None;
}

fn first_json_value(
    payload: &serde_json::Map<std::string::String, serde_json::Value>,
    keys: &[&str],
) -> std::option::Option<serde_json::Value> {
    for key in keys {
        if let Some(value) = payload.get(*key) {
            return Some(value.clone());
        }
    }
    return None;
}

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

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

fn snake_to_camel(key: &str) -> std::string::String {
    let mut result = std::string::String::new();
    let mut uppercase_next = false;
    for character in key.chars() {
        if character == '_' {
            uppercase_next = true;
            continue;
        }
        if uppercase_next {
            for upper in character.to_uppercase() {
                result.push(upper);
            }
            uppercase_next = false;
        } else {
            result.push(character);
        }
    }
    return result;
}

fn instruction_id_for_anchor_event(
    event_index: usize,
    links: &[(u32, i64, serde_json::Value)],
) -> std::option::Option<i64> {
    if links.is_empty() {
        return None;
    }
    if event_index < links.len() {
        return Some(links[event_index].1);
    }
    return Some(links[links.len().saturating_sub(1)].1);
}

fn accounts_by_name_for_anchor_event(
    event_index: usize,
    links: &[(u32, i64, serde_json::Value)],
) -> serde_json::Value {
    if links.is_empty() {
        return serde_json::json!({});
    }
    if event_index < links.len() {
        return links[event_index].2.clone();
    }
    return links[links.len().saturating_sub(1)].2.clone();
}

fn accounts_by_name_json(
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> serde_json::Value {
    let mut object = serde_json::Map::new();
    if let Some(parsed_json) = parsed_json {
        if let Some(accounts_value) = parsed_json.get("accounts") {
            if let Some(accounts_array) = accounts_value.as_array() {
                for account in accounts_array {
                    let name = account.get("name").and_then(serde_json::Value::as_str);
                    let pubkey = account.get("pubkey").and_then(serde_json::Value::as_str);
                    if let (Some(name), Some(pubkey)) = (name, pubkey) {
                        object.insert(
                            name.to_string(),
                            serde_json::Value::String(pubkey.to_string()),
                        );
                    }
                }
            }
        }
    }
    let fallback_names = [
        "account0",
        "account1",
        "account2",
        "account3",
        "account4",
        "account5",
        "account6",
        "account7",
        "account8",
        "account9",
        "account10",
        "account11",
        "account12",
        "account13",
        "account14",
        "account15",
        "account16",
        "account17",
        "account18",
        "account19",
        "account20",
    ];
    for (index, account) in accounts.iter().enumerate() {
        if index >= fallback_names.len() {
            continue;
        }
        object
            .insert(fallback_names[index].to_string(), serde_json::Value::String(account.clone()));
    }
    return serde_json::Value::Object(object);
}

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

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

fn 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") {
        Some(logs) => logs,
        None => return messages,
    };
    let logs_array = match logs.as_array() {
        Some(logs_array) => logs_array,
        None => return messages,
    };
    for value in logs_array {
        if let Some(text) = value.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 values_result = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(accounts_json);
    let values = match values_result {
        Ok(values) => values,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse pump_fees accounts_json: {}",
                error
            )));
        },
    };
    let mut accounts = std::vec::Vec::new();
    for value in values {
        if let Some(text) = value.as_str() {
            accounts.push(text.to_string());
        } else if let Some(object) = value.as_object() {
            if let Some(pubkey) = object.get("pubkey").and_then(serde_json::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 text = match parsed_json {
        Some(text) => text,
        None => return Ok(None),
    };
    let parsed = serde_json::from_str::<serde_json::Value>(text.as_str());
    match parsed {
        Ok(parsed) => return Ok(Some(parsed)),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse pump_fees parsed_json: {}",
                error
            )));
        },
    }
}

fn decode_instruction_data_text(
    data_json: std::option::Option<&std::string::String>,
) -> std::option::Option<std::string::String> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return None,
    };
    let value_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    if let Ok(value) = value_result {
        if let Some(text) = value.as_str() {
            return Some(text.to_string());
        }
        if let Some(object) = value.as_object() {
            if let Some(text) = object.get("data").and_then(serde_json::Value::as_str) {
                return Some(text.to_string());
            }
        }
    }
    let trimmed = data_json.trim();
    if trimmed.is_empty() {
        return None;
    }
    return Some(trimmed.to_string());
}

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

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

#[cfg(test)]
mod tests {
    #[test]
    fn decodes_get_fees_instruction_args() {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&super::PUMP_FEES_GET_FEES_DISCRIMINATOR);
        data.push(1_u8);
        data.extend_from_slice(&123456789_u128.to_le_bytes());
        data.extend_from_slice(&42_u64.to_le_bytes());
        data.push(0_u8);
        let encoded = bs58::encode(data).into_string();
        let data_json = serde_json::json!(encoded).to_string();
        let decoded_result = super::parse_pump_fees_instruction_data(Some(&data_json));
        let decoded = match decoded_result {
            Ok(Some(decoded)) => decoded,
            Ok(None) => panic!("expected decoded get_fees"),
            Err(error) => panic!("unexpected decode error: {}", error),
        };
        assert_eq!(decoded.spec.name, "get_fees");
        assert_eq!(decoded.discriminator_hex, "e7257e55cf5b3f34");
        assert_eq!(decoded.args_json["is_pump_pool"], serde_json::json!(true));
        assert_eq!(decoded.args_json["market_cap_lamports"], serde_json::json!("123456789"));
        assert_eq!(decoded.args_json["trade_size_lamports"], serde_json::json!("42"));
        assert_eq!(decoded.args_json["is_new_quote_mint"], serde_json::json!(false));
    }

    #[test]
    fn decodes_update_fee_shares_shareholders() {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&super::PUMP_FEES_UPDATE_FEE_SHARES_DISCRIMINATOR);
        data.extend_from_slice(&1_u32.to_le_bytes());
        data.extend_from_slice(&[7_u8; 32]);
        data.extend_from_slice(&2500_u16.to_le_bytes());
        let encoded = bs58::encode(data).into_string();
        let data_json = serde_json::json!(encoded).to_string();
        let decoded_result = super::parse_pump_fees_instruction_data(Some(&data_json));
        let decoded = match decoded_result {
            Ok(Some(decoded)) => decoded,
            Ok(None) => panic!("expected decoded update_fee_shares"),
            Err(error) => panic!("unexpected decode error: {}", error),
        };
        assert_eq!(decoded.spec.name, "update_fee_shares");
        assert_eq!(decoded.discriminator_hex, "bd0d8863bba4ed23");
        assert_eq!(decoded.args_json["shareholders"][0]["share_bps"], serde_json::json!(2500));
    }

    #[test]
    fn decodes_social_fee_claimed_anchor_event() {
        let mut data = pump_fees_anchor_event_prefix(
            &super::PUMP_FEES_SOCIAL_FEE_PDA_CLAIMED_DISCRIMINATOR,
        );
        data.extend_from_slice(&123_i64.to_le_bytes());
        let user = "user-1".as_bytes();
        data.extend_from_slice(&(user.len() as u32).to_le_bytes());
        data.extend_from_slice(user);
        data.push(2_u8);
        data.extend_from_slice(&[1_u8; 32]);
        data.extend_from_slice(&[2_u8; 32]);
        data.extend_from_slice(&[3_u8; 32]);
        data.extend_from_slice(&99_u64.to_le_bytes());
        data.extend_from_slice(&100_u64.to_le_bytes());
        data.extend_from_slice(&101_u64.to_le_bytes());
        data.extend_from_slice(&102_u64.to_le_bytes());
        data.extend_from_slice(&201_u64.to_le_bytes());
        data.extend_from_slice(&[4_u8; 32]);
        let decoded = decode_synthetic_anchor_event(data, "social fee event");
        assert_eq!(decoded.spec.name, "SocialFeePdaClaimed");
        assert_eq!(decoded.discriminator_hex, "3212c141edd2eaec");
        assert_eq!(decoded.fields_json["user_id"], serde_json::json!("user-1"));
        assert_eq!(decoded.fields_json["amount_claimed"], serde_json::json!("99"));
    }

    #[test]
    fn decodes_unobserved_idl_admin_anchor_events() {
        let mut authority_data = pump_fees_anchor_event_prefix(
            &super::PUMP_FEES_SET_AUTHORITY_EVENT_DISCRIMINATOR,
        );
        authority_data.extend_from_slice(&11_i64.to_le_bytes());
        authority_data.extend_from_slice(&[1_u8; 32]);
        authority_data.extend_from_slice(&[2_u8; 32]);
        let authority_event = decode_synthetic_anchor_event(authority_data, "set authority event");
        assert_eq!(authority_event.spec.name, "SetAuthorityEvent");
        assert_eq!(authority_event.discriminator_hex, "12af8442d0c957f2");
        assert_eq!(authority_event.fields_json["timestamp"], serde_json::json!("11"));
        assert_eq!(
            authority_event.fields_json["old_authority"],
            serde_json::json!(bs58::encode([1_u8; 32]).into_string())
        );
        assert_eq!(
            authority_event.fields_json["new_authority"],
            serde_json::json!(bs58::encode([2_u8; 32]).into_string())
        );

        let mut claim_rate_limit_data = pump_fees_anchor_event_prefix(
            &super::PUMP_FEES_SET_CLAIM_RATE_LIMIT_EVENT_DISCRIMINATOR,
        );
        claim_rate_limit_data.extend_from_slice(&12_i64.to_le_bytes());
        claim_rate_limit_data.extend_from_slice(&999_u64.to_le_bytes());
        let claim_rate_limit_event =
            decode_synthetic_anchor_event(claim_rate_limit_data, "set claim rate limit event");
        assert_eq!(claim_rate_limit_event.spec.name, "SetClaimRateLimitEvent");
        assert_eq!(claim_rate_limit_event.discriminator_hex, "0d8f8febb5133328");
        assert_eq!(
            claim_rate_limit_event.fields_json["claim_rate_limit"],
            serde_json::json!("999")
        );

        let mut disable_flags_data = pump_fees_anchor_event_prefix(
            &super::PUMP_FEES_SET_DISABLE_FLAGS_EVENT_DISCRIMINATOR,
        );
        disable_flags_data.extend_from_slice(&13_i64.to_le_bytes());
        disable_flags_data.push(7_u8);
        let disable_flags_event =
            decode_synthetic_anchor_event(disable_flags_data, "set disable flags event");
        assert_eq!(disable_flags_event.spec.name, "SetDisableFlagsEvent");
        assert_eq!(disable_flags_event.discriminator_hex, "0508b3413137917e");
        assert_eq!(
            disable_flags_event.fields_json["disable_flags"],
            serde_json::json!(7)
        );

        let mut social_claim_authority_data = pump_fees_anchor_event_prefix(
            &super::PUMP_FEES_SET_SOCIAL_CLAIM_AUTHORITY_EVENT_DISCRIMINATOR,
        );
        social_claim_authority_data.extend_from_slice(&14_i64.to_le_bytes());
        social_claim_authority_data.extend_from_slice(&[3_u8; 32]);
        let social_claim_authority_event = decode_synthetic_anchor_event(
            social_claim_authority_data,
            "set social claim authority event",
        );
        assert_eq!(
            social_claim_authority_event.spec.name,
            "SetSocialClaimAuthorityEvent"
        );
        assert_eq!(
            social_claim_authority_event.discriminator_hex,
            "3c767f84ef34fe0e"
        );
        assert_eq!(
            social_claim_authority_event.fields_json["social_claim_authority"],
            serde_json::json!(bs58::encode([3_u8; 32]).into_string())
        );
    }

    #[test]
    fn ignores_truncated_known_instruction_payload() {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&super::PUMP_FEES_GET_FEES_DISCRIMINATOR);
        let encoded = bs58::encode(data).into_string();
        let data_json = serde_json::json!(encoded).to_string();
        let decoded_result = super::parse_pump_fees_instruction_data(Some(&data_json));
        match decoded_result {
            Ok(None) => {},
            Ok(Some(decoded)) => panic!("unexpected decoded truncated instruction: {}", decoded.spec.name),
            Err(error) => panic!("truncated instruction should be ignored: {}", error),
        }
    }

    #[test]
    fn ignores_truncated_known_anchor_event_payload() {
        let data = pump_fees_anchor_event_prefix(
            &super::PUMP_FEES_SOCIAL_FEE_PDA_CLAIMED_DISCRIMINATOR,
        );
        let encoded = {
            use base64::Engine as _;
            base64::engine::general_purpose::STANDARD.encode(data)
        };
        let decoded_result = super::decode_pump_fees_anchor_event_from_base64(encoded.as_str());
        match decoded_result {
            Ok(None) => {},
            Ok(Some(decoded)) => panic!("unexpected decoded truncated anchor event: {}", decoded.spec.name),
            Err(error) => panic!("truncated anchor event should be ignored: {}", error),
        }
    }

    fn pump_fees_anchor_event_prefix(event_discriminator: &[u8; 8]) -> std::vec::Vec<u8> {
        let mut data = std::vec::Vec::new();
        data.extend_from_slice(&super::PUMP_FEES_ANCHOR_SELF_CPI_LOG_DISCRIMINATOR);
        data.extend_from_slice(event_discriminator);
        return data;
    }

    fn decode_synthetic_anchor_event(
        data: std::vec::Vec<u8>,
        context: &str,
    ) -> super::PumpFeesAnchorEventData {
        let encoded = {
            use base64::Engine as _;
            base64::engine::general_purpose::STANDARD.encode(data)
        };
        let decoded_result = super::decode_pump_fees_anchor_event_from_base64(encoded.as_str());
        match decoded_result {
            Ok(Some(decoded)) => return decoded,
            Ok(None) => panic!("expected decoded {}", context),
            Err(error) => panic!("unexpected decode error for {}: {}", context, error),
        }
    }
}
