1use std::time::UNIX_EPOCH;
2use reqwest::Client;
3use serde_json::{json, Value};
4use thiserror::Error;
5
6mod api_structs;
7use api_structs::*;
8
9const BASE_URL: &str = "https://api.gopluslabs.io/api/v1";
10
11
12#[derive(Error, Debug)]
13pub enum GpError {
14 #[error("Status {0} - {1}")]
15 RequestError(u32, String),
16 #[error("Parsing failed - {0}")]
17 ParseError(String),
18}
19
20impl From<reqwest::Error> for GpError {
21 fn from(value: reqwest::Error) -> Self {
22 match value.to_string().contains("missing field") {
23 true => Self::ParseError(value.to_string()),
24 false => Self::RequestError(value.status().unwrap().as_u16().into(), value.to_string())
25 }
26
27 }
28}
29
30#[derive(Default)]
32pub struct Session {
33 inner: Client,
34 access_token: Option<String>,
35}
36
37pub enum V2ApprovalERC {
39 ERC20,
40 ERC721,
41 ERC1155
42}
43
44impl Session {
45 pub fn new() -> Self {
46 let app_key = std::env::var("GP_PUBLIC");
48 let secret_key = std::env::var("GP_SECRET");
49
50 if app_key.is_err() || secret_key.is_err(){
51 tracing::warn!("Set enviornment variables to get access code");
53 tracing::warn!(" `export GP_PUBLIC = $APP_PUBLIC_KEY$`");
54 tracing::warn!(" `export GP_PUBLIC = $APP_PRIVATE_KEY$`");
55 Self {
56 inner: Client::new(),
57 access_token: None,
58 }
59 }
60 else {
61 use sha1::{Sha1, Digest};
63 let mut hasher = Sha1::new();
64 let time: u64 = std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
65 let hash_str = format!("{}{}{}", app_key.unwrap(), time, secret_key.unwrap());
66 hasher.update(hash_str);
67 let f = hasher.finalize();
68 let str_hash = format!("{:x}", f);
69
70 Self {
71 inner: Client::new(),
72 access_token: Some(str_hash),
73 }
74 }
75
76
77 }
78
79 pub async fn supported_chains(&self) -> Result<SupportedChainsResponse, GpError> {
90 let url = format!("{BASE_URL}/supported_chains");
91 let res = self
92 .inner
93 .get(url)
94 .header("access_token", self.access_token.clone().unwrap_or("None".to_string()))
95 .send()
96 .await?
97 .error_for_status()?
98 .json::<SupportedChainsResponse>()
99 .await?;
100
101 Ok(res)
102 }
103
104 pub async fn token_risk(&self, chain_id: &str, addr: &str) -> Result<TokenResponse, anyhow::Error> {
118 let url = format!(
119 "{}/token_security/{}", BASE_URL, chain_id
120 );
121
122 Ok(self.inner.get(url)
123 .header("access_token", self.access_token.clone().unwrap_or("None".to_string()))
124 .query(&[("contract_addresses", addr)])
125 .send()
126 .await?
127 .error_for_status()?
128 .json::<TokenResponse>()
129 .await?)
130 }
131
132 pub async fn address_risk(&self, addr: &str, chain_id: Option<&str>) -> Result<AccountRiskResponse, GpError> {
153 let url = format!("{}/address_security/{}", BASE_URL, addr);
154
155 Ok(self.inner.get(url)
156 .header("access_token", self.access_token.clone().unwrap_or("None".to_string()))
157 .query(&[("chain_id", chain_id.unwrap_or("None"))])
158 .send()
159 .await?
160 .error_for_status()?
161 .json::<AccountRiskResponse>()
162 .await?)
163 }
164
165 pub async fn approval_security_v1(&self, chain_id: &str, contract_addr: &str) -> Result<V1ApprovalResponse, GpError> {
166 let url = format!("{}/approval_security/{}", BASE_URL, chain_id);
167 Ok(self.inner.get(url)
168 .header("access_token", self.access_token.clone().unwrap_or("None".to_string()))
169 .query(&[("contract_addresses", contract_addr)])
170 .send()
171 .await?
172 .error_for_status()?
173 .json::<V1ApprovalResponse>()
174 .await?)
175 }
176
177
178 pub async fn approval_security_v2(&self, erc: V2ApprovalERC, chain_id: &str, address: &str) -> Result<V2ApprovalResponse, GpError> {
179 let base_url = "https://api.gopluslabs.io/api/v2";
180 let url = match erc {
181 V2ApprovalERC::ERC20 => format!("{}/token_approval_security/{}", base_url, chain_id),
182 V2ApprovalERC::ERC721 => format!("{}/nft721_approval_security/{}", base_url, chain_id),
183 V2ApprovalERC::ERC1155 => format!("{}/nft1155_approval_security/{}", base_url, chain_id),
184 };
185
186 Ok(self.inner.get(url)
187 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
188 .query(&[("addresses", address)])
189 .send()
190 .await?
191 .error_for_status()?
192 .json::<V2ApprovalResponse>()
193 .await?)
194
195
196 }
197
198 pub async fn abi_decode(&self,
221 chain_id: &str,
222 data: &str,
223 contract_addr: Option<&str>,
224 signer: Option<&str>,
225 txn_type: Option<&str>
226 ) -> Result<AbiDecodeResponse, anyhow::Error> {
227
228 let url = format!("{}/abi/input_decode", BASE_URL);
229
230 let params = json!({
231 "chain_id": chain_id,
232 "data": data,
233 "contract_address": contract_addr,
234 "signer": signer,
235 "transaction_type": txn_type
236 });
237
238 Ok(self.inner.post(url)
239 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
240 .json(¶ms)
241 .send()
242 .await?
243 .json::<AbiDecodeResponse>()
244 .await?)
245 }
246
247 pub async fn nft_risk(&self, chain_id: &str, contract_addr: &str, token_id: Option<&str>) -> Result<NftRiskResponse, GpError> {
263 let url = format!("{}/nft_security/{}",BASE_URL, chain_id);
264
265 Ok(self.inner.get(url)
266 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
267 .query(&[("contract_addresses", contract_addr), ("token_id", token_id.unwrap_or("None"))])
268 .send()
269 .await?
270 .json::<NftRiskResponse>()
271 .await?)
272 }
273
274 pub async fn dapp_risk_by_url(&self, dapp_url: &str) -> Result<Value, anyhow::Error> {
276 tracing::warn!("The only response I've been able to get is 'DAPP NOT FOUND'");
277 let url = format!("{}/dapp_security", BASE_URL);
278
279 Ok(self.inner.get(url)
280 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
281 .query(&[("url", dapp_url)])
282 .send()
283 .await?
284 .error_for_status()?
285 .json::<Value>()
286 .await?)
287 }
288
289 pub async fn phishing_site_risk(&self, site_url: &str) -> Result<PhishingSiteResponse, GpError> {
301 let url = format!("{}/phishing_site", BASE_URL);
302
303 Ok(self.inner.get(url)
304 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
305 .query(&[("url", site_url)])
306 .send()
307 .await?
308 .error_for_status()?
309 .json::<PhishingSiteResponse>()
310 .await?)
311 }
312
313 pub async fn rug_pull_risk(&self, chain_id: &str, contract_addr: &str) -> Result<RugPullRiskResponse, GpError> {
326 let url = format!("{}/rugpull_detecting/{}", BASE_URL, chain_id);
327
328 Ok(self.inner.get(url)
329 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
330 .query(&[("contract_addresses", contract_addr)])
331 .send()
332 .await?
333 .error_for_status()?
334 .json::<RugPullRiskResponse>()
335 .await?)
336 }
337
338 #[deprecated = "Token retrieved on initialization when keys are env variables.
339 Can be used if you compute signature (method in new()/docs)."]
340 pub async fn get_access_token(&mut self, app_key: &str, signature: &str, time: u64) -> Result<(), GpError> {
366 let url = format!("{}/token", BASE_URL);
367
368 let params = json!({
369 "app_key": app_key,
370 "sign": signature,
371 "time": time,
372 });
373
374 let access_code_res = self.inner.get(url)
375 .header("access_token", self.access_token.as_ref().unwrap_or(&"None".to_string()))
376 .json(¶ms)
377 .send()
378 .await?
379 .error_for_status()?
380 .json::<AccessCodeResponse>()
381 .await?;
382
383 if access_code_res.code == 1 {
384 tracing::trace!("New access token expires in {} minutes", (access_code_res.result.as_ref().unwrap().expires_in)/60);
385 self.access_token = Some(access_code_res.result.unwrap().access_token);
386 Ok(())
387
388 } else {
389 tracing::error!("Error getting access token\nCode: {}", access_code_res.code);
390 Err(GpError::RequestError(access_code_res.code, access_code_res.message))
391 }
392
393
394
395 }
396
397}
398
399
400
401
402pub fn interpret_gp_status_code(code: u32) -> &'static str {
403 match code {
404 1 => "Complete data prepared",
405 2 => "Partial data obtained. The complete data can be requested again in about 15 seconds.",
406 2004 => "Contract address format error!",
407 2018 => "ChainID not supported",
408 2020 => "Non-contract address",
409 2021 => "No info for this contract",
410 2022 => "Non-supported chainId",
411 2026 => "dApp not found",
412 2027 => "ABI not found",
413 2028 => "The ABI not support parsing",
414 4010 => "App_key not exist",
415 4011 => "Signature expiration (the same request parameters cannot be requested more than once)",
416 4012 => "Wrong Signature",
417 4023 => "Access token not found",
418 4029 => "Request limit reached",
419 5000 => "System error",
420 5006 => "Param error!",
421 _ => "Unknown status code",
422 }
423}