icon_sdk/
transaction_builder.rs

1use chrono::Utc;
2use hex::{decode, encode};
3use rust_decimal::Decimal;
4use secp256k1::{Message, Secp256k1, SecretKey};
5use serde::{Deserialize, Serialize};
6use serde_json::{json, Map, Value};
7use std::str::FromStr;
8use base64::{Engine as _, engine::{general_purpose as base64_encoder}};
9use hex::FromHex;
10use crate::icon_service::IconService;
11use crate::transaction::Transaction;
12use crate::utils::helpers::icx_to_hex;
13use crate::utils::serializer::Serializer;
14
15#[derive(Default, Serialize, Deserialize)]
16pub struct TransactionBuilder {
17    transaction: Transaction,
18}
19
20impl TransactionBuilder {
21    pub fn new(icon_service: &IconService) -> Self {
22        Self {
23            transaction: Transaction::new(icon_service),
24        }
25    }
26
27    pub fn method(mut self, method: &str) -> Self {
28        // Use as_object_mut() to get a mutable reference to the data object
29        if let Some(obj) = self.transaction.data.as_object_mut() {
30            // Insert or modify the "method" field
31            obj.insert("method".to_string(), json!(method));
32        }
33        self
34    }
35
36    pub fn set_params(mut self, params: &Map<String, Value>) -> Self {
37        // Ensure `data` has a "params" object; create it if not
38        let data_obj = self.transaction.data.as_object_mut().expect("data is not an object");
39        let params_obj = data_obj.entry("params").or_insert_with(|| json!({})).as_object_mut().unwrap();
40
41        // Insert or update the given parameters
42        for (key, value) in params {
43            params_obj.insert(key.clone(), value.clone());
44        }
45
46        self
47    }
48
49    pub fn block_height(self, block_height: &str) -> Self {
50        let mut params = Map::new();
51        params.insert("height".to_string(), json!(block_height));
52
53        self.set_params(&params)
54    }
55
56    pub fn block_hash(self, block_hash: &str) -> Self {
57        let mut params = Map::new();
58        params.insert("hash".to_string(), json!(block_hash));
59
60        self.set_params(&params)
61    }
62
63    pub fn address(self, address: &str) -> Self {
64        let mut params = Map::new();
65        params.insert("address".to_string(), json!(address));
66
67        self.set_params(&params)
68    }
69
70    pub fn tx_hash(self, tx_hash: &str) -> Self {
71        let mut params = Map::new();
72        params.insert("txHash".to_string(), json!(tx_hash));
73
74        self.set_params(&params)
75    }
76
77    pub fn from(self, from: &str) -> Self {
78        let mut params = Map::new();
79        params.insert("from".to_string(), json!(from));
80
81        self.set_params(&params)
82    }
83
84    pub fn to(self, to: &str) -> Self {
85        let mut params = Map::new();
86        params.insert("to".to_string(), json!(to));
87
88        self.set_params(&params)
89    }
90
91    pub fn value(self, value: &str) -> Self {
92        let mut params = Map::new();
93        let mut parsed_value = value.to_string();
94
95        if !parsed_value.starts_with("0x") {
96            match icx_to_hex(Decimal::from_str(value).expect("Invalid value")) {
97                Some(v) => {
98                    parsed_value = v;
99                }
100                None => panic!("Failed to convert value to hex"),
101            }
102        }
103
104        params.insert("value".to_string(), json!(parsed_value));
105
106
107        self.set_params(&params)
108    }
109
110    pub fn version(self, version: &str) -> Self {
111        let mut params = Map::new();
112        params.insert("version".to_string(), json!(version));
113
114        self.set_params(&params)
115    }
116
117    pub fn nid(self, nid: &str) -> Self {
118        let mut params = Map::new();
119        params.insert("nid".to_string(), json!(nid));
120
121        self.set_params(&params)
122    }
123
124    pub fn nonce(self, nonce: &str) -> Self {
125        let mut params = Map::new();
126        params.insert("nonce".to_string(), json!(nonce));
127
128        self.set_params(&params)
129    }
130
131    pub fn step_limit(self, step_limit: &str) -> Self {
132        let mut params = Map::new();
133        params.insert("stepLimit".to_string(), json!(step_limit));
134
135        self.set_params(&params)
136    }
137
138    pub fn timestamp(self) -> Self {
139        let now = Utc::now();
140        let timestamp_in_micros = now.timestamp_micros();
141        let hex_timestamp = format!("0x{:x}", timestamp_in_micros);
142
143        let mut params = Map::new();
144        params.insert("timestamp".to_string(), json!(hex_timestamp.to_string()));
145
146        self.set_params(&params)
147    }
148
149    pub fn message(self, message: &str) -> Self {
150        let mut params = Map::new();
151        params.insert("dataType".to_string(), json!("message"));
152        params.insert("data".to_string(), json!(format!("0x{}", encode(message))));
153
154
155        self.set_params(&params)
156    }
157
158    pub fn call(self, call_params: Value) -> Self {
159        let mut params = Map::new();
160        params.insert("dataType".to_string(), json!("call"));
161        params.insert("data".to_string(), json!(call_params));
162
163        self.set_params(&params)
164    }
165
166    pub fn sign(self, private_key: &str) -> Self {
167        let serialized_transaction = Serializer::serialize_transaction(&self.transaction.data["params"], true);
168        let serialized_transaction_bytes = Vec::from_hex(serialized_transaction).expect("Invalid hex string");
169
170        let secp = Secp256k1::new();
171        let secret_key = SecretKey::from_slice(&decode(private_key).expect("Invalid private key")).expect("Failed to create secret key");
172
173        let message = Message::from_digest_slice(serialized_transaction_bytes.as_slice()).expect("Failed to create message");
174        let sig = secp.sign_ecdsa_recoverable(&message, &secret_key);
175
176        let (rec_id, sig_bytes) = sig.serialize_compact();
177        // Concatenate r, s, and recovery ID
178        let signature = format!("{}{:02x}", encode(sig_bytes), rec_id.to_i32());
179
180        let signature_bytes = decode(signature).expect("Failed to decode hex");
181        let transaction_signature = base64_encoder::STANDARD.encode(signature_bytes);
182
183        let mut params = Map::new();
184        params.insert("signature".to_string(), json!(transaction_signature));
185
186        self.set_params(&params)
187    }
188
189    pub fn build(self) -> Transaction {
190        self.transaction
191    }
192}