solscan_api/
solscan.rs

1//! # The Solscan Wrapper
2//! This represent a wrapper for the SolscanAPI
3
4use reqwest::{Client, Error, StatusCode};
5use serde::de::DeserializeOwned;
6
7use crate::enums::solscan_endpoints::SolscanEndpoints;
8use crate::enums::solscan_errors::SolscanError;
9use crate::r#const::SOLSCANBASEURL;
10use crate::structs::account_info::AccountInfo;
11use crate::structs::block_result::BlockResult;
12use crate::structs::chain_info::ChainInfo;
13use crate::structs::sol_transfer::SolTransferList;
14use crate::structs::spl_transfer::SplTransfer;
15use crate::structs::token::Token;
16use crate::structs::token_holder::TokenHolders;
17use crate::structs::token_market_item::TokenMarketItem;
18use crate::structs::token_meta::TokenMeta;
19use crate::structs::transaction::Transaction;
20use crate::structs::transaction_last::TransactionLast;
21use crate::structs::transaction_list_item::TransactionListItem;
22
23/// `SolscanAPI` is a struct that contains a `String` for the SolscanURL and a `Client` which is the instance.
24///
25/// Properties:
26///
27/// A doc comment.
28/// A doc comment.
29/// A doc comment.
30/// * `base_url`: The base URL for the Solscan API.
31/// * `client`: This is the HTTP client that will be used to make requests to the Solscan API.
32pub struct SolscanAPI {
33    base_url: String,
34    client: Client,
35}
36
37
38/// Creating a default implementation for the SolscanAPI struct.
39impl Default for SolscanAPI {
40    /// It creates a new instance of the SolscanAPI struct.
41    ///
42    /// Returns:
43    ///
44    /// A new instance of the SolscanAPI struct.
45    fn default() -> Self {
46        SolscanAPI::new()
47    }
48}
49
50
51/// Implementation of the struct called SolscanAPI.
52impl SolscanAPI {
53    /// It creates a new instance of the SolscanAPI struct.
54    ///
55    /// Returns:
56    ///
57    /// A new instance of the SolscanAPI struct.
58    pub fn new() -> SolscanAPI {
59        SolscanAPI {
60            base_url: SOLSCANBASEURL.parse().unwrap(),
61            client: Client::new(),
62        }
63    }
64    /// > This function creates a new instance of the SolscanAPI struct, which is used to make
65    /// custom requests to the Solscan API (mostly useful for mockups)
66    ///
67    /// Arguments:
68    ///
69    /// * `solscan_url`: The URL of the Solscan API.
70    ///
71    /// Returns:
72    ///
73    /// A new instance of the SolscanAPI struct.
74    pub fn new_with_url(solscan_url: String) -> SolscanAPI {
75        SolscanAPI {
76            base_url: solscan_url,
77            client: Client::new(),
78        }
79    }
80
81    //region private functions
82    /// It takes a string, appends it to the base url, makes a get request, checks the status code, and
83    /// returns the text of the response.
84    /// Represents the actual GET request.
85    ///
86    /// Arguments:
87    ///
88    /// * `url_path`: The path to the API endpoint you want to call.
89    ///
90    /// Returns:
91    ///
92    /// A Result<String, SolscanError>
93    async fn fetch(&self, url_path: String) -> Result<String, SolscanError> {
94        println!("{:?}", self.base_url.to_string() + url_path.as_str());
95        match {
96            self.client.get(self.base_url.to_string() + url_path.as_str())
97                .header("User-Agent", "Mozilla/5.0")
98                .send()
99                .await
100        } {
101            Ok(response) => {
102                if response.status() == 200 {
103                    match response.text().await {
104                        Ok(text) => { Ok(text) }
105                        Err(e) => {
106                            println!("{:?}", e);
107                            Err(SolscanError::APICConversionError)
108                        }
109                    }
110                } else {
111                    println!("API-Status Code is: {:?}", response.status());
112                    Err(SolscanError::APIWrongStatusCode)
113                }
114            }
115            Err(e) => {
116                println!("{:?}", e);
117                Err(SolscanError::APIError)
118            }
119        }
120    }
121
122    /// It chooses which endpoint to use to fetch data from the Solscan API.
123    ///
124    /// Arguments:
125    ///
126    /// * `endpoint`: SolscanEndpoints - This is the endpoint you want to use.
127    /// * `url_endpoint`: The endpoint to be appended to the base url.
128    ///
129    /// Returns:
130    ///
131    /// A Result<T, SolscanError>
132    async fn solscan_fetch<T: DeserializeOwned>(&self, endpoint: SolscanEndpoints, url_endpoint: &str) -> Result<T, SolscanError> {
133        match {
134            self.fetch(
135                match endpoint {
136                    SolscanEndpoints::BlockLast => endpoint.value().to_owned() + url_endpoint,
137                    SolscanEndpoints::BlockTransactions => endpoint.value().to_owned() + url_endpoint,
138                    SolscanEndpoints::Block => endpoint.value().to_owned() + url_endpoint,
139                    SolscanEndpoints::TransactionLast => endpoint.value().to_owned() + url_endpoint,
140                    SolscanEndpoints::Transaction => endpoint.value().to_owned() + url_endpoint,
141                    SolscanEndpoints::AccountTokens => endpoint.value().to_owned() + url_endpoint,
142                    SolscanEndpoints::AccountTransaction => endpoint.value().to_owned() + url_endpoint,
143                    SolscanEndpoints::AccountStakeAccounts => endpoint.value().to_owned() + url_endpoint,
144                    SolscanEndpoints::AccountSPLTransfers => endpoint.value().to_owned() + url_endpoint,
145                    SolscanEndpoints::AccountSolTransfers => endpoint.value().to_owned() + url_endpoint,
146                    SolscanEndpoints::Account => endpoint.value().to_owned() + url_endpoint,
147                    SolscanEndpoints::TokenHolders => endpoint.value().to_owned() + url_endpoint,
148                    SolscanEndpoints::TokenMeta => endpoint.value().to_owned() + url_endpoint,
149                    SolscanEndpoints::MarketToken => endpoint.value().to_owned() + url_endpoint,
150                    SolscanEndpoints::ChainInfo => endpoint.value().to_owned() + url_endpoint,
151                    _ => { "none".to_string() }
152                }
153            ).await
154        } {
155            Ok(api_data) => {
156                match serde_json::from_str::<T>(api_data.as_str()) {
157                    Ok(api_data) => {
158                        Ok(api_data)
159                    }
160                    Err(e) => {
161                        println!("{:?}", e);
162                        Err(SolscanError::SerializeError)
163                    }
164                }
165            }
166            Err(e) => {
167                println!("{:?}", e);
168                Err(SolscanError::APIError)
169            }
170        }
171    }
172    //endregion
173
174    //region public functions
175
176    //region Ping
177    /// It checks the status of the endpoint.
178    ///
179    /// Arguments:
180    ///
181    /// * `endpoint`: The endpoint to ping. If none is provided, the base url will be used.
182    ///
183    /// Returns:
184    ///
185    /// A Result<StatusCode, Error>
186    pub async fn ping_status(&self, endpoint: Option<String>) -> Result<StatusCode, Error> {
187        Ok(self.client.get(self.base_url.to_string() + endpoint.unwrap_or_default().as_str()).header("User-Agent", "Mozilla/5.0").send().await?.status())
188    }
189    //endregion
190
191    //region Block
192    /// It gets the last block
193    ///
194    /// Arguments:
195    ///
196    /// * `limit`: The number of blocks to return.
197    ///
198    /// Returns:
199    ///
200    /// A Result<Vec<BlockResult>, SolscanError>
201    pub async fn get_block_last(&self, limit: Option<i64>) -> Result<Vec<BlockResult>, SolscanError> {
202        let mut url_endpoint: String = "".to_string();
203        if limit.is_some() {
204            url_endpoint += &*format!("?limit={}", limit.unwrap());
205        }
206        self.solscan_fetch::<Vec<BlockResult>>(SolscanEndpoints::BlockLast, url_endpoint.as_str()).await
207    }
208    /// It gets the transactions for a block.
209    ///
210    /// Arguments:
211    ///
212    /// * `block`: The block number to get transactions for.
213    /// * `offset`: The offset of the first transaction to return.
214    /// * `limit`: The number of transactions to return.
215    ///
216    /// Returns:
217    ///
218    /// A Result<Vec<TransactionLast>, SolscanError>
219    pub async fn get_block_transactions(&self, block: i64, offset: Option<i64>, limit: Option<i64>) -> Result<Vec<TransactionLast>, SolscanError> {
220        let mut url_endpoint: String = format!("?block={}", block);
221        if offset.is_some() {
222            url_endpoint += &*format!("&offset={}", offset.unwrap());
223        }
224        if limit.is_some() {
225            url_endpoint += &*format!("&limit={}", limit.unwrap());
226        }
227        self.solscan_fetch::<Vec<TransactionLast>>(SolscanEndpoints::BlockTransactions, url_endpoint.as_str()).await
228    }
229    /// It gets the block information for a given block number.
230    ///
231    /// Arguments:
232    ///
233    /// * `block`: The block number you want to query
234    ///
235    /// Returns:
236    ///
237    /// A Result<BlockResult, SolscanError>
238    pub async fn get_block_block(&self, block: i64) -> Result<BlockResult, SolscanError> {
239        let url_endpoint: String = format!("/{}", block);
240        self.solscan_fetch::<BlockResult>(SolscanEndpoints::Block, url_endpoint.as_str()).await
241    }
242    //endregion
243
244    //region Transaction
245    /// It gets the last transactions from the blockchain.
246    ///
247    /// Arguments:
248    ///
249    /// * `limit`: The number of transactions to return.
250    ///
251    /// Returns:
252    ///
253    /// A  Result<Vec<TransactionLast>, SolscanError>
254    pub async fn get_transaction_last(&self, limit: Option<i64>) -> Result<Vec<TransactionLast>, SolscanError> {
255        let mut url_endpoint: String = "".to_string();
256        if limit.is_some() {
257            url_endpoint += &*format!("?limit={}", limit.unwrap())
258        }
259        self.solscan_fetch::<Vec<TransactionLast>>(SolscanEndpoints::TransactionLast, url_endpoint.as_str()).await
260    }
261    /// It fetches a transaction from the blockchain.
262    ///
263    /// Arguments:
264    ///
265    /// * `signature`: The transaction hash
266    ///
267    /// Returns:
268    ///
269    /// A Result<Transaction, SolscanError>
270    pub async fn get_transaction(&self, signature: &str) -> Result<Transaction, SolscanError> {
271        let url_endpoint: String = format!("/{}", signature);
272        self.solscan_fetch::<Transaction>(SolscanEndpoints::Transaction, url_endpoint.as_str()).await
273    }
274    //endregion
275
276    //region Transaction
277    /// It fetches the tokens associated with an account.
278    ///
279    /// Arguments:
280    ///
281    /// * `account`: The address of the account you want to get the tokens for.
282    ///
283    /// Returns:
284    ///
285    /// A Result<Vec<Token>, SolscanError>
286    pub async fn get_account_tokens(&self, account: &str) -> Result<Vec<Token>, SolscanError> {
287        let url_endpoint: String = format!("?account={}", account);
288        self.solscan_fetch::<Vec<Token>>(SolscanEndpoints::AccountTokens, url_endpoint.as_str()).await
289    }
290    /// It gets the transactions for a given account.
291    ///
292    /// Arguments:
293    ///
294    /// * `account`: The address of the account you want to get the transactions for.
295    /// * `before_hash`: The hash of the transaction you want to start from.
296    /// * `limit`: The number of transactions to return.
297    ///
298    /// Returns:
299    ///
300    /// A Result<Vec<TransactionListItem>, SolscanError>
301    pub async fn get_account_transactions(&self, account: &str, before_hash: Option<String>, limit: Option<i64>) -> Result<Vec<TransactionListItem>, SolscanError> {
302        let mut url_endpoint: String = format!("?account={}", account);
303        if before_hash.is_some() {
304            url_endpoint += &*format!("&beforeHash={}", before_hash.unwrap())
305        }
306        if limit.is_some() {
307            url_endpoint += &*format!("&limit={}", limit.unwrap())
308        }
309        self.solscan_fetch::<Vec<TransactionListItem>>(SolscanEndpoints::AccountTransaction, url_endpoint.as_str()).await
310    }
311    /// It returns a list of accounts that have staked to the given account.
312    ///
313    /// Arguments:
314    ///
315    /// * `account`: The account address to query
316    ///
317    /// Returns:
318    ///
319    /// A Result<Vec<Token>, SolscanError>
320    pub async fn get_account_stake_accounts(&self, account: &str) -> Result<Vec<Token>, SolscanError> {
321        let url_endpoint: String = format!("?account={}", account);
322        self.solscan_fetch::<Vec<Token>>(SolscanEndpoints::AccountStakeAccounts, url_endpoint.as_str()).await
323    }
324    /// It fetches the account spl transfer data from the solscan api.
325    ///
326    /// Arguments:
327    ///
328    /// * `account`: The account address to query
329    /// * `form_time`: The start time of the query.
330    /// * `to_time`: The time to end the search at.
331    /// * `offset`: The offset of the first result to return.
332    /// * `limit`: The number of results to return.
333    ///
334    /// Returns:
335    ///
336    /// A Result<SplTransfer, SolscanError>
337    pub async fn get_account_spl_transfer(&self, account: &str, form_time: Option<u64>, to_time: Option<u64>, offset: Option<i64>, limit: Option<i64>) -> Result<SplTransfer, SolscanError> {
338        let mut url_endpoint: String = format!("?account={}", account);
339        if form_time.is_some() {
340            url_endpoint += &*format!("&form_time={}", form_time.unwrap())
341        }
342        if to_time.is_some() {
343            url_endpoint += &*format!("&to_time={}", to_time.unwrap())
344        }
345        if offset.is_some() {
346            url_endpoint += &*format!("&offset={}", offset.unwrap())
347        }
348        if limit.is_some() {
349            url_endpoint += &*format!("&limit={}", limit.unwrap())
350        }
351        self.solscan_fetch::<SplTransfer>(SolscanEndpoints::AccountSPLTransfers, url_endpoint.as_str()).await
352    }
353    /// It gets the SOL transfers for a given account.
354    ///
355    /// Arguments:
356    ///
357    /// * `account`: The account address to query
358    /// * `form_time`: The start time of the query.
359    /// * `to_time`: The timestamp of the last block you want to include in the results.
360    /// * `offset`: The offset of the first result to return.
361    /// * `limit`: The number of results to return.
362    ///
363    /// Returns:
364    ///
365    /// A Result<SolTransferList, SolscanError>
366    pub async fn get_account_sol_transfer(&self, account: &str, form_time: Option<u64>, to_time: Option<u64>, offset: Option<i64>, limit: Option<i64>) -> Result<SolTransferList, SolscanError> {
367        let mut url_endpoint: String = format!("?account={}", account);
368        if form_time.is_some() {
369            url_endpoint += &*format!("&form_time={}", form_time.unwrap())
370        }
371        if to_time.is_some() {
372            url_endpoint += &*format!("&to_time={}", to_time.unwrap())
373        }
374        if offset.is_some() {
375            url_endpoint += &*format!("&offset={}", offset.unwrap())
376        }
377        if limit.is_some() {
378            url_endpoint += &*format!("&limit={}", limit.unwrap())
379        }
380        self.solscan_fetch::<SolTransferList>(SolscanEndpoints::AccountSolTransfers, url_endpoint.as_str()).await
381    }
382    /// It fetches the account information of the given account.
383    ///
384    /// Arguments:
385    ///
386    /// * `account`: The account address to query
387    ///
388    /// Returns:
389    ///
390    /// A Result<AccountInfo, SolscanError>
391    pub async fn get_account_account(&self, account: &str) -> Result<AccountInfo, SolscanError> {
392        let url_endpoint: String = format!("/{}", account);
393        self.solscan_fetch::<AccountInfo>(SolscanEndpoints::Account, url_endpoint.as_str()).await
394    }
395    //endregion
396
397    //region Transaction
398    /// It returns a list of token holders for a given token address.
399    ///
400    /// Arguments:
401    ///
402    /// * `account`: The address of the token contract
403    /// * `offset`: The offset of the first result to return.
404    /// * `limit`: The number of results to return.
405    ///
406    /// Returns:
407    ///
408    /// A Result<TokenHolders, SolscanError>
409    pub async fn get_token_holders(&self, account: &str, offset: Option<i64>, limit: Option<i64>) -> Result<TokenHolders, SolscanError> {
410        let mut url_endpoint: String = format!("?tokenAddress={}", account);
411        if offset.is_some() {
412            url_endpoint += &*format!("&offset={}", offset.unwrap())
413        }
414        if limit.is_some() {
415            url_endpoint += &*format!("&limit={}", limit.unwrap())
416        }
417        self.solscan_fetch::<TokenHolders>(SolscanEndpoints::TokenHolders, url_endpoint.as_str()).await
418    }
419    /// It fetches the token meta data for a given token address.
420    ///
421    /// Arguments:
422    ///
423    /// * `account`: The address of the token contract
424    ///
425    /// Returns:
426    ///
427    /// A Result<TokenMeta, SolscanError>
428    pub async fn get_token_meta(&self, account: &str) -> Result<TokenMeta, SolscanError> {
429        let url_endpoint: String = format!("?tokenAddress={}", account);
430        self.solscan_fetch::<TokenMeta>(SolscanEndpoints::TokenMeta, url_endpoint.as_str()).await
431    }
432    //endregion
433
434    //region MarketToken
435    /// It fetches the token market item for the given account.
436    ///
437    /// Arguments:
438    ///
439    /// * `account`: The address of the token contract
440    ///
441    /// Returns:
442    ///
443    /// A Result<TokenMarketItem, SolscanError>
444    pub async fn get_market_token(&self, account: &str) -> Result<TokenMarketItem, SolscanError> {
445        let url_endpoint: String = format!("/{}", account);
446        self.solscan_fetch::<TokenMarketItem>(SolscanEndpoints::MarketToken, url_endpoint.as_str()).await
447    }
448    //endregion
449
450    //region ChainInfo
451    /// It gets the chain info from the Solana blockchain.
452    ///
453    /// Returns:
454    ///
455    /// A Result<ChainInfo, SolscanError>
456    pub async fn get_chain_info(&self) -> Result<ChainInfo, SolscanError> {
457        let url_endpoint: String = "/".to_string();
458        self.solscan_fetch::<ChainInfo>(SolscanEndpoints::ChainInfo, url_endpoint.as_str()).await
459    }
460    //endregion
461}
462
463#[cfg(test)]
464pub mod test_solscan_inner {
465    use crate::r#const::SOLSCANBASEURL;
466    use crate::solscan::SolscanAPI;
467
468    #[test]
469    fn test_init_baseurl() {
470        let solscan_api = SolscanAPI::new();
471        assert_eq!(solscan_api.base_url, SOLSCANBASEURL);
472    }
473}