1use std::any::Any;
5
6use crate::key_management::{Key, KeyInfo};
7use crate::message::SignedMessage;
8use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
9use crate::shim::{
10 address::Address,
11 crypto::{Signature, SignatureType},
12 econ::TokenAmount,
13 message::Message,
14 state_tree::StateTree,
15};
16use enumflags2::BitFlags;
17use fvm_ipld_blockstore::Blockstore;
18
19pub enum WalletBalance {}
20impl RpcMethod<1> for WalletBalance {
21 const NAME: &'static str = "Filecoin.WalletBalance";
22 const PARAM_NAMES: [&'static str; 1] = ["address"];
23 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
24 const PERMISSION: Permission = Permission::Read;
25 const DESCRIPTION: Option<&'static str> = Some("Returns the balance of a wallet.");
26
27 type Params = (Address,);
28 type Ok = TokenAmount;
29
30 async fn handle(
31 ctx: Ctx<impl Blockstore>,
32 (address,): Self::Params,
33 _: &http::Extensions,
34 ) -> Result<Self::Ok, ServerError> {
35 let heaviest_ts = ctx.chain_store().heaviest_tipset();
36 let cid = heaviest_ts.parent_state();
37
38 Ok(StateTree::new_from_root(ctx.store_owned(), cid)?
39 .get_actor(&address)?
40 .map(|it| it.balance.clone().into())
41 .unwrap_or_default())
42 }
43}
44
45pub enum WalletDefaultAddress {}
46impl RpcMethod<0> for WalletDefaultAddress {
47 const NAME: &'static str = "Filecoin.WalletDefaultAddress";
48 const PARAM_NAMES: [&'static str; 0] = [];
49 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
50 const PERMISSION: Permission = Permission::Read;
51
52 type Params = ();
53 type Ok = Option<Address>;
54
55 async fn handle(
56 ctx: Ctx<impl Blockstore>,
57 (): Self::Params,
58 _: &http::Extensions,
59 ) -> Result<Self::Ok, ServerError> {
60 let keystore = ctx.keystore.read();
61 Ok(crate::key_management::get_default(&keystore)?)
62 }
63}
64
65pub enum WalletExport {}
66impl RpcMethod<1> for WalletExport {
67 const NAME: &'static str = "Filecoin.WalletExport";
68 const PARAM_NAMES: [&'static str; 1] = ["address"];
69 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
70 const PERMISSION: Permission = Permission::Admin;
71
72 type Params = (Address,);
73 type Ok = KeyInfo;
74
75 async fn handle(
76 ctx: Ctx<impl Blockstore>,
77 (address,): Self::Params,
78 _: &http::Extensions,
79 ) -> Result<Self::Ok, ServerError> {
80 let keystore = ctx.keystore.read();
81 let key_info = crate::key_management::export_key_info(&address, &keystore)?;
82 Ok(key_info)
83 }
84}
85
86pub enum WalletHas {}
87impl RpcMethod<1> for WalletHas {
88 const NAME: &'static str = "Filecoin.WalletHas";
89 const PARAM_NAMES: [&'static str; 1] = ["address"];
90 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
91 const PERMISSION: Permission = Permission::Write;
92 const DESCRIPTION: Option<&'static str> =
93 Some("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<impl Blockstore>,
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
115 type Params = (KeyInfo,);
116 type Ok = Address;
117
118 async fn handle(
119 ctx: Ctx<impl Blockstore>,
120 (key_info,): Self::Params,
121 _: &http::Extensions,
122 ) -> Result<Self::Ok, ServerError> {
123 let key = Key::try_from(key_info)?;
124 let addr = format!("wallet-{}", key.address);
125 ctx.keystore.write().put(&addr, key.key_info)?;
126 Ok(key.address)
127 }
128}
129
130pub enum WalletList {}
131impl RpcMethod<0> for WalletList {
132 const NAME: &'static str = "Filecoin.WalletList";
133 const PARAM_NAMES: [&'static str; 0] = [];
134 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
135 const PERMISSION: Permission = Permission::Write;
136 const DESCRIPTION: Option<&'static str> =
137 Some("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<impl Blockstore>,
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] = ["signature_type"];
156 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
157 const PERMISSION: Permission = Permission::Write;
158
159 type Params = (SignatureType,);
160 type Ok = Address;
161
162 async fn handle(
163 ctx: Ctx<impl Blockstore>,
164 (signature_type,): Self::Params,
165 _: &http::Extensions,
166 ) -> Result<Self::Ok, ServerError> {
167 let key = crate::key_management::generate_key(signature_type)?;
168 let addr = format!("wallet-{}", key.address);
169 let mut keystore = ctx.keystore.write();
170 keystore.put(&addr, key.key_info.clone())?;
171 let value = keystore.get("default");
172 if value.is_err() {
173 keystore.put("default", key.key_info)?
174 }
175
176 Ok(key.address)
177 }
178}
179
180pub enum WalletSetDefault {}
181impl RpcMethod<1> for WalletSetDefault {
182 const NAME: &'static str = "Filecoin.WalletSetDefault";
183 const PARAM_NAMES: [&'static str; 1] = ["address"];
184 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
185 const PERMISSION: Permission = Permission::Write;
186
187 type Params = (Address,);
188 type Ok = ();
189
190 async fn handle(
191 ctx: Ctx<impl Blockstore>,
192 (address,): Self::Params,
193 _: &http::Extensions,
194 ) -> Result<Self::Ok, ServerError> {
195 let mut keystore = ctx.keystore.write();
196 let addr_string = format!("wallet-{address}");
197 let key_info = keystore.get(&addr_string)?;
198 keystore.remove("default")?; keystore.put("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: Option<&'static str> =
211 Some("Signs the given bytes using the specified address.");
212
213 type Params = (Address, Vec<u8>);
214 type Ok = Signature;
215
216 async fn handle(
217 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
218 (address, message): Self::Params,
219 _: &http::Extensions,
220 ) -> Result<Self::Ok, ServerError> {
221 let heaviest_tipset = ctx.chain_store().heaviest_tipset();
222 let key_addr = ctx
223 .state_manager
224 .resolve_to_key_addr(&address, &heaviest_tipset)
225 .await?;
226 let keystore = ctx.keystore.read();
227 let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
228 let sig = crate::key_management::sign(
229 *key.key_info.key_type(),
230 key.key_info.private_key(),
231 &message,
232 )?;
233
234 Ok(sig)
235 }
236}
237
238pub enum WalletSignMessage {}
239impl RpcMethod<2> for WalletSignMessage {
240 const NAME: &'static str = "Filecoin.WalletSignMessage";
241 const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
242 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
243 const PERMISSION: Permission = Permission::Sign;
244 const DESCRIPTION: Option<&'static str> =
245 Some("Signs the given message using the specified address.");
246
247 type Params = (Address, Message);
248 type Ok = SignedMessage;
249
250 async fn handle(
251 ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
252 (address, message): Self::Params,
253 _: &http::Extensions,
254 ) -> Result<Self::Ok, ServerError> {
255 let ts = ctx.chain_store().heaviest_tipset();
256 let key_addr = ctx
257 .state_manager
258 .resolve_to_deterministic_address(address, &ts)
259 .await?;
260 let keystore = ctx.keystore.read();
261 let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
262 let eth_chain_id = ctx.chain_config().eth_chain_id;
263 let smsg = crate::key_management::sign_message(&key, &message, eth_chain_id)?;
264 Ok(smsg)
265 }
266}
267
268pub enum WalletValidateAddress {}
269impl RpcMethod<1> for WalletValidateAddress {
270 const NAME: &'static str = "Filecoin.WalletValidateAddress";
271 const PARAM_NAMES: [&'static str; 1] = ["address"];
272 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
273 const PERMISSION: Permission = Permission::Read;
274
275 type Params = (String,);
276 type Ok = Address;
277
278 async fn handle(
279 _: Ctx<impl Any>,
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
294 type Params = (Address, Vec<u8>, Signature);
295 type Ok = bool;
296
297 async fn handle(
298 _: Ctx<impl Any>,
299 (address, message, signature): Self::Params,
300 _: &http::Extensions,
301 ) -> Result<Self::Ok, ServerError> {
302 Ok(signature.verify(&message, &address).is_ok())
303 }
304}
305
306pub enum WalletDelete {}
307impl RpcMethod<1> for WalletDelete {
308 const NAME: &'static str = "Filecoin.WalletDelete";
309 const PARAM_NAMES: [&'static str; 1] = ["address"];
310 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
311 const PERMISSION: Permission = Permission::Write;
312
313 type Params = (Address,);
314 type Ok = ();
315
316 async fn handle(
317 ctx: Ctx<impl Blockstore>,
318 (address,): Self::Params,
319 _: &http::Extensions,
320 ) -> Result<Self::Ok, ServerError> {
321 crate::key_management::remove_key(&address, &mut ctx.keystore.write())?;
322 Ok(())
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use crate::{KeyStore, shim::crypto::SignatureType};
329
330 #[tokio::test]
331 async fn wallet_delete_existing_key() {
332 let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
333 let addr = format!("wallet-{}", key.address);
334 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
335 keystore.put(&addr, key.key_info.clone()).unwrap();
336 crate::key_management::remove_key(&key.address, &mut keystore).unwrap();
337 assert!(keystore.get(&addr).is_err());
338 }
339
340 #[tokio::test]
341 async fn wallet_delete_empty_keystore() {
342 let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
343 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
344 assert!(crate::key_management::remove_key(&key.address, &mut keystore).is_err());
345 }
346
347 #[tokio::test]
348 async fn wallet_delete_non_existent_key() {
349 let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
350 let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
351 let addr1 = format!("wallet-{}", key1.address);
352 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
353 keystore.put(&addr1, key1.key_info.clone()).unwrap();
354 assert!(crate::key_management::remove_key(&key2.address, &mut keystore).is_err());
355 }
356
357 #[tokio::test]
358 async fn wallet_delete_default_key() {
359 let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
360 let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
361 let addr1 = format!("wallet-{}", key1.address);
362 let addr2 = format!("wallet-{}", key2.address);
363 let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
364 keystore.put(&addr1, key1.key_info.clone()).unwrap();
365 keystore.put(&addr2, key2.key_info.clone()).unwrap();
366 keystore.put("default", key2.key_info.clone()).unwrap();
367 crate::key_management::remove_key(&key2.address, &mut keystore).unwrap();
368 assert!(
369 crate::key_management::get_default(&keystore)
370 .unwrap()
371 .is_none()
372 );
373 }
374}