aptos_network_sdk/
contract.rs

1use aptos_network_tool::address::address_to_bytes;
2use futures::future::join_all;
3// src/contract.rs
4use serde_json::{Value, json};
5use std::{collections::HashMap, sync::Arc, time::Duration};
6
7use crate::{
8    Aptos,
9    trade::Trade,
10    types::{
11        ContractCall, ContractReadResult, ContractWriteResult, EntryFunctionPayload, Event,
12        ViewRequest,
13    },
14    wallet::Wallet,
15};
16
17/// default contract
18pub const COIN_STORE: &str = "0x1::coin::CoinStore";
19pub const APTOS_COIN: &str = "0x1::aptos_coin::AptosCoin";
20pub const COIN: &str = "0x1::coin";
21/// default contract function
22pub const TRANSFER: &str = "transfer";
23pub const BALANCE: &str = "balance";
24pub const REGISTER: &str = "register";
25pub const MINT: &str = "mint";
26pub const BURN: &str = "burn";
27
28pub struct Contract {}
29impl Contract {
30    /// read contract data (view read)
31    pub async fn read(
32        client: Arc<Aptos>,
33        contract_call: &ContractCall,
34    ) -> Result<ContractReadResult, String> {
35        let function = format!(
36            "{}::{}::{}",
37            contract_call.module_address, contract_call.module_name, contract_call.function_name
38        );
39        let view_request = ViewRequest {
40            function,
41            type_arguments: contract_call.type_arguments.clone(),
42            arguments: contract_call.arguments.clone(),
43        };
44        match client.view(&view_request).await {
45            Ok(result) => Ok(ContractReadResult {
46                success: true,
47                data: Value::Array(result),
48                error: None,
49            }),
50            Err(e) => Ok(ContractReadResult {
51                success: false,
52                data: Value::Null,
53                error: Some(e.to_string()),
54            }),
55        }
56    }
57
58    /// write contract
59    pub async fn write(
60        client: Arc<Aptos>,
61        wallet: Arc<Wallet>,
62        contract_call: ContractCall,
63    ) -> Result<ContractWriteResult, String> {
64        let function_str = format!(
65            "{}::{}::{}",
66            contract_call.module_address, contract_call.module_name, contract_call.function_name
67        );
68        let function_vec = function_str.as_bytes().to_vec();
69        let mut type_args: Vec<Vec<u8>> = Vec::new();
70        contract_call
71            .type_arguments
72            .iter()
73            .for_each(|s| type_args.push(s.as_bytes().to_vec()));
74        let mut args: Vec<Vec<u8>> = Vec::new();
75        contract_call
76            .arguments
77            .iter()
78            .for_each(|s| args.push(s.as_str().unwrap().to_string().as_bytes().to_vec()));
79        let payload = EntryFunctionPayload {
80            module_address: address_to_bytes(&contract_call.module_address)
81                .unwrap()
82                .to_vec(),
83            module_name: address_to_bytes(&contract_call.module_name)
84                .unwrap()
85                .to_vec(),
86            function_name: function_vec,
87            type_arguments: type_args,
88            arguments: args,
89        };
90        let raw_txn = Trade::create_call_contract_tx(
91            Arc::clone(&client),
92            Arc::clone(&wallet),
93            None,
94            30,
95            2000,
96            100,
97            payload,
98        )
99        .await;
100        // use wallet sign
101        let signature = wallet.sign(&serde_json::to_vec(&raw_txn).unwrap()).unwrap();
102        let signed_txn = json!({
103            "transaction": raw_txn,
104            "signature": {
105                "type": "ed25519_signature",
106                "public_key": wallet.public_key_hex()?,
107                "signature": hex::encode(signature)
108            }
109        });
110        match client.submit_transaction(&signed_txn).await {
111            Ok(transaction) => {
112                // awaiting
113                if let Ok(confirmed_txn) = client.waiting_transaction(&transaction.hash, 30).await {
114                    Ok(ContractWriteResult {
115                        success: confirmed_txn.success,
116                        transaction_hash: confirmed_txn.hash,
117                        gas_used: confirmed_txn.max_gas_amount.unwrap(),
118                        events: confirmed_txn
119                            .events
120                            .into_iter()
121                            .map(|e| {
122                                json!({
123                                    "type": e.r#type,
124                                    "data": e.data,
125                                    "sequence_number": e.sequence_number
126                                })
127                            })
128                            .collect(),
129                        error: if confirmed_txn.success {
130                            None
131                        } else {
132                            Some(confirmed_txn.vm_status)
133                        },
134                    })
135                } else {
136                    Ok(ContractWriteResult {
137                        success: false,
138                        transaction_hash: transaction.hash,
139                        gas_used: "0".to_string(),
140                        events: Vec::new(),
141                        error: Some("Transaction confirmation timeout".to_string()),
142                    })
143                }
144            }
145            Err(e) => Ok(ContractWriteResult {
146                success: false,
147                transaction_hash: String::new(),
148                gas_used: "0".to_string(),
149                events: Vec::new(),
150                error: Some(e.to_string()),
151            }),
152        }
153    }
154
155    /// batch read
156    pub async fn batch_read(
157        client: Arc<Aptos>,
158        calls: Vec<ContractCall>,
159    ) -> Result<Vec<ContractReadResult>, String> {
160        let mut results = Vec::new();
161        for call in calls {
162            results.push(Contract::read(Arc::clone(&client), &call).await.unwrap());
163        }
164        Ok(results)
165    }
166
167    /// listen contract events
168    pub async fn listen_events(
169        client: Arc<Aptos>,
170        address: &str,
171        event_type: &str,
172        callback: impl Fn(Result<Value, String>),
173        interval_secs: u64,
174    ) -> Result<(), ()> {
175        let mut last_sequence_number: Option<u64> = None;
176        loop {
177            match client
178                .get_account_event_vec(address, event_type, Some(100), None)
179                .await
180            {
181                Ok(events) => {
182                    for event in events {
183                        if let Ok(current_seq) = event.sequence_number.parse::<u64>() {
184                            if let Some(last_seq) = last_sequence_number {
185                                if current_seq > last_seq {
186                                    callback(Ok(event.data.clone()));
187                                }
188                            } else {
189                                callback(Ok(event.data.clone()));
190                            }
191                            // update last sequence number
192                            last_sequence_number = Some(current_seq);
193                        }
194                    }
195                }
196                Err(e) => callback(Err(format!("no event exists: {:?}", e).to_string())),
197            }
198            tokio::time::sleep(Duration::from_secs(interval_secs)).await;
199        }
200    }
201
202    /// Event Listener - contains complete event information
203    pub async fn listen_events_all_info(
204        client: Arc<Aptos>,
205        address: &str,
206        event_type: &str,
207        callback: impl Fn(Result<Event, String>),
208        interval_secs: u64,
209    ) -> Result<(), ()> {
210        let mut last_sequence_number: Option<u64> = None;
211        loop {
212            match client
213                .get_account_event_vec(address, event_type, Some(100), None)
214                .await
215            {
216                Ok(events) => {
217                    for event in events {
218                        if let Ok(current_seq) = event.sequence_number.parse::<u64>() {
219                            if let Some(last_seq) = last_sequence_number {
220                                if current_seq > last_seq {
221                                    callback(Ok(event.clone()));
222                                }
223                            } else {
224                                callback(Ok(event.clone()));
225                            }
226                            last_sequence_number = Some(current_seq);
227                        }
228                    }
229                }
230                Err(e) => callback(Err(format!("no event exists: {:?}", e).to_string())),
231            }
232            tokio::time::sleep(Duration::from_secs(interval_secs)).await;
233        }
234    }
235
236    /// get contract resource
237    pub async fn get_contract_resource(
238        client: Arc<Aptos>,
239        address: &str,
240        resource_type: &str,
241    ) -> Result<Option<Value>, String> {
242        match client.get_account_resource(address, resource_type).await {
243            Ok(resource) => match resource {
244                Some(r) => Ok(Some(r.data)),
245                None => Err(format!("get contract resource error: resource is none").to_string()),
246            },
247            Err(e) => Err(format!("get contract resource error: {:?}", e).to_string()),
248        }
249    }
250
251    /// Get contract status snapshot
252    pub async fn get_contract_state_snapshot(
253        client: Arc<Aptos>,
254        address: &str,
255        resource_types: Vec<&str>,
256    ) -> Result<HashMap<String, Option<Value>>, String> {
257        let mut snapshot = HashMap::new();
258        for resource_type in resource_types {
259            match Self::get_contract_resource(Arc::clone(&client), address, resource_type).await {
260                Ok(Some(data)) => {
261                    snapshot.insert(resource_type.to_string(), Some(data));
262                }
263                Ok(None) => {
264                    snapshot.insert(resource_type.to_string(), None);
265                }
266                Err(e) => {
267                    snapshot.insert(resource_type.to_string(), None);
268                    eprintln!("Error fetching resource {}: {}", resource_type, e);
269                }
270            }
271        }
272        Ok(snapshot)
273    }
274
275    /// Verify contract call parameters
276    pub fn validate_contract_call(contract_call: &ContractCall) -> Result<(), String> {
277        if contract_call.module_address.is_empty() {
278            return Err("Module address cannot be empty".to_string());
279        }
280        if contract_call.module_name.is_empty() {
281            return Err("Module name cannot be empty".to_string());
282        }
283        if contract_call.function_name.is_empty() {
284            return Err("Function name cannot be empty".to_string());
285        }
286        // Verify address format
287        if !contract_call.module_address.starts_with("0x") {
288            return Err("Module address must start with 0x".to_string());
289        }
290        Ok(())
291    }
292
293    /// Estimating contract call gas fees
294    pub async fn estimate_gas_cost(
295        client: Arc<Aptos>,
296        wallet: Arc<Wallet>,
297        contract_call: &ContractCall,
298    ) -> Result<u64, String> {
299        // estimate gas
300        let simulation_result = Self::simulate_call_contract(client, wallet, contract_call).await?;
301        simulation_result
302            .get("gas_used")
303            .and_then(|g| g.as_str())
304            .and_then(|g| g.parse().ok())
305            .ok_or_else(|| "Failed to estimate gas cost".to_string())
306    }
307
308    /// Retry failed contract calls
309    pub async fn retry_failed_call(
310        client: Arc<Aptos>,
311        wallet: Arc<Wallet>,
312        contract_call: ContractCall,
313        max_retries: u32,
314        retry_delay_secs: u64,
315    ) -> Result<ContractWriteResult, String> {
316        let mut retries = 0;
317        while retries < max_retries {
318            match Self::write(
319                Arc::clone(&client),
320                Arc::clone(&wallet),
321                contract_call.clone(),
322            )
323            .await
324            {
325                Ok(result) if result.success => return Ok(result),
326                Ok(result) => {
327                    eprintln!("Call failed on attempt {}: {:?}", retries + 1, result.error);
328                }
329                Err(e) => {
330                    eprintln!("Error on attempt {}: {}", retries + 1, e);
331                }
332            }
333            retries += 1;
334            if retries < max_retries {
335                tokio::time::sleep(Duration::from_secs(retry_delay_secs)).await;
336            }
337        }
338        Err(format!("Failed after {} retries", max_retries))
339    }
340
341    /// Batch resource query
342    pub async fn batch_get_resources(
343        client: Arc<Aptos>,
344        address: &str,
345        resource_types: Vec<&str>,
346    ) -> Result<HashMap<String, Option<Value>>, String> {
347        let mut tasks = Vec::new();
348        for resource_type in resource_types {
349            let client_clone = Arc::clone(&client);
350            let address = address.to_string();
351            let resource_type = resource_type.to_string();
352            tasks.push(async move {
353                match client_clone
354                    .get_account_resource(&address, &resource_type)
355                    .await
356                {
357                    Ok(Some(resource)) => (resource_type, Some(resource.data)),
358                    Ok(None) => (resource_type, None),
359                    Err(_) => (resource_type, None),
360                }
361            });
362        }
363        let results = join_all(tasks).await;
364        let mut resource_map = HashMap::new();
365        for (resource_type, data) in results {
366            resource_map.insert(resource_type, data);
367        }
368        Ok(resource_map)
369    }
370
371    /// Batch call contract write function
372    pub async fn batch_write(
373        client: Arc<Aptos>,
374        wallet: Arc<Wallet>,
375        calls: Vec<ContractCall>,
376    ) -> Result<Vec<Value>, String> {
377        let mut results = Vec::new();
378        for call in calls {
379            match Self::write(Arc::clone(&client), Arc::clone(&wallet), call).await {
380                Ok(result) => results.push(json!(result)),
381                Err(e) => results.push(json!({
382                    "success": false,
383                    "error": e
384                })),
385            }
386        }
387        Ok(results)
388    }
389
390    /// Simulate contract call execution (estimate Gas)
391    pub async fn simulate_call_contract(
392        client: Arc<Aptos>,
393        wallet: Arc<Wallet>,
394        contract_call: &ContractCall,
395    ) -> Result<Value, String> {
396        let function = format!(
397            "{}::{}::{}",
398            contract_call.module_address, contract_call.module_name, contract_call.function_name
399        );
400        todo!();
401        let payload = json!({
402            "function": function,
403            "type_arguments": contract_call.type_arguments,
404            "arguments": contract_call.arguments,
405            "sender": wallet.address().map_err(|e| e.to_string())?,
406        });
407
408        // test data
409        todo!();
410        Ok(json!({
411            "gas_used": "1000",
412            "success": true,
413            "vm_status": "Executed successfully"
414        }))
415    }
416
417    /// Get the ABI information of the contract
418    pub async fn get_contract_abi(
419        client: Arc<Aptos>,
420        module_address: &str,
421        module_name: &str,
422    ) -> Result<Option<Value>, String> {
423        Ok(None)
424    }
425
426    /// Check if the contract has been published
427    pub async fn is_contract_deployed(
428        client: Arc<Aptos>,
429        module_address: &str,
430        module_name: &str,
431    ) -> Result<bool, String> {
432        match Self::get_contract_abi(client, module_address, module_name).await {
433            Ok(Some(_)) => Ok(true),
434            Ok(None) => Ok(false),
435            Err(_) => Ok(false),
436        }
437    }
438
439    /// Get contract event list
440    pub async fn get_contract_events(
441        client: Arc<Aptos>,
442        address: &str,
443        event_handle: &str,
444        limit: Option<u64>,
445        start: Option<u64>,
446    ) -> Result<Vec<Value>, String> {
447        let events = client
448            .get_account_event_vec(address, event_handle, limit, start)
449            .await?;
450        let value_events: Vec<Value> = events
451            .into_iter()
452            .map(|event| {
453                json!({
454                    "type": event.r#type,
455                    "data": event.data,
456                    "sequence_number": event.sequence_number,
457                    "guid": event.guid
458                })
459            })
460            .collect();
461        Ok(value_events)
462    }
463
464    /// Parsing complex type parameters
465    pub fn parse_complex_type_arguments(type_args: Vec<&str>) -> Vec<String> {
466        type_args.into_iter().map(|s| s.to_string()).collect()
467    }
468
469    /// Constructing complex call parameters
470    pub fn build_complex_arguments(args: Vec<&str>) -> Vec<Value> {
471        args.into_iter()
472            .map(|s| Value::String(s.to_string()))
473            .collect()
474    }
475
476    /// Contract call result analyzer
477    pub fn analyze_contract_result(result: &Value) -> HashMap<String, String> {
478        let mut analysis = HashMap::new();
479        if let Some(success) = result.get("success").and_then(|s| s.as_bool()) {
480            analysis.insert(
481                "status".to_string(),
482                if success {
483                    "success".to_string()
484                } else {
485                    "failed".to_string()
486                },
487            );
488        }
489        if let Some(gas_used) = result.get("gas_used").and_then(|g| g.as_str()) {
490            analysis.insert("gas_used".to_string(), gas_used.to_string());
491        }
492        if let Some(error) = result.get("error").and_then(|e| e.as_str()) {
493            analysis.insert("error".to_string(), error.to_string());
494        }
495        analysis
496    }
497
498    /// Release new contract module
499    pub async fn deploy_contract(
500        client: Arc<Aptos>,
501        wallet: Arc<Wallet>,
502        module_bytes: Vec<u8>,
503        metadata: Option<Value>,
504    ) -> Result<Value, String> {
505        // Use existing transaction build and commit logic
506        let contract_call = ContractCall {
507            module_address: wallet.address().map_err(|e| e.to_string())?,
508            module_name: "".to_string(), // Deploying a contract does not require a module name
509            function_name: "deploy".to_string(),
510            type_arguments: vec![],
511            arguments: vec![json!(hex::encode(module_bytes))],
512        };
513        Self::write(client, wallet, contract_call)
514            .await
515            .map(|result| json!(result))
516    }
517
518    /// Update a deployed contract
519    pub async fn upgrade_contract(
520        client: Arc<Aptos>,
521        wallet: Arc<Wallet>,
522        module_name: &str,
523        new_module_bytes: Vec<u8>,
524    ) -> Result<Value, String> {
525        let contract_call = ContractCall {
526            module_address: wallet.address().map_err(|e| e.to_string())?,
527            module_name: module_name.to_string(),
528            function_name: "upgrade".to_string(),
529            type_arguments: vec![],
530            arguments: vec![json!(hex::encode(new_module_bytes))],
531        };
532        Self::write(client, wallet, contract_call)
533            .await
534            .map(|result| json!(result))
535    }
536}
537
538/// Contract tool
539pub struct ContractUtils;
540
541impl ContractUtils {
542    /// Generate standard contract calls
543    pub fn create_standard_call(
544        module_address: &str,
545        module_name: &str,
546        function_name: &str,
547        type_arguments: Vec<String>,
548        arguments: Vec<Value>,
549    ) -> ContractCall {
550        ContractCall {
551            module_address: module_address.to_string(),
552            module_name: module_name.to_string(),
553            function_name: function_name.to_string(),
554            type_arguments,
555            arguments,
556        }
557    }
558
559    /// Parsing event data into a structured format
560    pub fn parse_event_data(event_data: Value, event_schema: &[&str]) -> HashMap<String, Value> {
561        let mut parsed = HashMap::new();
562
563        if let Value::Object(map) = event_data {
564            for (key, value) in map {
565                if event_schema.contains(&key.as_str()) {
566                    parsed.insert(key, value);
567                }
568            }
569        }
570        parsed
571    }
572
573    /// Calculate the contract call signature
574    pub fn calculate_call_signature(contract_call: &ContractCall, nonce: &str) -> String {
575        use sha3::{Digest, Sha3_256};
576        let mut hasher = Sha3_256::new();
577        hasher.update(contract_call.module_address.as_bytes());
578        hasher.update(contract_call.module_name.as_bytes());
579        hasher.update(contract_call.function_name.as_bytes());
580        hasher.update(nonce.as_bytes());
581        format!("0x{}", hex::encode(hasher.finalize()))
582    }
583}