1use crate::key_management::{Key, KeyInfo};
5use crate::message::SignedMessage;
6use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
7use crate::shim::{
8 address::Address,
9 crypto::{Signature, SignatureType},
10 econ::TokenAmount,
11 message::Message,
12 state_tree::StateTree,
13};
14use enumflags2::BitFlags;
15
16pub enum WalletBalance {}
17impl RpcMethod<1> for WalletBalance {
18 const NAME: &'static str = "Filecoin.WalletBalance";
19 const PARAM_NAMES: [&'static str; 1] = ["address"];
20 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
21 const PERMISSION: Permission = Permission::Read;
22 const DESCRIPTION: &'static str = "Returns the balance of a wallet.";
23
24 type Params = (Address,);
25 type Ok = TokenAmount;
26
27 async fn handle(
28 ctx: Ctx,
29 (address,): Self::Params,
30 _: &http::Extensions,
31 ) -> Result<Self::Ok, ServerError> {
32 let heaviest_ts = ctx.chain_store().heaviest_tipset();
33 let cid = heaviest_ts.parent_state();
34
35 Ok(StateTree::new_from_root(ctx.db(), cid)?
36 .get_actor(&address)?
37 .map(|it| it.balance.clone().into())
38 .unwrap_or_default())
39 }
40}
41
42pub enum WalletDefaultAddress {}
43impl RpcMethod<0> for WalletDefaultAddress {
44 const NAME: &'static str = "Filecoin.WalletDefaultAddress";
45 const PARAM_NAMES: [&'static str; 0] = [];
46 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
47 const PERMISSION: Permission = Permission::Read;
48 const DESCRIPTION: &'static str =
49 "Returns the default wallet address, or null if no default is set.";
50
51 type Params = ();
52 type Ok = Option<Address>;
53
54 async fn handle(
55 ctx: Ctx,
56 (): Self::Params,
57 _: &http::Extensions,
58 ) -> Result<Self::Ok, ServerError> {
59 let keystore = ctx.keystore.read();
60 Ok(crate::key_management::get_default(&keystore)?)
61 }
62}
63
64pub enum WalletExport {}
65impl RpcMethod<1> for WalletExport {
66 const NAME: &'static str = "Filecoin.WalletExport";
67 const PARAM_NAMES: [&'static str; 1] = ["address"];
68 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
69 const PERMISSION: Permission = Permission::Admin;
70 const DESCRIPTION: &'static str =
71 "Exports the key information, including the private key, for the given wallet address.";
72
73 type Params = (Address,);
74 type Ok = KeyInfo;
75
76 async fn handle(
77 ctx: Ctx,
78 (address,): Self::Params,
79 _: &http::Extensions,
80 ) -> Result<Self::Ok, ServerError> {
81 let keystore = ctx.keystore.read();
82 let key_info = crate::key_management::export_key_info(&address, &keystore)?;
83 Ok(key_info)
84 }
85}
86
87pub enum WalletHas {}
88impl RpcMethod<1> for WalletHas {
89 const NAME: &'static str = "Filecoin.WalletHas";
90 const PARAM_NAMES: [&'static str; 1] = ["address"];
91 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
92 const PERMISSION: Permission = Permission::Write;
93 const DESCRIPTION: &'static str = "Indicates whether the given address exists in the wallet.";
94
95 type Params = (Address,);
96 type Ok = bool;
97
98 async fn handle(
99 ctx: Ctx,
100 (address,): Self::Params,
101 _: &http::Extensions,
102 ) -> Result<Self::Ok, ServerError> {
103 let keystore = ctx.keystore.read();
104 Ok(crate::key_management::try_find_key(&address, &keystore).is_ok())
105 }
106}
107
108pub enum WalletImport {}
109impl RpcMethod<1> for WalletImport {
110 const NAME: &'static str = "Filecoin.WalletImport";
111 const PARAM_NAMES: [&'static str; 1] = ["key"];
112 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
113 const PERMISSION: Permission = Permission::Admin;
114 const DESCRIPTION: &'static str = "Imports a key into the wallet and returns its address.";
115
116 type Params = (KeyInfo,);
117 type Ok = Address;
118
119 async fn handle(
120 ctx: Ctx,
121 (key_info,): Self::Params,
122 _: &http::Extensions,
123 ) -> Result<Self::Ok, ServerError> {
124 let key = Key::try_from(key_info)?;
125 let addr = format!("wallet-{}", key.address);
126 ctx.keystore.write().put(&addr, key.key_info)?;
127 Ok(key.address)
128 }
129}
130
131pub enum WalletList {}
132impl RpcMethod<0> for WalletList {
133 const NAME: &'static str = "Filecoin.WalletList";
134 const PARAM_NAMES: [&'static str; 0] = [];
135 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
136 const PERMISSION: Permission = Permission::Write;
137 const DESCRIPTION: &'static str = "Returns a list of all addresses in the wallet.";
138
139 type Params = ();
140 type Ok = Vec<Address>;
141
142 async fn handle(
143 ctx: Ctx,
144 (): Self::Params,
145 _: &http::Extensions,
146 ) -> Result<Self::Ok, ServerError> {
147 let keystore = ctx.keystore.read();
148 Ok(crate::key_management::list_addrs(&keystore)?)
149 }
150}
151
152pub enum WalletNew {}
153impl RpcMethod<1> for WalletNew {
154 const NAME: &'static str = "Filecoin.WalletNew";
155 const PARAM_NAMES: [&'static str; 1] = ["signatureType"];
156 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
157 const PERMISSION: Permission = Permission::Write;
158 const DESCRIPTION: &'static str = "Generates a new key of the given signature type, adds it to the wallet, and returns its address.";
159
160 type Params = (SignatureType,);
161 type Ok = Address;
162
163 async fn handle(
164 ctx: Ctx,
165 (signature_type,): Self::Params,
166 _: &http::Extensions,
167 ) -> Result<Self::Ok, ServerError> {
168 let key = crate::key_management::generate_key(signature_type)?;
169 let addr = format!("wallet-{}", key.address);
170 let mut keystore = ctx.keystore.write();
171 keystore.put(&addr, key.key_info.clone())?;
172 let value = keystore.get("default");
173 if value.is_err() {
174 keystore.put("default", key.key_info)?
175 }
176
177 Ok(key.address)
178 }
179}
180
181pub enum WalletSetDefault {}
182impl RpcMethod<1> for WalletSetDefault {
183 const NAME: &'static str = "Filecoin.WalletSetDefault";
184 const PARAM_NAMES: [&'static str; 1] = ["address"];
185 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
186 const PERMISSION: Permission = Permission::Write;
187 const DESCRIPTION: &'static str = "Sets the given address as the default wallet address.";
188
189 type Params = (Address,);
190 type Ok = ();
191
192 async fn handle(
193 ctx: Ctx,
194 (address,): Self::Params,
195 _: &http::Extensions,
196 ) -> Result<Self::Ok, ServerError> {
197 let mut keystore = ctx.keystore.write();
198 let key_info = crate::key_management::try_find(&address, &keystore)?;
199 keystore.set_default(key_info)?;
200 Ok(())
201 }
202}
203
204pub enum WalletSign {}
205impl RpcMethod<2> for WalletSign {
206 const NAME: &'static str = "Filecoin.WalletSign";
207 const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
208 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
209 const PERMISSION: Permission = Permission::Sign;
210 const DESCRIPTION: &'static str = "Signs the given bytes using the specified address.";
211
212 type Params = (Address, Vec<u8>);
213 type Ok = Signature;
214
215 async fn handle(
216 ctx: Ctx,
217 (address, message): Self::Params,
218 _: &http::Extensions,
219 ) -> Result<Self::Ok, ServerError> {
220 let heaviest_tipset = ctx.chain_store().heaviest_tipset();
221 let key_addr = ctx
222 .state_manager
223 .resolve_to_deterministic_address(address, &heaviest_tipset)
224 .await?;
225 let keystore = ctx.keystore.read();
226 let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
227 let sig = crate::key_management::sign(
228 *key.key_info.key_type(),
229 key.key_info.private_key(),
230 &message,
231 )?;
232
233 Ok(sig)
234 }
235}
236
237pub enum WalletSignMessage {}
238impl RpcMethod<2> for WalletSignMessage {
239 const NAME: &'static str = "Filecoin.WalletSignMessage";
240 const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
241 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
242 const PERMISSION: Permission = Permission::Sign;
243 const DESCRIPTION: &'static str = "Signs the given message using the specified address.";
244
245 type Params = (Address, Message);
246 type Ok = SignedMessage;
247
248 async fn handle(
249 ctx: Ctx,
250 (address, message): Self::Params,
251 _: &http::Extensions,
252 ) -> Result<Self::Ok, ServerError> {
253 let ts = ctx.chain_store().heaviest_tipset();
254 let key_addr = ctx
255 .state_manager
256 .resolve_to_deterministic_address(address, &ts)
257 .await?;
258 let keystore = ctx.keystore.read();
259 let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
260 let eth_chain_id = ctx.chain_config().eth_chain_id;
261 let smsg = crate::key_management::sign_message(&key, &message, eth_chain_id)?;
262 Ok(smsg)
263 }
264}
265
266pub enum WalletValidateAddress {}
267impl RpcMethod<1> for WalletValidateAddress {
268 const NAME: &'static str = "Filecoin.WalletValidateAddress";
269 const PARAM_NAMES: [&'static str; 1] = ["address"];
270 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
271 const PERMISSION: Permission = Permission::Read;
272 const DESCRIPTION: &'static str =
273 "Validates the given string as a Filecoin address and returns the parsed address.";
274
275 type Params = (String,);
276 type Ok = Address;
277
278 async fn handle(
279 _: Ctx,
280 (s,): Self::Params,
281 _: &http::Extensions,
282 ) -> Result<Self::Ok, ServerError> {
283 Ok(s.parse()?)
284 }
285}
286
287pub enum WalletVerify {}
288impl RpcMethod<3> for WalletVerify {
289 const NAME: &'static str = "Filecoin.WalletVerify";
290 const PARAM_NAMES: [&'static str; 3] = ["address", "message", "signature"];
291 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
292 const PERMISSION: Permission = Permission::Read;
293 const DESCRIPTION: &'static str =
294 "Returns whether the given signature is valid for the message and address.";
295
296 type Params = (Address, Vec<u8>, Signature);
297 type Ok = bool;
298
299 async fn handle(
300 _: Ctx,
301 (address, message, signature): Self::Params,
302 _: &http::Extensions,
303 ) -> Result<Self::Ok, ServerError> {
304 Ok(signature.verify(&message, &address).is_ok())
305 }
306}
307
308pub enum WalletDelete {}
309impl RpcMethod<1> for WalletDelete {
310 const NAME: &'static str = "Filecoin.WalletDelete";
311 const PARAM_NAMES: [&'static str; 1] = ["address"];
312 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
313 const PERMISSION: Permission = Permission::Write;
314 const DESCRIPTION: &'static str = "Removes the key for the given address from the wallet.";
315
316 type Params = (Address,);
317 type Ok = ();
318
319 async fn handle(
320 ctx: Ctx,
321 (address,): Self::Params,
322 _: &http::Extensions,
323 ) -> Result<Self::Ok, ServerError> {
324 crate::key_management::remove_key(&address, &mut ctx.keystore.write())?;
325 Ok(())
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use crate::{KeyStore, shim::crypto::SignatureType};
332
333 #[tokio::test]
334 async fn wallet_delete_existing_key() {
335 let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
336 let addr = format!("wallet-{}", key.address);
337 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
338 keystore.put(&addr, key.key_info.clone()).unwrap();
339 crate::key_management::remove_key(&key.address, &mut keystore).unwrap();
340 assert!(keystore.get(&addr).is_err());
341 }
342
343 #[tokio::test]
344 async fn wallet_delete_empty_keystore() {
345 let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
346 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
347 assert!(crate::key_management::remove_key(&key.address, &mut keystore).is_err());
348 }
349
350 #[tokio::test]
351 async fn wallet_delete_non_existent_key() {
352 let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
353 let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
354 let addr1 = format!("wallet-{}", key1.address);
355 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
356 keystore.put(&addr1, key1.key_info.clone()).unwrap();
357 assert!(crate::key_management::remove_key(&key2.address, &mut keystore).is_err());
358 }
359
360 #[tokio::test]
361 async fn wallet_delete_default_key() {
362 let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
363 let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
364 let addr1 = format!("wallet-{}", key1.address);
365 let addr2 = format!("wallet-{}", key2.address);
366 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
367 keystore.put(&addr1, key1.key_info.clone()).unwrap();
368 keystore.put(&addr2, key2.key_info.clone()).unwrap();
369 keystore.put("default", key2.key_info.clone()).unwrap();
370 crate::key_management::remove_key(&key2.address, &mut keystore).unwrap();
371 assert!(
372 crate::key_management::get_default(&keystore)
373 .unwrap()
374 .is_none()
375 );
376 }
377}