Skip to main content

goldrush_sdk/
balances.rs

1use crate::{Error, GoldRushClient};
2use crate::models::balances::BalancesResponse;
3use reqwest::Method;
4
5/// Options for customizing balance queries.
6#[derive(Debug, Clone, Default)]
7pub struct BalancesOptions {
8    /// Quote currency for pricing (e.g., "USD", "ETH").
9    pub quote_currency: Option<String>,
10    
11    /// Whether to include NFT balances.
12    pub nft: Option<bool>,
13    
14    /// Whether to include spam tokens.
15    pub no_spam: Option<bool>,
16    
17    /// Page number for pagination (0-indexed).
18    pub page_number: Option<u32>,
19    
20    /// Number of items per page.
21    pub page_size: Option<u32>,
22}
23
24impl BalancesOptions {
25    /// Create new default options.
26    pub fn new() -> Self {
27        Self::default()
28    }
29    
30    /// Set the quote currency.
31    pub fn quote_currency<S: Into<String>>(mut self, currency: S) -> Self {
32        self.quote_currency = Some(currency.into());
33        self
34    }
35    
36    /// Include or exclude NFT balances.
37    pub fn nft(mut self, include_nft: bool) -> Self {
38        self.nft = Some(include_nft);
39        self
40    }
41    
42    /// Exclude spam tokens.
43    pub fn no_spam(mut self, exclude_spam: bool) -> Self {
44        self.no_spam = Some(exclude_spam);
45        self
46    }
47    
48    /// Set page number for pagination.
49    pub fn page_number(mut self, page: u32) -> Self {
50        self.page_number = Some(page);
51        self
52    }
53    
54    /// Set page size.
55    pub fn page_size(mut self, size: u32) -> Self {
56        self.page_size = Some(size);
57        self
58    }
59}
60
61impl GoldRushClient {
62    /// Get token balances for a wallet address.
63    ///
64    /// # Arguments
65    ///
66    /// * `chain_name` - The blockchain name (e.g., "eth-mainnet", "matic-mainnet")
67    /// * `address` - The wallet address to query
68    /// * `options` - Optional query parameters
69    ///
70    /// # Example
71    ///
72    /// ```rust,no_run
73    /// use goldrush_sdk::{GoldRushClient, BalancesOptions};
74    ///
75    /// # async fn example(client: GoldRushClient) -> Result<(), goldrush_sdk::Error> {
76    /// let options = BalancesOptions::new()
77    ///     .quote_currency("USD")
78    ///     .no_spam(true);
79    ///     
80    /// let balances = client
81    ///     .get_token_balances_for_wallet_address(
82    ///         "eth-mainnet",
83    ///         "0xfc43f5f9dd45258b3aff31bdbe6561d97e8b71de",
84    ///         Some(options)
85    ///     )
86    ///     .await?;
87    /// # Ok(())
88    /// # }
89    /// ```
90    pub async fn get_token_balances_for_wallet_address(
91        &self,
92        chain_name: &str,
93        address: &str,
94        options: Option<BalancesOptions>,
95    ) -> Result<BalancesResponse, Error> {
96        // TODO: Confirm exact endpoint path with maintainers
97        let path = format!("/v1/{}/address/{}/balances_v2/", chain_name, address);
98        
99        let mut builder = self.build_request(Method::GET, &path);
100        
101        // Add query parameters if options are provided
102        if let Some(opts) = options {
103            if let Some(currency) = opts.quote_currency {
104                builder = builder.query(&[("quote-currency", currency)]);
105            }
106            if let Some(include_nft) = opts.nft {
107                builder = builder.query(&[("nft", include_nft.to_string())]);
108            }
109            if let Some(no_spam) = opts.no_spam {
110                builder = builder.query(&[("no-spam", no_spam.to_string())]);
111            }
112            if let Some(page_num) = opts.page_number {
113                builder = builder.query(&[("page-number", page_num.to_string())]);
114            }
115            if let Some(page_sz) = opts.page_size {
116                builder = builder.query(&[("page-size", page_sz.to_string())]);
117            }
118        }
119        
120        self.send_with_retry(builder).await
121    }
122    
123    /// Get historical portfolio balances for an address.
124    ///
125    /// # Arguments
126    ///
127    /// * `chain_name` - The blockchain name
128    /// * `address` - The wallet address to query
129    /// * `options` - Optional query parameters
130    ///
131    /// # Example
132    ///
133    /// ```rust,no_run
134    /// use goldrush_sdk::GoldRushClient;
135    ///
136    /// # async fn example(client: GoldRushClient) -> Result<(), goldrush_sdk::Error> {
137    /// let portfolio = client
138    ///     .get_historical_portfolio_for_wallet_address(
139    ///         "eth-mainnet",
140    ///         "0xfc43f5f9dd45258b3aff31bdbe6561d97e8b71de",
141    ///         None
142    ///     )
143    ///     .await?;
144    /// # Ok(())
145    /// # }
146    /// ```
147    pub async fn get_historical_portfolio_for_wallet_address(
148        &self,
149        chain_name: &str,
150        address: &str,
151        options: Option<BalancesOptions>,
152    ) -> Result<BalancesResponse, Error> {
153        // TODO: Confirm exact endpoint path with maintainers
154        let path = format!("/v1/{}/address/{}/portfolio_v2/", chain_name, address);
155        
156        let mut builder = self.build_request(Method::GET, &path);
157        
158        if let Some(opts) = options {
159            if let Some(currency) = opts.quote_currency {
160                builder = builder.query(&[("quote-currency", currency)]);
161            }
162            if let Some(page_num) = opts.page_number {
163                builder = builder.query(&[("page-number", page_num.to_string())]);
164            }
165            if let Some(page_sz) = opts.page_size {
166                builder = builder.query(&[("page-size", page_sz.to_string())]);
167            }
168        }
169        
170        self.send_with_retry(builder).await
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use serde_json::json;
178
179    #[test]
180    fn test_balances_options_builder() {
181        let options = BalancesOptions::new()
182            .quote_currency("USD")
183            .nft(true)
184            .no_spam(true)
185            .page_size(50);
186            
187        assert_eq!(options.quote_currency, Some("USD".to_string()));
188        assert_eq!(options.nft, Some(true));
189        assert_eq!(options.no_spam, Some(true));
190        assert_eq!(options.page_size, Some(50));
191    }
192
193    #[test]
194    fn test_deserialize_balances_response() {
195        let json_data = json!({
196            "data": {
197                "address": "0x123",
198                "chain_id": 1,
199                "items": [{
200                    "contract_address": "0xA0b86a33E6441e6b32f6aDaa51a3FC6F1b6a3B9a",
201                    "contract_ticker_symbol": "COVALENT",
202                    "contract_name": "Covalent Query Token",
203                    "balance": "1000000000000000000",
204                    "quote_rate": 1.23,
205                    "quote": 1.23,
206                    "token_type": "cryptocurrency",
207                    "is_spam": false,
208                    "contract_decimals": 18
209                }]
210            },
211            "error": null,
212            "pagination": {
213                "has_more": false,
214                "page_number": 0,
215                "page_size": 100,
216                "total_count": 1
217            }
218        });
219
220        let response: BalancesResponse = serde_json::from_value(json_data).unwrap();
221        assert!(response.data.is_some());
222        
223        let data = response.data.unwrap();
224        assert_eq!(data.items.len(), 1);
225        assert_eq!(data.items[0].contract_ticker_symbol, Some("COVALENT".to_string()));
226        assert_eq!(data.items[0].balance, "1000000000000000000");
227    }
228}