Skip to main content

forest/rpc/methods/
wallet.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::any::Any;
5
6use crate::db::EthMappingsStore;
7use crate::key_management::{Key, KeyInfo};
8use crate::message::SignedMessage;
9use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
10use crate::shim::{
11    address::Address,
12    crypto::{Signature, SignatureType},
13    econ::TokenAmount,
14    message::Message,
15    state_tree::StateTree,
16};
17use enumflags2::BitFlags;
18use fvm_ipld_blockstore::Blockstore;
19
20pub enum WalletBalance {}
21impl RpcMethod<1> for WalletBalance {
22    const NAME: &'static str = "Filecoin.WalletBalance";
23    const PARAM_NAMES: [&'static str; 1] = ["address"];
24    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
25    const PERMISSION: Permission = Permission::Read;
26    const DESCRIPTION: Option<&'static str> = Some("Returns the balance of a wallet.");
27
28    type Params = (Address,);
29    type Ok = TokenAmount;
30
31    async fn handle(
32        ctx: Ctx<impl Blockstore>,
33        (address,): Self::Params,
34        _: &http::Extensions,
35    ) -> Result<Self::Ok, ServerError> {
36        let heaviest_ts = ctx.chain_store().heaviest_tipset();
37        let cid = heaviest_ts.parent_state();
38
39        Ok(StateTree::new_from_root(ctx.store_owned(), cid)?
40            .get_actor(&address)?
41            .map(|it| it.balance.clone().into())
42            .unwrap_or_default())
43    }
44}
45
46pub enum WalletDefaultAddress {}
47impl RpcMethod<0> for WalletDefaultAddress {
48    const NAME: &'static str = "Filecoin.WalletDefaultAddress";
49    const PARAM_NAMES: [&'static str; 0] = [];
50    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
51    const PERMISSION: Permission = Permission::Read;
52
53    type Params = ();
54    type Ok = Option<Address>;
55
56    async fn handle(
57        ctx: Ctx<impl Blockstore>,
58        (): Self::Params,
59        _: &http::Extensions,
60    ) -> Result<Self::Ok, ServerError> {
61        let keystore = ctx.keystore.read();
62        Ok(crate::key_management::get_default(&keystore)?)
63    }
64}
65
66pub enum WalletExport {}
67impl RpcMethod<1> for WalletExport {
68    const NAME: &'static str = "Filecoin.WalletExport";
69    const PARAM_NAMES: [&'static str; 1] = ["address"];
70    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
71    const PERMISSION: Permission = Permission::Admin;
72
73    type Params = (Address,);
74    type Ok = KeyInfo;
75
76    async fn handle(
77        ctx: Ctx<impl Blockstore>,
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: Option<&'static str> =
94        Some("Indicates whether the given address exists in the wallet.");
95
96    type Params = (Address,);
97    type Ok = bool;
98
99    async fn handle(
100        ctx: Ctx<impl Blockstore>,
101        (address,): Self::Params,
102        _: &http::Extensions,
103    ) -> Result<Self::Ok, ServerError> {
104        let keystore = ctx.keystore.read();
105        Ok(crate::key_management::try_find_key(&address, &keystore).is_ok())
106    }
107}
108
109pub enum WalletImport {}
110impl RpcMethod<1> for WalletImport {
111    const NAME: &'static str = "Filecoin.WalletImport";
112    const PARAM_NAMES: [&'static str; 1] = ["key"];
113    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
114    const PERMISSION: Permission = Permission::Admin;
115
116    type Params = (KeyInfo,);
117    type Ok = Address;
118
119    async fn handle(
120        ctx: Ctx<impl Blockstore>,
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: Option<&'static str> =
138        Some("Returns a list of all addresses in the wallet.");
139
140    type Params = ();
141    type Ok = Vec<Address>;
142
143    async fn handle(
144        ctx: Ctx<impl Blockstore>,
145        (): Self::Params,
146        _: &http::Extensions,
147    ) -> Result<Self::Ok, ServerError> {
148        let keystore = ctx.keystore.read();
149        Ok(crate::key_management::list_addrs(&keystore)?)
150    }
151}
152
153pub enum WalletNew {}
154impl RpcMethod<1> for WalletNew {
155    const NAME: &'static str = "Filecoin.WalletNew";
156    const PARAM_NAMES: [&'static str; 1] = ["signature_type"];
157    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
158    const PERMISSION: Permission = Permission::Write;
159
160    type Params = (SignatureType,);
161    type Ok = Address;
162
163    async fn handle(
164        ctx: Ctx<impl Blockstore>,
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
188    type Params = (Address,);
189    type Ok = ();
190
191    async fn handle(
192        ctx: Ctx<impl Blockstore>,
193        (address,): Self::Params,
194        _: &http::Extensions,
195    ) -> Result<Self::Ok, ServerError> {
196        let mut keystore = ctx.keystore.write();
197        let addr_string = format!("wallet-{address}");
198        let key_info = keystore.get(&addr_string)?;
199        keystore.remove("default")?; // This line should unregister current default key then continue
200        keystore.put("default", key_info)?;
201        Ok(())
202    }
203}
204
205pub enum WalletSign {}
206impl RpcMethod<2> for WalletSign {
207    const NAME: &'static str = "Filecoin.WalletSign";
208    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
209    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
210    const PERMISSION: Permission = Permission::Sign;
211    const DESCRIPTION: Option<&'static str> =
212        Some("Signs the given bytes using the specified address.");
213
214    type Params = (Address, Vec<u8>);
215    type Ok = Signature;
216
217    async fn handle(
218        ctx: Ctx<impl Blockstore + EthMappingsStore + Send + Sync + 'static>,
219        (address, message): Self::Params,
220        _: &http::Extensions,
221    ) -> Result<Self::Ok, ServerError> {
222        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
223        let key_addr = ctx
224            .state_manager
225            .resolve_to_key_addr(&address, &heaviest_tipset)
226            .await?;
227        let keystore = ctx.keystore.read();
228        let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
229        let sig = crate::key_management::sign(
230            *key.key_info.key_type(),
231            key.key_info.private_key(),
232            &message,
233        )?;
234
235        Ok(sig)
236    }
237}
238
239pub enum WalletSignMessage {}
240impl RpcMethod<2> for WalletSignMessage {
241    const NAME: &'static str = "Filecoin.WalletSignMessage";
242    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
243    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
244    const PERMISSION: Permission = Permission::Sign;
245    const DESCRIPTION: Option<&'static str> =
246        Some("Signs the given message using the specified address.");
247
248    type Params = (Address, Message);
249    type Ok = SignedMessage;
250
251    async fn handle(
252        ctx: Ctx<impl Blockstore + EthMappingsStore + Send + Sync + 'static>,
253        (address, message): Self::Params,
254        _: &http::Extensions,
255    ) -> Result<Self::Ok, ServerError> {
256        let ts = ctx.chain_store().heaviest_tipset();
257        let key_addr = ctx
258            .state_manager
259            .resolve_to_deterministic_address(address, &ts)
260            .await?;
261        let keystore = ctx.keystore.read();
262        let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
263        let eth_chain_id = ctx.chain_config().eth_chain_id;
264        let smsg = crate::key_management::sign_message(&key, &message, eth_chain_id)?;
265        Ok(smsg)
266    }
267}
268
269pub enum WalletValidateAddress {}
270impl RpcMethod<1> for WalletValidateAddress {
271    const NAME: &'static str = "Filecoin.WalletValidateAddress";
272    const PARAM_NAMES: [&'static str; 1] = ["address"];
273    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
274    const PERMISSION: Permission = Permission::Read;
275
276    type Params = (String,);
277    type Ok = Address;
278
279    async fn handle(
280        _: Ctx<impl Any>,
281        (s,): Self::Params,
282        _: &http::Extensions,
283    ) -> Result<Self::Ok, ServerError> {
284        Ok(s.parse()?)
285    }
286}
287
288pub enum WalletVerify {}
289impl RpcMethod<3> for WalletVerify {
290    const NAME: &'static str = "Filecoin.WalletVerify";
291    const PARAM_NAMES: [&'static str; 3] = ["address", "message", "signature"];
292    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
293    const PERMISSION: Permission = Permission::Read;
294
295    type Params = (Address, Vec<u8>, Signature);
296    type Ok = bool;
297
298    async fn handle(
299        _: Ctx<impl Any>,
300        (address, message, signature): Self::Params,
301        _: &http::Extensions,
302    ) -> Result<Self::Ok, ServerError> {
303        Ok(signature.verify(&message, &address).is_ok())
304    }
305}
306
307pub enum WalletDelete {}
308impl RpcMethod<1> for WalletDelete {
309    const NAME: &'static str = "Filecoin.WalletDelete";
310    const PARAM_NAMES: [&'static str; 1] = ["address"];
311    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
312    const PERMISSION: Permission = Permission::Write;
313
314    type Params = (Address,);
315    type Ok = ();
316
317    async fn handle(
318        ctx: Ctx<impl Blockstore>,
319        (address,): Self::Params,
320        _: &http::Extensions,
321    ) -> Result<Self::Ok, ServerError> {
322        crate::key_management::remove_key(&address, &mut ctx.keystore.write())?;
323        Ok(())
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use crate::{KeyStore, shim::crypto::SignatureType};
330
331    #[tokio::test]
332    async fn wallet_delete_existing_key() {
333        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
334        let addr = format!("wallet-{}", key.address);
335        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
336        keystore.put(&addr, key.key_info.clone()).unwrap();
337        crate::key_management::remove_key(&key.address, &mut keystore).unwrap();
338        assert!(keystore.get(&addr).is_err());
339    }
340
341    #[tokio::test]
342    async fn wallet_delete_empty_keystore() {
343        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
344        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
345        assert!(crate::key_management::remove_key(&key.address, &mut keystore).is_err());
346    }
347
348    #[tokio::test]
349    async fn wallet_delete_non_existent_key() {
350        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
351        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
352        let addr1 = format!("wallet-{}", key1.address);
353        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
354        keystore.put(&addr1, key1.key_info.clone()).unwrap();
355        assert!(crate::key_management::remove_key(&key2.address, &mut keystore).is_err());
356    }
357
358    #[tokio::test]
359    async fn wallet_delete_default_key() {
360        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
361        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
362        let addr1 = format!("wallet-{}", key1.address);
363        let addr2 = format!("wallet-{}", key2.address);
364        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
365        keystore.put(&addr1, key1.key_info.clone()).unwrap();
366        keystore.put(&addr2, key2.key_info.clone()).unwrap();
367        keystore.put("default", key2.key_info.clone()).unwrap();
368        crate::key_management::remove_key(&key2.address, &mut keystore).unwrap();
369        assert!(
370            crate::key_management::get_default(&keystore)
371                .unwrap()
372                .is_none()
373        );
374    }
375}