1use crate::{
2 AptosClient,
3 types::{ContractCall, EntryFunctionPayload},
4 wallet::Wallet,
5};
6use aptos_network_tool::{address::address_to_bytes, signature::serialize_transaction_and_sign};
7use futures::future::join_all;
8use serde::{Deserialize, Serialize};
9use serde_json::{Value, json};
10use std::{
11 collections::HashMap,
12 sync::Arc,
13 time::{SystemTime, UNIX_EPOCH},
14};
15use tokio::sync::Semaphore;
16
17pub struct Trade;
18
19impl Trade {
20 pub async fn create_transfer_tx(
22 client: Arc<AptosClient>,
23 sender: Arc<Wallet>,
24 recipient: &str,
25 amount: u64,
26 sequence_number: Option<u64>,
27 expiration_secs: u64,
28 max_gas_amount: u64,
29 gas_unit_price: u64,
30 ) -> Result<Value, String> {
31 let sequence_number = match sequence_number {
32 Some(seq) => seq,
33 None => {
34 let account_info = client
35 .get_account_info(&sender.address().unwrap())
36 .await
37 .unwrap();
38 account_info.sequence_number.parse().unwrap()
39 }
40 };
41 let current_timestamp = SystemTime::now()
42 .duration_since(UNIX_EPOCH)
43 .unwrap()
44 .as_secs();
45 let expiration_timestamp = current_timestamp + expiration_secs;
46 let payload = json!({
48 "type": "entry_function_payload",
49 "function": "0x1::coin::transfer",
50 "type_arguments": ["0x1::aptos_coin::AptosCoin"],
51 "arguments": [recipient, amount.to_string()]
52 });
53 let raw_txn = json!({
55 "sender": sender.address(),
56 "sequence_number": sequence_number.to_string(),
57 "max_gas_amount": max_gas_amount.to_string(),
58 "gas_unit_price": gas_unit_price.to_string(),
59 "expiration_timestamp_secs": expiration_timestamp.to_string(),
60 "payload": payload
61 });
62 Ok(raw_txn)
63 }
64
65 pub async fn create_token_transfer_tx(
67 client: Arc<AptosClient>,
68 sender: Wallet,
69 recipient: &str,
70 token_type: &str,
71 amount: u64,
72 sequence_number: Option<u64>,
73 expiration_secs: u64,
74 max_gas_amount: u64,
75 gas_unit_price: u64,
76 ) -> Result<Value, String> {
77 let chain_id = client.get_chain_info().await.unwrap().chain_id;
78 let sequence_number = match sequence_number {
79 Some(seq) => seq,
80 None => {
81 client
82 .get_account_sequence_number(&sender.address()?)
83 .await?
84 }
85 };
86 let current_timestamp = SystemTime::now()
87 .duration_since(UNIX_EPOCH)
88 .unwrap()
89 .as_secs();
90 let expiration_timestamp = current_timestamp + expiration_secs;
91 let payload = json!({
93 "type": "entry_function_payload",
94 "function": "0x1::coin::transfer",
95 "type_arguments": [token_type],
96 "arguments": [recipient, amount.to_string()]
97 });
98 let raw_txn = json!({
100 "sender": sender.address().unwrap(),
101 "sequence_number": sequence_number.to_string(),
102 "max_gas_amount": max_gas_amount.to_string(),
103 "gas_unit_price": gas_unit_price.to_string(),
104 "expiration_timestamp_secs": expiration_timestamp.to_string(),
105 "payload": payload,
106 "chain_id": chain_id
107 });
108 Ok(raw_txn)
109 }
110
111 pub async fn create_sign_submit_transfer_tx(
113 client: Arc<AptosClient>,
114 wallet: Arc<Wallet>,
115 recipient: &str,
116 amount: u64,
117 sequence_number: Option<u64>,
118 expiration_secs: u64,
119 max_gas_amount: u64,
120 gas_unit_price: u64,
121 ) -> Result<String, String> {
122 let raw_txn = Trade::create_transfer_tx(
124 Arc::clone(&client),
125 Arc::clone(&wallet),
126 recipient,
127 amount,
128 sequence_number,
129 expiration_secs,
130 max_gas_amount,
131 gas_unit_price,
132 )
133 .await
134 .unwrap();
135 let message_to_sign = serialize_transaction_and_sign(&raw_txn)?;
137 match wallet.sign(&message_to_sign) {
139 Ok(signature_bytes) => {
140 match Trade::create_signed_transaction_tx(
142 Arc::clone(&wallet),
143 raw_txn,
144 signature_bytes,
145 ) {
146 Ok(signed_txn) => {
147 match client.submit_transaction(&signed_txn).await {
149 Ok(result) => {
150 return Ok(result.hash);
151 }
152 Err(e) => return Err(format!("submit transaction error: {:?}", e)),
153 }
154 }
155 Err(e) => return Err(format!("build signed transaction error: {:?}", e)),
156 }
157 }
158 Err(e) => {
159 return Err(format!("wallet sign error:{:?}", e).to_string());
160 }
161 }
162 }
163
164 pub async fn create_call_contract_tx(
166 client: Arc<AptosClient>,
167 sender: Arc<Wallet>,
168 sequence_number: Option<u64>,
169 expiration_secs: u64,
170 max_gas_amount: u64,
171 gas_unit_price: u64,
172 payload: EntryFunctionPayload,
173 ) -> Result<Value, String> {
174 let sequence_number = match sequence_number {
175 Some(seq) => seq,
176 None => client
177 .get_account_sequence_number(&sender.address().unwrap())
178 .await
179 .unwrap(),
180 };
181 let chain_id = client.get_chain_info().await.unwrap().chain_id;
182 let current_timestamp = std::time::SystemTime::now()
184 .duration_since(std::time::UNIX_EPOCH)
185 .unwrap()
186 .as_secs();
187 let expiration_timestamp = current_timestamp + expiration_secs;
189 let raw_txn = json!({
191 "sender": sender.address()?,
192 "sequence_number": sequence_number.to_string(),
193 "max_gas_amount": max_gas_amount.to_string(),
194 "gas_unit_price": gas_unit_price.to_string(),
195 "expiration_timestamp_secs": expiration_timestamp.to_string(),
196 "payload": payload,
197 "chain_id": chain_id
198 });
199 Ok(raw_txn)
200 }
201
202 pub async fn create_customize_call_contract_tx(
204 client: Arc<AptosClient>,
205 module_address: &str,
206 module_name: &str,
207 function_name: &str,
208 type_arguments: Vec<String>,
209 arguments: Vec<Value>,
210 sender: Arc<Wallet>,
211 sequence_number: Option<u64>,
212 expiration_secs: u64,
213 max_gas_amount: u64,
214 gas_unit_price: u64,
215 ) -> Result<Value, String> {
216 let function_str = format!("{}::{}::{}", module_address, module_name, function_name);
217 let function_vec = function_str.as_bytes().to_vec();
218 let mut type_args: Vec<Vec<u8>> = Vec::new();
219 type_arguments
220 .iter()
221 .for_each(|s| type_args.push(s.as_bytes().to_vec()));
222 let mut args: Vec<Vec<u8>> = Vec::new();
223 arguments
224 .iter()
225 .for_each(|s| args.push(s.as_str().unwrap().to_string().as_bytes().to_vec()));
226 let payload = EntryFunctionPayload {
227 module_address: address_to_bytes(module_address).unwrap().to_vec(),
228 module_name: address_to_bytes(module_name).unwrap().to_vec(),
229 function_name: function_vec,
230 type_arguments: type_args,
231 arguments: args,
232 };
233 Trade::create_call_contract_tx(
234 client,
235 sender,
236 sequence_number,
237 expiration_secs,
238 max_gas_amount,
239 gas_unit_price,
240 payload,
241 )
242 .await
243 }
244
245 pub fn create_signed_transaction_tx(
247 wallet: Arc<Wallet>,
248 raw_txn: Value,
249 signature: Vec<u8>,
250 ) -> Result<Value, String> {
251 let public_key_hex = wallet
252 .public_key_hex()
253 .map_err(|e| format!("get public key hex: {}", e))?;
254 Ok(json!({
255 "transaction": raw_txn,
256 "signature": {
257 "type": "ed25519_signature",
258 "public_key": public_key_hex,
259 "signature": hex::encode(signature)
260 }
261 }))
262 }
263
264 pub async fn get_address_transactions(
293 client: Arc<AptosClient>,
294 address: &str,
295 query: TransactionQuery,
296 ) -> Result<Vec<Transaction>, String> {
297 client
298 .get_account_transaction_vec(address, query.limit, query.start)
299 .await
300 }
301
302 pub async fn get_transactions_involving_both_addresses(
337 client: Arc<AptosClient>,
338 address_a: &str,
339 address_b: &str,
340 limit: Option<u64>,
341 start: Option<u64>,
342 ) -> Result<Vec<Transaction>, String> {
343 let query = TransactionQuery { start, limit };
344 let transactions = Self::get_address_transactions(client, address_a, query).await?;
345 let filtered_transactions: Vec<Transaction> = transactions
346 .into_iter()
347 .filter(|txn| Self::transaction_involves_address(txn, address_b))
348 .collect();
349 Ok(filtered_transactions)
350 }
351
352 pub async fn get_transactions_by_recipient_sender(
396 client: Arc<AptosClient>,
397 address_a: &str, address_b: &str, limit: Option<u64>,
400 start: Option<u64>,
401 ) -> Result<Vec<Transaction>, String> {
402 let query = TransactionQuery { start, limit };
403 let transactions =
404 Self::get_address_transactions(Arc::clone(&client), address_b, query).await?;
405 let filtered_transactions: Vec<Transaction> = transactions
406 .into_iter()
407 .filter(|txn| Self::is_transfer_from_to(txn, address_b, address_a))
408 .collect();
409 Ok(filtered_transactions)
410 }
411
412 fn transaction_involves_address(transaction: &Transaction, address: &str) -> bool {
414 match &transaction.transaction_type {
415 TransactionType::UserTransaction(user_txn) => {
416 if user_txn.sender == address {
418 return true;
419 }
420 Self::payload_contains_address(&user_txn.payload, address)
422 }
423 TransactionType::PendingTransaction(pending_txn) => {
424 pending_txn.sender == address
425 || Self::payload_contains_address(&pending_txn.payload, address)
426 }
427 _ => false,
428 }
429 }
430
431 fn is_transfer_from_to(
433 transaction: &Transaction,
434 from_address: &str,
435 to_address: &str,
436 ) -> bool {
437 match &transaction.transaction_type {
438 TransactionType::UserTransaction(user_txn) => {
439 if user_txn.sender != from_address {
440 return false;
441 }
442 Self::is_transfer_to_address(&user_txn.payload, to_address)
443 }
444 TransactionType::PendingTransaction(pending_txn) => {
445 if pending_txn.sender != from_address {
446 return false;
447 }
448 Self::is_transfer_to_address(&pending_txn.payload, to_address)
449 }
450 _ => false,
451 }
452 }
453
454 fn payload_contains_address(payload: &Payload, address: &str) -> bool {
456 for arg in &payload.arguments {
457 if let Some(arg_str) = arg.as_str() {
458 if arg_str == address {
459 return true;
460 }
461 }
462 }
463 false
464 }
465
466 fn is_transfer_to_address(payload: &Payload, recipient_address: &str) -> bool {
468 if payload.function.ends_with("::coin::transfer") {
469 if let Some(first_arg) = payload.arguments.first() {
470 if let Some(recipient) = first_arg.as_str() {
471 return recipient == recipient_address;
472 }
473 }
474 }
475 false
476 }
477
478 pub fn get_user_transaction(transaction: &Transaction) -> Option<&UserTransaction> {
480 match &transaction.transaction_type {
481 TransactionType::UserTransaction(user_txn) => Some(user_txn),
482 _ => None,
483 }
484 }
485
486 pub fn get_transfer_info(transaction: &Transaction) -> Option<TransferInfo> {
488 let user_txn = Self::get_user_transaction(transaction)?;
489 if user_txn.payload.function.ends_with("::coin::transfer") {
490 if user_txn.payload.arguments.len() >= 2 {
491 let recipient = user_txn.payload.arguments[0].as_str()?.to_string();
492 let amount = user_txn.payload.arguments[1].as_str()?.parse().ok()?;
493 let token_type = if !user_txn.payload.type_arguments.is_empty() {
495 user_txn.payload.type_arguments[0].clone()
496 } else {
497 "0x1::aptos_coin::AptosCoin".to_string()
498 };
499 Some(TransferInfo {
500 from: user_txn.sender.clone(),
501 to: recipient,
502 amount,
503 token_type,
504 })
505 } else {
506 None
507 }
508 } else {
509 None
510 }
511 }
512
513 pub fn get_events_by_type<'a>(
515 transaction: &'a Transaction,
516 event_type: &str,
517 ) -> Vec<&'a Event> {
518 transaction
519 .events
520 .iter()
521 .filter(|event| event.r#type.contains(event_type))
522 .collect()
523 }
524
525 pub fn analyze_resource_changes(transaction: &Transaction) -> ResourceChanges {
527 let mut changes = ResourceChanges::default();
528 for change in &transaction.changes {
529 match change.change_type.as_str() {
530 "write_resource" => {
531 if let Some(_data) = &change.data {
532 changes.resources_modified += 1;
533 }
534 }
535 "write_table_item" => {
536 changes.table_items_modified += 1;
537 }
538 "delete_resource" => {
539 changes.resources_deleted += 1;
540 }
541 "delete_table_item" => {
542 changes.table_items_deleted += 1;
543 }
544 _ => {}
545 }
546 }
547 changes
548 }
549}
550
551pub struct BatchTradeHandle;
553
554impl BatchTradeHandle {
555 pub async fn process_batch(
557 client: Arc<AptosClient>,
558 wallet: Arc<Wallet>,
559 calls: Vec<ContractCall>,
560 concurrency: usize,
561 ) -> Result<Vec<Value>, String> {
562 let semaphore = Arc::new(Semaphore::new(concurrency));
563 let mut tasks = Vec::new();
564 for call in calls {
565 let client_clone = Arc::clone(&client);
566 let wallet_clone = Arc::clone(&wallet);
567 let semaphore_clone = Arc::clone(&semaphore);
568
569 let task = async move {
570 let _permit = semaphore_clone.acquire().await.map_err(|e| e.to_string())?;
571 match crate::contract::Contract::write(client_clone, wallet_clone, call).await {
572 Ok(result) => Ok(json!(result)),
573 Err(e) => Err(e),
574 }
575 };
576 tasks.push(task);
577 }
578 let results = join_all(tasks).await;
579 let mut final_results = Vec::new();
580 for result in results {
581 match result {
582 Ok(value) => final_results.push(value),
583 Err(e) => final_results.push(json!({
584 "success": false,
585 "error": e
586 })),
587 }
588 }
589 Ok(final_results)
590 }
591
592 pub async fn batch_get_resources(
594 client: Arc<AptosClient>,
595 addresses: Vec<String>,
596 resource_types: Vec<&str>,
597 ) -> Result<HashMap<String, HashMap<String, Option<Value>>>, String> {
598 let mut all_results = HashMap::new();
599 for address in addresses {
600 match crate::contract::Contract::batch_get_resources(
601 Arc::clone(&client),
602 &address,
603 resource_types.clone(),
604 )
605 .await
606 {
607 Ok(resources) => {
608 all_results.insert(address, resources);
609 }
610 Err(e) => {
611 eprintln!("Failed to get resources for address: {}", e);
612 }
613 }
614 }
615 Ok(all_results)
616 }
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct Transaction {
623 pub version: String,
625 pub hash: String,
627 pub state_change_hash: String,
629 pub event_root_hash: String,
631 pub state_checkpoint_hash: Option<String>,
633 pub gas_used: String,
635 pub success: bool,
637 pub vm_status: String,
639 pub accumulator_root_hash: String,
641 pub changes: Vec<WriteSetChange>,
643 pub events: Vec<Event>,
645 pub timestamp: String,
647 pub max_gas_amount: String,
649 #[serde(flatten)]
652 pub transaction_type: TransactionType,
653}
654
655#[derive(Debug, Clone, Serialize, Deserialize)]
656#[serde(tag = "type")]
657pub enum TransactionType {
658 #[serde(rename = "pending_transaction")]
659 PendingTransaction(PendingTransaction),
660 #[serde(rename = "user_transaction")]
661 UserTransaction(UserTransaction),
662 #[serde(rename = "genesis_transaction")]
663 GenesisTransaction(GenesisTransaction),
664 #[serde(rename = "block_metadata_transaction")]
665 BlockMetadataTransaction(BlockMetadataTransaction),
666 #[serde(rename = "state_checkpoint_transaction")]
667 StateCheckpointTransaction(StateCheckpointTransaction),
668}
669
670#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct PendingTransaction {
672 pub hash: String,
673 pub sender: String,
674 pub sequence_number: String,
675 pub max_gas_amount: String,
676 pub gas_unit_price: String,
677 pub expiration_timestamp_secs: String,
678 pub payload: Payload,
679 pub signature: Option<Signature>,
680}
681
682#[derive(Debug, Clone, Serialize, Deserialize)]
683pub struct UserTransaction {
684 pub sender: String,
685 pub sequence_number: String,
686 pub max_gas_amount: String,
687 pub gas_unit_price: String,
688 pub expiration_timestamp_secs: String,
689 pub payload: Payload,
690 pub signature: Signature,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
694pub struct GenesisTransaction {
695 pub payload: Payload,
696 pub events: Vec<Event>,
697}
698
699#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct BlockMetadataTransaction {
701 pub id: String,
702 pub round: String,
703 pub previous_block_votes_bitvec: Vec<u8>,
704 pub proposer: String,
705 pub timestamp: String,
706 pub events: Vec<Event>,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
710pub struct StateCheckpointTransaction {
711 pub timestamp: String,
712 pub version: String,
713 pub hash: String,
714 pub state_change_hash: String,
715 pub event_root_hash: String,
716 pub state_checkpoint_hash: Option<String>,
717 pub gas_used: String,
718 pub success: bool,
719 pub vm_status: String,
720 pub accumulator_root_hash: String,
721 pub changes: Vec<WriteSetChange>,
722 pub events: Vec<Event>,
723}
724
725#[derive(Debug, Clone, Serialize, Deserialize)]
726pub struct Payload {
727 #[serde(rename = "type")]
728 pub payload_type: String,
729 pub function: String,
730 pub type_arguments: Vec<String>,
731 pub arguments: Vec<Value>,
732 #[serde(skip_serializing_if = "Option::is_none")]
733 pub code: Option<Code>,
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize)]
737pub struct Code {
738 pub bytecode: String,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize)]
742pub struct Signature {
743 #[serde(rename = "type")]
744 pub signature_type: String,
745 pub public_key: String,
746 pub signature: String,
747}
748
749#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct Event {
751 pub guid: Guid,
752 pub sequence_number: String,
753 pub r#type: String,
754 pub data: Value,
755}
756
757#[derive(Debug, Clone, Serialize, Deserialize)]
758pub struct Guid {
759 pub creation_number: String,
760 pub account_address: String,
761}
762
763#[derive(Debug, Clone, Serialize, Deserialize)]
764pub struct WriteSetChange {
765 #[serde(rename = "type")]
766 pub change_type: String,
767 pub address: String,
768 pub state_key_hash: String,
769 pub data: Option<Value>,
770 pub handle: Option<String>,
771 pub key: Option<String>,
772 pub value: Option<String>,
773}
774
775#[derive(Debug, Clone, Serialize, Deserialize)]
776pub struct SubmitTransactionRequest {
777 pub sender: String,
778 pub sequence_number: String,
779 pub max_gas_amount: String,
780 pub gas_unit_price: String,
781 pub expiration_timestamp_secs: String,
782 pub payload: Payload,
783 pub signature: Signature,
784}
785
786#[derive(Debug, Clone, Serialize, Deserialize)]
787pub struct TransactionQuery {
788 pub start: Option<u64>,
789 pub limit: Option<u64>,
790}
791
792#[derive(Debug, Clone, Serialize, Deserialize)]
793pub struct TransferInfo {
794 pub from: String,
795 pub to: String,
796 pub amount: u64,
797 pub token_type: String,
798}
799
800#[derive(Debug, Clone, Serialize, Deserialize, Default)]
801pub struct ResourceChanges {
802 pub resources_modified: usize,
803 pub resources_deleted: usize,
804 pub table_items_modified: usize,
805 pub table_items_deleted: usize,
806}
807
808impl Transaction {
809 pub fn is_successful(&self) -> bool {
811 self.success
812 }
813
814 pub fn get_timestamp(&self) -> Option<u64> {
816 self.timestamp.parse().ok()
817 }
818
819 pub fn get_gas_used(&self) -> Option<u64> {
821 self.gas_used.parse().ok()
822 }
823
824 pub fn is_user_transaction(&self) -> bool {
826 matches!(self.transaction_type, TransactionType::UserTransaction(_))
827 }
828
829 pub fn get_sender(&self) -> Option<&str> {
831 match &self.transaction_type {
832 TransactionType::UserTransaction(user_txn) => Some(&user_txn.sender),
833 TransactionType::PendingTransaction(pending_txn) => Some(&pending_txn.sender),
834 _ => None,
835 }
836 }
837}