aptos_network_sdk/
token.rs

1/// This module is used for token management utilities to create, register, and manage tokens on Aptos.
2use crate::{
3    Aptos,
4    dex::DexAggregator,
5    global::mainnet::{
6        protocol_address::{
7            ANIMESWAP_PROTOCOL_ADDRESS, AUXSWAP_PROTOCOL_ADDRESS, CELLANASWAP_PROTOCOL_ADDRESS,
8            LIQUIDSWAP_PROTOCOL_ADDRESS, PANCAKESWAP_FACTORY_PROTOCOL_ADDRESS,
9            THALA_PROTOCOL_ADDRESS,
10        },
11        sys_address::X_3,
12        token_address::{USDC, USDT, WORMHOLE_USDC},
13    },
14};
15use crate::{
16    global::mainnet::{
17        sys_address::X_1,
18        sys_module::{coin, managed_coin},
19    },
20    types::ContractCall,
21    wallet::Wallet,
22};
23use serde_json::Value;
24use serde_json::json;
25use std::sync::Arc;
26
27/// token search manager
28pub struct TokenManager;
29
30impl TokenManager {
31    /// create token
32    ///
33    /// # Params
34    /// client - aptos client
35    /// wallet - wallet
36    /// name - full name of the token
37    /// symbol - token symbol
38    /// decimals - number of decimal places
39    /// initial_supply - Initial token supply
40    ///
41    /// # Example
42    /// ```rust
43    /// use std::sync::Arc;
44    /// use crate::{Aptos, Wallet, token::TokenManager};
45    /// use crate::global::rpc::APTOS_MAINNET_URL;
46    ///
47    /// async fn example() -> Result<(), String> {
48    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
49    /// let wallet = Arc::new(Wallet::from_private_key("0x..."));
50    ///
51    /// let result = TokenManager::create_token(
52    ///     client,
53    ///     wallet,
54    ///     "Test Token",
55    ///     "TT",
56    ///     8,
57    ///     1_000_000_000,
58    /// ).await?;
59    ///  Ok(())
60    /// }
61    /// ```
62    pub async fn create_token(
63        client: Arc<Aptos>,
64        wallet: Arc<Wallet>,
65        name: &str,
66        symbol: &str,
67        decimals: u8,
68        initial_supply: u64,
69    ) -> Result<Value, String> {
70        let contract_call = ContractCall {
71            module_address: X_1.to_string(),
72            module_name: managed_coin::name.to_string(),
73            function_name: managed_coin::initialize.to_string(),
74            type_arguments: vec![],
75            arguments: vec![
76                json!(name),
77                json!(symbol),
78                json!(decimals),
79                json!(initial_supply.to_string()),
80            ],
81        };
82        crate::contract::Contract::write(client, wallet, contract_call)
83            .await
84            .map(|result| json!(result))
85    }
86
87    /// register token
88    ///
89    /// # Params
90    /// client - Aptos client
91    /// wallet - Wallet
92    /// token_type - Full token type string ("0x1::coin::CoinStore<0x123::my_token::MyToken>")
93    ///
94    /// # Example
95    /// ```rust
96    /// use std::sync::Arc;
97    /// use crate::{Aptos, Wallet, token::TokenManager};
98    /// use crate::global::rpc::APTOS_MAINNET_URL;
99    /// async fn example() -> Result<(), String> {
100    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
101    /// let wallet = Arc::new(Wallet::from_private_key("0x..."));
102    /// let token_type = "0x123::my_token::MyToken";
103    ///
104    /// let result = TokenManager::register_token(client, wallet, token_type).await?;
105    /// Ok(())
106    /// }
107    /// ```
108    pub async fn register_token(
109        client: Arc<Aptos>,
110        wallet: Arc<Wallet>,
111        token_type: &str,
112    ) -> Result<Value, String> {
113        let contract_call = ContractCall {
114            module_address: X_1.to_string(),
115            module_name: coin::name.to_string(),
116            function_name: coin::register.to_string(),
117            type_arguments: vec![token_type.to_string()],
118            arguments: vec![],
119        };
120        crate::contract::Contract::write(client, wallet, contract_call)
121            .await
122            .map(|result| json!(result))
123    }
124
125    /// mint token
126    ///
127    /// # Arguments
128    /// client - aptos client
129    /// wallet - wallet
130    /// token_type - full token type string
131    /// recipient - recipient address
132    /// amount - amount of tokens to mint
133    ///
134    /// # Example
135    /// ```rust
136    /// use std::sync::Arc;
137    /// use crate::{Aptos, Wallet, token::TokenManager};
138    /// use crate::global::rpc::APTOS_MAINNET_URL;
139    ///
140    /// async fn example() -> Result<(), String> {
141    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
142    /// let wallet = Arc::new(Wallet::from_private_key("0x..."));
143    /// let token_type = "0x123::my_token::MyToken";
144    ///
145    /// let result = TokenManager::mint_token(
146    ///     client,
147    ///     wallet,
148    ///     token_type,
149    ///     "0x789...",
150    ///     100_000_000,
151    /// ).await?;
152    /// Ok(())
153    /// }
154    /// ```
155    pub async fn mint_token(
156        client: Arc<Aptos>,
157        wallet: Arc<Wallet>,
158        token_type: &str,
159        recipient: &str,
160        amount: u64,
161    ) -> Result<Value, String> {
162        let contract_call = ContractCall {
163            module_address: X_1.to_string(),
164            module_name: managed_coin::name.to_string(),
165            function_name: managed_coin::mint.to_string(),
166            type_arguments: vec![token_type.to_string()],
167            arguments: vec![json!(recipient), json!(amount.to_string())],
168        };
169        crate::contract::Contract::write(client, wallet, contract_call)
170            .await
171            .map(|result| json!(result))
172    }
173
174    /// burn token
175    pub async fn burn_token(
176        client: Arc<Aptos>,
177        wallet: Arc<Wallet>,
178        token_type: &str,
179        amount: u64,
180    ) -> Result<Value, String> {
181        let contract_call = ContractCall {
182            module_address: X_1.to_string(),
183            module_name: managed_coin::name.to_string(),
184            function_name: managed_coin::burn.to_string(),
185            type_arguments: vec![token_type.to_string()],
186            arguments: vec![json!(amount.to_string())],
187        };
188        crate::contract::Contract::write(client, wallet, contract_call)
189            .await
190            .map(|result| json!(result))
191    }
192
193    /// get token metadata
194    ///
195    /// # Params
196    /// client - aptos client
197    /// token_type - full token type string
198    ///
199    /// # Example
200    /// ```rust
201    /// use std::sync::Arc;
202    /// use crate::{Aptos, token::TokenManager};
203    /// use crate::global::rpc::APTOS_MAINNET_URL;
204    ///
205    /// # async fn example() -> Result<(), String> {
206    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
207    /// let token_type = "0x1::aptos_coin::AptosCoin";
208    ///
209    /// let metadata = TokenManager::get_token_metadata(client, token_type).await?;
210    /// println!("Token metadata: {:?}", metadata);
211    /// Ok(())
212    /// }
213    /// ```
214    pub async fn get_token_metadata(
215        client: Arc<Aptos>,
216        token_type: &str,
217    ) -> Result<Value, String> {
218        let resource_type = format!("0x1::coin::CoinInfo<{}>", token_type);
219        client
220            .get_account_resource(X_1, &resource_type)
221            .await
222            .map(|opt| opt.map(|r| r.data).unwrap_or(Value::Null))
223            .map_err(|e| e.to_string())
224    }
225
226    /// get token balance
227    ///
228    /// # Arguments
229    /// client - aptos client instance
230    /// address - account address to check balance for
231    /// token_type - full token type string
232    ///
233    /// # Example
234    /// ```rust
235    /// use std::sync::Arc;
236    /// use crate::{Aptos, token::TokenManager};
237    /// use crate::global::rpc::APTOS_MAINNET_URL;
238    ///
239    /// async fn example() -> Result<(), String> {
240    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
241    /// let address = "0x123...";
242    /// let token_type = "0x1::aptos_coin::AptosCoin";
243    ///
244    /// let balance = TokenManager::get_token_balance(client, address, token_type).await?;
245    /// println!("Balance: {}", balance);
246    /// Ok(())
247    /// }
248    /// ```
249    pub async fn get_token_balance(
250        client: Arc<Aptos>,
251        address: &str,
252        token_type: &str,
253    ) -> Result<u64, String> {
254        client.get_token_balance(address, token_type).await
255    }
256}
257
258/// token utils
259pub struct TokenUtils;
260
261impl TokenUtils {
262    /// Building standard token types
263    ///
264    /// # Params
265    /// creator - token creator address
266    /// collection - collection name
267    /// name - token name
268    ///
269    /// # Example
270    /// ```rust
271    /// use crate::token::TokenUtils;
272    /// use crate::global::rpc::APTOS_MAINNET_URL;
273    ///
274    /// let token_type = TokenUtils::build_standard_token_type(
275    ///     "0x123",
276    ///     "my_collection",
277    ///     "MyToken"
278    /// );
279    /// assert_eq!(token_type, "0x123::my_collection::MyToken");
280    /// ```
281    pub fn build_standard_token_type(creator: &str, collection: &str, name: &str) -> String {
282        format!("{}::{}::{}", creator, collection, name)
283    }
284
285    /// parse token type
286    ///
287    /// # Arguments
288    /// token_type - Full token type string
289    ///
290    /// # Example
291    /// ```rust
292    /// use crate::token::TokenUtils;
293    ///
294    /// let token_type = "0x123::my_collection::MyToken";
295    /// if let Some((creator, collection, name)) = TokenUtils::parse_token_type(token_type) {
296    ///     println!("Creator: {}, Collection: {}, Name: {}", creator, collection, name);
297    /// }
298    /// ```
299    pub fn parse_token_type(token_type: &str) -> Option<(String, String, String)> {
300        let parts: Vec<&str> = token_type.split("::").collect();
301        if parts.len() == 3 {
302            Some((
303                parts[0].to_string(),
304                parts[1].to_string(),
305                parts[2].to_string(),
306            ))
307        } else {
308            None
309        }
310    }
311    /// verify token address format
312    ///
313    /// # Params
314    /// address - Address string to validate
315    ///
316    /// # Example
317    /// ```rust
318    /// use crate::token::TokenUtils;
319    ///
320    /// assert!(TokenUtils::is_valid_token_address("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"));
321    /// assert!(!TokenUtils::is_valid_token_address("invalid_address"));
322    /// ```
323    pub fn is_valid_token_address(address: &str) -> bool {
324        address.starts_with("0x") && address.len() == 66
325    }
326}
327
328pub struct TokenSearchManager;
329
330impl TokenSearchManager {
331    /// get token by symbol
332    ///
333    /// # Params
334    /// client - aptos client
335    /// symbol - token symbol
336    ///
337    /// # Example
338    /// ```rust
339    /// use std::sync::Arc;
340    /// use crate::{Aptos, token::TokenSearchManager};
341    /// use crate::global::rpc::APTOS_MAINNET_URL;
342    ///
343    /// async fn example() -> Result<(), String> {
344    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
345    ///
346    /// let results = TokenSearchManager::get_token_by_symbol(client, "USDC").await?;
347    /// for token in results {
348    ///     println!("Found token: {} ({})", token.symbol, token.address);
349    /// }
350    /// Ok(())
351    /// }
352    pub async fn get_token_by_symbol(
353        client: Arc<Aptos>,
354        symbol: &str,
355    ) -> Result<Vec<TokenSearchResult>, String> {
356        let mut results = Vec::new();
357        let search_symbol = symbol.to_uppercase();
358        let protocol_addresses = vec![
359            // Thala
360            THALA_PROTOCOL_ADDRESS,
361            // Liquidswap
362            LIQUIDSWAP_PROTOCOL_ADDRESS,
363            // PancakeSwap
364            PANCAKESWAP_FACTORY_PROTOCOL_ADDRESS,
365            USDC,          // USDC
366            USDT,          // USDT
367            WORMHOLE_USDC, // Wormhole USDC
368        ];
369        for address in protocol_addresses {
370            if let Ok(modules) = client.get_account_module_vec(address).await {
371                for module in modules {
372                    if let Some(abi) = module.abi {
373                        if let Some(abi_obj) = abi.as_object() {
374                            if let Some(token_info) =
375                                Self::get_token_info_from_abi(abi_obj, address)
376                            {
377                                if token_info.symbol.to_uppercase().contains(&search_symbol) {
378                                    results.push(token_info);
379                                }
380                            }
381                        }
382                    }
383                }
384            }
385        }
386        if let Ok(coin_infos) =
387            Self::get_coin_infos_by_symbol(Arc::clone(&client), &search_symbol).await
388        {
389            results.extend(coin_infos);
390        }
391        if let Ok(pool_tokens) =
392            Self::search_tokens_from_pools(Arc::clone(&client), &search_symbol).await
393        {
394            for token in pool_tokens {
395                if !results.iter().any(|r| r.address == token.address) {
396                    results.push(token);
397                }
398            }
399        }
400        results.sort_by(|a, b| a.symbol.cmp(&b.symbol));
401        results.dedup_by(|a, b| a.address == b.address);
402        Ok(results)
403    }
404
405    /// get_token_info_from_abi
406    fn get_token_info_from_abi(
407        abi: &serde_json::Map<String, Value>,
408        module_address: &str,
409    ) -> Option<TokenSearchResult> {
410        if let Some(structs) = abi.get("structs").and_then(|v| v.as_array()) {
411            for struct_info in structs {
412                if let Some(name) = struct_info.get("name").and_then(|v| v.as_str()) {
413                    if name.contains("CoinInfo") || name.contains("Token") {
414                        if let (Some(symbol), Some(name_val), Some(decimals)) = (
415                            Self::get_string_field(struct_info, "symbol"),
416                            Self::get_string_field(struct_info, "name"),
417                            Self::get_u64_field(struct_info, "decimals"),
418                        ) {
419                            let module_name = abi
420                                .get("name")
421                                .and_then(|v| v.as_str())
422                                .unwrap_or("unknown");
423                            let address =
424                                format!("{}::{}::{}", module_address, module_name, symbol);
425                            return Some(TokenSearchResult {
426                                symbol: symbol.to_string(),
427                                address,
428                                name: name_val.to_string(),
429                                decimals: decimals as u8,
430                                verified: Self::is_verified_token(&symbol),
431                            });
432                        }
433                    }
434                }
435            }
436        }
437        None
438    }
439
440    /// get string field from struct info
441    fn get_string_field(struct_info: &Value, field_name: &str) -> Option<String> {
442        if let Some(fields) = struct_info.get("fields").and_then(|v| v.as_array()) {
443            for field in fields {
444                if let (Some(name), Some(value)) = (
445                    field.get("name").and_then(|v| v.as_str()),
446                    field.get("value").and_then(|v| v.as_str()),
447                ) {
448                    if name == field_name {
449                        return Some(value.to_string());
450                    }
451                }
452            }
453        }
454        None
455    }
456
457    /// get u64 field
458    fn get_u64_field(struct_info: &Value, field_name: &str) -> Option<u64> {
459        if let Some(fields) = struct_info.get("fields").and_then(|v| v.as_array()) {
460            for field in fields {
461                if let (Some(name), Some(value)) = (
462                    field.get("name").and_then(|v| v.as_str()),
463                    field.get("value").and_then(|v| v.as_u64()),
464                ) {
465                    if name == field_name {
466                        return Some(value);
467                    }
468                }
469            }
470        }
471        None
472    }
473
474    /// get coin infos by symbol
475    async fn get_coin_infos_by_symbol(
476        client: Arc<Aptos>,
477        symbol: &str,
478    ) -> Result<Vec<TokenSearchResult>, String> {
479        let mut results = Vec::new();
480        let known_accounts = vec![X_1, X_3];
481        for account in known_accounts {
482            if let Ok(resources) = client.get_account_resource_vec(account).await {
483                for resource in resources {
484                    if resource.r#type.starts_with("0x1::coin::CoinInfo<") {
485                        if let Some(token_info) =
486                            Self::get_token_info_from_resource(&resource, symbol).await
487                        {
488                            results.push(token_info);
489                        }
490                    }
491                }
492            }
493        }
494        Ok(results)
495    }
496
497    /// get token info from resource
498    async fn get_token_info_from_resource(
499        resource: &crate::types::Resource,
500        search_symbol: &str,
501    ) -> Option<TokenSearchResult> {
502        if let Value::Object(data) = &resource.data {
503            if let (Some(symbol_value), Some(name_value), Some(decimals_value)) =
504                (data.get("symbol"), data.get("name"), data.get("decimals"))
505            {
506                let symbol = symbol_value.as_str().unwrap_or("").to_string();
507                let name = name_value.as_str().unwrap_or("").to_string();
508                let decimals = decimals_value.as_u64().unwrap_or(0) as u8;
509                if symbol
510                    .to_uppercase()
511                    .contains(&search_symbol.to_uppercase())
512                {
513                    let token_address = resource
514                        .r#type
515                        .trim_start_matches("0x1::coin::CoinInfo<")
516                        .trim_end_matches('>')
517                        .to_string();
518                    let symbol_clone = symbol.clone();
519                    return Some(TokenSearchResult {
520                        symbol: symbol_clone,
521                        address: token_address,
522                        name,
523                        decimals,
524                        verified: Self::is_verified_token(&symbol),
525                    });
526                }
527            }
528        }
529        None
530    }
531
532    /// search tokens from pools
533    async fn search_tokens_from_pools(
534        client: Arc<Aptos>,
535        search_symbol: &str,
536    ) -> Result<Vec<TokenSearchResult>, String> {
537        let mut results = Vec::new();
538        // Check the liquidity pools of major DEXs
539        let dex_addresses = vec![
540            LIQUIDSWAP_PROTOCOL_ADDRESS,
541            THALA_PROTOCOL_ADDRESS,
542            PANCAKESWAP_FACTORY_PROTOCOL_ADDRESS,
543            ANIMESWAP_PROTOCOL_ADDRESS,
544            AUXSWAP_PROTOCOL_ADDRESS,
545            CELLANASWAP_PROTOCOL_ADDRESS,
546        ];
547        for dex_address in dex_addresses {
548            if let Ok(resources) = client.get_account_resource_vec(dex_address).await {
549                for resource in resources {
550                    if resource.r#type.contains("::liquidity_pool::")
551                        || resource.r#type.contains("::Pool<")
552                        || resource.r#type.contains("::LiquidityPool<")
553                    {
554                        let token_types = Self::get_token_types_from_pool(&resource.r#type);
555                        for token_type in token_types {
556                            if let Some(token_info) = Self::get_token_info_from_type(
557                                Arc::clone(&client),
558                                &token_type,
559                                search_symbol,
560                            )
561                            .await
562                            {
563                                results.push(token_info);
564                            }
565                        }
566                    }
567                }
568            }
569        }
570        Ok(results)
571    }
572
573    /// get token types from pool
574    fn get_token_types_from_pool(pool_type: &str) -> Vec<String> {
575        let mut token_types = Vec::new();
576        if let Some(start_idx) = pool_type.find('<') {
577            if let Some(end_idx) = pool_type.rfind('>') {
578                let type_args = &pool_type[start_idx + 1..end_idx];
579                let types: Vec<&str> = type_args.split(',').map(|s| s.trim()).collect();
580                for token_type in types {
581                    token_types.push(token_type.to_string());
582                }
583            }
584        }
585        token_types
586    }
587
588    /// get token info from type
589    async fn get_token_info_from_type(
590        client: Arc<Aptos>,
591        token_type: &str,
592        search_symbol: &str,
593    ) -> Option<TokenSearchResult> {
594        let parts: Vec<&str> = token_type.split("::").collect();
595        if parts.len() >= 3 {
596            let address = parts[0];
597            let module = parts[1];
598            let token_name = parts[2];
599            if token_name
600                .to_uppercase()
601                .contains(&search_symbol.to_uppercase())
602            {
603                if let Ok(metadata) =
604                    DexAggregator::get_token_metadata(Arc::clone(&client), token_type).await
605                {
606                    return Some(TokenSearchResult {
607                        symbol: token_name.to_string(),
608                        address: token_type.to_string(),
609                        name: metadata.name,
610                        decimals: metadata.decimals,
611                        verified: Self::is_verified_token(token_name),
612                    });
613                }
614                return Some(TokenSearchResult {
615                    symbol: token_name.to_string(),
616                    address: token_type.to_string(),
617                    name: format!("{} Token", token_name),
618                    decimals: 8,
619                    verified: Self::is_verified_token(token_name),
620                });
621            }
622        }
623        None
624    }
625
626    /// is verified token
627    fn is_verified_token(symbol: &str) -> bool {
628        let verified_tokens = vec!["APT", "USDC", "USDT", "THL", "CAKE", "CELL"];
629        verified_tokens.contains(&symbol.to_uppercase().as_str())
630    }
631
632    /// get top token vec
633    ///
634    /// # Params
635    /// client - aptos client
636    ///
637    /// # Example
638    /// ```rust
639    /// use std::sync::Arc;
640    /// use crate::{Aptos, token::TokenSearchManager};
641    /// use crate::global::rpc::APTOS_MAINNET_URL;
642    ///
643    /// async fn example() -> Result<(), String> {
644    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
645    ///
646    /// let top_tokens = TokenSearchManager::get_top_token_vec(client).await?;
647    /// for token in top_tokens {
648    ///     println!("{}: ${} (24h volume: {})", token.symbol, token.price, token.volume_24h);
649    /// }
650    /// Ok(())
651    /// }
652    /// ```
653    pub async fn get_top_token_vec(client: Arc<Aptos>) -> Result<Vec<TopToken>, String> {
654        let mut top_tokens = Vec::new();
655        let base_token = "0x1::aptos_coin::AptosCoin";
656        if let Ok(resources) = client
657            .get_account_resource_vec(LIQUIDSWAP_PROTOCOL_ADDRESS)
658            .await
659        {
660            for resource in resources {
661                if resource.r#type.contains("::liquidity_pool::LiquidityPool<")
662                    && resource.r#type.contains(base_token)
663                {
664                    if let Some(token_info) =
665                        Self::get_token_from_pool_resource(&resource, base_token).await
666                    {
667                        let price =
668                            Self::estimate_token_price(Arc::clone(&client), &token_info.address)
669                                .await
670                                .unwrap_or(0.0);
671                        let volume =
672                            Self::estimate_volume(Arc::clone(&client), &token_info.address)
673                                .await
674                                .unwrap_or(0);
675                        top_tokens.push(TopToken {
676                            symbol: token_info.symbol,
677                            address: token_info.address,
678                            name: token_info.name,
679                            price,
680                            volume_24h: volume,
681                            change_24h: 0.0,
682                        });
683                    }
684                }
685            }
686        }
687        // sort by transaction volume
688        top_tokens.sort_by(|a, b| b.volume_24h.cmp(&a.volume_24h));
689        // limit 10
690        if top_tokens.len() > 10 {
691            top_tokens.truncate(10);
692        }
693        Ok(top_tokens)
694    }
695
696    /// get token from pool resource
697    async fn get_token_from_pool_resource(
698        resource: &crate::types::Resource,
699        base_token: &str,
700    ) -> Option<TokenSearchResult> {
701        let token_types = Self::get_token_types_from_pool(&resource.r#type);
702        for token_type in token_types {
703            if token_type != base_token {
704                let parts: Vec<&str> = token_type.split("::").collect();
705                if parts.len() >= 3 {
706                    let symbol = parts[2].to_string();
707                    return Some(TokenSearchResult {
708                        symbol: symbol.clone(),
709                        address: token_type,
710                        name: format!("{} Token", symbol),
711                        decimals: 8,
712                        verified: Self::is_verified_token(&symbol),
713                    });
714                }
715            }
716        }
717        None
718    }
719
720    /// estimate token price
721    async fn estimate_token_price(
722        client: Arc<Aptos>,
723        token_address: &str,
724    ) -> Result<f64, String> {
725        let base_token = "0x1::aptos_coin::AptosCoin";
726        DexAggregator::get_token_price(client, token_address)
727            .await
728            .map(|prices| prices.first().map(|p| p.price).unwrap_or(0.0))
729    }
730
731    /// estimate volume
732    async fn estimate_volume(client: Arc<Aptos>, token_address: &str) -> Result<u64, String> {
733        let volume = match token_address {
734            "0x1::aptos_coin::AptosCoin" => 5_000_000_000, // apt
735            addr if addr.contains("usd") || addr.contains("stable") => 2_000_000_000,
736            addr if addr.contains("wormhole") => 1_000_000_000,
737            _ => 500_000_000,
738        };
739        Ok(volume)
740    }
741
742    /// get token trading pairs
743    ///
744    /// # Params
745    /// client - aptos client
746    /// token_address - token address
747    ///
748    /// # Example
749    /// ```rust
750    /// use std::sync::Arc;
751    /// use crate::{Aptos, token::TokenSearchManager};
752    /// use crate::global::rpc::APTOS_MAINNET_URL;
753    ///
754    /// async fn example() -> Result<(), String> {
755    /// let client = Arc::new(Aptos::new(APTOS_MAINNET_URL));
756    /// let token_address = "0x1::aptos_coin::AptosCoin";
757    ///
758    /// let pairs = TokenSearchManager::get_token_trading_pairs(client, token_address).await?;
759    /// for pair in pairs {
760    ///     println!("{} - {} on {:?}", pair.token_a, pair.token_b, pair.dexes);
761    /// }
762    /// Ok(())
763    /// }
764    /// ```
765    pub async fn get_token_trading_pairs(
766        client: Arc<Aptos>,
767        token_address: &str,
768    ) -> Result<Vec<TradePair>, String> {
769        DexAggregator::find_token_liquidity_pools(client, token_address)
770            .await
771            .map(|pools| {
772                pools
773                    .into_iter()
774                    .map(|pool| TradePair {
775                        token_a: pool.token_a,
776                        token_b: pool.token_b,
777                        dexes: vec![pool.dex],
778                        total_liquidity: pool.liquidity,
779                    })
780                    .collect()
781            })
782    }
783}
784
785/// token search result
786#[derive(Debug, Clone)]
787pub struct TokenSearchResult {
788    pub symbol: String,
789    pub address: String,
790    pub name: String,
791    pub decimals: u8,
792    pub verified: bool,
793}
794
795/// top token
796#[derive(Debug, Clone)]
797pub struct TopToken {
798    pub symbol: String,
799    pub address: String,
800    pub name: String,
801    pub price: f64,
802    pub volume_24h: u64,
803    pub change_24h: f64,
804}
805
806/// new token
807#[derive(Debug, Clone)]
808pub struct NewToken {
809    pub symbol: String,
810    pub address: String,
811    pub name: String,
812    pub launch_time: u64,
813    pub initial_liquidity: u64,
814}
815
816/// trade pair
817#[derive(Debug, Clone)]
818pub struct TradePair {
819    pub token_a: String,
820    pub token_b: String,
821    pub dexes: Vec<String>,
822    pub total_liquidity: u64,
823}