1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! # Coinbase Advanced Fee API
//!
//! `fee` gives access to the Fee API and the various endpoints associated with it.
//! Currently the only endpoint available is the Transaction Summary endpoint.

use crate::signer::Signer;
use crate::utils::{CBAdvError, Result};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::fmt;

/// Pricing tier for user, determined by notional (USD) volume.
#[serde_as]
#[derive(Deserialize, Debug)]
pub struct FeeTier {
    /// Current fee teir for the user.
    pub pricing_tier: String,
    /// Lower bound (inclusive) of pricing tier in notional volume.
    #[serde_as(as = "DisplayFromStr")]
    pub usd_from: u32,
    /// Upper bound (exclusive) of pricing tier in notional volume.
    #[serde_as(as = "DisplayFromStr")]
    pub usd_to: u32,
    /// Taker fee rate, applied if the order takes liquidity.
    #[serde_as(as = "DisplayFromStr")]
    pub taker_fee_rate: f64,
    /// Maker fee rate, applied if the order creates liquidity.
    #[serde_as(as = "DisplayFromStr")]
    pub maker_fee_rate: f64,
}

/// Represents a decimal number with precision.
#[serde_as]
#[derive(Deserialize, Debug)]
pub struct MarginRate {
    /// Value of the margin rate.
    #[serde_as(as = "DisplayFromStr")]
    pub value: f64,
}

// Represents a Tax.
#[serde_as]
#[derive(Deserialize, Debug)]
pub struct Tax {
    /// Amount of tax.
    #[serde_as(as = "DisplayFromStr")]
    pub value: f64,
    /// Type of tax. Possible values: [INCLUSIVE, EXCLUSIVE]
    pub r#type: String,
}

/// Represents the transaction summary for fees received from the API.
#[derive(Deserialize, Debug)]
pub struct TransactionSummary {
    /// Total volume across assets, denoted in USD.
    pub total_volume: f64,
    /// Total fees across assets, denoted in USD.
    pub total_fees: f64,
    /// Fee tier for the summary.
    pub fee_tier: FeeTier,
    /// Margin rate for the summary.
    pub margin_rate: Option<MarginRate>,
    /// Goods and Services Tax rate.
    pub goods_and_services_tax: Option<Tax>,
    /// Advanced Trade volume (non-inclusive of Pro) across assets, denoted in USD.
    pub advanced_trade_only_volume: f64,
    /// Advanced Trade fees (non-inclusive of Pro) across assets, denoted in USD.
    pub advanced_trade_only_fees: f64,
    /// Coinbase Pro volume across assets, denoted in USD.
    pub coinbase_pro_volume: f64,
    /// Coinbase Pro fees across assets, denoted in USD.
    pub coinbase_pro_fees: f64,
}

/// Represents parameters that are optional for transaction summary API request.
#[derive(Serialize, Default, Debug)]
pub struct TransactionSummaryQuery {
    /// Start date for the summary.
    pub start_date: Option<String>,
    /// End date for the summary.
    pub end_date: Option<String>,
    /// String of the users native currency, default is USD.
    pub user_native_currency: Option<String>,
    /// Type of products to return. Valid options: SPOT or FUTURE
    pub product_type: Option<String>,
}

impl fmt::Display for TransactionSummaryQuery {
    /// Converts the object into HTTP request parameters.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut query: String = "".to_string();

        query = match &self.start_date {
            Some(v) => format!("{}&start_date={}", query, v),
            _ => query,
        };

        query = match &self.end_date {
            Some(v) => format!("{}&end_date={}", query, v),
            _ => query,
        };

        query = match &self.user_native_currency {
            Some(v) => format!("{}&user_native_currency={}", query, v),
            _ => query,
        };

        query = match &self.product_type {
            Some(v) => format!("{}&product_type={}", query, v),
            _ => query,
        };

        match query.is_empty() {
            true => write!(f, ""),
            false => write!(f, "{}", query[1..].to_string()),
        }
    }
}

/// Provides access to the Fee API for the service.
pub struct FeeAPI {
    /// Object used to sign requests made to the API.
    signer: Signer,
}

impl FeeAPI {
    /// Resource for the API.
    const RESOURCE: &str = "/api/v3/brokerage/transaction_summary";

    /// Creates a new instance of the Fee API. This grants access to product information.
    ///
    /// # Arguments
    ///
    /// * `signer` - A Signer that include the API Key & Secret along with a client to make
    /// requests.
    pub(crate) fn new(signer: Signer) -> Self {
        Self { signer }
    }

    /// Obtains fee transaction summary from the API.
    ///
    /// # Arguments
    ///
    /// * `query` - Optional paramaters used to modify the resulting scope of the
    /// summary.
    ///
    /// # Endpoint / Reference
    ///
    #[allow(rustdoc::bare_urls)]
    /// https://api.coinbase.com/api/v3/brokerage/transaction_summary
    ///
    /// <https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_gettransactionsummary>
    pub async fn get(&self, query: &TransactionSummaryQuery) -> Result<TransactionSummary> {
        match self.signer.get(Self::RESOURCE, &query.to_string()).await {
            Ok(value) => match value.json::<TransactionSummary>().await {
                Ok(resp) => Ok(resp),
                Err(_) => Err(CBAdvError::BadParse("fee summary object".to_string())),
            },
            Err(error) => Err(error),
        }
    }
}