forest/rpc/methods/
wallet.rs

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