1#[cfg(not(test))]
2use ic_cdk::api::time as ic_timestamp;
3#[cfg(not(test))]
4use ic_cdk::api::call::call_with_payment as ic_call;
5use ic_cdk::export::{
6 candid::CandidType,
7 serde::{Deserialize, Serialize},
8 Principal,
9};
10#[cfg(test)]
11mod mocks;
12#[cfg(test)]
13use mocks::{ic_call, ic_timestamp};
14
15mod utils;
16use utils::{get_address_from_public_key, get_derivation_path};
17
18mod ecdsa;
19use ecdsa::reply::*;
20use ecdsa::request::*;
21
22pub mod state;
23use state::*;
24
25pub mod transaction;
26use transaction::*;
27
28#[derive(CandidType, Serialize, Debug)]
29pub struct CreateAddressResponse {
30 pub address: String,
31}
32#[derive(CandidType, Deserialize, Debug)]
33pub struct SignTransactionResponse {
34 pub sign_tx: Vec<u8>,
35}
36#[derive(CandidType, Deserialize, Debug)]
37pub struct DeployContractResponse {
38 pub tx: Vec<u8>,
39}
40#[derive(CandidType, Deserialize, Debug)]
41pub struct TransferERC20Response {
42 pub tx: Vec<u8>,
43}
44#[derive(CandidType, Deserialize, Debug)]
45pub struct UserResponse {
46 pub address: String,
47 pub transactions: TransactionChainData,
48}
49
50pub fn init(env_opt: Option<Environment> ) {
51 if let Some(env) = env_opt {
52 STATE.with(|s| {
53 let mut state = s.borrow_mut();
54 state.config = Config::from(env);
55 })
56 }
57}
58
59pub async fn create_address(principal_id: Principal) -> Result<CreateAddressResponse, String> {
60 let state = STATE.with(|s| s.borrow().clone());
61 let user = state.users.get(&principal_id);
62
63 if let Some(_) = user {
64 return Err("this wallet already exist".to_string());
65 }
66
67 let key_id = EcdsaKeyId {
68 curve: EcdsaCurve::Secp256k1,
69 name: state.config.key_name
70 };
71
72 let caller = get_derivation_path(principal_id);
73
74 let request = ECDSAPublicKey {
75 canister_id: None,
76 derivation_path: vec![caller],
77 key_id: key_id.clone(),
78 };
79
80 let (res,): (ECDSAPublicKeyResponse,) = ic_call(
81 Principal::management_canister(),
82 "ecdsa_public_key",
83 (request,),
84 0 as u64
85 )
86 .await
87 .map_err(|e| format!("Failed to call ecdsa_public_key {}", e.1))?;
88
89 let address = get_address_from_public_key(res.public_key.clone()).unwrap();
90
91 let mut user = UserData::default();
92 user.public_key = res.public_key;
93
94 STATE.with(|s| {
95 let mut state = s.borrow_mut();
96 state.users.insert(principal_id, user);
97 });
98
99 Ok(CreateAddressResponse { address })
100}
101
102pub async fn sign_transaction(
103 hex_raw_tx: Vec<u8>,
104 chain_id: u64,
105 principal_id: Principal,
106) -> Result<SignTransactionResponse, String> {
107 let state = STATE.with(|s| s.borrow().clone());
108 let user;
109
110 if let Some(i) = state.users.get(&principal_id) {
111 user = i.clone();
112 } else {
113 return Err("this user does not exist".to_string());
114 }
115
116 let mut tx = transaction::get_transaction(&hex_raw_tx, chain_id.clone()).unwrap();
117
118 let message = tx.get_message_to_sign().unwrap();
119
120 assert!(message.len() == 32);
121
122 let key_id = EcdsaKeyId {
123 curve: EcdsaCurve::Secp256k1,
124 name: state.config.key_name,
125 };
126
127 let caller = get_derivation_path(principal_id);
128
129 let request = SignWithECDSA {
130 message_hash: message.clone(),
131 derivation_path: vec![caller],
132 key_id: key_id.clone(),
133 };
134
135 let (res,): (SignWithECDSAResponse,) = ic_call(
136 Principal::management_canister(),
137 "sign_with_ecdsa",
138 (request,),
139 state.config.sign_cycles
140 )
141 .await
142 .map_err(|e| format!("Failed to call sign_with_ecdsa {}", e.1))?;
143
144 let signed_tx = tx.sign(res.signature.clone(), user.public_key).unwrap();
145
146 STATE.with(|s| {
147 let mut state = s.borrow_mut();
148 let user = state.users.get_mut(&principal_id).unwrap();
149
150 let mut transaction = Transaction::default();
151 transaction.data = signed_tx.clone();
152 transaction.timestamp = ic_timestamp();
153
154 if let Some(user_tx) = user.transactions.get_mut(&chain_id) {
155 user_tx.transactions.push(transaction);
156 user_tx.nonce = tx.get_nonce().unwrap() + 1;
157 } else {
158 let mut chain_data = TransactionChainData::default();
159 chain_data.nonce = tx.get_nonce().unwrap() + 1;
160 chain_data.transactions.push(transaction);
161
162 user.transactions.insert(chain_id, chain_data);
163 }
164 });
165
166 Ok(SignTransactionResponse { sign_tx: signed_tx })
167}
168
169pub async fn deploy_contract(
170 principal_id: Principal,
171 bytecode: Vec<u8>,
172 chain_id: u64,
173 max_priority_fee_per_gas: u64,
174 gas_limit: u64,
175 max_fee_per_gas: u64,
176) -> Result<DeployContractResponse, String> {
177 let users = STATE.with(|s| s.borrow().users.clone());
178 let user;
179
180 if let Some(i) = users.get(&principal_id) {
181 user = i.clone();
182 } else {
183 return Err("this user does not exist".to_string());
184 }
185
186 let nonce: u64;
187 if let Some(user_transactions) = user.transactions.get(&chain_id) {
188 nonce = user_transactions.nonce;
189 } else {
190 nonce = 0;
191 }
192 let data = "0x".to_owned() + &utils::vec_u8_to_string(&bytecode);
193 let tx = transaction::Transaction1559 {
194 nonce,
195 chain_id,
196 max_priority_fee_per_gas,
197 gas_limit,
198 max_fee_per_gas,
199 to: "0x".to_string(),
200 value: 0,
201 data,
202 access_list: vec![],
203 v: "0x00".to_string(),
204 r: "0x00".to_string(),
205 s: "0x00".to_string(),
206 };
207
208 let raw_tx = tx.serialize().unwrap();
209 let res = sign_transaction(raw_tx, chain_id, principal_id)
210 .await
211 .unwrap();
212
213 Ok(DeployContractResponse { tx: res.sign_tx })
214}
215
216pub async fn transfer_erc_20(
217 principal_id: Principal,
218 chain_id: u64,
219 max_priority_fee_per_gas: u64,
220 gas_limit: u64,
221 max_fee_per_gas: u64,
222 address: String,
223 value: u64,
224 contract_address: String,
225) -> Result<TransferERC20Response, String> {
226 let users = STATE.with(|s| s.borrow().users.clone());
227 let user;
228
229 if let Some(i) = users.get(&principal_id) {
230 user = i.clone();
231 } else {
232 return Err("this user does not exist".to_string());
233 }
234
235 let nonce: u64;
236 if let Some(user_transactions) = user.transactions.get(&chain_id) {
237 nonce = user_transactions.nonce;
238 } else {
239 nonce = 0;
240 }
241
242 let data = "0x".to_owned() + &utils::get_transfer_data(&address, value).unwrap();
243
244 let tx = transaction::Transaction1559 {
245 nonce,
246 chain_id,
247 max_priority_fee_per_gas,
248 gas_limit,
249 max_fee_per_gas,
250 to: contract_address,
251 value: 0,
252 data,
253 access_list: vec![],
254 v: "0x00".to_string(),
255 r: "0x00".to_string(),
256 s: "0x00".to_string(),
257 };
258
259 let raw_tx = tx.serialize().unwrap();
260
261 let res = sign_transaction(raw_tx, chain_id, principal_id)
262 .await
263 .unwrap();
264
265 Ok(TransferERC20Response { tx: res.sign_tx })
266}
267
268pub fn get_caller_data(principal_id: Principal, chain_id: u64) -> Option<UserResponse> {
269 let users = STATE.with(|s| s.borrow().users.clone());
270 let user;
271 if let Some(i) = users.get(&principal_id) {
272 user = i.clone();
273 } else {
274 return None;
275 }
276
277 let address = get_address_from_public_key(user.public_key.clone()).unwrap();
278
279 let transaction_data = user
280 .transactions
281 .get(&chain_id)
282 .cloned()
283 .unwrap_or_else(|| TransactionChainData::default());
284
285 Some(UserResponse {
286 address,
287 transactions: transaction_data,
288 })
289}
290
291pub fn clear_caller_history(principal_id: Principal, chain_id: u64) -> Result<(), String> {
292 let users = STATE.with(|s| s.borrow().users.clone());
293
294 if let None = users.get(&principal_id) {
295 return Err("this user does not exist".to_string());
296 }
297
298 STATE.with(|s| {
299 let mut state = s.borrow_mut();
300 let user = state.users.get_mut(&principal_id).unwrap();
301 let user_tx = user.transactions.get_mut(&chain_id);
302 if let Some(user_transactions) = user_tx {
303 user_transactions.transactions.clear();
304 }
305 });
306
307 Ok(())
308}
309
310pub fn pre_upgrade() {
311 STATE.with(|s| {
312 ic_cdk::storage::stable_save((s,)).unwrap();
313 });
314}
315
316pub fn post_upgrade() {
317 let (s_prev,): (State,) = ic_cdk::storage::stable_restore().unwrap();
318 STATE.with(|s| {
319 *s.borrow_mut() = s_prev;
320 });
321}
322#[cfg(test)]
323mod tests;