//! Types and constants for version 1 of the Titan WebSocket API.
//!
//! Messages sent across the WebSocket connection are expected to be encoded using [MessagePack],
//! along with optional compression. Whether compression is enabled and which algorithm is used is
//! negotiated via the [`Sec-WebSocket-Protocol`][sec-ws-proto] header during the WebSocket handshake using the
//! various subprotocol constants defined in this module.
//!
//! Structs are encoded with field names converted to `camelCase` except where otherwise specified.
//! Specifically, many commonly-used types such as [`crate::common::AccountMeta`] have encodings with
//! shorter field names to save space.
//!
//! For optional fields in structs, if the value is not set, it should be skipped while serializing
//! to save space.
//!
//! Enumerations are encoded depending on the type of enumeration.
//! Simple enumerations are encoded as their underlying values. The enumeration definition will
//! specify exactly what this is.
//!
//! Complex enumerations (e.g. those whose values have parameters) are encoded as single-entry maps,
//! where the key is a string with the name of the enumeration kind and the value is the encoded
//! parameter(s). If the enumeration has a single parameter, the value will be that parameter's
//! encoding. If the enumeration has multiple parameters, the value will be an array containing the
//! encoded parameters.
//!
//! [MessagePack]: https://msgpack.org/index.html
//! [sec-ws-proto]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-WebSocket-Protocol

use std::collections::HashMap;

use crate::common::{
    instruction::AddressLookupTableAccount, AccountMeta, BoundedValueWithDefault, Instruction,
    Pubkey,
};

/// Major version number of this API.
pub const VERSION_MAJOR: u16 = 1;
/// Minor version number of this API.
///
/// Incremented when non-breaking changes are made to the type definitions, such as adding new
/// optional fields to request types or new fields to response types that are not crucial for the
/// client to understand.
pub const VERSION_MINOR: u16 = 4;

/// Base subprotocol identifier for this version of the WebSocket API.
///
/// For this version of the protocol, all messages are encoded via [MessagePack] with no
/// compression. Compression may be separately provided via WebSocket protocol extensions if
/// supported by both the client and server.
///
/// To be used with the [`Sec-WebSocket-Protocol`] header for sub-protocol negotiation.
///
/// [MessagePack]: https://msgpack.org/index.html
/// ['Sec-WebSocket-Protocol`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-WebSocket-Protocol
pub const WEBSOCKET_SUBPROTO_BASE: &str = "v1.api.titan.ag";

/// Subprotocol identifier for this version of the WebSocket API with [zstd] compression.
///
/// If this protocol is selected, after encoding messages using [MessagePack], messages must be
/// compressed via [zstd] with compression level 3.
///
/// [MessagePack]: https://msgpack.org/index.html
/// [zstd]: https://github.com/facebook/zstd
pub const WEBSOCKET_SUBPROTO_ZSTD: &str = "v1.api.titan.ag+zstd";

/// Subprotocol identifier for this version of the WebSocket API with [brotli] compression.
///
/// If this protocol is selected, after encoding messages using [MessagePack], messages must be
/// compressed via [brotli] with quality level 11.
///
/// [MessagePack]: https://msgpack.org/index.html
/// [brotli]: https://github.com/google/brotli
pub const WEBSOCKET_SUBPROTO_BROTLI: &str = "v1.api.titan.ag+br";

/// Subprotocol identifier for this version of the WebSocket API with [gzip] compression.
///
/// If this protocol is selected, after encoding messages using [MessagePack], messages must be
/// compressed via [gzip].
///
/// [MessagePack]: https://msgpack.org/index.html
/// [gzip]: https://www.gzip.org/
pub const WEBSOCKET_SUBPROTO_GZIP: &str = "v1.api.titan.ag+gzip";

/// Array containing all the valid WeBSocket subprotocol identifiers for this version of the API.
///
/// Protocols are ordered roughly in descending order of preference in terms of compression ratio
/// and speed.
pub const WEBSOCKET_SUBPROTOCOLS: [&str; 4] = [
    WEBSOCKET_SUBPROTO_ZSTD,
    WEBSOCKET_SUBPROTO_BROTLI,
    WEBSOCKET_SUBPROTO_GZIP,
    WEBSOCKET_SUBPROTO_BASE,
];

/// Information about the version of the protocol being spoken by the server.
///
/// Only changes to the major and minor versions affect the format of data sent across the network.
/// Patch version increases note that internal logic changes in the server have occurred that do
/// not otherwise impact the API (e.g. bug fixes).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VersionInfo {
    /// Major version number.
    ///
    /// Incremented when the data format changes in backwards-incompatible ways.
    pub major: u16,
    /// Minor version number.
    ///
    /// Incremented when the data format changes in backwards-compatible ways.
    pub minor: u16,
    /// Patch version number.
    ///
    /// Incremented when changes to the protocol that do not affect the data format have occurred.
    /// Primarily informational.
    pub patch: u16,
}

impl VersionInfo {
    /// Constructs a new `VersionInfo` from its parts.
    pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
        Self {
            major,
            minor,
            patch,
        }
    }

    /// Computes a serial number representation of this version.
    ///
    /// This is constructed in such a way that comparisons of serial numbers respect the priority
    /// of major, minor, and patch versions.
    pub const fn serial(&self) -> u64 {
        ((self.major as u64) << 32) + ((self.minor as u64) << 16) + (self.minor as u64)
    }
}

/// Type of swap to perform.
///
/// Affects how the swap `amount` is handled.
///
/// Values are encoded as strings exactly matching the names.
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SwapMode {
    /// Swap amount is the number of input tokens that should be swapped.
    #[default]
    ExactIn,
    /// Swap amount is the number of output tokens that should be received.
    ExactOut,
}

/// Which version of the swap routing engine to use.
///
/// Currently defaults to [`V2`](SwapVersion::V2). V2 will be deprecated in a future release, at
/// which point the default will switch to [`V3`](SwapVersion::V3). After the deprecation date,
/// V2 will no longer receive technical support and clients should migrate to V3.
///
/// Values are encoded as their `u8` discriminant: `2` for V2, `3` for V3.
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(into = "u8", try_from = "u8")
)]
#[repr(u8)]
pub enum SwapVersion {
    /// Version 2 of the swap routing engine (current default, will be deprecated).
    #[default]
    V2 = 2,
    /// Version 3 of the swap routing engine.
    V3 = 3,
}

impl From<SwapVersion> for u8 {
    fn from(v: SwapVersion) -> u8 {
        v as u8
    }
}

impl TryFrom<u8> for SwapVersion {
    type Error = u8;

    fn try_from(v: u8) -> Result<Self, Self::Error> {
        match v {
            2 => Ok(Self::V2),
            3 => Ok(Self::V3),
            other => Err(other),
        }
    }
}

/// A templated route which Titan can use when determining eligible routes.
/// If possible, Titan will insert at the specified index a swap that meets
/// Solana's sizing requirements. This is incompatible with the options,
/// `accounts_limit_total`, `accounts_limit_writable` and `size_constraint`.
/// An error will be returned if they are both specified.
#[derive(Default, PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransactionTemplate {
    /// A vector of instructions. It is assumed that you have included
    /// the input mint and output mint account creation/deletion when necessary.
    #[cfg_attr(feature = "serde", serde(rename = "i"))]
    pub instructions: Vec<Instruction>,
    /// Address lookup tables (ATLs) used in the instruction. Should be provided in the same order that
    /// you will use the address lookup tables when compiling the message. This is because Solana
    /// uses the ALTs in a greedy fashion and their effect is dependent on their ordering . Any ALTs used
    /// by Titan will be used to extend the vector.
    #[cfg_attr(feature = "serde", serde(rename = "a"))]
    pub alts: Vec<AddressLookupTableAccount>,
    /// Additional account metas that need to be added into consideration for sizing
    #[cfg_attr(feature = "serde", serde(rename = "m", default))]
    pub account_metas: Vec<AccountMeta>,
}

/// Parameters for a swap request.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapParams {
    /// Address of the input mint of the swap.
    pub input_mint: Pubkey,
    /// Address of the desired output token for the swap.
    pub output_mint: Pubkey,
    /// Raw number of tokens to swap, not scaled by decimals.
    pub amount: u64,
    /// Swap mode for how the amount should be interpreted.
    ///
    /// Either ExactIn or ExactOut, defaults to ExactIn.
    /// ExactOut is for supporting use cases where you need an exact output token amount, like
    /// payments. In this case, slippage would be on the input token. May not always be supported.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub swap_mode: Option<SwapMode>,
    /// Allowed slippage in basis points.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub slippage_bps: Option<u16>,
    /// If set, constrain quotes to the given set of DEXes.
    ///
    /// Note: setting both `dexes` and `exclude_dexes` may result in excluding all dexes, resulting
    /// in no routes.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub dexes: Option<Vec<String>>,
    /// If set, exclude the following DEXes when determining routes.
    ///
    /// Note: setting both `dexes` and `exclude_dexes` may result in excluding all dexes, resulting
    /// in no routes.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub exclude_dexes: Option<Vec<String>>,
    /// If set to true, only direct routes between the input and output mint will be considered.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub only_direct_routes: Option<bool>,
    /// If set to true, will request that quote providers restrict their quotes to transactions
    /// that will fit within the size constraint.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub add_size_constraint: Option<bool>,
    /// Transaction size constraint in bytes. Defaults to 1232. Only enabled if
    /// `add_size_constraint` is also set.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub size_constraint: Option<u32>,
    /// If set, limit quotes to the given set of provider IDs.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub providers: Option<Vec<String>>,
    /// If set, limit total number of accounts used by routes to the specified value.
    ///
    /// If not set, any number of accounts that still allows for an executable transaction is
    /// allowed. As of writing, this is 256 accounts.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub accounts_limit_total: Option<u16>,
    /// If set, limit total number of writable accounts used by routes to the specified value.
    ///
    /// If not set, any number of accounts that still allows for an executable transaction is
    /// allowed. As of writing, this is 64 writable accounts.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub accounts_limit_writable: Option<u16>,
    /// A templated route which Titan can use when determining eligible routes.
    /// If possible, Titan will insert at the specified index a swap that meets
    /// Solana's sizing requirements. This is incompatible with the options,
    /// `accounts_limit_total`, `accounts_limit_writable` and `size_constraint`.
    /// An error will be returned if they are both specified.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub transaction_template: Option<TransactionTemplate>,
}

/// Server default settings and constraints for [`SwapParams`].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapSettings {
    /// Default and bounds for [`SwapParams::slippage_bps`].
    pub slippage_bps: BoundedValueWithDefault<u16>,
    /// Default value for [`SwapParams::only_direct_routes`].
    pub only_direct_routes: bool,
    /// Default value for [`SwapParams::add_size_constraint`].
    pub add_size_constraint: bool,
    /// Default value for [`SwapParams::size_constraint`].
    pub size_constraint: u32,
}

/// Parameters for how quote updates should be handled.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct QuoteUpdateParams {
    /// How often the server should send updates for this quote request, in milliseconds.
    ///
    /// If not specified, the server default will be used.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub interval_ms: Option<u64>,
    /// Maximum number of quotes per update the server should return. If more quotes are available,
    /// the worst will be filtered out, based on amount in/out depending on swap mode.
    ///
    /// If not specified, the server default will be used.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub num_quotes: Option<u32>,
}

/// Contains information about the server's settings for quote updates.
///
/// Contains minimum, maximum, and default values as needed for the various settings, so that the
/// client can query information about these settings to ensure that its requests contain valid
/// values.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct QuoteUpdateSettings {
    /// Bounds and defaults for the `interval_ms` field of [`QuoteUpdateParams`].
    pub interval_ms: BoundedValueWithDefault<u64>,
    /// Bounds and defaults for the [`QuoteUpdateParams::num_quotes`] field.
    pub num_quotes: BoundedValueWithDefault<u32>,
}

/// Parameters for how transaction data should be generated for swaps.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct TransactionParams {
    /// Public key of the user requesting the swap, needed for transaction generation.
    pub user_public_key: Pubkey,
    /// If true, close the input token account as part of the transaction.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub close_input_token_account: Option<bool>,
    /// If true, an idempotent ATA will be added to the transactions, if supported by the
    /// providers.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub create_output_token_account: Option<bool>,
    /// The address of an associated token account that will be used to collect fees.
    ///
    /// Unless `fee_is_input_mint` is set to true, must be in terms of the output mint.
    ///
    /// The fee acount must already exist, or the user must add the ATA creation
    /// instruction themselves.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub fee_account: Option<Pubkey>,
    /// Fee amount to take, in basis points.
    ///
    /// If not specified, default fee for the requester is used.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub fee_bps: Option<u16>,
    /// Whether the fee should be taken in terms of the input mint.
    /// Default is false, in which case the fee is taken in terms of the output mint.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub fee_from_input_mint: Option<bool>,
    /// Address of the token account into which to place the output of the swap.
    /// If not specified, the funds will be deposited into an ATA associated with the user's
    /// wallet.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub output_account: Option<Pubkey>,
    /// Which version of the swap routing engine to use.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub titan_swap_version: Option<SwapVersion>,
    /// The address that will receive any positive slippage fees.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub positive_slippage_fee_receiver: Option<Pubkey>,
    /// The address of the payer for the transaction.
    /// If not specified, defaults to the user's public key.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub payer: Option<Pubkey>,
    /// If true, leave the output as wSOL instead of unwrapping to native SOL when the output mint
    /// is the wrapped SOL mint. Defaults to false.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub output_wsol: Option<bool>,
}

/// Contains information about the server's defaults and paramter bounds for transactions.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct TransactionSettings {
    /// Default value for [`TransactionParams::close_input_token_account`].
    pub close_input_token_account: bool,
    /// Default value for [`TransactionParams::create_output_token_account`].
    pub create_output_token_account: bool,
}

/// Contains information about settings or limits set for the specific connection to the server.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct ConnectionSettings {
    /// Number of concurrent streams the user is allowed.
    pub concurrent_streams: u32,
}

/// Settings and paramter bounds as configured on the server.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct ServerSettings {
    /// Settings and parameter bounds for quote updates.
    pub quote_update: QuoteUpdateSettings,
    /// Settings and parameter bounds for swaps.
    pub swap: SwapSettings,
    /// Settings and parameter bounds for transaction generation.
    pub transaction: TransactionSettings,
    /// Settings and limits for the user's connection to the server.
    pub connection: ConnectionSettings,
}

/// Contains information about the servers settings.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct ServerInfo {
    /// Server protocol version information.
    pub protocol_version: VersionInfo,
    /// Server settings and parameter bounds.
    pub settings: ServerSettings,
}

/// A request from the user to provide quotes for a swap.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapQuoteRequest {
    /// Parameters for the swap.
    pub swap: SwapParams,
    /// Parameters for transaction generation.
    pub transaction: TransactionParams,
    /// Parameters for quote updating.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub update: Option<QuoteUpdateParams>,
}

/// A request from the user to quote swapping an amount directly between two tokens,
/// which effectively gives the relative price of the tokens at that value.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapPriceRequest {
    /// Address of the input mint of the swap.
    pub input_mint: Pubkey,
    /// Address of the desired output token for the swap.
    pub output_mint: Pubkey,
    /// Raw number of tokens to swap, not scaled by decimals.
    pub amount: u64,
    /// If set, constrain quotes to the given set of DEXes.
    ///
    /// Note: setting both `dexes` and `exclude_dexes` may result in excluding all dexes, resulting
    /// in no routes.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub dexes: Option<Vec<String>>,
    /// If set, exclude the following DEXes when determining routes.
    ///
    /// Note: setting both `dexes` and `exclude_dexes` may result in excluding all dexes, resulting
    /// in no routes.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub exclude_dexes: Option<Vec<String>>,
}

/// Information about a single step in a route plan for a swap.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct RoutePlanStep {
    /// Which AMM is being executed on at this step.
    pub amm_key: Pubkey,
    /// Label for the protocol being used.
    ///
    /// Examples: "Raydium AMM", "Phoenix", etc.
    pub label: String,
    /// Address of the input mint for this swap.
    pub input_mint: Pubkey,
    /// Address of the output mint for this swap.
    pub output_mint: Pubkey,
    /// How many input tokens are expected to go through this step.
    pub in_amount: u64,
    /// How many output tokens are expected to come out of this step.
    pub out_amount: u64,
    /// What what proportion, in parts per billion, of the order flow is allocated to flow through this pool.
    pub alloc_ppb: u32,
    /// Address of the mint in which the fee is charged.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub fee_mint: Option<Pubkey>,
    /// The amount of tokens charged as a fee for this swap.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub fee_amount: Option<u64>,
    /// Context slot for the pool data, if known.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub context_slot: Option<u64>,
}

/// Information about a fee that is claimed by a platform for use of its services as part of a
/// quote.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct PlatformFee {
    /// Amount of tokens taken as a fee.
    pub amount: u64,
    /// Fee percentage, in basis points.
    pub fee_bps: u8,
}

/// Information about a route for a swap.
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapRoute {
    /// How many input tokens are expected to go through this route.
    pub in_amount: u64,
    /// How many output tokens are expected to come out of this route.
    pub out_amount: u64,
    /// Amount of slippage encurred, in basis points.
    pub slippage_bps: u16,
    /// Platform fee information, if such a fee is charged by the provider.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub platform_fee: Option<PlatformFee>,
    /// Topologically ordered DAG containing the steps that comprise this route.
    ///
    /// May be empty if the provider does not provide information about the steps taken, or there
    /// are no steps (e.g. RFQ providers).
    pub steps: Vec<RoutePlanStep>,
    /// Instructions needed to execute the route.
    ///
    /// May be empty if the provider does not support the user building their own transactions.
    /// In this case, the transaction to sign should be present in the [`SwapRoute::transaction`]
    /// field.
    pub instructions: Vec<Instruction>,
    /// Address lookup tables necessary to load.
    ///
    /// May be empty if the provider does not support the user building their own transactions.
    /// In this case, the transaction to sign should be present in the [`SwapRoute::transaction`]
    /// field.
    pub address_lookup_tables: Vec<Pubkey>,
    /// Context slot for the route provided.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub context_slot: Option<u64>,
    /// Amount of time taken to generate the quote in seconds, if known.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub time_taken_ns: Option<u64>,
    /// If this route expires by time, the time at which it expires, as a millisecond UNIX timestamp.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub expires_at_ms: Option<u64>,
    /// If this route expires by slot, the last slot at which the route is valid.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub expires_after_slot: Option<u64>,
    /// The number of compute units this transaction is expected to consume, if known.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub compute_units: Option<u64>,
    /// The number of compute units that the server recommends setting for this route. Includes a
    /// buffer to allow for fluctuations in compute unit usage from various protocols as well as
    /// other instructions the user may wish to add, such as tips.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub compute_units_safe: Option<u64>,
    /// Raw transaction to be signed by the user. May be returned instead of instructions and ALTs.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", with = "serde_bytes", default)
    )]
    pub transaction: Option<Vec<u8>>,
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    /// Reference ID for Pyth Express Relay and HashFlow RFQ providers.
    pub reference_id: Option<String>,
}

/// Possible value for a flag in the flags map.
///
/// Only simple values like strings, booleans, integers, and floating point numbers are allowed.
///
/// Values are serialized untagged, e.g. as the inner type.
///
/// Available since: v1.3
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
pub enum MetadataValue {
    /// Value contains a single String.
    String(String),
    /// Value contains a single boolean.
    Bool(bool),
    /// Value contains a single integer.
    Int(i64),
    /// Value contains a single floating point number.
    Float(f64),
}

// Custom deserializer using serde-untagged to have better performance than the default untagged
// deserialization method, especially since this is a very simple struct that is very easy to tell
// which variant is needed based on the type.
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for MetadataValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        serde_untagged::UntaggedEnumVisitor::new()
            .string(|string| Ok(Self::String(String::from(string))))
            .i64(|int| Ok(Self::Int(int)))
            .f64(|value| Ok(Self::Float(value)))
            .bool(|value| Ok(Self::Bool(value)))
            .deserialize(deserializer)
    }
}

/// A set of quotes for a swap requested by the user.
///
/// If multiple providers are able to provide quotes, one route per provider will be returned.
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapQuotes {
    /// Identifier for this particular set of quotes.
    pub id: String,
    /// Address of the input mint for this quote.
    pub input_mint: Pubkey,
    /// Address of the output mint for this quote.
    pub output_mint: Pubkey,
    /// What swap mode was used for the quotes.
    pub swap_mode: SwapMode,
    /// Amount that was used for the quotes.
    pub amount: u64,
    /// A mapping of a provider identifier to their quoted route.
    pub quotes: HashMap<String, SwapRoute>,
    /// Optional informational flags attached to the quote.
    ///
    /// Available since: v1.3
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub metadata: Option<HashMap<String, MetadataValue>>,
}

/// A pricing request from a user.
///
/// Only one price will be returned for the token pair via Argos.
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct SwapPrice {
    /// Identifier for this particular set of prices.
    pub id: String,
    /// Address of the input mint for this price.
    pub input_mint: Pubkey,
    /// Address of the output mint for this price.
    pub output_mint: Pubkey,
    /// Amount that was used for the price.
    pub amount_in: u64,
    /// The amount out of the best simulated quote for pricing.
    pub amount_out: u64,
}

/// Response to a successful request to start a stream of quotes for a swap.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct QuoteSwapStreamResponse {
    /// The interval, in milliseconds, in which the server will provide updates to the quotes.
    pub interval_ms: u64,
    /// The maximum number of quotes that will be returned per update.
    pub num_quotes: u32,
}

/// Parameters for a `GetInfo` request.
///
/// Currently contains no options, allowing for further expansion and to allow serialization of all
/// request types to be consistent.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GetInfoRequest {
    /// Dummy attribute so this will be serialized as expected.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub dummy: Option<bool>,
}

/// Paramters for a [`RequestData::StopStream`] request.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StopStreamRequest {
    /// Identifier of the stream to stop.
    pub id: u32,
}

/// Response to a successful request to stop a data stream.
///
/// Note: If a stream had data already prepared, there may still be a [`StreamData`][streamdata]
/// message sent for the stream before the [`StreamEnd`][streamend] is sent.
///
/// [streamdata]: ServerMessage::StreamData
/// [streamend]: ServerMessage::StreamEnd
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StopStreamResponse {
    /// Identifier of the stream that was stopped.
    pub id: u32,
}

/// Request to retrieve information about known venues for dex aggregator providers.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct GetVenuesRequest {
    /// Whether to include the program ID for each venue.
    #[cfg_attr(
        feature = "serde",
        serde(default, skip_serializing_if = "Option::is_none")
    )]
    pub include_program_ids: Option<bool>,
}

/// Information about known venues for dex aggregators.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct VenueInfo {
    /// Labels for known venues, used in the `dexes` and `exclude_dexes` swap parameters.
    pub labels: Vec<String>,
    /// Program IDs for each venue, if requested.
    #[cfg_attr(
        feature = "serde",
        serde(default, skip_serializing_if = "Option::is_none")
    )]
    pub program_ids: Option<Vec<Pubkey>>,
}

/// Request to retrieve information about the enabled quote providers.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct ListProvidersRequest {
    /// If true, will include icon URLs.
    #[cfg_attr(
        feature = "serde",
        serde(default, skip_serializing_if = "Option::is_none")
    )]
    pub include_icons: Option<bool>,
}

/// Information about a particular provider.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
pub struct ProviderInfo {
    /// ID of the provider as returned in quotes and used in the `providers` parameter for
    /// requests.
    pub id: String,
    /// Human-readable name for the provider.
    pub name: String,
    /// What kind of provider this is.
    pub kind: ProviderKind,
    /// URI for a 48x48 pixel icon for the provider, if requested.
    #[cfg_attr(
        feature = "serde",
        serde(default, skip_serializing_if = "Option::is_none")
    )]
    pub icon_uri_48: Option<String>,
}

/// What kind of quote provider a particular provider is.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ProviderKind {
    /// Provider is an aggregator of decentralized exchanges.
    DexAggregator,
    /// Provider is a Request-For-Quotes provider.
    RFQ,
    /// Catch-all for deserializing additional types added in the future.
    #[cfg_attr(feature = "serde", serde(untagged))]
    Other(String),
}

/// Data for a request from the user sent on the WebSocket.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(clippy::large_enum_variant)]
pub enum RequestData {
    /// Request that the server respond with information on its settings and parameter bounds.
    GetInfo(GetInfoRequest),
    /// User requests a stream of quotes for a swap between two tokens.
    NewSwapQuoteStream(SwapQuoteRequest),
    /// User requests to stop a stream of data.
    StopStream(StopStreamRequest),
    /// User requets information about known venues for dex aggregator providers.
    GetVenues(GetVenuesRequest),
    /// User requests information about enabled quote providers.
    ListProviders(ListProvidersRequest),
    /// User requests the price of a swap directly between two tokens.
    GetSwapPrice(SwapPriceRequest),
    /// Other type of un-handled message type.
    #[cfg(feature = "enum-other")]
    #[cfg_attr(feature = "serde", serde(untagged))]
    Other(rmpv::Value),
}

/// Data for a successful response to a request.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ResponseData {
    /// Response for a [`GetInfo`][ResponseData::GetInfo] request.
    GetInfo(ServerInfo),
    /// Response for a [`NewSwapQuoteStream`][RequestData::NewSwapQuoteStream] request.
    NewSwapQuoteStream(QuoteSwapStreamResponse),
    /// Successful response to a [`StopStream`][RequestData::StopStream] request.
    StreamStopped(StopStreamResponse),
    /// Successful response to a [`GetVenues`][RequestData::GetVenues] request.
    GetVenues(VenueInfo),
    /// Successful response to a [`ListProviders`][RequestData::ListProviders] request.
    ListProviders(Vec<ProviderInfo>),
    /// Successful response to a [`GetPrice`][RequestData::GetSwapPrice] request.
    GetSwapPrice(SwapPrice),
    /// Other type of un-handled message type.
    #[cfg(feature = "enum-other")]
    #[cfg_attr(feature = "serde", serde(untagged))]
    Other(rmpv::Value),
}

/// A request sent by the client.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ClientRequest {
    /// Identifier for the request.
    ///
    /// The response(s) will contain the same ID that is specified here so that the client can
    /// correlate the response(s) with the request.
    pub id: u32,
    /// Data for the request.
    pub data: RequestData,
}

/// Notification that a new stream has been started by the server.
///
/// Returned as part of a successful response so that the client can allocate resources for the
/// stream.
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamStart {
    /// ID of the new stream.
    ///
    /// This ID will be present in [`StreamData`] and [`StreamEnd`] messages for this stream.
    pub id: u32,
    /// The type of data that will be sent in this stream.
    pub data_type: StreamDataType,
}

/// Data packet for a stream.
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, PartialEq)]
pub struct StreamData {
    /// ID of the stream.
    pub id: u32,
    /// Sequence number of this data packet.
    pub seq: u32,
    /// Data payload.
    pub payload: StreamDataPayload,
}

/// Notification that a stream has closed and will no longer receive data.
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamEnd {
    /// ID of the stream that has ended.
    pub id: u32,
    /// If the stream ended due to an error, contains the code denoting the specific error that
    /// occurred.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub error_code: Option<u32>,
    /// If the stream ended due to an error, contains a message explaining what happened.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub error_message: Option<String>,
}

/// A response to a successful request.
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, PartialEq)]
pub struct ResponseSuccess {
    /// Identifier of the request that triggered this response.
    pub request_id: u32,
    /// The response data.
    pub data: ResponseData,
    /// If this request starts a new stream, contains information about the stream ID and its data
    /// type.
    #[cfg_attr(
        feature = "serde",
        serde(skip_serializing_if = "Option::is_none", default)
    )]
    pub stream: Option<StreamStart>,
}

/// A response to a request that resulted in an error.
#[cfg_attr(
    feature = "serde",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResponseError {
    /// Identifier of the request that triggered this response.
    pub request_id: u32,
    /// A numeric error code representing the specific error that occurred.
    pub code: u32,
    /// A message describing the error.
    pub message: String,
}

/// Types of data that can be streamed to the client.
///
/// Included in a [`StreamStart`] message so that the client may prepare for the incoming data.
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum StreamDataType {
    /// Stream contains [`SwapQuotes`](struct@SwapQuotes) as the data values.
    SwapQuotes,
    /// Other type added in a future version.
    #[cfg(feature = "enum-other")]
    #[cfg_attr(feature = "serde", serde(untagged))]
    Other(String),
}

/// Payload for a [`StreamData`] message.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum StreamDataPayload {
    /// Payload contains a set of quotes for a swap request.
    SwapQuotes(SwapQuotes),
    /// Other type of un-handled message type.
    #[cfg(feature = "enum-other")]
    #[cfg_attr(feature = "serde", serde(untagged))]
    Other(rmpv::Value),
}

/// A message sent by the server to the client.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum ServerMessage {
    /// Successful response to a request, which may optionally start a stream.
    Response(ResponseSuccess),
    /// An error response to a request.
    Error(ResponseError),
    /// Data for a stream.
    StreamData(StreamData),
    /// Notification that a stream has ended.
    StreamEnd(StreamEnd),
    /// Other type of un-handled message type.
    #[cfg(feature = "enum-other")]
    #[cfg_attr(feature = "serde", serde(untagged))]
    Other(rmpv::Value),
}

#[cfg(test)]
mod test {
    use super::*;

    use hex_literal::hex;
    use rmp_serde::{decode::Error as DecError, encode::Error as EncError};
    use rmpv::Value;

    const WSOL_ADDR: Pubkey = Pubkey::from_str_const("So11111111111111111111111111111111111111112");
    const USDC_ADDR: Pubkey =
        Pubkey::from_str_const("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
    const METEORA_DLMM_WSOL_USDC: Pubkey =
        Pubkey::from_str_const("2kSaQT7BcB92uVeDtmxsDJm9fbGxupf64KgtsF5xzLhf");

    #[test]
    fn test_encode_version_info() -> Result<(), EncError> {
        let info = VersionInfo::new(1, 2, 3);
        let expected = hex!("83a56d616a6f7201a56d696e6f7202a5706174636803");
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, expected);
        Ok(())
    }

    #[test]
    fn test_decode_version_info() -> Result<(), DecError> {
        let info = VersionInfo::new(1, 2, 3);
        let data = hex!("83a56d616a6f7201a56d696e6f7202a5706174636803");
        let decoded = rmp_serde::from_slice::<VersionInfo>(&data)?;
        assert_eq!(decoded, info);
        Ok(())
    }

    #[test]
    fn test_encode_swap_mode() -> Result<(), EncError> {
        let exact_in_data = hex!("a74578616374496e");
        let exact_out_data = hex!("a845786163744f7574");

        assert_eq!(rmp_serde::to_vec_named(&SwapMode::ExactIn)?, &exact_in_data);
        assert_eq!(
            rmp_serde::to_vec_named(&SwapMode::ExactOut)?,
            &exact_out_data
        );

        Ok(())
    }

    #[test]
    fn test_decode_swap_mode() -> Result<(), DecError> {
        let exact_in_data = hex!("a74578616374496e");
        let exact_out_data = hex!("a845786163744f7574");

        assert_eq!(
            rmp_serde::from_slice::<SwapMode>(&exact_in_data)?,
            SwapMode::ExactIn
        );
        assert_eq!(
            rmp_serde::from_slice::<SwapMode>(&exact_out_data)?,
            SwapMode::ExactOut
        );

        Ok(())
    }

    #[test]
    fn test_encode_swap_params() -> Result<(), EncError> {
        let data = hex!("83a9696e7075744d696e74c420069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f00000000001aa6f75747075744d696e74c420c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61a6616d6f756e74ce3b9aca00");
        let info = SwapParams {
            input_mint: WSOL_ADDR,
            output_mint: USDC_ADDR,
            amount: 1_000_000_000,
            ..Default::default()
        };
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_swap_params() -> Result<(), DecError> {
        let data = hex!("83a9696e7075744d696e74c420069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f00000000001aa6f75747075744d696e74c420c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61a6616d6f756e74ce3b9aca00");
        let info = SwapParams {
            input_mint: WSOL_ADDR,
            output_mint: USDC_ADDR,
            amount: 1_000_000_000,
            ..Default::default()
        };
        let decoded: SwapParams = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, info);

        Ok(())
    }

    #[test]
    fn test_encode_swap_settings() -> Result<(), EncError> {
        let expected_rmp = Value::Map(vec![
            (
                Value::from("slippageBps"),
                Value::Map(vec![
                    (Value::from("min"), Value::from(0u16)),
                    (Value::from("max"), Value::from(10_000u16)),
                    (Value::from("default"), Value::from(100u16)),
                ]),
            ),
            (Value::from("onlyDirectRoutes"), Value::from(false)),
            (Value::from("addSizeConstraint"), Value::from(true)),
            (Value::from("sizeConstraint"), Value::from(1232)),
        ]);
        let data = rmp_serde::to_vec_named(&expected_rmp).expect("expected rmp should encode");
        let info = SwapSettings {
            slippage_bps: BoundedValueWithDefault {
                min: 0,
                max: 10_000,
                default: 100,
            },
            only_direct_routes: false,
            add_size_constraint: true,
            size_constraint: 1232,
        };
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_swap_settings() -> Result<(), DecError> {
        let expected_rmp = Value::Map(vec![
            (
                Value::from("slippageBps"),
                Value::Map(vec![
                    (Value::from("min"), Value::from(0u16)),
                    (Value::from("max"), Value::from(10_000u16)),
                    (Value::from("default"), Value::from(100u16)),
                ]),
            ),
            (Value::from("onlyDirectRoutes"), Value::from(false)),
            (Value::from("addSizeConstraint"), Value::from(true)),
            (Value::from("sizeConstraint"), Value::from(1232)),
        ]);
        let data = rmp_serde::to_vec_named(&expected_rmp).expect("expected rmp should encode");
        let info = SwapSettings {
            slippage_bps: BoundedValueWithDefault {
                min: 0,
                max: 10_000,
                default: 100,
            },
            only_direct_routes: false,
            add_size_constraint: true,
            size_constraint: 1232,
        };
        let decoded: SwapSettings = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, info);

        Ok(())
    }

    #[test]
    fn test_encode_quote_update_params() -> Result<(), EncError> {
        let data = hex!("81aa696e74657276616c4d73cd03e8");
        let info = QuoteUpdateParams {
            interval_ms: Some(1000),
            ..Default::default()
        };
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, data);

        let empty_data = hex!("80");
        let info = QuoteUpdateParams {
            interval_ms: None,
            ..Default::default()
        };
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, empty_data);

        Ok(())
    }

    #[test]
    fn test_decode_quote_update_params() -> Result<(), DecError> {
        let data = hex!("81aa696e74657276616c4d73cd03e8");
        let info = QuoteUpdateParams {
            interval_ms: Some(1000),
            ..Default::default()
        };
        let decoded: QuoteUpdateParams = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, info);

        let empty_data = hex!("80");
        let info = QuoteUpdateParams {
            interval_ms: None,
            ..Default::default()
        };
        let decoded: QuoteUpdateParams = rmp_serde::from_slice(&empty_data)?;
        assert_eq!(decoded, info);

        Ok(())
    }

    #[test]
    fn test_encode_transaction_params() -> Result<(), EncError> {
        let data = hex!("82ad757365725075626c69634b6579c42019fc949cf6d288d076e39f923bf6f1b45da023310e606afc40cc080d5b25983ab6636c6f7365496e707574546f6b656e4163636f756e74c3");
        let info = TransactionParams {
            user_public_key: METEORA_DLMM_WSOL_USDC,
            close_input_token_account: Some(true),
            create_output_token_account: None,
            ..Default::default()
        };
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_transaction_params() -> Result<(), DecError> {
        let data = hex!("82ad757365725075626c69634b6579c42019fc949cf6d288d076e39f923bf6f1b45da023310e606afc40cc080d5b25983ab6636c6f7365496e707574546f6b656e4163636f756e74c3");
        let info = TransactionParams {
            user_public_key: METEORA_DLMM_WSOL_USDC,
            close_input_token_account: Some(true),
            create_output_token_account: None,
            ..Default::default()
        };
        let decoded: TransactionParams = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, info);

        Ok(())
    }

    #[test]
    fn test_encode_transaction_settings() -> Result<(), EncError> {
        let data = hex!("82b6636c6f7365496e707574546f6b656e4163636f756e74c2b86372656174654f7574707574546f6b656e4163636f756e74c3");
        let info = TransactionSettings {
            close_input_token_account: false,
            create_output_token_account: true,
        };
        let encoded = rmp_serde::to_vec_named(&info)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_transaction_settings() -> Result<(), DecError> {
        let data = hex!("82b6636c6f7365496e707574546f6b656e4163636f756e74c2b86372656174654f7574707574546f6b656e4163636f756e74c3");
        let info = TransactionSettings {
            close_input_token_account: false,
            create_output_token_account: true,
        };
        let decoded: TransactionSettings = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, info);

        Ok(())
    }

    #[test]
    fn test_encode_route_plan_step() -> Result<(), EncError> {
        let data = hex!("88a6616d6d4b6579c42019fc949cf6d288d076e39f923bf6f1b45da023310e606afc40cc080d5b25983aa56c6162656cac4d6574656f726120444c4d4da9696e7075744d696e74c420069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f00000000001aa6f75747075744d696e74c420c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61a8696e416d6f756e74ce3b9aca00a96f7574416d6f756e74ce08d0ed70a8616c6c6f63507062ce3b9aca00ab636f6e74657874536c6f74ce00bc614e");
        let step = RoutePlanStep {
            amm_key: METEORA_DLMM_WSOL_USDC,
            label: "Meteora DLMM".into(),
            input_mint: WSOL_ADDR,
            output_mint: USDC_ADDR,
            in_amount: 1_000_000_000, // 1 SOL
            out_amount: 147_910_000,  // 147.91 USDC
            alloc_ppb: 1_000_000_000, // 100%
            fee_mint: None,
            fee_amount: None,
            context_slot: Some(12345678),
        };

        let encoded = rmp_serde::to_vec_named(&step)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_route_plan_step() -> Result<(), DecError> {
        let data = hex!("88a6616d6d4b6579c42019fc949cf6d288d076e39f923bf6f1b45da023310e606afc40cc080d5b25983aa56c6162656cac4d6574656f726120444c4d4da9696e7075744d696e74c420069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f00000000001aa6f75747075744d696e74c420c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61a8696e416d6f756e74ce3b9aca00a96f7574416d6f756e74ce08d0ed70a8616c6c6f63507062ce3b9aca00ab636f6e74657874536c6f74ce00bc614e");
        let step = RoutePlanStep {
            amm_key: METEORA_DLMM_WSOL_USDC,
            label: "Meteora DLMM".into(),
            input_mint: WSOL_ADDR,
            output_mint: USDC_ADDR,
            in_amount: 1_000_000_000, // 1 SOL
            out_amount: 147_910_000,  // 147.91 USDC
            alloc_ppb: 1_000_000_000, // 100%
            fee_mint: None,
            fee_amount: None,
            context_slot: Some(12345678),
        };

        let decoded: RoutePlanStep = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, step);

        Ok(())
    }

    #[test]
    fn test_encode_request_data() -> Result<(), EncError> {
        // GetInfo
        let data = hex!("81a7476574496e666f80");
        let req = RequestData::GetInfo(GetInfoRequest { dummy: None });

        let encoded = rmp_serde::to_vec_named(&req)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_request_data() -> Result<(), DecError> {
        let data = hex!("81a7476574496e666f80");
        let req = RequestData::GetInfo(GetInfoRequest { dummy: None });

        let decoded: RequestData = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, req);

        Ok(())
    }

    #[test]
    fn test_encode_server_message() -> Result<(), EncError> {
        let data = hex!("81a953747265616d456e6483a26964cd3039a96572726f72436f6465cd01f8ac6572726f724d657373616765bb626f622074726970706564206f7665722074686520726f75746572");
        let message = ServerMessage::StreamEnd(StreamEnd {
            id: 12345,
            error_code: Some(504),
            error_message: Some("bob tripped over the router".into()),
        });

        let encoded = rmp_serde::to_vec_named(&message)?;
        assert_eq!(encoded, data);

        Ok(())
    }

    #[test]
    fn test_decode_server_message() -> Result<(), DecError> {
        let data = hex!("81a953747265616d456e6483a26964cd3039a96572726f72436f6465cd01f8ac6572726f724d657373616765bb626f622074726970706564206f7665722074686520726f75746572");
        let message = ServerMessage::StreamEnd(StreamEnd {
            id: 12345,
            error_code: Some(504),
            error_message: Some("bob tripped over the router".into()),
        });

        let decoded: ServerMessage = rmp_serde::from_slice(&data)?;
        assert_eq!(decoded, message);
        Ok(())
    }

    #[test]
    fn test_other_server_message() -> Result<(), DecError> {
        let unhandled = Value::Map(vec![(
            Value::from("SomeOtherWeirdMessageType"),
            Value::from(1),
        )]);
        let unhandled_data = rmp_serde::to_vec_named(&unhandled).expect("should encode");
        let decoded: ServerMessage = rmp_serde::from_slice(&unhandled_data)?;
        let unhandled_sm = ServerMessage::Other(unhandled);
        assert_eq!(decoded, unhandled_sm);
        Ok(())
    }

    #[test]
    fn test_other_request_data() -> Result<(), DecError> {
        // client request
        let bad_request_data = Value::Map(vec![(
            Value::from("BadDataType"),
            Value::Map(vec![(Value::from("foo"), Value::from("bar"))]),
        )]);
        let unhandled = Value::Map(vec![
            (Value::from("id"), Value::from(1)),
            (Value::from("data"), bad_request_data.clone()),
        ]);
        let unhandled_data = rmp_serde::to_vec_named(&unhandled).expect("should encode");
        let decoded: ClientRequest = rmp_serde::from_slice(&unhandled_data)?;
        let unhandled_msg = ClientRequest {
            id: 1,
            data: RequestData::Other(bad_request_data),
        };
        assert_eq!(decoded, unhandled_msg);
        Ok(())
    }

    #[test]
    fn test_metadata_map() -> Result<(), DecError> {
        let mut metadata_map = HashMap::with_capacity(4);
        metadata_map.insert(
            String::from("StringFlag"),
            MetadataValue::String(String::from("StringValue")),
        );
        metadata_map.insert(String::from("IntFlag"), MetadataValue::Int(1234));
        metadata_map.insert(String::from("BoolFlag"), MetadataValue::Bool(true));
        metadata_map.insert(String::from("FloatFlag"), MetadataValue::Float(12.5));
        let encoded = rmp_serde::to_vec_named(&metadata_map).expect("should encode");
        let decoded: HashMap<String, MetadataValue> = rmp_serde::from_slice(&encoded)?;

        assert_eq!(decoded, metadata_map);

        Ok(())
    }
}
