aptos_network_sdk/
trade.rs

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    /// build transfer info
21    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        // build transaction payload
47        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        // build raw transaction
54        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    /// build token transfer
66    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        // build transaction payload
92        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        // build raw transaction
99        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    /// create sign and submit transfer tx
112    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        // build raw transaction
123        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        // serialize transaction and sign
136        let message_to_sign = serialize_transaction_and_sign(&raw_txn)?;
137        // wallet sign
138        match wallet.sign(&message_to_sign) {
139            Ok(signature_bytes) => {
140                // create signed transaction tx
141                match Trade::create_signed_transaction_tx(
142                    Arc::clone(&wallet),
143                    raw_txn,
144                    signature_bytes,
145                ) {
146                    Ok(signed_txn) => {
147                        // submit transaction
148                        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    /// build call contract tx
165    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        // current timestamp
183        let current_timestamp = std::time::SystemTime::now()
184            .duration_since(std::time::UNIX_EPOCH)
185            .unwrap()
186            .as_secs();
187        // expiration time
188        let expiration_timestamp = current_timestamp + expiration_secs;
189        // build raw transaction
190        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    /// create customize call contract tx
203    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    ///  build signed transaction
246    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    /// Retrieves transaction history for a specified address with pagination support
265    ///
266    /// # Params
267    /// client - Aptos Client
268    /// address - address
269    /// query - query:
270    ///     - start - page
271    ///     - limit - page size
272    ///
273    /// # Returns
274    /// Ok(Vec<Transaction>) - Transaction vec
275    /// Err(String) - Error
276    ///
277    /// # Examples
278    /// ```rust
279    /// use std::sync::Arc;
280    ///
281    /// let client = Arc::new(AptosClient::new(AptosClientType::Mainnet));
282    /// let query = TransactionQuery {
283    ///     start: Some(0),
284    ///     limit: Some(10)
285    /// };
286    ///
287    /// match Trade::get_address_transactions(client, "0x1234...", query).await {
288    ///     Ok(transactions) => println!("Search {} transactions", transactions.len()),
289    ///     Err(e) => println!("Error: {}", e),
290    /// }
291    /// ```
292    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    /// Filters transactions from address_a to only include those involving address_b
303    ///
304    /// # Params
305    /// client - aptos client
306    /// address_a - The primary account address to fetch transactions from
307    /// address_b - Address B used as filtering condition
308    /// limit - data limit
309    /// start - starting sequence number for pagination (Optional)
310    ///
311    /// # Returns
312    /// Ok(Vec<Transaction>) - Filtered vector of transactions where both addresses are involved
313    /// Err(String) - Error message if the request fails
314    ///
315    /// # Examples
316    /// ```
317    /// use std::sync::Arc;
318    ///
319    /// let client = Arc::new(AptosClient::new(AptosClientType::Mainnet));
320    ///
321    /// // Find all transactions where 0x1234... and 0x5678... interacted
322    /// match Trade::get_transactions_involving_both_addresses(
323    ///     client,
324    ///     "0x1234...",
325    ///     "0x5678...",
326    ///     Some(50),  // Limit to 50 transactions
327    ///     None       // Start from most recent
328    /// ).await {
329    ///     Ok(shared_transactions) => {
330    ///         println!("Search {} shared transactions", shared_transactions.len());
331    ///     },
332    ///     Err(e) => println!("Error: {}", e),
333    /// }
334    /// ```
335    ///
336    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    /// Retrieves transactions where address_b is the sender and address_a is the recipient
353    ///
354    /// This function specifically finds transfer transactions where `address_b` (payer) sent funds
355    /// to `address_a` (recipient). It searches through `address_b`'s transaction history and
356    /// filters for coin transfer operations targeting `address_a`.
357    ///
358    /// # Params
359    /// client - aptos client
360    /// address_a - The primary account address to fetch transactions from
361    /// address_b - Address B used as filtering condition
362    /// limit - data limit
363    /// start - starting sequence number for pagination (Optional)
364    ///
365    /// # Returns
366    /// Ok(Vec<Transaction>) - Transaction vec
367    /// Err(String) - Error message
368    ///
369    /// # Examples
370    /// ```
371    /// use std::sync::Arc;
372    ///
373    /// let client = Arc::new(AptosClient::new(AptosClientType::Mainnet));
374    ///
375    /// // Find all payments from Alice (0x5678...) to Bob (0x1234...)
376    /// match Trade::get_transactions_by_recipient_sender(
377    ///     client,
378    ///     "0x1234...",  // address A - recipient
379    ///     "0x5678...",  // address B - sender
380    ///     Some(100),    // Limit to 100 transactions
381    ///     None          // Start from most recent
382    /// ).await {
383    ///     Ok(payments) => {
384    ///         println!("Found {} payments from Alice to Bob", payments.len());
385    ///         for payment in payments {
386    ///             if let Some(transfer_info) = Trade::get_transfer_info(&payment) {
387    ///                 println!("Amount: {} {}", transfer_info.amount, transfer_info.token_type);
388    ///             }
389    ///         }
390    ///     },
391    ///     Err(e) => println!("Error: {}", e),
392    /// }
393    /// ```
394    ///
395    pub async fn get_transactions_by_recipient_sender(
396        client: Arc<AptosClient>,
397        address_a: &str, // Receiver
398        address_b: &str, // Payer
399        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    /// Check if the transaction involves the specified address
413    fn transaction_involves_address(transaction: &Transaction, address: &str) -> bool {
414        match &transaction.transaction_type {
415            TransactionType::UserTransaction(user_txn) => {
416                // Check sender
417                if user_txn.sender == address {
418                    return true;
419                }
420                // Check the address parameter in the payload
421                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    /// Check if the transaction is a transfer from from_address to to_address
432    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    /// Check if the payload contains the specified address
455    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    /// Check if the payload is a transfer to the specified address
467    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    /// Get user transaction details
479    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    /// Get transfer information in the transaction
487    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                // Extract token type
494                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    /// Get events of a specific type in transaction events
514    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    /// Analyze resource changes in transactions
526    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
551/// batch transaction processor
552pub struct BatchTradeHandle;
553
554impl BatchTradeHandle {
555    /// Processing batch transactions with concurrency control
556    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    /// Read resources in batches
593    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/// Represents a transaction on the Aptos blockchain
620/// Contains all relevant information about a transaction including metadata, payload, and execution results
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct Transaction {
623    /// The version number of the transaction (global sequence number)
624    pub version: String,
625    /// The hash of the transaction (unique identifier)
626    pub hash: String,
627    /// Hash representing the state changes caused by this transaction
628    pub state_change_hash: String,
629    /// Root hash of the event accumulator after this transaction
630    pub event_root_hash: String,
631    /// Hash of the state checkpoint (if this is a checkpoint transaction)
632    pub state_checkpoint_hash: Option<String>,
633    /// Amount of gas used by the transaction
634    pub gas_used: String,
635    /// Whether the transaction executed successfully
636    pub success: bool,
637    /// Status message from the virtual machine after execution
638    pub vm_status: String,
639    /// Root hash of the transaction accumulator
640    pub accumulator_root_hash: String,
641    /// List of state changes (resources modified, tables updated, etc.)
642    pub changes: Vec<WriteSetChange>,
643    /// Events emitted during transaction execution
644    pub events: Vec<Event>,
645    /// Timestamp when the transaction was executed (in microseconds)
646    pub timestamp: String,
647    /// Maximum gas amount that could be used for this transaction
648    pub max_gas_amount: String,
649    /// The specific type of transaction and its payload data
650    /// Uses serde flatten to include all transaction-type-specific fields
651    #[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    /// Check if the transaction was successful
810    pub fn is_successful(&self) -> bool {
811        self.success
812    }
813
814    /// Get transaction timestamp
815    pub fn get_timestamp(&self) -> Option<u64> {
816        self.timestamp.parse().ok()
817    }
818
819    /// Get the amount of gas used
820    pub fn get_gas_used(&self) -> Option<u64> {
821        self.gas_used.parse().ok()
822    }
823
824    /// Check whether it is a user transaction
825    pub fn is_user_transaction(&self) -> bool {
826        matches!(self.transaction_type, TransactionType::UserTransaction(_))
827    }
828
829    /// Get the sender address in this transaction.
830    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}