ic_evm_sign/
lib.rs

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;