1use 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
27pub struct TokenManager;
29
30impl TokenManager {
31 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 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 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 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 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 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
258pub struct TokenUtils;
260
261impl TokenUtils {
262 pub fn build_standard_token_type(creator: &str, collection: &str, name: &str) -> String {
282 format!("{}::{}::{}", creator, collection, name)
283 }
284
285 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 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 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_PROTOCOL_ADDRESS,
361 LIQUIDSWAP_PROTOCOL_ADDRESS,
363 PANCAKESWAP_FACTORY_PROTOCOL_ADDRESS,
365 USDC, USDT, WORMHOLE_USDC, ];
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 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 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 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 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 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 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 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 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 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 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 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 top_tokens.sort_by(|a, b| b.volume_24h.cmp(&a.volume_24h));
689 if top_tokens.len() > 10 {
691 top_tokens.truncate(10);
692 }
693 Ok(top_tokens)
694 }
695
696 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 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 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, 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 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#[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#[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#[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#[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}