ic_web3_rs/api/
personal.rs

1//! `Personal` namespace
2
3use crate::{
4    api::Namespace,
5    helpers::{self, CallFuture},
6    transports::ic_http_client::CallOptions,
7    types::{Address, Bytes, RawTransaction, TransactionRequest, H256, H520},
8    Transport,
9};
10
11/// `Personal` namespace
12#[derive(Debug, Clone)]
13pub struct Personal<T> {
14    transport: T,
15}
16
17impl<T: Transport> Namespace<T> for Personal<T> {
18    fn new(transport: T) -> Self
19    where
20        Self: Sized,
21    {
22        Personal { transport }
23    }
24
25    fn transport(&self) -> &T {
26        &self.transport
27    }
28}
29
30impl<T: Transport> Personal<T> {
31    /// Returns a list of available accounts.
32    pub fn list_accounts(&self, options: CallOptions) -> CallFuture<Vec<Address>, T::Out> {
33        CallFuture::new(self.transport.execute("personal_listAccounts", vec![], options))
34    }
35
36    /// Creates a new account and protects it with given password.
37    /// Returns the address of created account.
38    pub fn new_account(&self, password: &str, options: CallOptions) -> CallFuture<Address, T::Out> {
39        let password = helpers::serialize(&password);
40        CallFuture::new(self.transport.execute("personal_newAccount", vec![password], options))
41    }
42
43    /// Unlocks the account with given password for some period of time (or single transaction).
44    /// Returns `true` if the call was successful.
45    pub fn unlock_account(
46        &self,
47        address: Address,
48        password: &str,
49        duration: Option<u16>,
50        options: CallOptions,
51    ) -> CallFuture<bool, T::Out> {
52        let address = helpers::serialize(&address);
53        let password = helpers::serialize(&password);
54        let duration = helpers::serialize(&duration);
55        CallFuture::new(
56            self.transport
57                .execute("personal_unlockAccount", vec![address, password, duration], options),
58        )
59    }
60
61    /// Sends a transaction from locked account.
62    /// Returns transaction hash.
63    pub fn send_transaction(
64        &self,
65        transaction: TransactionRequest,
66        password: &str,
67        options: CallOptions,
68    ) -> CallFuture<H256, T::Out> {
69        let transaction = helpers::serialize(&transaction);
70        let password = helpers::serialize(&password);
71        CallFuture::new(
72            self.transport
73                .execute("personal_sendTransaction", vec![transaction, password], options),
74        )
75    }
76
77    /// Signs an Ethereum specific message with `sign(keccak256("\x19Ethereum Signed Message: " + len(data) + data)))`
78    ///
79    /// The account does not need to be unlocked to make this call, and will not be left unlocked after.
80    /// Returns encoded signature.
81    pub fn sign(
82        &self,
83        data: Bytes,
84        account: Address,
85        password: &str,
86        options: CallOptions,
87    ) -> CallFuture<H520, T::Out> {
88        let data = helpers::serialize(&data);
89        let address = helpers::serialize(&account);
90        let password = helpers::serialize(&password);
91        CallFuture::new(
92            self.transport
93                .execute("personal_sign", vec![data, address, password], options),
94        )
95    }
96
97    /// Signs a transaction without dispatching it to the network.
98    /// The account does not need to be unlocked to make this call, and will not be left unlocked after.
99    /// Returns a signed transaction in raw bytes along with it's details.
100    pub fn sign_transaction(
101        &self,
102        transaction: TransactionRequest,
103        password: &str,
104        options: CallOptions,
105    ) -> CallFuture<RawTransaction, T::Out> {
106        let transaction = helpers::serialize(&transaction);
107        let password = helpers::serialize(&password);
108        CallFuture::new(
109            self.transport
110                .execute("personal_signTransaction", vec![transaction, password], options),
111        )
112    }
113
114    /// Imports a raw key and protects it with the given password.
115    /// Returns the address of created account.
116    pub fn import_raw_key(
117        &self,
118        private_key: &[u8; 32],
119        password: &str,
120        options: CallOptions,
121    ) -> CallFuture<Address, T::Out> {
122        let private_key = hex::encode(private_key);
123        let private_key = helpers::serialize(&private_key);
124        let password = helpers::serialize(&password);
125
126        CallFuture::new(
127            self.transport
128                .execute("personal_importRawKey", vec![private_key, password], options),
129        )
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::Personal;
136    use crate::{
137        api::Namespace,
138        rpc::Value,
139        transports::ic_http_client::CallOptions,
140        types::{Address, Bytes, RawTransaction, TransactionRequest, H160, H520},
141    };
142    use hex_literal::hex;
143
144    const EXAMPLE_TX: &str = r#"{
145    "raw": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
146    "tx": {
147      "hash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b",
148      "nonce": "0x0",
149      "blockHash": "0xbeab0aa2411b7ab17f30a99d3cb9c6ef2fc5426d6ad6fd9e2a26a6aed1d1055b",
150      "blockNumber": "0x15df",
151      "transactionIndex": "0x1",
152      "from": "0x407d73d8a49eeb85d32cf465507dd71d507100c1",
153      "to": "0x853f43d8a49eeb85d32cf465507dd71d507100c1",
154      "value": "0x7f110",
155      "gas": "0x7f110",
156      "gasPrice": "0x09184e72a000",
157      "input": "0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360"
158    }
159  }"#;
160
161    rpc_test! (
162      Personal:list_accounts,CallOptions::default() => "personal_listAccounts",Vec::<String>::new();
163      Value::Array(vec![Value::String("0x0000000000000000000000000000000000000123".into())]) => vec![Address::from_low_u64_be(0x123)]
164    );
165
166    rpc_test! (
167      Personal:new_account, "hunter2",CallOptions::default() => "personal_newAccount", vec![r#""hunter2""#];
168      Value::String("0x0000000000000000000000000000000000000123".into()) => Address::from_low_u64_be(0x123)
169    );
170
171    rpc_test! (
172      Personal:unlock_account, Address::from_low_u64_be(0x123), "hunter2", None,CallOptions::default()
173      =>
174      "personal_unlockAccount", vec![r#""0x0000000000000000000000000000000000000123""#, r#""hunter2""#, r#"null"#];
175      Value::Bool(true) => true
176    );
177
178    rpc_test! (
179      Personal:send_transaction, TransactionRequest {
180        from: Address::from_low_u64_be(0x123), to: Some(Address::from_low_u64_be(0x123)),
181        gas: None, gas_price: Some(0x1.into()),
182        value: Some(0x1.into()), data: None,
183        nonce: None, condition: None,
184        transaction_type: None, access_list: None,
185        max_fee_per_gas: None, max_priority_fee_per_gas: None,
186      }, "hunter2",CallOptions::default()
187      =>
188      "personal_sendTransaction", vec![r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#, r#""hunter2""#];
189      Value::String("0x0000000000000000000000000000000000000000000000000000000000000123".into()) => Address::from_low_u64_be(0x123)
190    );
191
192    rpc_test! (
193      Personal:sign_transaction, TransactionRequest {
194        from: hex!("407d73d8a49eeb85d32cf465507dd71d507100c1").into(),
195        to: Some(hex!("853f43d8a49eeb85d32cf465507dd71d507100c1").into()),
196        gas: Some(0x7f110.into()),
197        gas_price: Some(0x09184e72a000u64.into()),
198        value: Some(0x7f110.into()),
199        data: Some(hex!("603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360").into()),
200        nonce: Some(0x0.into()),
201        condition: None,
202        transaction_type: None,
203        access_list: None,
204        max_fee_per_gas: None,
205        max_priority_fee_per_gas: None,
206      }, "hunter2",CallOptions::default()
207      =>
208      "personal_signTransaction", vec![r#"{"data":"0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360","from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","gas":"0x7f110","gasPrice":"0x9184e72a000","nonce":"0x0","to":"0x853f43d8a49eeb85d32cf465507dd71d507100c1","value":"0x7f110"}"#, r#""hunter2""#];
209      ::serde_json::from_str(EXAMPLE_TX).unwrap()
210      => ::serde_json::from_str::<RawTransaction>(EXAMPLE_TX).unwrap()
211    );
212
213    rpc_test! {
214      Personal:import_raw_key, &[0u8; 32], "hunter2",CallOptions::default() =>
215      "personal_importRawKey", vec![r#""0000000000000000000000000000000000000000000000000000000000000000""#, r#""hunter2""#];
216      Value::String("0x0000000000000000000000000000000000000123".into()) => Address::from_low_u64_be(0x123)
217    }
218
219    rpc_test! {
220      Personal:sign, Bytes(hex!("7f0d39b8347598e20466233ce2fb3e824f0f93dfbf233125d3ab09b172c62591ec24dc84049242e364895c3abdbbd843d4a0a188").to_vec()), H160(hex!("7f0d39b8347598e20466233ce2fb3e824f0f93df")), "hunter2",CallOptions::default()
221      =>
222      "personal_sign", vec![r#""0x7f0d39b8347598e20466233ce2fb3e824f0f93dfbf233125d3ab09b172c62591ec24dc84049242e364895c3abdbbd843d4a0a188""#, r#""0x7f0d39b8347598e20466233ce2fb3e824f0f93df""#, r#""hunter2""#];
223      Value::String("0xdac1cba443d72e2088ed0cd2e6608ce696eb4728caf119dcfeea752f57a1163274de0b25007aa70201d0d80190071b26be2287b4a473767e5f7bc443c080b4fc1c".into()) => H520(hex!("dac1cba443d72e2088ed0cd2e6608ce696eb4728caf119dcfeea752f57a1163274de0b25007aa70201d0d80190071b26be2287b4a473767e5f7bc443c080b4fc1c"))
224    }
225}